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.