Back in the days when I had to write some more advanced shaders for my final university project, I realized that the current system has some limitations which make it not very convenient to use for more complex shaders. So I started experimenting with some new ideas. We finally decided now that it would be a good idea to take them over to Horde 1.0, even though it is a major change in the way shaders are written.
The updated shader system has three new main features:
- Shader code is no longer written in the CData section of an XML file. This makes the shader files cleaner and more readable. Contexts are defined in a special FX section (still in XML). The change also makes it possible to share code within a single shader file.
- The shader system has a recursive #include mechanism; the days of the XML InsCode and DefCode elements are over. Code resources can also include other code resources now.
- Automatic shader combination generation (ubershaders) based on flags: this is the most important change. You can now use #defines for flags with a special naming convention in your shaders and set them in material files. Horde will automatically compile the required shader combinations for you. This can save you from a lot of code duplication and makes it much easier for users to enable and disable shader features.
Here is an excerpt from the new model.shader which is some sort of standard shader used for geometry. It looks much nicer with proper syntax highlighting (I have a style definition for Notepad++ if anyone is interested).
Code:
[[FX]]
<!--
// =================================================================================================
Model Shader
Supported Flags:
_F01_Skinning
_F02_NormalMapping
_F03_ParallaxMapping
// =================================================================================================
-->
<Context id="ATTRIBPASS">
<Shaders vertex="VS_GENERAL" fragment="FS_ATTRIBPASS" />
</Context>
<Context id="SHADOWMAP">
<Shaders vertex="VS_SHADOWMAP" fragment="FS_SHADOWMAP" />
</Context>
<Context id="LIGHTING">
<RenderConfig writeDepth="false" blendMode="ADD" />
<Shaders vertex="VS_GENERAL" fragment="FS_LIGHTING" />
</Context>
<Context id="AMBIENT">
<Shaders vertex="VS_GENERAL" fragment="FS_AMBIENT" />
</Context>
[[VS_GENERAL]]
// =================================================================================================
#ifdef _F03_ParallaxMapping
#define _F02_NormalMapping
#endif
#include "utilityLib/vertCommon.glsl"
#ifdef _F01_Skinning
#include "utilityLib/vertSkinning.glsl"
#endif
uniform vec3 viewer;
attribute vec2 texCoords0;
attribute vec3 normal;
#ifdef _F02_NormalMapping
attribute vec3 tangent, bitangent;
#endif
varying vec4 pos, vsPos;
varying vec2 texCoords;
#ifdef _F02_NormalMapping
varying mat3 tsbMat;
#else
varying vec3 tsbNormal;
#endif
#ifdef _F03_ParallaxMapping
varying vec3 eyeTS;
#endif
void main( void )
{
#ifdef _F01_Skinning
mat4 skinningMat = calcSkinningMat();
mat3 skinningMatVec = getSkinningMatVec( skinningMat );
#endif
// Calculate normal
#ifdef _F01_Skinning
vec3 _normal = calcWorldVec( skinVec( normal, skinningMatVec ) );
#else
vec3 _normal = calcWorldVec( normal );
#endif
// Calculate tangent and bitangent
#ifdef _F02_NormalMapping
#ifdef _F01_Skinning
vec3 _tangent = calcWorldVec( skinVec( tangent, skinningMatVec ) );
vec3 _bitangent = calcWorldVec( skinVec( bitangent, skinningMatVec ) );
#else
vec3 _tangent = calcWorldVec( tangent );
vec3 _bitangent = calcWorldVec( bitangent );
#endif
tsbMat = calcTanToWorldMat( _tangent, _bitangent, _normal );
#else
tsbNormal = _normal;
#endif
// Calculate world space position
#ifdef _F01_Skinning
pos = calcWorldPos( skinPos( gl_Vertex, skinningMat ) );
#else
pos = calcWorldPos( gl_Vertex );
#endif
vsPos = calcViewPos( pos );
// Calculate tangent space eye vector
#ifdef _F03_ParallaxMapping
eyeTS = calcTanVec( viewer - pos.xyz, _tangent, _bitangent, _normal );
#endif
// Calculate texture coordinates and clip space position
texCoords = texCoords0;
gl_Position = gl_ModelViewProjectionMatrix * pos;
}
[[FS_ATTRIBPASS]]
// =================================================================================================
#ifdef _F03_ParallaxMapping
#define _F02_NormalMapping
#endif
#include "utilityLib/fragDeferredWrite.glsl" />
uniform sampler2D tex0;
#ifdef _F02_NormalMapping
uniform sampler2D tex1;
#endif
varying vec4 pos;
varying vec2 texCoords;
#ifdef _F02_NormalMapping
varying mat3 tsbMat;
#else
varying vec3 tsbNormal;
#endif
#ifdef _F03_ParallaxMapping
varying vec3 eyeTS;
#endif
void main( void )
{
vec3 newCoords = vec3( texCoords, 0 );
#ifdef _F03_ParallaxMapping
const float plxScale = 0.03;
const float plxBias = -0.015;
// Iterative parallax mapping
vec3 eye = normalize( eyeTS );
for( int i = 0; i < 4; ++i )
{
vec4 nmap = texture2D( tex1, newCoords.st );
float height = nmap.a * plxScale + plxBias;
newCoords += (height - newCoords.p) * nmap.z * eye;
}
#endif
vec3 albedo = texture2D( tex0, newCoords.st ).rgb;
#ifdef _F02_NormalMapping
vec3 normalMap = texture2D( tex1, newCoords.st ).rgb * 2.0 - 1.0;
vec3 normal = tsbMat * normalMap;
#else
vec3 normal = tsbNormal;
#endif
vec3 newPos = pos.xyz;
#ifdef _F03_ParallaxMapping
newPos += vec3( 0.0, newCoords.p, 0.0 );
#endif
setMatID( 1.0 );
setPos( newPos );
setNormal( normalize( normal ) );
setAlbedo( albedo );
setSpecMask( 0.1 );
}
At the moment the new format is completely orthogonal to the old one, so old shaders compile without any problems. The changes can be found in the svn repository. Please tell us your opinion on the updated system...