OpenGL Shaders (GLSL)

From FreeSpace Wiki
Revision as of 05:14, 3 May 2015 by Echelon9 (talk | contribs) (Known issues: OS X 10.6 is ancient now, so remove from known issues list)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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