Crystal Space Shader Weaver
===========================
The Crystal Space "shader weaver" generates Crystal Space shaders out of a
graph. The weaver has some convenience functions and features, such as
automatic conversion of coordinates and normals between spaces and efficient
culling of vertex-to-fragment data which is not required, and has a very
modular approach to describing shaders, simplifying the creation of complex
shaders.
Prerequisites
-------------
It is recommended to read the "Introduction to Crystal Space Shaders": the
weaver system is working atop the classic XML shader system; some elements
are used verbatim (e.g. bindings), some concepts are used in a similar
fashion (e.g. techniques).
Basic Concept
-------------
The initial motivation for creation of came from so-called `Abstract Shade
Trees `_ wherein
shaders are described by a tree structure. But unlike "classic" trees, where
nodes where operations such as "dot product" or "read texture", the node
operations are relatively high-level. There are only simple connections between
individual nodes and the compiler figures out which inputs and outputs to
match based on their types.
The Crystal Space shader weaver provides the essential feature of Abstract
Shader Trees, the input/output matching and type conversion. However, out of
practical necessity, inputs and outputs can also be manually connected.
Effectively this makes it possible to specify shaders both in a high- and
low-level fashion, solely depending on whether more or less complex nodes are
used in the graph.
Nomenclature and Structure
--------------------------
A "weaver shader" consists, like an XML shader, out of multiple techniques
and passes. However, the format inside the passes is vastly different.
The shader itself is described by a graph. The nodes are called ``snippets``,
links between snippet ``connections``.
A snippet can either be a *compound snippet* or an *atom snippet*.
**Compound snippets** themselves can contain multiple techniques, each
again containing a subgraph describing their behaviour.
**Atom snippets** contain actual shader program code emitted in the actual
shader. An atom snippet consists of multiple *blocks*: putting a meaningful
statement into a shader usually requires changes at multiple places (e.g. for
a constant, you need to declare a variable the constant is assigned to,
bind a shader variable to the program variable, optionally provide a default
value, and finally read out the program variable and put it into a place
known to the generator). This fact is accomodated by these "block"s. Each
block contains a fragment of shader markup or shader program code, inserted
at a place specified in the block definition.
Furthermore, atom snippets declare *input* and *output* values. Each can
possess additional auxiliary information, called *attributes*. Inputs can
optionally have default values.
Combiners
~~~~~~~~~
The shader weaver is, conceptually, not tied to an underlying technology for
writing shaders. The job of combining the different fragments of a shader
program to a whole is delegated to other plugins, named "combiner". Two
combiners are available: a "default" combiner for elements of shaders
independent from the shader program(s) (such as buffer and texture bindings)
and a user-set combiner responsible for generating the actual shader
program(s).
In practice, the only user settable combiner available is one for Cg.
Input Resolution
----------------
Input to a weaver snippet is resolved as follows: first, the outputs of
snippets immediately predecessing the current snippets are considered.
An output is not considered twice for a node - ie when an output was
already matched to another input of the current snippet it is not considered
dor automatic matching again.
When there is a direct match between the type of an input and an output, the
matching output is used. Otherwise, a conversion from the type of the output
and the input is attempted. If such a conversion exists, its "cost" is
reported. When all outputs were checked, the one with the lowest cost is
used. However, if no output can be converted, the search continues with
all snippets predecessing the predecessor and so on. If no output is found in
any of the checked snippets a user-specified default is used. If no default
was given the input value is undefined.
.. @@@ TODO private inputs. Other properties?
"Output" Snippets
~~~~~~~~~~~~~~~~~
Any shader essentially needs to output two values: a coordinate in camera
space, used by the hardware to determine the rasterization coordinates, and a
color, used by the hardware to determine the color to raster.
The weaver looks for outputs for a color value and a (camera) position value,
starting at all snippets which are not serving as input to other snippets.
It scans the tree upwards until the needed output values are found. They are
then used to provide a camera coordinate and a raster color for the hardware.
Basic Weaver Shader
-------------------
As "classic" Crystal Space shaders, weaver shaders are written in XML. The
outermost element must be ``shader``. A ``name`` attribute must be present as
well. The ``compiler`` attribute must have the value "shaderweaver",
identifying the shader as handled by the ``shaderweaver`` plugin.
A weaver shader also consist of multiple techniques, each containing multiple
passes.
The skeleton for a weaver shader is:
::
...
Setting the combiner
~~~~~~~~~~~~~~~~~~~~
In a ``pass`` node, the combiner(s) to be used when generating the shader must
be specified. In case an atom snippet uses multiple combiners this selects
which of the blocks targetted at the different combiners are used.
However, currently the only available combiner is the Cg one.
The syntax for setting a combiner is:
::
Adding Minimal Nodes
~~~~~~~~~~~~~~~~~~~~
As described in `"Output" Snippets`_, we need to have at least a position
output node and some color output node to get any result from the shader.
Among the snippet shipping with Crystal Space are of course ones for these
basic tasks. The ``position`` snippet transforms the vertex position to camera
space; the ``surface-classic`` snippet reads a color from the "diffuse"
texture.
::
The ``file`` attribute identifies the snippet source file. The ``id`` attribute
assigns each snippet a unique name: this is later needed for specifying
connections between snippets.
The shader as a whole looks like this (``simpleweaver1.xml``):
::
The result:
.. image:: crystal000.png
Adding Lighting
~~~~~~~~~~~~~~~
The stock snippets also contain snippets for lighting. These are split into two
parts: first, a snippet for the *lighting computation* itself. These are
realized in different techniques - presently, snippet for per-vertex and
per-fragment lighting are available.
The second part is the *light application*. This snippet basically takes
various inputs - light diffuse color. surface diffuse color, light specular
color, surface specular color, ambient color - and combines them into a
single lit color value.
To include the necessary lighting snippets:
::
Notably the ``apply_lighting`` snippet has multiple inputs.
To that end, connections between the snippets providing the data and
``apply_lighting`` snippet and the snippets providing the source data are
needed. Currently, we have a diffuse surface color, provided by the ``surface``
snippet, and a light diffuse color, provided by the ``lighting`` snippet [#]_.
Also, the ``apply_lighting`` snippet has multiple inputs of the same type -
thus explicit connections are used to disambiguate.
Inspect the contents of the snippet XML files to determine the available
outputs and inputs. This leads to the following connections:
::
Each connection is specified by a ``connection`` element. The ``from`` and
``to`` attributes contain the IDs of the snippets to connect; the output values
of the "from" snippets are connected with the input values of the "to" snippet.
The connection between "position" and "lighting" has no sub-elements - this
means the automatic input/output matching is used.
The connections to "apply_lighting", on the other hand, have ``explicit``
children - these explicitly map outputs to inputs, avoiding ambiguities and
resolving mistaken input/output matches performed by the weaver.
Finally, the "lighting" snippet needs the subset of lights to light as input.
This is achieved with the following elements:
::
0
4
``parameter`` values are simple constants or values sourced from shader
variables that can be connected to snippets to serve as inputs. Here, two
constants for the first light to handle [#]_ and the number of lights to
handle are passed to the "apply_lighting" snippet.
Set the ``lights`` attribute of the ``shader`` node to 4.
.. [#] Actually, the ``lighting`` snippet also provides a specular color.
.. [#] Setting a different first light is useful when doing multiple lighting
passes.
The complete shader is in ``simpleweaver2.xml``. The result is:
.. image:: crystal001.png
Adding a Normal Map
~~~~~~~~~~~~~~~~~~~
The lighting snippet computes lighting per fragment [#]_, but it doesn't look
like that. Using per-fragment normals would be nice. To that end, we merely
need to add a snippet that provides normals from a normal map and connect that
as input to the ``lighting`` snippet:
::
The complete shader is in ``simpleweaver3.xml``. The result is:
.. image:: crystal002.png
In contrast, adding normal map support in a "classic" shader would have
required manual bindings for normal, tangent and bitangent, normal map texture,
and additional code to translate directions from or to tangent space and
pushing some of that information from vertex to fragment program. To be honest,
that is all still done - however, conveniently and reusably packaged into the
``normalmap.xml`` [#]_.
.. [#] The "ppl" in ``lighting-ppl.xml`` stands for - the slightly inaccurate -
"per pixel lighting".
.. [#] Actually, multiple snippets working together, and generated shader
code.
Adding More Advanced Effects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One of the benefits of using a shader weaver is the ability to make creation
of complex effects easier. Say, for example, you want to blend the surface to
a reflection of the environment, based on the fresnel factor for a point.
This can be done achieved with a couple more snippets and connections:
::
The complete shader is in ``simpleweaver4.xml``. The result is:
.. image:: crystal003.png