Shaders in Oolite
Shaders are programs which run on a graphics processing unit. They provide a more flexible alternative or supplement to textures for specifying objects’ appearance. There are two widely-used types of shader, vertex shaders and fragment shaders (also known, less accurately, as pixel shaders), which are generally used in combination. Shaders can be implemented in a number of special-purpose programming languages; Oolite uses the OpenGL Shading Language, also known as GLslang or GLSL.
Shaders are supported in Oolite 1.67 for Mac OS X and later. At the time of writing, no released version of Oolite for other platforms supports shaders, but the next Windows release will.
Specifying shaders
Shaders are specified in a dictionary named shaders in the ship’s definition in shipdata.plist. The keys of this dictionary are names of textures used in the ship’s or entity’s .dat file, and the values are dictionaries specifying shaders to use instead. The elements are: The uniform variables currently provided by Oolite are:
Name | Type | Description |
---|---|---|
textures | array of strings |
A list of textures used by the shader. |
vertex_shader | string |
The name of a vertex shader file to use. Oolite will search the Shaders folder of all installed OXPs for a shader of the appropriate name. (Not implemented in 1.67.1 or earlier.) |
glsl-vertex | string |
GLslang code to use as a vertex shader. This is ignored if vertex_shader is specified. |
fragment_shader | string |
The name of a fragment shader file to use. Oolite will search the Shaders folder of all installed OXPs for a shader of the appropriate name. (Not implemented in 1.67.1 or earlier.) |
glsl-fragment | string |
GLslang code to use as a fragment shader. This is ignored if fragment_shader is specified. |
glsl | string |
Synonym for glsl-fragment. |
An example
A full explanation of GLslang is beyond the scope of this article, but for illustrative purposes, here is an overview of the fragment shader code in the Freaky Thargoids example OXP.
The shader code, which is specified in shipdata.plist, looks like this:
uniform sampler2D tex0; uniform sampler2D tex1; uniform float time; float wave(float t) { /* approximates a sine waveform by summing 4 triangular waveforms */ float s0 = t; s0 -= floor(s0); float sum = abs( s0 - 0.5); float s1 = t - 0.125; s1 -= floor(s1); sum += abs( s1 - 0.5) - 0.25; float s2 = t - 0.250; s2 -= floor(s2); sum += abs( s2 - 0.5) - 0.25; float s3 = t - 0.375; s3 -= floor(s3); sum += abs( s3 - 0.5) - 0.25; return sum; } void main() { float t1 = 1.5 * time + texture2D(tex1, gl_TexCoord[0].st).a; float effect = wave(t1); vec4 base = texture2D(tex0, gl_TexCoord[0].st); vec4 glow = texture2D(tex1, gl_TexCoord[0].st); gl_FragColor = gl_Color * base + effect * glow; }
The first section declares three uniform variables, which are used to pass information from Oolite to the shader. The two sampler2D
variables are used to read from the two textures used by the shader. The float
variable timer is a number which increases by 1.0 each second.
This is followed by a custom function wave(), which is an approximation of the trigonometric sine function].
The last part is the function main(), which is the function executed by the GPU. It first calculates a light intensity value, based on the time (multiplying by 1.5 gives a frequency of 1.5 pulsations per second, or 1/1.5 = 0.666… Hz) and the alpha channel of tex1, the glow and phase map texture. It then reads texture values from each texture, then combines them based on the calculated light value, and assigns the result to gl_FragColor, which is the colour of the generated pixel.
Uniform reference
The uniform variables currently provided by Oolite are:
Name | Type | Description |
---|---|---|
tex0 | sampler2D |
Sampler for the first texture. |
tex1 | sampler2D |
Sampler for the second texture. |
… | ||
texN | sampler2D |
Sampler for the N+1th texture. |
time | float |
Uniformly increasing timer, in seconds. |
engine_level | float |
Engine thrust. 0.0 for no movement, 1.0 for full thrust. Greater than 1.0 for injectors or hyperspeed? Untested. |
laser_heat_level | float |
Laser temperature, ranging from 0.0 to 1.0. |
hull_heat_level | float |
Hull temperature. 1.0 is damage level. (Not implemented in 1.67.1 or earlier.) |
Limitations
The current implementation does not provide the information necessary to implement the most common form of normal mapping.