How To Start
The simplest way is taking an existing shader that is close to your needs and add / remove elements. Creating a shader from scratch can be difficult.
To load an existing shader you simply open it from the File Browser. Alternatively you can open one that is already wired into your compound by Alt + Doubleclicking on it.
Never modify an existing shader. Always save it with a new name, but never into the Common libraries. Use one of your own project folders. Read more about this here.
Architecture of a shader
As an example let’s open the Norm_Spec.xshad which is a material shader and supports Normal and Specular maps. Read more about shader categories and naming in Shader Categories and Naming Convention.
Material shaders always have two main sections: Vertex shader, for calculations on each vertices of the rendered mesh, and Pixel shader, for calculations on each pixel of the rendered polygon.
Note that shaders that require automatic tesselation of the mesh (for e.g. the Displace shaders) uses further blocks named Hull shader (for controlling the tesselation) and Domain shader (for vertex calculations of the tesselated mesh). But we won’t go into details regarding these in this documentation.
Property and wire data types used in a shader graph are very similar to the ones in compounds. But there are important differences. Please read more on the data types in Pin Data Types (Compound and Shader), look for the Shaders section.
In most cases this module does not need modification. If you’re not interested in its technical details, you can skip to Pixel shader.
Enter the Vertex module with a double-click.
The vertex shader runs on each vertex of the mesh. Its main task is to transform the vertex positions and normals into world and camera spaces using matrices coming from the render system. It can do other transformations as well.
You can get the all the data stored in a vertex from the Material Vertex Input module.
As you can see the World Transformation matrix is also coming from here, meaning it’s not a global system value, but a per-vertex value. The reason for this is that if you use a skinned mesh the actual world transformation is blended from multiple bones, thus each vertex can have a different world transformation.
After that the vertex position, normal and tangent vectors have to be transformed using the world transformation. The vertex position is also have to transformed into another space as well, the Camera Projection Space. Please enter the Position & Normal box if you’re interested in the details.
Tangent and Binormal together with the Normal constitute the base vectors of the Tangent Space at the givel vertex. We won’t go into details here, the point is that these vectors are needed when we’re using a Normal Map.
The results are: Proj Position, which is the final position of the vertex on the 2D screen; World Position, which is the position of the vertex in world space; and World Normal, Tangent, Binormal, which are the tangent space vectors expressed in world space coordinates. The latter four can be used in various ways, but their main usage is in the lighting calculations.
There are some additional properties named Prev xxxx, they represent the corresponding data of the previous rendered frame. They’re used when a Velocity Map has to be produced. Not Jittered xxxx positions are used when TXAA or a similar anti-aliasing method is used. We’re not going into details here.
Texture Coordinates (UV) also can be transformed here.
As you can see the transformation itself is coming from an exposed pin named Tex Coord Transf here. Exposions like this will appear on the shader module when the shader is imported into a compound. Thus user will have full control over the values of these input, in this case he can specify an arbitrary transformation for the UV.
Note that a vertex can have two sets of Texture Coordinates. The second one usually used with global textures like Light Map. Since this particular shader does not support Light or similar maps, it’s not needed to forward Tex Coords 2 to the ouput.
The above results must be fed into a Material Vertex Output module.
These outputs are used like this. The system renders the meshes triangle by triangle. For each triangle the vertex shader is run on all the three vertices. Based on the calculated Proj Position of the vertices the system start the render the triangle on the screen, pixel by pixel. For each pixel the pixel shader is run with input values interpolated from the three vertex outputs of the vertex shader.
Enter the Pixel module with a double-click.
Pixel shaders run on each rendered pixel of the mesh surface using the interpolated data coming from the vertex shader as described above.
You can get the interpolated from the Material Pixel Input module.
As you can see some of the data is presented in a slightly different way than in the vertex shader to make the usage easier. For e.g. the tangent space vectors are not provided individually but are already unified into a Tangent To World matrix that can be used directly to transform the Normal Map vectors right away.
The next importart group of modules are the texture samplers. They have Texture properties that are exposed. After importing the shader into a compound these pins will appear as video input pin enabling wiring textures into them. A Sample module extracts a color from a texture at a specific location determined by the Texture Coordinates (UV). These are usually directly wired from the interpolated Tex Coords input of the pixel shader.
The third input is a Sampler State which describes the mode of the sampling: the filtering, the addressing modes etc.
The Sampler outputs the RGB Color and the alpha channel (Opacity) separately. Note that the Aximmetry node system expects premultipled RGB throughout the entire graph, therefore Color Maps will also arrive in this format. Therefore Unpremultiply is advised to be ON if you want to handle transparent colors properly in the shader.
This is not the case for the Normal Map. It must not be unpremultiplied, and we only need its RGB part.
Colors then can be modulated by will either by factors exposed to the user or by factors coming from the vertex shader.
But Normal Map values have to be handled very differently. These represents 3D normal vectors instead of colors. So firstly the color value coming from the texture have to be converted into a normal vector, secondly this vector have to be transformed into world space to enable correct light calculations. Both function is fullfilled by the Tex To Normal module.
Its Transformation is coming from the Tangent To World input of the pixel shader, we already mention this above.
If you expect BC5 (a.k.a. 3Dc) format textures as normal maps, you also have to turn on Calc Norm Z'. Otherwise it should be OFF.
Now we have all the data necessary for lighting calculations.
These are separated into two phases. There is a Material Precalc module that makes some preliminary calculations. The reason for this that these calculated data are not only useful for lighting calculations, but also with other special operation. For e.g. if you want reflection you’ll need Reflection Direction and Fresnel Value. For lighting only Fresnel Value is needed.
The second, much larger phase is performed by the Lighting module. It computes the effect of all the light sources of the scene on the pixel we are working on, taking its world position, normal etc. into account, and also applying user-specifiable parameters like Ambent, Diffuse etc. colors of the material.
The calculated Specular highlight of the pixel comes out separately in the Additive output. The reason for this is that it have to be handled specially on transparent materials: it must not be faded by the Opacity value, the highlight have to shine on an almost completely transparent surface (like glass) as well. (If you do reflection, it also have to be added to the Additive output, see the shader Norm_Refl.xshad.)
All the final rendering step into the target image(s) - taking color, opacity, additive color, highlight, normal etc. into account - is performed by the Material Pixel Output module. It can write multiple targets simultaneously: final color image, normal map, velocity map, mask. These are the result coming from the Camera module you use for rendering the scene.
An example modification
Suppose you want to add an additional texture that is overlayed on the Color Map. Let’s call it Overlay Map. It has to be sampled by the secondary UV set of the mesh with an arbitrary Tex Coord Transformation.
Firstly save the shader as a different file named for e.g. Norm_Spec_Overlay.xshad somewhere in your project folders.
You have to modify the vertex shader to make use of Tex Coord 2 of the vertex. First simply wire it directly to the output:
Insert a Transform module and expose its Transformation to provide an UV transformation function.
You need a new Sampler for the Overlay Texture. Name it Sample Overlay. It’s Texture input must be exposed.
Its Tex Coords must be wired from the Tex Coords 2 input which is an interpolated value from the corresponding vertex shader value you provided before. The Sampler State can be the same as with the other textures.
Now you only have to blend its output with the Color Map’s output. Use the Blend module with Overlay mode.
That’s it. This is a basic example, but shows the main steps well.
Of couse you can do any color, vector or scalar modifications on any inputs as you want using the Math modules.