The Flexible Rendering Pipeline
Introduction
The rendering pipeline defines the steps which are necessary to bring the abstract 3D data to a visible result on
the screen. Horde3D has a configurable rendering pipeline which makes it possible to employ a plenty of different
rendering techniques, including standard forward rendering and deferred shading. The system allows to define several
render targets and to specify render commands to fill the targets with data. This gives much power and flexibility to
the engine and enables the creation of most post processing effects like HDR, motion blur or depth of field as well as
the usage of different rendering algorithms.
System Overview
Horde is heavily based on shaders which are small programs that are executed on the graphics card at different stages
of the hardware pipeline. Horde supports vertex, fragment, geometry, tesselation and compute shaders. Vertex shaders influence
directly the geometry which is rendered and determine e.g. the position and texture coordinates of the vertices.
The fragment shaders are used for calculating the pixel colors in the rasterization process. Geometry shaders are used
for modifying geometry in shaders, for example, procedurally creating a line. Tesselation shaders can be used for dynamic
level of detail (LOD) of geometry objects based on the camera position. Compute shaders are used for async computing that
may even be not related to rendering - some projects use compute shaders for ai processing.
Shader code in Horde is specified in the OpenGL Shading Language (GLSL). The engine has a text based shader format
which makes it possible to define different contexts. A context of a Horde shader is defined for a situation in the
rendering process where the shader is executed.
For example, a shader usually has a shadowmap context which is used when shadows are generated and a lighting context
which is responsible for interactions of an object with light sources. For the rendering process shaders usually
require some input data. This consists on the one hand of the geometric data of a model like the vertex positions,
normals and texture coordinates. On the other hand there are textures used for rendering and arbitrary variables,
so called uniforms, which can be defined by the user. For example a uniform could be used to define a force vector
to do some wind physics for a tree model in the vertex shader. This mutable data is bound to the shader via the
concept of materials. A material in Horde consists of a shader and a list of texture maps and uniforms which are
assigned to that shader.
Lighting and Shadows
Horde supports basically two different approaches for lighting namely forward and deferred shading. Forward shading
is the standard technique that is used in many applications. With this technique the geometry is rendered
once for each light source using a special fragment shader that calculates the light contribution. The disadvantage here
is of course that the geometry has to be drawn several times which can result in poor performance in scenes with
many polygons and light sources. Deferred shading in contrast does the lighting as a post processing step. The idea
is to store some attributes for each pixel on the screen in a special buffer often called the G-Buffer. These attributes
usually include the position of a fragment, the normal and its color. To do the actual lighting it is only necessary to
draw a fullscreen quad since the required information can easily be read from the G-Buffer. The advantage is that the
lighting performance is completely independent of the geometry complexity now since each light just requires drawing
a screen-space quad. Unfortunately this approach has also its drawbacks as it is difficult to do anti-aliasing or
handle translucent geometry. Discussing the two techniques in detail here would be out of the scope of this manual so
please refer to one of the many online and book resources available for this topic.
Light sources in Horde are defined as scene nodes. Each light node has two special attributes called lightingContext
and shadowContext. The values of these attributes correspond to shader context names defined in the shader resources.
When Horde is instructed to perform forward lighting the engine first builds a list of the geometry that needs to be
drawn. After that it draws each object from that list using the shader context that is specified for the light source.
If the context cannot be found for a material, the object using this material is ignored and thus not rendered. This
is basically working the same way for doing the lighting and building the shadow map.
For deferred shading you also need to specify a material for the light source. The shader defined in that material is
used to draw the screen-space quads where the light's lightingContext attribute specifies the shader context which is
used. The calculation of the shadows is working the same way as for forward shading where all geometry is rendered to
the shadow map using the shadowContext. When doing forward shading you could also specify a material for a light source.
The shader is ignored in this case but it is possible to bind several textures which can for example be used to create
some sort of slide-projector.
With the shader context system it is possible to define arbitrary light source types. The behavior of a light (e.g.
directional or spot) and its interaction with a material are defined entirely in the shaders. If you want to create
a new type of light source you just have to assign a name to the lightingContext attribute and define an appropriate
shader context for all the materials (respectively shaders) that shall interact with that light source.
Pipeline Configuration
The flexible rendering pipeline allows to define render targets and commands which determine the steps taken
to render the scene. The commands are specified within a XML file. Most rendering commands use the attributes
class and context. The class is defined in the materials and determines what geometry should be
rendered. It is possible to use the tilde operator ~ as a logical NOT meaning that all geometry except
the one with the specified material class will be drawn. The context is finally available for specifying the
rendering technique which should be used for the current draw call.
Pipeline Syntax
The following XML elements and attributes are supported.
Pipeline |
root element of the document {1}
|
Setup |
initialization section of pipeline {0,1}
|
RenderTarget |
definition of a render target; child of Setup element {*}
id |
unique name of the render target {required} |
depthBuf |
flag specifying whether depth buffer is used for target {required}; possible values: true, false |
numColBufs |
number of color buffers {required}; possible values: 0, 1, 2, 3, 4 |
format |
pixel format of render target {optional}; possible values: RGBA8, RGBA16F, RGBA32F; default: RGBA8 |
width |
width of render target in pixels where 0 means width of the main framebuffer {optional}; default: 0 |
height |
height of render target in pixels where 0 means height of the main framebuffer {optional}; default: 0 |
scale |
scale factor which is multiplied with the size of the render target {optional}; default: 1.0 |
maxSamples |
the maximum number of samples used when anti-aliasing is enabled {optional}; default: 0 |
|
CommandQueue |
ordered list of commands {0, 1}
|
Stage |
definition of a set of render commands; child of CommandQueue element {*}
id |
unique name of the stage {required} |
enabled |
flag indicating whether stage is enabled by default {optional}; default: true |
link |
material resource used to bind stage-specific data {optional}; default: empty string |
|
SwitchTarget |
command for setting the currently active render target to which data is rendered; the command implicitely
calls the UnbindBuffers command before the desired render target is activated; child of Stage element {*}
target |
name of the render target which was defined in the Setup section or empty string to bind the output buffer
assigned to the current camera {required} |
|
BindBuffer |
command for binding a color or depth buffer of a render target as texture map; child of Stage element {*}
sampler |
name of texture sampler to which the buffer is bound {required} |
sourceRT |
name of render target {required} |
bufIndex |
index of color buffer or 32 as special value for binding the depth buffer {required} |
|
UnbindBuffers |
command for unbinding all buffers that were bound to texture samplers before;
child of Stage element {*}
|
ClearTarget |
command for clearing the currently bound render target; child of Stage element {*}
depthBuf |
flag specifying whether depth buffer is cleared {optional}; possible values: true, false; default: false |
colBuf0 |
flag specifying whether first color buffer is cleared {optional}; possible values: true, false; default: false |
colBuf1 |
flag specifying whether second color buffer is cleared {optional}; possible values: true, false; default: false |
colBuf2 |
flag specifying whether third color buffer is cleared {optional}; possible values: true, false; default: false |
colBuf3 |
flag specifying whether fourth color buffer is cleared {optional}; possible values: true, false; default: false |
col_R |
red component of clear color {optional}; default: 0.0 |
col_G |
green component of clear color {optional}; default: 0.0 |
col_B |
blue component of clear color {optional}; default: 0.0 |
col_A |
alpha component of clear color {optional}; default: 0.0 |
|
DrawGeometry |
command for rendering the scene geometry; child of Stage element {*}
context |
name of the shader context used for rendering {required} |
class |
material class used for including/excluding objects {optional}; default: empty string, meaning all classes |
order |
rendering order (sorting) of scene nodes {optional}; values: NONE, STATECHANGES, FRONT_TO_BACK, BACK_TO_FRONT; default: STATECHANGES |
|
DrawOverlays |
command for rendering all overlays; child of Stage element {*}
context |
name of the shader context used for rendering {required} |
|
DrawQuad |
command for drawing a fullscreen quad to the screen; child of Stage element {*}
material |
material resource used for rendering {required} |
context |
name of the shader context used for rendering {required} |
|
DoForwardLightLoop |
command for performing forward lighting by rendering all affected geometry; the default shader context
used for rendering is the lighting context attribute of the corresponding light source;
child of Stage element {*}
class |
material class used for including/excluding objects {optional}; default: empty string, meaning all classes |
context |
shader context used for doing lighting {optional}; default: empty string, meaning context assigned to light source |
order |
rendering order (sorting) of scene nodes {optional}; values: NONE, FRONT_TO_BACK, BACK_TO_FRONT, STATECHANGES; default: NONE |
noShadows |
flag for disabling shadowing {optional}; default: false |
|
DoDeferredLightLoop |
command for performing deferred lighting by drawing screen-space quads; the default shader context used for rendering
is the one which is stored as attribute of the corresponding light source;
child of Stage element {*}
context |
shader context used for doing lighting {optional}; default: empty string, meaning context assigned to light source |
|
SetUniform |
command for setting a material uniform to specified values; child of Stage element {*}
material |
material resource which contains the uniform to be set {required} |
uniform |
name of the uniform {required} |
a |
value of the first component {optional}; default: 0.0 |
b |
value of the second component {optional}; default: 0.0 |
c |
value of the third component {optional}; default: 0.0 |
d |
value of the fourth component {optional}; default: 0.0 |
|
Sample showing simple deferred shading pipeline
<Pipeline>
<Setup>
<RenderTarget id="GBUFFER" depthBuf="true" numColBufs="3" format="RGBA16F" scale="1.0" />
</Setup>
<CommandQueue>
<Stage id="Attribpass">
<SwitchTarget target="GBUFFER" />
<ClearTarget depthBuf="true" colBuf0="true" />
<DrawGeometry context="ATTRIBPASS" class="~Translucent" />
</Stage>
<Stage id="Lighting">
<SwitchTarget target="" />
<ClearTarget colBuf0="true" />
<BindBuffer sampler="gbuf0" sourceRT="GBUFFER" bufIndex="0" />
<BindBuffer sampler="gbuf1" sourceRT="GBUFFER" bufIndex="1" />
<BindBuffer sampler="gbuf2" sourceRT="GBUFFER" bufIndex="2" />
<DrawQuad material="light.material.xml" context="AMBIENT" />
<DoDeferredLightLoop />
<UnbindBuffers />
</Stage>
<Stage id="Overlays">
<DrawOverlays context="OVERLAY" />
</Stage>
</CommandQueue>
</Pipeline>
Shaders
Horde3D shaders are stored in text files with the extension .shader. A shader file consists of different
sections: one FX section and an arbitrary number of named code sections. A section is introduced by the tag
[[id]] where id is a unique identifier used as name of the section. A special identifier is
FX which introduces an FX section.
Horde3D supports Ubershaders using static branching (compiler defines) and can automatically compile different
shader combinations based on a set of given shader flags. This approach helps to solve a common problem:
imagine you have one shader that does skinning and one that does parallax mapping. Now you want a shader that does
both. Usually you would have to create a new shader file which combines the code of the two other shaders. It
is quite obvious that you can quickly get many permutations of features and that writing different shader
files for them can become very tedious or even unmanagable. With the help of Ubershaders, the shader creation workflow can
be greatly simplified. You just need one shader file where the features are exposed with compiler defines. A feature
is enabled by setting the corresponding flag (e.g. in a material file). Horde3D can check automatically if a
desired combination is not yet existing and will compile it on the fly. Please note, even though Ubershaders are
a powerful feature, they need to be used with care since it is easy to generate a huge number of combinations which
would take a long time to load.
FX Section
The FX section defines and configures the shader contexts, custom texture samplers and custom (user-defined)
uniforms used in hardware shaders. Please note that it is strictly required that all custom samplers and
uniforms referenced in hardware shaders are declared in the FX section so that they can be used with the
Horde3D material system.
The Horde3D FX syntax is similar to that of CgFX. Standard C++ block and single line comments are supported.
All keywords and enums are case insensitive. However, identifiers like the name of a context are case
sensitive. Identifiers may only use alphanumeric characters and the underscore. Floating point numbers
may not contain any whitespace.
Elements can optionally store meta information as annotations. This is useful for better tool support.
For example, a uniform could store a minimum and maximum value range. It is suggested that annotations have
the form required by CgFX, hence datatype identifier = value; However, this is not a requirement since
annotations are just skipped by Horde's parser.
It is possible to specify different parameters for specific render interfaces (currently only contexts can be redefined).
If render interface tag is not found, parameter is shared for all render interfaces. In case of contexts OpenGL 2
render device is used by default.
Supported render interfaces:
OpenGL2 - default render interface (no tag required) |
OpenGL4 |
Sample: using different shaders for various render interfaces
context AMBIENT
{
VertexShader = compile GLSL VS_GENERAL;
PixelShader = compile GLSL FS_AMBIENT;
}
OpenGL4
{
context AMBIENT
{
VertexShader = compile GLSL VS_GENERAL_GL4;
PixelShader = compile GLSL FS_AMBIENT_GL4;
}
}
The following syntax is supported. Optional elements are put in square brackets and default values are
highlighted as bold. Alternative values are separated by a slash.
sampler2D / samplerCube / sampler3D [< annotation(s) >] id |
[ = sampler_state { |
[ Texture = "TextureResName" ; ] |
[ TexUnit = -1 / 0 / 1 / 2 / 3 / 4 / 5 / 6 / 7 / 8 / 9 / 10 / 11 ; ] |
[ Address = Wrap / Clamp ; ] |
[ Filter = None / Bilinear / Trilinear ; ] |
[ MaxAnisotropy = 1 / 2 / 4 / 8 / 16 ; ] |
[ Usage = Texture / ComputeImageRO / ComputeImageWO / ComputeImageRW ; ] |
} ] ; |
Remarks:
- The specified sampler type (2d, 3d, cube) and the bound texture resource type have to match
- Texture defines a default texture that is used if no texture map is specified in a material; the initial value for sampler2D is
$Tex2D (white default texture), $Tex3D (white default texture) is for sampler3D and $TexCube (default cube map with black faces) for samplerCube
- TexUnit defines the texture unit used by the sampler; use -1 for automatic assignment
- Aliasing for texture units is possible, meaning samplers can share the same texture unit if
they are not used in the same shader context
- Texture can be set to act as a storage via Usage parameter. Can be used in compute and fragment shaders only. Up to 8 textures can be used for compute purposes simultaneously.
Available in OpenGL 4 render interface.
float [< annotation(s) >] id [ = a ] ; |
float4 [< annotation(s) >] id [ = { a, b, c, d } ] ; |
Remarks:
- Declares and optionally initializes a uniform
- The default value for each component is 0.0
buffer [< annotation(s) >] id; |
Remarks:
- Declares a buffer that can be used for reading and writing (specified in code section). Used primarily for compute shaders
context [< annotation(s) >] id |
{ |
VertexShader = compile GLSL codesection ; |
PixelShader = compile GLSL codesection ; |
GeometryShader = compile GLSL codesection ; |
ComputeShader = compile GLSL codesection ; |
TessEvalShader = compile GLSL codesection ; |
TessControlShader = compile GLSL codesection ; |
[ ZWriteEnable = false / true ; ] |
[ ZEnable = true / false ; ] |
[ ZFunc = Always / Equal / Less / LessEqual / Greater / GreaterEqual ; ] |
[ BlendMode = Replace / Blend / Add / AddBlended / Mult ; ] |
[ BlendMode = { Zero / One / SrcAlpha / OneMinusSrcAlpha / DestAlpha / OneMinusDestAlpha / DestColor / SrcColor / OneMinusDestColor / OneMinusSrcColor }; ] |
[ CullMode = Back / Front / None ; ] |
[ AlphaToCoverage = false / true ; ] |
[ TessPatchVertices = 1 / .. / 32 ; ] |
} |
Remarks:
- Shaders reference a code section that must be defined in the same FX file
- Context can contain either vertex, pixel, geometry, tesselation shaders or compute shader
- Geometry and tesselation steps are optional
- Geometry, compute and tesselation shaders are available only for OpenGL4 render interface
- TessPatchVertices - number of vertices per patch. Used for tesselation. Available only for OpenGL4 render interface.
- BlendMode may either be set the old way - with one word definition, or by using a new form, i.e BlendMode = { One, One } for more blending control.
Code Section
A code section contains the GLSL code. Besides the usual GLSL keywords, Horde3D introduces an additional preprocessor
command: #include. The include directive is used to insert the content of a code resource at the corresponding
location. The name of the code file needs to be enclosed by a pair of quotation marks.
The code section can also contain the shader flags used for the automatic combination generation. The shader flags
have a special naming convention: _F<digit><digit>_<name>.
The following would be a valid flag: _F06_MyFlag. The flag must have a number between 01 and 32 (note the
leading zero). This number is exclusively used to identify the flag. The name is optional and just exists for
convenience reasons in order to improve the code readability.
Sample
[[FX]]
sampler albedoMap = sampler_state
{
Filter = Bilinear;
};
float4 brightness = {0.5, 0, 0, 0};
context OVERLAY
{
VertexShader = compile GLSL VS_OVERLAY;
PixelShader = compile GLSL FS_OVERLAY;
}
[[VS_OVERLAY]]
// ============================================================================
uniform mat4 projMat;
attribute vec2 vertPos;
attribute vec2 texCoords0;
varying vec2 texCoords;
void main( void )
{
texCoords = vec2( texCoords0.s, -texCoords0.t );
gl_Position = projMat * vec4( vertPos.x, vertPos.y, 1, 1 );
}
[[FS_OVERLAY]]
// ============================================================================
uniform vec4 brightness;
uniform sampler2D albedoMap;
varying vec2 texCoords;
void main( void )
{
vec4 albedo = texture2D( albedoMap, texCoords );
#ifdef _F07_BrightMult
albedo *= brightness.a;
#endif
gl_FragColor = albedo;
}
Predefined GLSL Uniforms and Attributes
Horde defines some standard uniforms and attributes which can be used by shaders. The engine automatically detects
which input data is required and binds it to the shader programs.
Note: Not all uniforms and attributes are available for every pipeline step, e.g. light source
parameters are only available when doing lighting calculations and particle specific data only when
rendering emitters.
General vector/matrix uniforms
uniform vec2 frameBufSize |
dimensions (width and height) of the currently active frame buffer |
uniform mat4 viewMat |
viewing matrix |
uniform mat4 viewMatInv |
inverse viewing matrix |
uniform mat4 projMat |
projection matrix |
uniform mat4 viewProjMat |
premultiplied view and projection matrix |
uniform mat4 viewProjMatInv |
inverse view-projection matrix |
uniform vec3 viewerPos |
position of the viewer (virtual camera) |
uniform vec4 lightPos |
position of the light source in xyz components and radius in w |
uniform vec4 lightDir |
direction vector of the light source in xyz components and cosine of light FOV in w (for spotlights) |
uniform vec3 lightColor |
(diffuse) color of the light source |
uniform vec4 shadowSplitDists |
split distances determining which of the four shadow maps has to be sampled |
uniform mat4 shadowMats[4] |
light transformation matrices for individual shadow maps |
uniform float shadowMapSize |
size of the shadow map texture in pixels |
uniform float shadowBias |
bias used for shadow mapping to reduce precision issues |
General sampler uniforms
uniform sampler2D shadowMap |
shadow map texture |
Per-instance vector/matrix uniforms
uniform mat4 worldMat |
matrix used for transforming vertex positions of currently rendered mesh to world space |
uniform mat3 worldNormalMat |
matrix used for transforming tangent space basis of currently rendered mesh to world space |
uniform float nodeId |
identifier value of currently rendered node (default value is node handle) |
uniform vec4 customInstData[4] |
custom per-instance node data; only available for models |
uniform vec4 skinMatRows[i*3] |
first three rows of skinning matrices for skeletal animation;
fourth row is always (0, 0, 0, 1); only available for models. Currently, for OpenGL2 i = 75, for OpenGL4 i = 330. |
Overlay specific vector/matrix uniforms
uniform vec4 olayColor |
color of overlay |
Particle specific vector/matrix uniforms
uniform vec3 parPosArray[64] |
position array of particle batch |
uniform vec2 parSizeAndRotArray[64] |
combined size and rotation array of particle batch |
uniform vec4 parColorArray[64] |
color array of particle batch |
General vertex attributes
Attribute keywords vary in different render interfaces. In OpenGL 2 it is attribute, in OpenGL 4 it is in.
attribute/in vec3 vertPos |
vertex position |
attribute/in vec2 texCoords0 |
first set of texture mapping coordinates |
Model specific vertex attributes
attribute/in vec2 texCoords1 |
second set of texture mapping coordinates |
attribute/in vec3 normal |
normal vector of vertex |
attribute/in vec4 tangent |
tangent vector of vertex in xyz; handedness multiplicator (1.0, -1.0) of bitangent in w |
attribute/in vec4 joints |
four joint indices referencing to skinning matrices |
attribute/in vec4 weights |
four vertex weights for the four joint indices |
Particle specific vertex attributes
attribute/in float parIdx |
index of current particle in position, size and color arrays |
Remarks:
- The vertex position is usually available in the vec3 attribute vertPos. An exception are overlays
where the position has just 2 components (x and y).
- The camera transformation is stored in viewMat. It is also available when rendering a fullscreen
quad (useful for some post-processing effects).