Introduction to Crystal Space Shaders
=====================================
.. @@@ Worthwhile to explain:
shader variables
Overview
------------
This document describes the basic structure of a Crystal Space shader by
successively adding more features that require different settings in the
Crystal Space shader definition.
Cg will be used in the shader created. However, Cg is not the focus of this
tutorial and is only explained tangentially.
Nomenclature
------------
In different contexts and programs the word "shader" has different meaning.
For example, in offline rendering it is used in a lot of places ("surface
shader", "light shader") and is, generally, a description of appearance of
something. In DirectX the programs running on the vertex resp. fragment units
of graphics hardware are called "shaders" ("vertex shaders" and "pixel
shaders"). OpenGL used to call the same "vertex programs" and "fragment
programs", but recently also adopted the name "shader".
In Crystal Space, a shader somewhat follows the offline rendering nomenclature
by basically being a flexible description of the appearance of a surface.
"Flexible" means that a shader can take parameters which influence it's
output. A simple example for that is a surface texture. The "description" of
the appearance is (usually) given as a vertex and shader program.
Structure
---------
A Crystal Space shader consists of multiple components, nested in each other.
Techniques
~~~~~~~~~~
At the top level, a shader consists of one or more techniques. Each technique
has an associated priority. At runtime, of all available techniques, the
highest one supported by the used graphics hardware is used. The idea behind
techniques is to provide the same, or similar, surface appearance described
in multiple ways, fitting hardware with different capabilities. A common
scenario is that a certain shader can be drawn in a single rendering pass on
some hardware, but requires multiple passes on other.
Passes
~~~~~~
Rendering passes are the next level below techniques. Each technique consists
of one or more passes. Each pass can have different, individual rendering
settings (such as the shader programs used). Passes have a specific order;
when a mesh is rendered with a shader technique consisting of multiple passes,
it is effectively rendered once for each pass, with the respective rendering
settings applied for each time it is rendered. The order the passes are
applied is the order they were defined.
Pass contents: programs and bindings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Inside passes most of the actually interesting settings can be found.
Foremost, passes contain *programs* for software vertex processing (called
"vertex processors"), hardware vertex processing (vertex programs) and
hardware fragment processing (fragment programs). These are implemented
through Crystal Space plugins and thus different implementations and
languages are possible. Vertex and fragment programs can be provided as
assembly (ARB vertex/fragment programs) or high-level shading language
programs (Cg). However, also setups for the fixed-function pipeline are
subsumed under "programs" - while technically inaccurate the abstraction
is practical.
Passes also contain information on how data provided by Crystal Space is
connected to the shader programs. These contain *buffer bindings* (linking
buffers of standard and arbitrary per-vertex data to vertex program inputs)
and *texture bindings* (linking textures to fragment program inputs). In
addition, most program plugins also have a facility to map shader variable
values to program inputs constant for one rendered mesh.
Furthermore, some rendering options can also be set, like Z buffer mode or
blending mode.
Shader step by step
-------------------
The following sections will cover the creation of a shader, in multiple steps.
Basic elements will be added until the first "milestone" - a simple, working
shader. Based on that more advanced features will be added.
To test the various steps of the development a simple CS program "shadertest"
is provided along with this document. It is, in essence, a slightly modified
"simple2" tutorial from Crystal Space, but with various code snippets to
play around with the shader(s) created.
Basic Structure
~~~~~~~~~~~~~~~
Crystal Space shaders are XML-based. The root node for a shader is a
``shader`` node:
::
...
Each shader needs to have a ``name`` and a ``compiler`` attribute. The
compiler specifies what plugin is used to interpret the shader XML. The
currently available compilers are ``xmlshader`` and ``shaderweaver``.
``xmlshader`` provides the explicit technique/pass/program-based model
described in this document. ``shaderweaver`` takes a shader described
as a graph from which a "classic" Crystal Space shader is generated. It is not
covered in this document.
.. @@@ ref to a weaver doc, if we have one
A shader must have at least one technique:
::
...
The ``priority`` is an integer value which sets an order of techniques: the
technique with the highest priority is attempted to be loaded; if loading
it is used whenever the shader is used for rendering. If loading does not
succeed, the technique with the next lowest priority is used. And so on,
until a priority is found or no further priorities are available (in which
case searching is continued in the optional *fallback shader*).
Note that the order techniques are defined does not matter, they are ordered
by their priorities when a shader is loaded.
.. @@@ ref to syntax
Also, a shader needs at least one pass:
::
...
A ``pass`` node doesn't have any attributes. Keep in mind that, unlike
``technique`` nodes, their order matters.
Simple Programs
~~~~~~~~~~~~~~~
The parts primarily responsible for the effect of a shader are the *shader
programs*. Here, we will add programs for the simplest shader: rendering
an object in a solid color.
The programs here written will be in Cg. If you are familiar with C or any
language with some relation to it (C++, Java, ...) the syntax should be easy
to understand.
To add a program, you first need to add a node for it's type. We start with
the vertex program, for that we add:
::
...
The ``plugin`` attribute specifies the shader program plugin to use. For Cg
programs this must be ``glcg``. Other plugins you can encounter in the shaders
shipped with Crystal Space are ``glfixed`` (for fixed function setups) and
``glarb`` (for ARB assembly shaders), however, neither will be further
explained in this document.
The contents under the ``vp`` node are entirely plugin-dependent. Cg vertex
programs require another nested node:
::
...
The program itself is specified in a ``program`` node:
::
This little program merely transforms the position of a vertex (in object
space) into camera space (where the point if the viewer is at the origin,
looking down the Z axis). The ``POSITION`` keyword (a Cg "semantic" on the
``positionObj`` parameter signals that the values for it should come from the
vertex position per-vertex data. The way ``ModelViewProj`` is defined binds
it to the combined object-to-camera and projection matrices. Finally, the
``POSITION`` on the return value indicates the output is the transformed vertex
position. [#]_
.. [#] For more details refer to the Cg user manual or one of the Cg tutorials
on the web.
The fragment program follows the same pattern:
::
Here the ``COLOR`` indicates that the return value is the output color. The
output is itself is just a constant color, a shader of yellow here.
The complete program is:
::
And the result when running ``shadertest``:
.. image:: crystal000.png
Adding Texture Mapping
~~~~~~~~~~~~~~~~~~~~~~
Drawing a solid colored mesh is not exactly exciting. As the next step, the
shader shall get its output color from a texture.
The changed Cg code can be inspected in ``shadertest_2.xml``. Notable is that
the programs need additional inputs: the vertex program requires texture
coordinates, the fragment program takes a texture as input. These need some
additional markup to assign *bindings* between CS resources and the shader
program inputs. [#]_
.. [#] A third change is that the texture coordinate has to be passed from the
vertex to the fragment program. However, this does not affect or require
any settings in the bindings part of the shader.
The two new data, texture coordinates and texture, are of different nature and
are thus bound in different ways.
The texture coordinates are provided by the mesh in a *render buffer*. Any
per-vertex data is stored in render buffers in Crystal Space. Other buffers
you are likely to see are for vertex normals, tangents and bitangents, vertex
colors, or additional texture coordinate sets. [#]_
.. [#] In fact, even the object space position is delivered in a render buffer.
However, it is implicitly bound and thus does not need any special
binding markup.
To bind a buffer, a ``buffer`` element is used:
::
The ``name`` attribute identifies the buffer. It can be one of a set of
predefined names (such as "texture coordinate", "normal", "color") or an
arbitrary name. The ``destination`` attribute specifies the target of the
binding. The possible values are actually dependent on the type of vertex
program used. For Cg programs you can use actual variable identifiers as
destinations.
.. @@@ should point to a list of predefined names
To bind a texture, a ``texture`` element is used:
::
Similarly, the ``name`` attribute identifies the texture. There is no set
of predefined names, however, there are some "common" names which, by
convention, should have the same meaning everywhere in the world of Crystal
Space. "tex diffuse" is such a name and identifies the diffuse texture of a
surface. Here the ``destination`` attribute specifies the target of the
binding as well. Again, the possible values are actually dependent on the
type of fragment program used, and with Cg programs you can use actual variable
identifiers.
And the result when running ``shadertest``:
.. image:: crystal001.png
Adding Per-Vertex Lighting
~~~~~~~~~~~~~~~~~~~~~~~~~~
As the next step, we will add lighting to the shader. Just some simple
per-vertex lighting, nothing too fancy. The modified Cg code can be inspected
in ``shadertest_3.xml``.
Of the input data used you'll notice that some additional per-vertex data is
used: the vertex normal. Binding that value to the Cg inputs is, as before,
done with a ``buffer`` element.
However, you will also notice the light parameters are declared slightly
differently, as *uniform*. These values are "uniform" for a single rendered
mesh (thus they could also be called "constants", although, they are not
constant in the strictest sense).
For a rendered mesh, Crystal Space will select a set of lights and will provide
them to the shader in different shader variables. As with all information, a
connection between the data provided by Crystal Space and the declaration in
a shader program needs to be made. To that end, the ``variablemap`` element
maps an arbitrary shader variable to a program input:
::
The ``name`` attribute identifies the shader variable. There are some
predefined variables which contain information provided by Crystal Space [#]_.
But in general, shader variables can be arbitrarily named. Commonly, a shader
may use some custom shader variable to provide a parameter to the effect
which is then set in a material using the shader. E.g. the "specular"
parameter that usually sets the material's specular color. Here the
``destination`` attribute specifies the target of the bindinshader variable
value. The possible values are actually dependent on the type of shader
program used. Wnd with Cg programs you can use actual variable identifiers.
.. [#] http://www.crystalspace3d.org/docs/online/manual/Shader-Variables.html#0
contains a list with engine-provided shader variables.
Note that some of the variables used as a target are arrays. On the Crystal
Space side, most light information is passed as a *shader variable array*. The
mapping between array items in the Cg side and array items on the Crystal
Space sides happens automatically. If you would want to use a single element
of a shader variable array you would use typical array element syntax in
``name``, e.g. ``light diffuse[0]``.
Another subtle but important change is the ``lights`` attribute to the
``shader`` element. I tells Crystal Space how many lights a shader can handle -
this information is used to (possibly) render a mesh in multiple passes.
And the result when running ``shadertest``:
.. image:: crystal002.png
Adding Ambient Lighting
~~~~~~~~~~~~~~~~~~~~~~~
Crystal Space provides the ambient light color in a shader variable ``light
ambient``. To apply ambient lighting simply add the value of this shader
variable to the computed diffuse lighting. The modified Cg code can be
inspected in ``shadertest_4.xml``.
And the result when running ``shadertest``:
.. image:: crystal003.png
If you compare it to the previous image you can see the teapot on this one is
slightly brighter.
Making the Shader Support Multiple Passes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As mentioned before, Crystal Space can render a mesh in multiple passes when
more lights are affecting a mesh than the shader has support for. To test this,
change the ``lights`` attribute to one (``shadertest_5.xml``). The result:
.. image:: crystal003.png
But wait. These are not all lights! Where are the others?
The reason lies in the way the shader output is blended with the framebuffer.
Or rather, how it is not, as the default behaviour is just overwriting the
last color. So for this shader, the lighting for the last light computed
overrides all lights before.
The solution is to use additive blending:
::
However, this is incorrect for the first pass. The first pass needs to stay
a simple "overwrite" blending. But how to toggle the blending for different
passes?
The easiest way is to use Crystal Space's facility to create multiple
variations out of a single shader source, the *shader conditions*. These
are checks such as "does a shader variable exists", "does a shader variable
have a certain type", "does a shader variable have a certain value", which in
itself can be combined with logical operations. The conditions are specified
inside XML processing instructions.
"Overwrite" blending should be applied in the very first pass rendered. This
is the case if two conditions are met: first, the number of the light
rendering pass (passed in the shader variable ``pass number``) is 0; second,
the shader variable ``pass do_ambient``, generally specified in the render
layers setup, is present. Otherwise, additive blending should be used.
This translates to the following Crystal Space shader condition:
::
The complete source is in ``shadertest_6.xml``, the result is:
.. image:: crystal005.png
However, if you compare it with the result image from `Adding Ambient Lighting`_
you will notice that the latest result is lighter, This is because the ambient
contribution is now added multiple times, specifically, once with each pass.
The solution is to only add ambient in the first pass as well. To achieve this
use the same condition used to change the mixmode, only in the Cg code now:
::
...
]]>
OUT.litColor = lightingDiffuse;
OUT.litColor = lightingDiffuse + IN.ambient;
`_
on the Crystal Space home page is a tutorial creating a shader rooted in a
practical problem.