OpenGL Shaders (GLSL)

From FreeSpace Wiki
Jump to: navigation, search


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


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 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"


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.

uniform mat4 envMatrix;
varying vec3 envReflect;

varying mat3 tbnMatrix;

#ifdef FLAG_FOG
varying float fogDist;

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;

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

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

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

 // Check necessary for ATI specific behavior
	gl_ClipVertex = (gl_ModelViewMatrix * gl_Vertex);

The fragment shader, main-f.sdr:

uniform int n_lights;

uniform sampler2D sBasemap;

uniform sampler2D sGlowmap;

uniform sampler2D sSpecmap;

uniform samplerCube sEnvmap;
uniform bool alpha_spec;
varying vec3 envReflect;

uniform sampler2D sNormalmap;
varying mat3 tbnMatrix;

#ifdef FLAG_FOG
varying float fogDist;

varying vec4 position;
varying vec3 lNormal;

  #define MAX_LIGHTS 2
  #define MAX_LIGHTS 8

#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 ENV_ALPHA_FACTOR			0.3
#define GLOW_MAP_INTENSITY 			1.5

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
	// 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);
	vec3 normal = lNormal;
	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)
		float specularIntensity = 1.0;
		float attenuation = 1.0;

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

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

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

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

			lightDir = normalize(gl_LightSource[i] -;

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

			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;
	lightAmbientDiffuse = gl_Color;
	lightSpecular = gl_SecondaryColor;

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

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

 #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;
 // Glow color
	fragmentColor.rgb += texture2D(sGlowmap, texCoord).rgb * GLOW_MAP_INTENSITY;

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

	gl_FragColor = fragmentColor;

Preprocessor defines

    • 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.
    • 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
    • The shader is expected to handle the application of the glowmap. This flag has one associated uniform:
    • uniform sampler2D sGlowmap // The glow texture
    • The shader is expected to handle the application of the specular map. This flag has one associated uniform:
    • uniform sampler2D sSpecmap // The specular texture
    • 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;
    • This shader is expected to handle normal mapping. One associated uniform:
    • uniform sampler2D sNormalmap // The normal map
    • This shader is expected to handle parallax mapping. One associated uniform:
    • uniform sampler2D sHeightmap // The height map
    • Fogging effects are enabled (as in a nebula). No associated uniforms.
FS2 Open, 3.6.14:
    • 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:

OpenGL Org's Typhoon Labs Shader Designer

nVidia Shader applications:

ATI Shader tools

Apple OpenGL tools