OGRE  1.12.13
Object-Oriented Graphics Rendering Engine
Runtime Shader Generation

Writing shading programs is a common task when developing 3D based application. Most of the visual effects used by 3D based applications involve shader programs. Additionally with D3D11/ GL3, support for fixed pipeline functionality was removed. Meaning you can only render objects using shaders.

While GPU Program Scripts offer you maximal control and flexibility over how your objects are rendered, writing and maintaining them is also a very time consuming task.

Instead Ogre can also automatically generate shaders on the fly, based on object material properties, scene setup and other user definitions. While the resulting shaders are less optimized, they offer the following advantages:

  • Save development time e.g. when your target scene has dynamic lights and the number changes, fog changes and the number of material attributes increases the total count of needed shaders dramatically. It can easily cross 100 and it becomes a time consuming development task.
  • Reusable code - once you've written the shader extension you can use it anywhere due to its independent nature.
  • Custom shaders extension library - enjoy the shared library of effects created by the community. Unlike hand written shader code, which may require many adjustments to be plugged into your own shader code, using the extensions library requires minimum changes.

The system is implemented as a component, so you can enable/ disable it at compile time.

  • RTSS: Run Time Shader System
    The RTSS is not another Uber shader with an exploding amount of #ifdefs that make it increasingly difficult to add new functionality. Instead, it manages a set of opaque isolated components (SubRenderStates) where each implements a specific effect. These "effects" notably include full Fixed Function emulation. At the core these components are plain shader files providing a set of functions. The shaders are based on properties defined in Material Scripts.

Uber shader tips

In case, you are not conviced and want to go with your hand-rolled uber shader, here are some tips:

  1. Ogre supports #include directives universally - even with GLSL, so use them to split up your shader.
  2. The OgreUnifiedShader.h header provides macros to map GLSL to HLSL and (to some extent) Metal. This allows you to write shader code once and use it for multiple rendersystems.
  3. The HLSL_SM4Support.hlsl helper allows mapping HLSL9/ Cg to HLSL SM4 (D3D11), if you only target D3D.

Then you can have a shader skeleton like this:

#ifdef USE_UV
#include "parameters_uv.glsl"
#endif
#ifdef USE_SKINNING
#include <parameters_skinning.glsl>
#endif
...
void main()
{
#ifdef USE_UV
#include <transform_uv.glsl>
#endif
#ifdef USE_SKINNING
#include <transform_skinning.glsl>
#endif
#ifdef USE_TANGENT
#include <construct_tbn.glsl>
#endif
...
gl_Position = ...;
}

then in the material file, you can instanciate it as:

vertex_program TextureAndSkinning glsl
{
source UberShader_vp.glsl
preprocessor_defines USE_UV,USE_SKINNING
default_params
{
...
}
}

and reference it with your materials.

Incidentally, this is very similar to what the RTSS is doing internally. Except, you do not need the preprocessor_defines part, as it can derive automatically from the material what needs to be done.

Historical background

When the early graphic cards came into the market they contained a fixed but large set of functions with which you could influence how 3D object were rendered. These included influencing object positions using matrices, calculating the effect of textures on a pixel, calculating the effect of lights on vertices and so on. These set of functions and their implementation in hardware became later known as the graphic card fixed pipeline (or Fixed Function Pipeline).

As graphic cards became more powerful and graphic application became more complex, a need for new ways to manipulate the rendering of 3D models became apparent. This need saw the introduction of shaders.

Shaders are small custom made programs that run directly on the graphics card. Using these programs, one could replace the calculations that were made by the fixed pipeline and add new functionality. However there was a catch: If shaders are used on an object, the object can no longer use any of the functionality of the fixed pipeline. Any calculation that was used in the fixed pipeline needed to be recreated in the shaders. With early graphics applications this was not problematic. Shaders were simple and their numbers were kept low. However as applications grew in complexity this meant that the need for shaders grew as well. As a programmer you were left with 2 choices, both bad. Either create an exuberant amount of small shaders that soon became too many to effectively maintain. Or create an uber shader, a huge complex shader, that soon became too complex to effectively maintain as well.

The RTSS seeks to fix those problems by automatically generating shaders based on the operations previously required from the fixed pipeline and new capabilities required by the user.

With the introduction of the version 11 of Direct3D, a new reason for having an RTSS like system became apparent. With D3D11 support for fixed pipeline functionality was removed. Meaning, you can only render objects using shaders. The RTSS is an excellent tool for this purpose.