Difference between revisions of "Shaders in Oolite"
m (Removed code tags from plist types) |
(Updating BB links) |
||
(23 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
+ | <small>'''''See also:''' [[Materials in Oolite]]''</small> | ||
+ | |||
'''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 '''[http://en.wikipedia.org/wiki/GLSL OpenGL Shading Language]''', also known as '''GLslang''' or '''GLSL'''. | '''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 '''[http://en.wikipedia.org/wiki/GLSL OpenGL Shading Language]''', also known as '''GLslang''' or '''GLSL'''. | ||
− | Shaders are supported in Oolite 1.67 for Mac OS X and later. | + | Shaders are supported in Oolite 1.67 for Mac OS X and later, and from 1.68 on other platforms. However, the shader support is still evolving. This page currently documents the state in Oolite 1.72. |
+ | This page was last worked on in 2010. | ||
== Specifying shaders == | == 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 | + | 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: |
− | |||
{| class="wikitable" border="1" cellpadding="3" cellspacing="0" | {| class="wikitable" border="1" cellpadding="3" cellspacing="0" | ||
|+ ''shaders'' dictionary values | |+ ''shaders'' dictionary values | ||
Line 13: | Line 15: | ||
| ''textures'' || array of strings || A list of textures used by the shader. | | ''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. ( | + | | ''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. (Requires Oolite 1.68 or later.) |
|- | |- | ||
− | | '' | + | | ''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. (Requires Oolite 1.68 or later.) |
|- | |- | ||
− | | '' | + | | ''uniforms'' || dictionary || Uniforms to pass to the shaders. |
− | |||
− | |||
− | |||
− | |||
|} | |} | ||
− | |||
== An example == | == An example == | ||
Line 31: | Line 28: | ||
This vertex shader, like many vertex shaders, exists primarily to prepare information for the fragment shader. | This vertex shader, like many vertex shaders, exists primarily to prepare information for the fragment shader. | ||
<pre>varying vec3 v_normal; | <pre>varying vec3 v_normal; | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
void main() | void main() | ||
{ | { | ||
v_normal = normalize(gl_NormalMatrix * gl_Normal); | v_normal = normalize(gl_NormalMatrix * gl_Normal); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; | gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; | ||
gl_Position = ftransform(); | gl_Position = ftransform(); | ||
− | }</pre> | + | } |
− | The first | + | </pre> |
+ | The first line declares a '''varying variable'''. This may sound like an oxymoron, but varying variables have the special property that they are interpolated across geometry and passed to the fragment shader. For instance, if you set a varying variable to red at one corner of a triangle, green at a second corner, and blue at the third, the colour as seen by the fragment shader will vary smoothly across the triangle:<br> | ||
[[Image:Glsl varying demo.png]]<br clear="all"> | [[Image:Glsl varying demo.png]]<br clear="all"> | ||
− | + | This is followed by the function <code>main()</code>, which is the function that will be called by the GPU for each vertex. This sets the varying normal (direction facing out from the surface), and performs additional set-up required for positions and texture co-ordinates to make sense in the fragment shader. | |
=== Fragment shader === | === Fragment shader === | ||
A fragment shader is called for each fragment of a generated polygon. A '''fragment''' is a pixel, in screen space, on which the polygon is potentially visible. (It is ''potentially'' visible because the shader may discard the fragment, and later polygons which are closer to the camera may draw over it.) | A fragment shader is called for each fragment of a generated polygon. A '''fragment''' is a pixel, in screen space, on which the polygon is potentially visible. (It is ''potentially'' visible because the shader may discard the fragment, and later polygons which are closer to the camera may draw over it.) | ||
− | <pre>/ | + | <pre>// Information from Oolite. |
− | + | uniform sampler2D tex0; | |
− | + | uniform sampler2D tex1; | |
− | + | uniform float time; | |
− | |||
− | uniform sampler2D tex0; | ||
− | uniform sampler2D tex1; | ||
− | uniform float | + | // Information from shipdata.plist. |
+ | uniform float reciprocalFrequency; | ||
− | + | // Information from vertex shader. | |
− | + | varying vec3 v_normal; | |
− | |||
− | |||
− | varying vec3 | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
float wave(float t) | float wave(float t) | ||
{ | { | ||
− | + | return sin(t * 6.28318530718) * 0.5 + 0.25; | |
− | + | } | |
− | + | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | #define LIGHT(idx) \ | |
− | } | + | { \ |
+ | vec3 lightVector = normalize(gl_LightSource[idx].position.xyz); \ | ||
+ | color += gl_FrontMaterial.diffuse * gl_LightSource[idx].diffuse * max(dot(v_normal, lightVector), 0.0); \ | ||
+ | } | ||
void main(void) | void main(void) | ||
{ | { | ||
− | + | // Calculate illumination. | |
− | + | vec4 color = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; | |
− | + | // LIGHT(0); | |
− | // Calculate | + | LIGHT(1); |
− | |||
− | |||
− | color | ||
− | |||
− | |||
− | |||
// Load texture data | // Load texture data | ||
Line 137: | Line 84: | ||
// Calculate glow intensity | // Calculate glow intensity | ||
− | float t1 = | + | float t1 = reciprocalFrequency * time + glowMap.a; |
float lightLevel = wave(t1); | float lightLevel = wave(t1); | ||
Line 143: | Line 90: | ||
color += lightLevel * glowMap; | color += lightLevel * glowMap; | ||
− | gl_FragColor = color; | + | gl_FragColor = vec4(color.rgb, 1.0); |
− | }</pre> | + | } |
+ | </pre> | ||
− | The first section declares | + | The first section declares several ''uniform variables'', which are used to pass information from Oolite to the shader. The two <code>sampler2D</code> variables are used to read from the two textures used by the shader. The <code>float</code> variable <code>timer</code> is a number which increases by 1.0 each second. The <code>float</code> <code>reciprocalFrequency</code> is set in <code>shipData.plist</code>; this allows a shader to be used for different ships (or different shaders on the same ship) with small variations. Uniform variables can only be read, not written to. |
− | The <code> | + | The <code>varying</code> declaration is the same as in the vertex shader, but fragment shaders can only read varying variables, not write to them. |
− | The <code> | + | The custom function <code>wave()</code> is used to smoothly vary the glow map from on to off. |
− | + | This is followed by a macro definition, <code>LIGHT</code>. A macro is substituted into the code each time it occurs; in <code>main()</code>, <code>LIGHT(0)</code> and <code>LIGHT(1)</code> will be replaced by the text of the macro definition, with <code>idx</code> in the macro being replaced with 0 or 1 respectively. Ideally, this would be a function, like <code>wave()</code>, but for technical reasons this causes performance problems. Specifically, under Apple’s implementation of GLSL (and probably others), this causes a set of uniforms for every light the graphics card supports to be pulled into the shader. | |
− | The last part is the function | + | The last part is the function <code>main()</code>, which is the function executed by the GPU. It first calculates the contribution of light source 1, using the information prepared in the vertex shader and interpolated by the GPU. (Light 0 is used for the demo screen and shipyard; light 1 is the sun, or an arbitrary light source when in witchspace. Future versions of Oolite may change this, though. Ideally, the shader would check light 0, but in current versions of Oolite this causes problems.) It then loads values from the two textures. The first, the colour map value, is simply multiplied by the incoming light. For the second, the glow map, an intensity value is calculated based on the time, the <code>reciprocalFrequency</code> uniform and the alpha channel of the glow map, which specifies animation phase. The glow map is likewise multiplied by its intensity value, and added to the total light. Finally, the combined alpha channel is forced to 1.0, and the result assigned to the special variable <code>gl_FragColor</code>, which determines the colour of the generated fragment. |
− | == | + | == Uniforms == |
− | + | Uniforms are variables whose value is set by the host application, i.e. Oolite. Oolite automatically supplies one uniform of type <code>sampler2D</code> for each texture, named <code>tex0</code>, <code>tex1</code> etc. It is also possible to bind uniforms to certain properties of the entity the shader is being applied to, for instance, its engine level. Additionally, constant values can be specified for uniforms, allowing a single shader to be used on different entities with differences in configuration. | |
+ | |||
+ | For more information on uniforms, see [[Shaders in Oolite: uniforms]]. | ||
+ | |||
+ | == Macros == | ||
+ | These preprocessor macros are defined by Oolite and may be used with the <code>#if</code>/<code>#endif</code> directives to selectively remove sections of code. | ||
+ | |||
{| class="wikitable" border="1" cellpadding="3" cellspacing="0" | {| class="wikitable" border="1" cellpadding="3" cellspacing="0" | ||
− | |+ | + | |+ Macros |
− | ! Name | + | ! Name !! Description |
− | |- | + | |- |
− | + | | <code>OO_LIGHT_0_FIX</code>||In current versions of Oolite, light number 0 is effectively always attached to the camera, giving the effect of the player shining a light on anything they’re looking at. When this is fixed, <code>OO_LIGHT_0_FIX</code> will be defined. (Not yet defined in any version of Oolite.) | |
|- | |- | ||
− | + | | <code>OO_REDUCED_COMPLEXITY</code>||Defined if user has selected lower-complexity shaders. | |
|- | |- | ||
− | | | + | | <code>OO_TEXTURE_UNIT_COUNT</code>||An integer specifying the number of texture units supported by hardware, which is the maximum number of ''tex'''N''''' uniforms that will be meaningful. This should be the same as ''gl_MaxTextureUnits'', but a macro has the advantage that it can be used to completely exclude parts of the shader. (Defined in Oolite 1.69 and later.) |
|- | |- | ||
− | + | | <code>OO_USER_DEFINED_BINDINGS</code>||Defined to indicate that [[Shaders in Oolite: uniforms|arbitrary uniform bindings]] are available. (Defined in Oolite 1.69 and later.) | |
|- | |- | ||
− | + | | <code>OO_TANGENT_ATTR</code>||Defined to indicate that the <code>tangent</code> vertex attribute is available. (Defined in Oolite 1.73 and later.) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
|} | |} | ||
− | |||
== Limitations == | == Limitations == | ||
The current implementation does not provide the information necessary to implement the most common form of [http://en.wikipedia.org/wiki/Normal_mapping normal mapping]. | The current implementation does not provide the information necessary to implement the most common form of [http://en.wikipedia.org/wiki/Normal_mapping normal mapping]. | ||
+ | |||
+ | == Links == | ||
+ | *[[OXP howto texture]] (mostly 2006, but with up-to-date links) | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=4&t=3460 Shader's Outpost thread] (2007-date) with links and downloadable .oxp examples | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=4&t=13815 A request for a simple example of using shaders] (2013) | ||
+ | *[https://www.youtube.com/watch?v=hWeN_66guzg Oolite Crosire's Reshade Tutorial] (2015 YouTube video - by Getafix - using Crosire's Reshade v1.0.0 post-processing injector) | ||
+ | |||
+ | *[https://thebookofshaders.com/ The book of Shaders] - with working examples of complex shaders (2015 - by Patricio Gonzalez Vivo & Jen Lowe) | ||
+ | |||
+ | [[Category:Oolite]] | ||
+ | [[Category:Oolite scripting]] |
Latest revision as of 03:24, 29 February 2024
See also: Materials 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, and from 1.68 on other platforms. However, the shader support is still evolving. This page currently documents the state in Oolite 1.72.
This page was last worked on in 2010.
Contents
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:
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. (Requires Oolite 1.68 or later.) |
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. (Requires Oolite 1.68 or later.) |
uniforms | dictionary | Uniforms to pass to the shaders. |
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.
Vertex shader
This vertex shader, like many vertex shaders, exists primarily to prepare information for the fragment shader.
varying vec3 v_normal; void main() { v_normal = normalize(gl_NormalMatrix * gl_Normal); gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; gl_Position = ftransform(); }
The first line declares a varying variable. This may sound like an oxymoron, but varying variables have the special property that they are interpolated across geometry and passed to the fragment shader. For instance, if you set a varying variable to red at one corner of a triangle, green at a second corner, and blue at the third, the colour as seen by the fragment shader will vary smoothly across the triangle:
This is followed by the function main()
, which is the function that will be called by the GPU for each vertex. This sets the varying normal (direction facing out from the surface), and performs additional set-up required for positions and texture co-ordinates to make sense in the fragment shader.
Fragment shader
A fragment shader is called for each fragment of a generated polygon. A fragment is a pixel, in screen space, on which the polygon is potentially visible. (It is potentially visible because the shader may discard the fragment, and later polygons which are closer to the camera may draw over it.)
// Information from Oolite. uniform sampler2D tex0; uniform sampler2D tex1; uniform float time; // Information from shipdata.plist. uniform float reciprocalFrequency; // Information from vertex shader. varying vec3 v_normal; float wave(float t) { return sin(t * 6.28318530718) * 0.5 + 0.25; } #define LIGHT(idx) \ { \ vec3 lightVector = normalize(gl_LightSource[idx].position.xyz); \ color += gl_FrontMaterial.diffuse * gl_LightSource[idx].diffuse * max(dot(v_normal, lightVector), 0.0); \ } void main(void) { // Calculate illumination. vec4 color = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; // LIGHT(0); LIGHT(1); // Load texture data vec2 texCoord = gl_TexCoord[0].st; vec4 colorMap = texture2D(tex0, texCoord); vec4 glowMap = texture2D(tex1, texCoord); // Multiply illumination by base colour color *= colorMap; // Calculate glow intensity float t1 = reciprocalFrequency * time + glowMap.a; float lightLevel = wave(t1); // Add glow. color += lightLevel * glowMap; gl_FragColor = vec4(color.rgb, 1.0); }
The first section declares several 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. The float
reciprocalFrequency
is set in shipData.plist
; this allows a shader to be used for different ships (or different shaders on the same ship) with small variations. Uniform variables can only be read, not written to.
The varying
declaration is the same as in the vertex shader, but fragment shaders can only read varying variables, not write to them.
The custom function wave()
is used to smoothly vary the glow map from on to off.
This is followed by a macro definition, LIGHT
. A macro is substituted into the code each time it occurs; in main()
, LIGHT(0)
and LIGHT(1)
will be replaced by the text of the macro definition, with idx
in the macro being replaced with 0 or 1 respectively. Ideally, this would be a function, like wave()
, but for technical reasons this causes performance problems. Specifically, under Apple’s implementation of GLSL (and probably others), this causes a set of uniforms for every light the graphics card supports to be pulled into the shader.
The last part is the function main()
, which is the function executed by the GPU. It first calculates the contribution of light source 1, using the information prepared in the vertex shader and interpolated by the GPU. (Light 0 is used for the demo screen and shipyard; light 1 is the sun, or an arbitrary light source when in witchspace. Future versions of Oolite may change this, though. Ideally, the shader would check light 0, but in current versions of Oolite this causes problems.) It then loads values from the two textures. The first, the colour map value, is simply multiplied by the incoming light. For the second, the glow map, an intensity value is calculated based on the time, the reciprocalFrequency
uniform and the alpha channel of the glow map, which specifies animation phase. The glow map is likewise multiplied by its intensity value, and added to the total light. Finally, the combined alpha channel is forced to 1.0, and the result assigned to the special variable gl_FragColor
, which determines the colour of the generated fragment.
Uniforms
Uniforms are variables whose value is set by the host application, i.e. Oolite. Oolite automatically supplies one uniform of type sampler2D
for each texture, named tex0
, tex1
etc. It is also possible to bind uniforms to certain properties of the entity the shader is being applied to, for instance, its engine level. Additionally, constant values can be specified for uniforms, allowing a single shader to be used on different entities with differences in configuration.
For more information on uniforms, see Shaders in Oolite: uniforms.
Macros
These preprocessor macros are defined by Oolite and may be used with the #if
/#endif
directives to selectively remove sections of code.
Name | Description |
---|---|
OO_LIGHT_0_FIX |
In current versions of Oolite, light number 0 is effectively always attached to the camera, giving the effect of the player shining a light on anything they’re looking at. When this is fixed, OO_LIGHT_0_FIX will be defined. (Not yet defined in any version of Oolite.)
|
OO_REDUCED_COMPLEXITY |
Defined if user has selected lower-complexity shaders. |
OO_TEXTURE_UNIT_COUNT |
An integer specifying the number of texture units supported by hardware, which is the maximum number of texN uniforms that will be meaningful. This should be the same as gl_MaxTextureUnits, but a macro has the advantage that it can be used to completely exclude parts of the shader. (Defined in Oolite 1.69 and later.) |
OO_USER_DEFINED_BINDINGS |
Defined to indicate that arbitrary uniform bindings are available. (Defined in Oolite 1.69 and later.) |
OO_TANGENT_ATTR |
Defined to indicate that the tangent vertex attribute is available. (Defined in Oolite 1.73 and later.)
|
Limitations
The current implementation does not provide the information necessary to implement the most common form of normal mapping.
Links
- OXP howto texture (mostly 2006, but with up-to-date links)
- Shader's Outpost thread (2007-date) with links and downloadable .oxp examples
- A request for a simple example of using shaders (2013)
- Oolite Crosire's Reshade Tutorial (2015 YouTube video - by Getafix - using Crosire's Reshade v1.0.0 post-processing injector)
- The book of Shaders - with working examples of complex shaders (2015 - by Patricio Gonzalez Vivo & Jen Lowe)