Difference between revisions of "OpenGL Shaders (GLSL)"

From FreeSpace Wiki
Jump to: navigation, search
(Mac users: Updated to reflect current state of Mac OpenGL code)
(Known issues: OS X 10.6 is ancient now, so remove from known issues list)
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
==Introduction==
 
==Introduction==
As most of users know, FS2_Open has been ported to OpenGL API from its original DirectX API. Since 3.6.9. builds, OpenGL has been the preferred API and DirectX support is not guaranteed anymore in future builds. Eventually, DirectX support might be completely dropped.
 
  
In addition, starting with 3.6.10. nightly test builds, the graphic engine has been enhanced in order to use shaders following the OpenGL Shading Language (GLSL). This new rendering add-on has allowed the inclusion of normal and parallax mapping effects and a general improvement in the game looking with nearly no performance loss (or even with performance gains).  
+
Starting with FS2 Open version 3.6.10, the graphics pipeline has been rewritten to make it possible to use Shaders written in [[wikipedia:GLSL|GLSL]]. These allow the use of various new graphical effects, such as parallax mapping, normal mapping, or pixel-accurate lighting.
 +
In FSO version 3.6.12, a post-processing pipeline was added, allowing the application of screen-space effects, like bloom, or noise effects.
  
  
 
==Notes and system requirements==
 
==Notes and system requirements==
* Because of the used shader code, a Shader Model 3.0 capable GPU is needed for shaders to work. So you need:
+
In FSO 3.6.12, the basic requirements for the use of shaders are:
** nVidia: GeForce 6xxx or newer card.
+
*A Shader Model 3.0 capable GPU is needed for shaders to work. So you need:
** ATI/AMD: Radeon (X1xxx), HD 2xxx or newer card.
+
**nVidia: GeForce 6xxx or newer card.
** Others (Intel included): Possibly, there's no working card.
+
**ATI/AMD: Radeon (X1xxx), HD 2xxx or newer card.
* You also need a set of shader files (see [[OpenGL_Shaders_(GLSL)#Description_of_shaders|below]]).
+
*A set of shader files (as distributed with the [[MediaVPs]])
* If your card is not SM3.0 capable or you don't have the needed shader files, the game will automatically fall back to legacy fixed render pipeline, (you will lose normal and parallax effect).
 
* Legacy fixed render pipeline can be also forced through [[Command-Line_Reference#-no_glsl|-no_glsl flag]]. This flag is useful for troubleshooting or performance tests.
 
* Within F3 lab, if GLSL mode has been enabled, fixed render pipeline can be optionally switched on/off. In this way, shader vs. legacy rendering quality can be evaluated. Nevertheless, this setting is restricted to F3 lab, the rest of the game will continue using GLSL.
 
  
 +
As of version 3.6.13, the Shader Model requirement has been dropped to allow cards that are only capable of Shader Model 2 support to use shaders.
  
 
==Known issues==
 
==Known issues==
Line 33: Line 31:
 
** (If you prefer, you can look for just old Atioglxx.dll download links in HLP forums).
 
** (If you prefer, you can look for just old Atioglxx.dll download links in HLP forums).
  
 
===Mac users===
 
Shaders are now much improved on Mac OS X, and very closely match the feature set available on Windows. The central problem is performance slowdowns that are caused by bugs in Apple's graphics drivers. Environment mapping also leads to an odd effect in which portions of the desktop or other programs are rendered onto in-game objects.
 
* [http://scp.indiegames.us/mantis/view.php?id=1858 Mantis 1858 - Environmental mapping behaving oddly under Mac OS X for all shader-enabled builds]
 
  
 
===ForceWare 169.xx===
 
===ForceWare 169.xx===
 
These drivers are buggy and don't work fine (several graphical glitches). Why the hell are you using them? They're ancient. Update to a newer version NOW.
 
These drivers are buggy and don't work fine (several graphical glitches). Why the hell are you using them? They're ancient. Update to a newer version NOW.
 
  
 
==Description of shaders==
 
==Description of shaders==
Line 46: Line 39:
 
* They are placed in [[FS2_Data_Structure#effects|effects subfolder]] within general data structure. They work like any other game file: they can be packed in vp archives and they are fully affected by overall precedence priority.
 
* They are placed in [[FS2_Data_Structure#effects|effects subfolder]] within general data structure. They work like any other game file: they can be packed in vp archives and they are fully affected by overall precedence priority.
 
* Starting from version 3.6.10., MediaVP package includes a full set of shaders within mv_core.vp.
 
* Starting from version 3.6.10., MediaVP package includes a full set of shaders within mv_core.vp.
* The game uses a fixed number of shaders (66). Each one of them can be identified through its name with the next key. It also shows the order used in the name convention, so there's a ''lb'' shader but not a ''bl'' one.
+
* The MediaVPs shaders are so-called "unified" shaders. These are designed to perform all necessary operations in one file, where the specific behaviour for all possible usage scenarios is defined through the use of preprocessor directives. These shaders can be found in mv_root.vp, in the data/effects folder, under the names "main-f.sdr" and "main-v.sdr"
*# '''Null''': Just color/shading only, no lighting or textures used. As you can imagine, there's no mixed usage with any other kind of shader.
+
 
*# '''l''': lighting enabled.
+
==Examples==
*# '''f''': indicates fog based lighting condition effects. (Not to be confused with the -f that denotes fragment shaders).
+
===Per-Fragment Lighting Shaders for FSO 3.6.13===
*# '''b''': uses base/diffuse map.
+
 
*# '''g''': glow map. As always, this effect is switched on by [[Command-Line_Reference#-glow|-glow flag]] and it needs associated glow maps. (It only exists for fragment shaders).
+
These shaders use the so-called per-fragment lighting model. This means that, unlike the fixed-function graphics pipeline, lighting values are computed for every pixel (or fragment) on the screen. This leads to a more accurate, smoother lighting across the lit surface. The code examples here assume a working knowledge of GLSL, consult the GLSL Tutorial linked to below for details.
*# '''s''': specular map. As always, this effect is switched on by [[Command-Line_Reference#-spec|-spec flag]] and it needs associated specular maps. (It only exists for fragment shaders).
+
Here's an example of fixed-function lighting (Also called per-vertex lighting):
*# '''n''': normal map. This effect only exists in GLSL mode. It needs the usage of [[Command-Line_Reference#-normal|-normal flag]] and new normal maps.
+
[[image:Pointgl.gif]]
*# '''h''': height map (parallax mapping). This effect only exists in GLSL mode. It needs the usage of [[Command-Line_Reference#-height|-height flag]] AND [[Command-Line_Reference#-normal|-normal flag]]. So you cannot have parallax mapping without normal mapping. It also needs the usage of new height maps. (It only exists for fragment shaders).
+
 
*# '''e''': environment map. As always, this effect is switched on by [[Command-Line_Reference#-env|-env flag]] AND [[Command-Line_Reference#-spec|-spec flag]]. So you cannot have env mapping effect without specular bright effect. This happens because the env mapping effect is affected by the alpha channel on the specular map. This also happens in legacy fixed render pipeline mode.
+
And the same scene with per-fragment lighting:
*# The '''-v''' files are vertex shaders, the '''-f''' files are fragment shaders.
+
[[image:Pointpix.gif]]
* Vertex and framgent shaders are paired up by the needed functions in the fragment part. Therefore, there are less vertex shaders than fragment ones. The used vertex shaders are:
+
 
*# '''Null-v''', which offers color/shading only.
+
We start off with the vertex shader, main-v.sdr.
*# '''l*-v''', which offers lighting and mapping, (ie. textures), support.
+
 
*# '''*f*-v''', which gives you fog support.
+
<pre>#ifdef FLAG_ENV_MAP
*# '''*n*-v''', which provides access to the tangent space data needed for normal maps.
+
uniform mat4 envMatrix;
*# '''*e-v''', which adds the env translation matrix.
+
varying vec3 envReflect;
*# Special case: '''b-v''', which offers mapping support, (ie. textures), but no lighting.
+
#endif
* Examples of vertex and fragment pairs:
+
 
** The only special thing that ''lbgs-f'' needs is lighting, which means it only has to be paired up with ''l-v''.
+
#ifdef FLAG_NORMAL_MAP
** But ''lbgse-f'' needs envmap support too, so it is paired up with ''le-v'' which has access to the env translation matrix.
+
varying mat3 tbnMatrix;
** In F3 lab, if all the textures are turned off, so there's only lighting applied over the white bare flat surfaces of the POF model, ''l-v'' and ''Null-f'' are the used shaders.
+
#endif
** In F3 lab, if all the textures AND also the lighting are turned off, ''Null-v'' and ''Null-f'' are used. Only a white silhouette of the ship is rendered.
+
 
** ''b-v'' and ''b-f'' / ''bg-f''. This pair is used in HUD target box, where the game renders a textured ship, with glowmaps (''bg-f'') or without them (''b-f''), but it lacks any lighting. It is also used in F3 lab if lighting is turned off while keeping the textures on.<br>''Side note: As the game doesn't use a g-f shader, in F3 lab, you cannot turn off lighting and diffuse textures while keeping glowmaps. In this situation glowmaps are also turned off.''
+
#ifdef FLAG_FOG
 +
varying float fogDist;
 +
#endif
 +
 
 +
varying vec4 position;
 +
varying vec3 lNormal;
 +
 +
void main()
 +
{
 +
gl_TexCoord[0] = gl_MultiTexCoord0;
 +
gl_Position = ftransform();
 +
gl_FrontColor = gl_Color;
 +
gl_FrontSecondaryColor = vec4(0.0, 0.0, 0.0, 1.0);
 +
 +
// Transform the normal into eye space and normalize the result.
 +
position = gl_ModelViewMatrix * gl_Vertex;
 +
vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
 +
lNormal = normal;
 +
 
 +
#ifdef FLAG_NORMAL_MAP
 +
// Setup stuff for normal maps
 +
vec3 t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz);
 +
vec3 b = cross(normal, t) * gl_MultiTexCoord1.w;
 +
tbnMatrix = mat3(t, b, normal);
 +
#endif
 +
 
 +
#ifdef FLAG_ENV_MAP
 +
// Environment mapping reflection vector.
 +
envReflect = reflect(position.xyz, normal);
 +
envReflect = vec3(envMatrix * vec4(envReflect, 0.0));
 +
envReflect = normalize(envReflect);
 +
#endif
 +
 
 +
#ifdef FLAG_FOG
 +
fogDist = clamp((gl_Position.z - gl_Fog.start) * 0.75 * gl_Fog.scale, 0.0, 1.0);
 +
#endif
 +
 
 +
#ifdef __GLSL_CG_DATA_TYPES
 +
// Check necessary for ATI specific behavior
 +
gl_ClipVertex = (gl_ModelViewMatrix * gl_Vertex);
 +
#endif
 +
}</pre>
 +
 
 +
The fragment shader, main-f.sdr:
 +
 
 +
<pre>#ifdef FLAG_LIGHT
 +
uniform int n_lights;
 +
#endif
 +
 
 +
#ifdef FLAG_DIFFUSE_MAP
 +
uniform sampler2D sBasemap;
 +
#endif
 +
 
 +
#ifdef FLAG_GLOW_MAP
 +
uniform sampler2D sGlowmap;
 +
#endif
 +
 
 +
#ifdef FLAG_SPEC_MAP
 +
uniform sampler2D sSpecmap;
 +
#endif
 +
 
 +
#ifdef FLAG_ENV_MAP
 +
uniform samplerCube sEnvmap;
 +
uniform bool alpha_spec;
 +
varying vec3 envReflect;
 +
#endif
 +
 
 +
#ifdef FLAG_NORMAL_MAP
 +
uniform sampler2D sNormalmap;
 +
varying mat3 tbnMatrix;
 +
#endif
 +
 
 +
#ifdef FLAG_FOG
 +
varying float fogDist;
 +
#endif
 +
 
 +
varying vec4 position;
 +
varying vec3 lNormal;
 +
 
 +
#if SHADER_MODEL == 2
 +
  #define MAX_LIGHTS 2
 +
#else
 +
  #define MAX_LIGHTS 8
 +
#endif
 +
 
 +
#define SPEC_INTENSITY_POINT 4.3 // Point light
 +
#define SPEC_INTENSITY_DIRECTIONAL 1.5 // Directional light
 +
#define SPECULAR_FACTOR 1.75
 +
#define SPECULAR_ALPHA 0.1
 +
#define SPEC_FACTOR_NO_SPEC_MAP 0.6
 +
#define ENV_ALPHA_FACTOR 0.3
 +
#define GLOW_MAP_INTENSITY 1.5
 +
#define AMBIENT_LIGHT_BOOST 1.0
 +
 
 +
void main()
 +
{
 +
vec3 eyeDir = vec3(normalize(-position).xyz); // Camera is at (0,0,0) in ModelView space
 +
vec4 lightAmbientDiffuse = vec4(0.0, 0.0, 0.0, 1.0);
 +
vec4 lightDiffuse = vec4(0.0, 0.0, 0.0, 1.0);
 +
vec4 lightAmbient = vec4(0.0, 0.0, 0.0, 1.0);
 +
vec4 lightSpecular = vec4(0.0, 0.0, 0.0, 1.0);
 +
vec2 texCoord = gl_TexCoord[0].xy;
 +
 
 +
#ifdef FLAG_LIGHT
 +
  #ifdef FLAG_NORMAL_MAP
 +
// Normal map - convert from DXT5nm
 +
vec3 normal;
 +
normal.rg = (texture2D(sNormalmap, texCoord).ag * 2.0) - 1.0;
 +
normal.b = sqrt(1.0 - dot(normal.rg, normal.rg));
 +
normal = normalize(tbnMatrix * normal);
 +
  #else
 +
vec3 normal = lNormal;
 +
  #endif
 +
 +
vec3 lightDir;
 +
lightAmbient = gl_FrontMaterial.emission + (gl_LightModel.ambient * gl_FrontMaterial.ambient);
 +
 
 +
#pragma optionNV unroll all
 +
for (int i = 0; i < MAX_LIGHTS; ++i) {
 +
  #if SHADER_MODEL > 2
 +
if (i > n_lights)
 +
break;
 +
  #endif
 +
float specularIntensity = 1.0;
 +
float attenuation = 1.0;
 +
 
 +
// Attenuation and light direction
 +
  #if SHADER_MODEL > 2
 +
if (gl_LightSource[i].position.w == 1.0) {
 +
  #else
 +
if (gl_LightSource[i].position.w == 1.0 && i != 0) {
 +
  #endif
 +
// Positional light source
 +
float dist = distance(gl_LightSource[i].position.xyz, position.xyz);
 +
 
 +
float spotEffect = 1.0;
 +
 
 +
  #if SHADER_MODEL > 2
 +
if (gl_LightSource[i].spotCutoff < 91.0) {
 +
spotEffect = dot(normalize(gl_LightSource[i].spotDirection), normalize(-position.xyz));
 +
 
 +
if (spotEffect < gl_LightSource[i].spotCosCutoff) {
 +
spotEffect = 0.0;
 +
}
 +
}
 +
  #endif
 +
 
 +
attenuation = spotEffect / (gl_LightSource[i].constantAttenuation + (gl_LightSource[i].linearAttenuation * dist) + (gl_LightSource[i].quadraticAttenuation * dist * dist));
 +
 
 +
lightDir = normalize(gl_LightSource[i].position.xyz - position.xyz);
 +
 
 +
specularIntensity = SPEC_INTENSITY_POINT; // Point light
 +
} else {
 +
// Directional light source
 +
lightDir = normalize(gl_LightSource[i].position.xyz);
 +
 
 +
specularIntensity = SPEC_INTENSITY_DIRECTIONAL; // Directional light
 +
}
 +
 
 +
// Ambient and Diffuse
 +
lightAmbient += (gl_FrontLightProduct[i].ambient * attenuation);
 +
lightDiffuse += ((gl_FrontLightProduct[i].diffuse * max(dot(normal, lightDir), 0.0)) * attenuation);
 +
 
 +
// Specular
 +
float NdotHV = clamp(dot(normal, normalize(eyeDir + lightDir)), 0.0, 1.0);
 +
lightSpecular += ((gl_FrontLightProduct[i].specular * pow(max(0.0, NdotHV), gl_FrontMaterial.shininess)) * attenuation) * specularIntensity;
 +
}
 +
 
 +
lightAmbientDiffuse = lightAmbient + lightDiffuse;
 +
#else
 +
lightAmbientDiffuse = gl_Color;
 +
lightSpecular = gl_SecondaryColor;
 +
#endif
 +
 
 +
#ifdef FLAG_DIFFUSE_MAP
 +
// Base color
 +
vec4 baseColor = texture2D(sBasemap, texCoord);
 +
#else
 +
vec4 baseColor = gl_Color;
 +
#endif
 +
 +
vec4 fragmentColor;
 +
fragmentColor.rgb = baseColor.rgb * max(lightAmbientDiffuse.rgb * AMBIENT_LIGHT_BOOST, gl_LightModel.ambient.rgb - 0.425);
 +
fragmentColor.a = baseColor.a;
 +
 
 +
#ifdef FLAG_SPEC_MAP
 +
// Spec color
 +
fragmentColor.rgb += lightSpecular.rgb * (texture2D(sSpecmap, texCoord).rgb * SPECULAR_FACTOR);
 +
fragmentColor.a += (dot(lightSpecular.a, lightSpecular.a) * SPECULAR_ALPHA);
 +
#else
 +
fragmentColor.rgb += lightSpecular.rgb * (baseColor.rgb * SPEC_FACTOR_NO_SPEC_MAP);
 +
#endif
 +
 
 +
#ifdef FLAG_ENV_MAP
 +
// Env color
 +
vec3 envIntensity = (alpha_spec) ? vec3(texture2D(sSpecmap, texCoord).a) : texture2D(sSpecmap, texCoord).rgb;
 +
fragmentColor.a += (dot(textureCube(sEnvmap, envReflect).rgb, textureCube(sEnvmap, envReflect).rgb) * ENV_ALPHA_FACTOR);
 +
fragmentColor.rgb += textureCube(sEnvmap, envReflect).rgb * envIntensity;
 +
#endif
 +
 +
#ifdef FLAG_GLOW_MAP
 +
// Glow color
 +
fragmentColor.rgb += texture2D(sGlowmap, texCoord).rgb * GLOW_MAP_INTENSITY;
 +
#endif
 +
 
 +
#ifdef FLAG_FOG
 +
fragmentColor.rgb = mix(fragmentColor.rgb, gl_Fog.color.rgb, fogDist);
 +
#endif
 +
 
 +
gl_FragColor = fragmentColor;
 +
}</pre>
 +
 
 +
===Preprocessor defines===
 +
 
 +
*FLAG_LIGHT
 +
**The shader is expected to handle lighting. This flag has one associated uniform:
 +
**uniform int n_lights // The number of lights in the current scene, 0..8 inclusive.
 +
 
 +
*FLAG_DIFFUSE_MAP
 +
**The shader is expected to handle the application of the basic diffuse map. This flag has one associated uniform:
 +
**uniform sampler2D sBasemap // The diffuse texture
 +
 
 +
*FLAG_GLOW_MAP
 +
**The shader is expected to handle the application of the glowmap. This flag has one associated uniform:
 +
**uniform sampler2D sGlowmap // The glow texture
 +
 
 +
*FLAG_SPEC_MAP
 +
**The shader is expected to handle the application of the specular map. This flag has one associated uniform:
 +
**uniform sampler2D sSpecmap // The specular texture
 +
 
 +
*FLAG_ENV_MAP
 +
**This shader is expected to handle environment mapping. There are several uniforms associated with this flag:
 +
**uniform samplerCube sEnvmap; //The cubemap
 +
**uniform bool alpha_spec; //Whether or not there's a specular map with an alpha channel present.
 +
**uniform mat4 envMatrix;
 +
 
 +
*FLAG_NORMAL_MAP
 +
**This shader is expected to handle normal mapping. One associated uniform:
 +
**uniform sampler2D sNormalmap // The normal map
  
 +
*FLAG_HEIGHT_MAP
 +
**This shader is expected to handle parallax mapping. One associated uniform:
 +
**uniform sampler2D sHeightmap // The height map
  
==Listing of Vertex to Fragment Sets==
+
*FLAG_FOG
 +
**Fogging effects are enabled (as in a nebula). No associated uniforms.
 +
{{Table3613|
 +
*SHADER_MODEL
 +
**An integer, which will tell you which shader model the current hardware supports.}}
  
<b>Basic Shaders:</b><br>
+
==Shader Limitations==
null-v: null-f<br>
+
*Due to limitations of OpenGL, only 8 lights can be handled at once. While the Engine keeps track of up to 256 lights per scene, only the 8 strongest ones are passed to the shader.
b-v: b-f, bg-f<br>
 
l-v: lb-f, lbg-f, lbgs-f, lbs-f, lg-f, lgs-f, ls-f, null-f<br>
 
le-v: lbgse-f, lbse-f, lgse-f, lse-f<br><br>
 
<b>NORMAL Map Shaders:</b><br>
 
ln-v: lbgn-f, lbgsn-f, lbn-f, lbsn-f, lgn-f, lgsn-f, ln-f, lsn-f<br>
 
lne-v: lbgsne-f, lbsne-f, lgsne-f, lsne-f<br><br>
 
<b>FOG Based Shaders:</b><br>
 
lf-v: lfb-f, lfbg-f, lfbgs-f, lfbs-f<br>
 
lfe-v: lfbgse-f, lfbse-f<br><br>
 
<b>FOG Based NORMAL Shaders:</b><br>
 
lfn-v: lfbgn-f, lfbgsn-f, lfbn-f, lfbsn-f<br>
 
lfne-v: lfbgsne-f, lfbsne-f<br>
 
  
  

Latest revision as of 05:14, 3 May 2015

Introduction

Starting with FS2 Open version 3.6.10, the graphics pipeline has been rewritten to make it possible to use Shaders written in GLSL. These allow the use of various new graphical effects, such as parallax mapping, normal mapping, or pixel-accurate lighting. In FSO version 3.6.12, a post-processing pipeline was added, allowing the application of screen-space effects, like bloom, or noise effects.


Notes and system requirements

In FSO 3.6.12, the basic requirements for the use of shaders are:

  • A Shader Model 3.0 capable GPU is needed for shaders to work. So you need:
    • nVidia: GeForce 6xxx or newer card.
    • ATI/AMD: Radeon (X1xxx), HD 2xxx or newer card.
  • A set of shader files (as distributed with the MediaVPs)

As of version 3.6.13, the Shader Model requirement has been dropped to allow cards that are only capable of Shader Model 2 support to use shaders.

Known issues

ATI/AMD X1xxx GPUs

These GPUs are, in theory, SM 3.0 capable so they should run the shaders fine. BUT THEY DON'T. The game usually crashes or freezes with these GPUs.

Several SCP people are studying the issue. Speculation about its lack of Vertex Texture Fetch support has been stated as the source of the issue. But nothing has been set as the final conclusion. Here you have a link to VTF article in opengl.org wiki.

Possible solutions:

  • Use -no_glsl.
  • Use-at-your-own-risk hack for Windows XP 32-bit users:
    • Update your Catalyst to the latest version.
    • Locate 7.9 or older Catalyst version.
    • Extract the installation archive but do not install it.
    • Grab Atioglxx.dll from it.
    • Copy Atioglxx.dll in FS2 folder. In this way your system will be updated to the last Catalyst version BUT FS2 will use an old one.
    • Run the game. Expect possible bad performance.
    • (If you prefer, you can look for just old Atioglxx.dll download links in HLP forums).


ForceWare 169.xx

These drivers are buggy and don't work fine (several graphical glitches). Why the hell are you using them? They're ancient. Update to a newer version NOW.

Description of shaders

  • Shader files have got .sdr extension.
  • They are placed in effects subfolder within general data structure. They work like any other game file: they can be packed in vp archives and they are fully affected by overall precedence priority.
  • Starting from version 3.6.10., MediaVP package includes a full set of shaders within mv_core.vp.
  • The MediaVPs shaders are so-called "unified" shaders. These are designed to perform all necessary operations in one file, where the specific behaviour for all possible usage scenarios is defined through the use of preprocessor directives. These shaders can be found in mv_root.vp, in the data/effects folder, under the names "main-f.sdr" and "main-v.sdr"

Examples

Per-Fragment Lighting Shaders for FSO 3.6.13

These shaders use the so-called per-fragment lighting model. This means that, unlike the fixed-function graphics pipeline, lighting values are computed for every pixel (or fragment) on the screen. This leads to a more accurate, smoother lighting across the lit surface. The code examples here assume a working knowledge of GLSL, consult the GLSL Tutorial linked to below for details. Here's an example of fixed-function lighting (Also called per-vertex lighting): Pointgl.gif

And the same scene with per-fragment lighting: Pointpix.gif

We start off with the vertex shader, main-v.sdr.

#ifdef FLAG_ENV_MAP
uniform mat4 envMatrix;
varying vec3 envReflect;
#endif

#ifdef FLAG_NORMAL_MAP
varying mat3 tbnMatrix;
#endif

#ifdef FLAG_FOG
varying float fogDist;
#endif

varying vec4 position;
varying vec3 lNormal;
	
void main()
{
	gl_TexCoord[0] = gl_MultiTexCoord0;
	gl_Position = ftransform();
	gl_FrontColor = gl_Color;
	gl_FrontSecondaryColor = vec4(0.0, 0.0, 0.0, 1.0);
	
 // Transform the normal into eye space and normalize the result.
	position = gl_ModelViewMatrix * gl_Vertex;
	vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
	lNormal = normal;

 #ifdef FLAG_NORMAL_MAP
 // Setup stuff for normal maps
	vec3 t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz);
	vec3 b = cross(normal, t) * gl_MultiTexCoord1.w;
	tbnMatrix = mat3(t, b, normal);
 #endif

 #ifdef FLAG_ENV_MAP
 // Environment mapping reflection vector.
	envReflect = reflect(position.xyz, normal);
	envReflect = vec3(envMatrix * vec4(envReflect, 0.0));
	envReflect = normalize(envReflect);
 #endif

 #ifdef FLAG_FOG
	fogDist = clamp((gl_Position.z - gl_Fog.start) * 0.75 * gl_Fog.scale, 0.0, 1.0);
 #endif

 #ifdef __GLSL_CG_DATA_TYPES
 // Check necessary for ATI specific behavior
	gl_ClipVertex = (gl_ModelViewMatrix * gl_Vertex);
 #endif
}

The fragment shader, main-f.sdr:

#ifdef FLAG_LIGHT
uniform int n_lights;
#endif

#ifdef FLAG_DIFFUSE_MAP
uniform sampler2D sBasemap;
#endif

#ifdef FLAG_GLOW_MAP
uniform sampler2D sGlowmap;
#endif

#ifdef FLAG_SPEC_MAP
uniform sampler2D sSpecmap;
#endif

#ifdef FLAG_ENV_MAP
uniform samplerCube sEnvmap;
uniform bool alpha_spec;
varying vec3 envReflect;
#endif

#ifdef FLAG_NORMAL_MAP
uniform sampler2D sNormalmap;
varying mat3 tbnMatrix;
#endif

#ifdef FLAG_FOG
varying float fogDist;
#endif

varying vec4 position;
varying vec3 lNormal;

#if SHADER_MODEL == 2
  #define MAX_LIGHTS 2
#else
  #define MAX_LIGHTS 8
#endif

#define SPEC_INTENSITY_POINT 		4.3 // Point light
#define SPEC_INTENSITY_DIRECTIONAL 	1.5 // Directional light
#define SPECULAR_FACTOR 			1.75
#define SPECULAR_ALPHA 				0.1
#define SPEC_FACTOR_NO_SPEC_MAP 	0.6
#define ENV_ALPHA_FACTOR			0.3
#define GLOW_MAP_INTENSITY 			1.5
#define AMBIENT_LIGHT_BOOST 		1.0

void main()
{
	vec3 eyeDir = vec3(normalize(-position).xyz); // Camera is at (0,0,0) in ModelView space
	vec4 lightAmbientDiffuse = vec4(0.0, 0.0, 0.0, 1.0);
	vec4 lightDiffuse = vec4(0.0, 0.0, 0.0, 1.0);
	vec4 lightAmbient = vec4(0.0, 0.0, 0.0, 1.0); 
	vec4 lightSpecular = vec4(0.0, 0.0, 0.0, 1.0);
	vec2 texCoord = gl_TexCoord[0].xy;

 #ifdef FLAG_LIGHT
  #ifdef FLAG_NORMAL_MAP
	// Normal map - convert from DXT5nm
	vec3 normal;
	normal.rg = (texture2D(sNormalmap, texCoord).ag * 2.0) - 1.0;
	normal.b = sqrt(1.0 - dot(normal.rg, normal.rg));
	normal = normalize(tbnMatrix * normal);
  #else
	vec3 normal = lNormal;
  #endif
	
	vec3 lightDir;
	lightAmbient = gl_FrontMaterial.emission + (gl_LightModel.ambient * gl_FrontMaterial.ambient);

	#pragma optionNV unroll all
	for (int i = 0; i < MAX_LIGHTS; ++i) {
	  #if SHADER_MODEL > 2
		if (i > n_lights)
			break;
	  #endif
		float specularIntensity = 1.0;
		float attenuation = 1.0;

		// Attenuation and light direction
	  #if SHADER_MODEL > 2
		if (gl_LightSource[i].position.w == 1.0) {
	  #else
		if (gl_LightSource[i].position.w == 1.0 && i != 0) {
	  #endif
			// Positional light source
			float dist = distance(gl_LightSource[i].position.xyz, position.xyz);

			float spotEffect = 1.0;
		  
		  #if SHADER_MODEL > 2
			if (gl_LightSource[i].spotCutoff < 91.0) {
				spotEffect = dot(normalize(gl_LightSource[i].spotDirection), normalize(-position.xyz));

				if (spotEffect < gl_LightSource[i].spotCosCutoff) {
					spotEffect = 0.0;
				}
			}
		  #endif

			attenuation = spotEffect / (gl_LightSource[i].constantAttenuation + (gl_LightSource[i].linearAttenuation * dist) + (gl_LightSource[i].quadraticAttenuation * dist * dist));

			lightDir = normalize(gl_LightSource[i].position.xyz - position.xyz);

			specularIntensity = SPEC_INTENSITY_POINT; // Point light
		} else {
			// Directional light source
			lightDir = normalize(gl_LightSource[i].position.xyz);

			specularIntensity = SPEC_INTENSITY_DIRECTIONAL; // Directional light
		}

		// Ambient and Diffuse
		lightAmbient += (gl_FrontLightProduct[i].ambient * attenuation);
		lightDiffuse += ((gl_FrontLightProduct[i].diffuse * max(dot(normal, lightDir), 0.0)) * attenuation);

		// Specular
		float NdotHV = clamp(dot(normal, normalize(eyeDir + lightDir)), 0.0, 1.0);
		lightSpecular += ((gl_FrontLightProduct[i].specular * pow(max(0.0, NdotHV), gl_FrontMaterial.shininess)) * attenuation) * specularIntensity;
	}

	lightAmbientDiffuse = lightAmbient + lightDiffuse;
 #else
	lightAmbientDiffuse = gl_Color;
	lightSpecular = gl_SecondaryColor;
 #endif

 #ifdef FLAG_DIFFUSE_MAP
 // Base color
	vec4 baseColor = texture2D(sBasemap, texCoord);
 #else
	vec4 baseColor = gl_Color;
 #endif
 
	vec4 fragmentColor;
	fragmentColor.rgb = baseColor.rgb * max(lightAmbientDiffuse.rgb * AMBIENT_LIGHT_BOOST, gl_LightModel.ambient.rgb - 0.425);
	fragmentColor.a = baseColor.a;

 #ifdef FLAG_SPEC_MAP
 // Spec color
	fragmentColor.rgb += lightSpecular.rgb * (texture2D(sSpecmap, texCoord).rgb * SPECULAR_FACTOR);
	fragmentColor.a += (dot(lightSpecular.a, lightSpecular.a) * SPECULAR_ALPHA);
 #else
	fragmentColor.rgb += lightSpecular.rgb * (baseColor.rgb * SPEC_FACTOR_NO_SPEC_MAP);
 #endif

 #ifdef FLAG_ENV_MAP
 // Env color
	vec3 envIntensity = (alpha_spec) ? vec3(texture2D(sSpecmap, texCoord).a) : texture2D(sSpecmap, texCoord).rgb;
	fragmentColor.a += (dot(textureCube(sEnvmap, envReflect).rgb, textureCube(sEnvmap, envReflect).rgb) * ENV_ALPHA_FACTOR);
	fragmentColor.rgb += textureCube(sEnvmap, envReflect).rgb * envIntensity;
 #endif
	
 #ifdef FLAG_GLOW_MAP
 // Glow color
	fragmentColor.rgb += texture2D(sGlowmap, texCoord).rgb * GLOW_MAP_INTENSITY;
 #endif

 #ifdef FLAG_FOG
	fragmentColor.rgb = mix(fragmentColor.rgb, gl_Fog.color.rgb, fogDist);
 #endif

	gl_FragColor = fragmentColor;
}

Preprocessor defines

  • FLAG_LIGHT
    • The shader is expected to handle lighting. This flag has one associated uniform:
    • uniform int n_lights // The number of lights in the current scene, 0..8 inclusive.
  • FLAG_DIFFUSE_MAP
    • The shader is expected to handle the application of the basic diffuse map. This flag has one associated uniform:
    • uniform sampler2D sBasemap // The diffuse texture
  • FLAG_GLOW_MAP
    • The shader is expected to handle the application of the glowmap. This flag has one associated uniform:
    • uniform sampler2D sGlowmap // The glow texture
  • FLAG_SPEC_MAP
    • The shader is expected to handle the application of the specular map. This flag has one associated uniform:
    • uniform sampler2D sSpecmap // The specular texture
  • FLAG_ENV_MAP
    • This shader is expected to handle environment mapping. There are several uniforms associated with this flag:
    • uniform samplerCube sEnvmap; //The cubemap
    • uniform bool alpha_spec; //Whether or not there's a specular map with an alpha channel present.
    • uniform mat4 envMatrix;
  • FLAG_NORMAL_MAP
    • This shader is expected to handle normal mapping. One associated uniform:
    • uniform sampler2D sNormalmap // The normal map
  • FLAG_HEIGHT_MAP
    • This shader is expected to handle parallax mapping. One associated uniform:
    • uniform sampler2D sHeightmap // The height map
  • FLAG_FOG
    • Fogging effects are enabled (as in a nebula). No associated uniforms.
FS2 Open, 3.6.14:
  • SHADER_MODEL
    • An integer, which will tell you which shader model the current hardware supports.

Shader Limitations

  • Due to limitations of OpenGL, only 8 lights can be handled at once. While the Engine keeps track of up to 256 lights per scene, only the 8 strongest ones are passed to the shader.


External links:

http://en.wikipedia.org/wiki/Shader

http://en.wikipedia.org/wiki/GLSL

http://www.opengl.org/documentation/glsl/

OpenGL Org's Typhoon Labs Shader Designer

http://www.opengl.org/sdk/tools/ShaderDesigner/

nVidia Shader applications:

http://developer.nvidia.com/object/fx_composer_home.html

http://developer.nvidia.com/object/nv_shader_debugger_home.html

http://developer.download.nvidia.com/shaderlibrary/webpages/shader_library.html

ATI Shader tools

http://developer.amd.com/gpu/rendermonkey/Pages/default.aspx

http://developer.amd.com/gpu/archive/ashli/Pages/default.aspx

Apple OpenGL tools

http://developer.apple.com/graphicsimaging/opengl/opengl_serious.html