OGRE  13.0
Object-Oriented Graphics Rendering Engine
Cross-platform Shaders

When targeting multiple graphics APIs, one typically needs to provide separate shaders for each API. This results in lots of duplicated code gets out of hand quickly.

To support using the same shader code for multiple APIs, Ogre provides the following mechanisms.

  • #include directives are universally supported - even with GLSL, so you can rely on them to organised your shaders.
  • There are several built-in defines that can be used to conditionally compile different versions of the same shader.

Built-in defines

The following defines are available:

  • The current shading language and native version: e.g. OGRE_GLSL=120, OGRE_HLSL=3
  • The current shader type: e.g. OGRE_VERTEX_SHADER, OGRE_FRAGMENT_SHADER
  • Whether Reversed Depth is enabled: OGRE_REVERSED_Z

Cross-platform macros

Additionally, the OgreUnifiedShader.h provides macros to map GLSL to HLSL and (to some extent) Metal.

As everything is handled by standard macros, the conversion can be performed by simply running the standard c preprocessor (cpp) on them - even without running Ogre.

In general, you have to do the following changes compared to regular GLSL:

  • Add the #include <OgreUnifiedShader.h> directive at the top of the file
  • Use the MAIN_PARAMETERS and MAIN_DECLARATION directives instead of void main()
  • Use the IN/ OUT macros to specify non-uniform parameters that are passed to the main function.
  • Declare Samplers with SAMPLER2D/3D/CUBE/.. macros instead of sampler2D/3D/Cube/..
  • Use mtxFromRows / mtxFromCols to construct matrices from vectors
  • Use the HLSL style mul instead of * to multiply matrices
  • Use vec2_splat(1.0) instead of the vec2(1.0) single component constructor.

Lets take a look on how to use the OgreUnifiedShader.h macros by starting with a simple GLSL shader:

uniform mat4 worldMatrix;
attribute vec4 vertex;
void main()
{
gl_Position = worldMatrix * vertex;
}

to make it cross-platform, we need to modify it as:

#include <OgreUnifiedShader.h>
uniform mat4 worldMatrix;
MAIN_PARAMETERS
IN(vec4 vertex, POSITION)
MAIN_DECLARATION
{
gl_Position = mul(worldMatrix, vertex);
}
Note
If you only target different versions of the same API, there are reduced headers:
  • HLSL_SM4Support.hlsl providing the SAMPLER* macros for mapping HLSL9/ Cg and HLSL SM4 (D3D11)
  • GLSL_GL3Support.glsl providing the IN/ OUT macros and texture aliases for mapping GLSL <= 120 and GLSL >= 130

Uber shader tips

To toggle features on and off use 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 instantiate 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.