Here's Example one - Cube with one side switching colour between red & green
I've not included any lighting calculations in this example so the shader code is as short as possible, here's the vertex shader, it's just a text file saved in this case with the file extension .vertex and can be opened in any text editor
Code: Select all
varying vec2 vTexCoord;
void main(void)
{
vTexCoord = gl_MultiTexCoord0.st;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
This simple vertex shader just calculates and passes the texture cooordinates for the current polygon fragment to the fragment shader as a vec2 called 'vTexCoord'
A 'vec' is just a collection of floating point numbers grouped together under a single variable name and since a texture is just a flat 2d image with an X axis (width) and a Y axis (height) then we only need 2 numbers to describe any pixels position on it so we use a vec2 to hold this data.
When we get to the fragment shader we're going to store colours using vec4's. Since a colour needs 4 components to describe it (the Red colour Value, the Green colour value, the blue Colour Value and the Alpha (Transparency) value we'll need four numbers to store those (and XYZ vertex positions could be stored in a vec3 if this vertex shader was going to be doing any transforms on the actual objects geometry.
To pass a vec to the fragment shader write 'varying' before its definition, see the line
varying vec2 vTexCoord; above, this exact same line needs to be duplicated in the fragment shader to complete the 'link'.
You can pass data from the vertex shader to the fragment shader but not the other way around.
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; is dealing with the objects geometry, i must confess i don't really know what this is doing
but it's a line that needs to be in the shader otherwise the whole thing fails to compile, 'gl_position' 'gl_ModelViewProjectionMatrix' and 'gl_Vertex' are all reserved openGl keywords
The shader needs to be saved to your OXP's Shaders folder (file and folder names are case sensitive) and listed in your ships Shader definitions in shipdata.plist
eg see lines 15 & 16 in shipdata.plist in Example_one_no_lighting.oxp
Code: Select all
vertex_shader = "example_one.vertex";
fragment_shader = "example_one.fragment";
Here's the fragment shader, notice the
'varying vec2 vTexCoord;' line linking this vec2 back to the same named one from the vertex shader.
To work in an useful way shaders often need links back to Oolite's game engine, these links are called 'Uniforms' and are defined in your shader and also in your shipdata.plist. Textures need to be sent from Oolite to your shader as uniforms, see the lines
uniform sampler2D uDiffuseMap; and
uniform sampler2D uMaskMap; that start off our fragment shader and lines 24 & 25 in the shipdata.plist:-
Code: Select all
uDiffuseMap = { type = texture; value = 0; };
uMaskMap = { type = texture; value = 1; };
Make sure the uniform names in shipdata.plist(here they are
uDiffuseMap &
uMaskMap) match in the shader, the way you list your textures in shipdata.plist is important too
Code: Select all
textures =
(
"Example_one_DiffuseTexture.png",
"Example_one_MaskTexture.png"
);
keep this order when you list them as uniforms (the shader snippet below also adds an extra uniform linking a variable called 'UTime' to Oolite's universalTime (oolites own 'gameclock')
Code: Select all
uniforms =
{
uDiffuseMap = { type = texture; value = 0; };
uMaskMap = { type = texture; value = 1; };
uTime = "universalTime";
};
I hope that's not too confusing, let's take a look at the fragment shader, i've added comments to the code that should hopefully explain stuff, just post on the forum for more detail on particular things, a lot of the code i can't follow myself especially the lighting code, i just know it has to be there and not fiddled with too much
OK, so to do the red/green texture colour switch we'll need to code up a timer, we've already got our shader uniform binding 'UTime' which is linking our shader to Oolite's Gameclock, since the gameclock will start at 0 seconds and just keep counting up for as long as the game is running it's not much use to us as a timer to switch between two settings, so what we'll do in this line in shader...
Code: Select all
float SwitchTime = step(0.0, sin(uTime));
...is to make a sine wave from current time value, then use a function called step to turn the sinewave into a square wave and then use the value of the squarewave (always either 0.0 or 1.0) to decide if either Red or Green gets added into the colour mix, here's a graph showing the sinewave generated from the game clock
when the value of the sine wave is above 0.0 the step function overwrites the value to 1.0 and when the sine wave falls below 0.0 the step function overwrites the value to 0.0 (shown as a blue line on the graph)
right, so now whe have a floating point number called 'SwitchTime' that is alternating between 0.0 and 1.0 every few seconds or so, we can use this along with a command called 'mix' to create our red/green switching colour and add it into the final colour result, we'll do this in the following line
Code: Select all
SwitchingColour = mix(Green, Red, SwitchTime);
as this is turning into a bit of an epic post i'll let the comments in the shader code posted below explain how this is working but think of it as two pots of paint with a mix percentage of paint from the second pot determining the final colour we get, as our mix percentage is either 0% and 100% because of our square wave then we either just get paint from the first pot (green) or paint from the second pot (red)
Code: Select all
// Uniforms from Oolite
uniform sampler2D uDiffuseMap;
uniform sampler2D uMaskMap;
uniform float uTime;
// Information from Vertex Shader
varying vec2 vTexCoord;
const vec4 Red = vec4(1.0, 0.0, 0.0, 1.0); // store the Red Colour in a vec4 called 'Red', the 4 numbers are the Red, Green, Blue and Alpha values, range is 0.000000 - 1.000000
const vec4 Green = vec4(0.0, 1.0, 0.0, 1.0); // store the Green Colour in a vec4 called 'Green'
void main(void)
{
/* - this symbol marks the start of a /comments section' which allow us to annotate the code
this symbol marks the end of our comments -> */
// two slashes like this // can also be used to add comments on a per line basis
/*
Calculate a sin wave powered by a 'link' back to Oolite's game clock, these links are knows as 'Uniforms'
the results from the sin wave calculation are being stored as a floating point number in a variable called
'SwitchTime', the word float that starts the line is telling the shader this.
Around our sin wave result is an openGL function called step, the definition of the step function is:
Step(edge, x) Returns 0.0 if x < edge, otherwise it returns 1.0
Basically doing this changes our sin wave to a square wave
where the output is either 0.0 (when the sin wave result is below 0.0) or 1.0 when the sin wave result is above 0.0
*/
float SwitchTime = step(0.0, sin(uTime));
/*
we could use a different function to step called smoothstep if we'd like the colour
to gradually change between red and green rather than instantly switch, to test this
comment out the 'float SwitchTime' line above and uncomment the float SwitchTime line below, remove the //'s
*/
// float SwitchTime = smoothstep(-1.0, 1.0, sin(uTime));
/*
OK, so now we have a floating point number that switches between 0.0 and 1.0 every few seconds, we'll use this
value in a 'mix' function and store the colour in a new vec4 called SwitchingColour
'mix' definition: mix(x,y,a) Returns x * (1-a) + y * a i.e. a linear blend of x and y
So basically this means mix together colour choice 1 'Green', colour choice 2 'Red', and how much percentage of
each colour goes into the final result ('SwitchTime') and since SwitchTime is only ever 0.0 or 1.0 the mix
amount is either going to 100% colour choice 1 'Green' (when SwitchTime = 0.0) or 100% colour choice two 'Red' (When SwitchTime = 1.0)
*/
vec4 SwitchingColour = mix(Green, Red, SwitchTime);
// initialise the vec4 that will contain the final colour result from this shader, fill it with zero's (the colour value for Black)
vec4 finalColor = vec4(0.0);
// Get the Texture Coordinates from the vertex shader, the UV map coordinates basically
vec2 texCoord = vTexCoord;
// Read in the texture map using the texture co-ordinates to locate the correct pixels for the polygon fragment we're currently working on, store them in a vec4 called diffuseMapColor
vec4 diffuseMapColor = texture2D(uDiffuseMap, texCoord);
// Read in the effects texture that is used as a mask texture to control the visibility of our colour changer effect
vec4 EffectMask = texture2D(uMaskMap, texCoord);
/*
Add in either the results of our Red/Green mix calculation above, multiply this by the mask texture so only areas in this texture
that have a colour value greater than 0.0 get the effect since as multiplying anything by 0.0 results in 0.0
(or no change to the originalcolour value we're adding 0.0 to it)
*/
diffuseMapColor += SwitchingColour * EffectMask;
// output the final colour result to the frame buffer for displaying on screen
gl_FragColor = diffuseMapColor;
}
OXP download and rendermonkey file (
http://developer.amd.com/resources/arch ... toolsuite/ note: Rendermonkey is Windows Only) is available here
https://www.box.com/s/druvgqm7fe6tnqntkx2s
There are 2 versions of the oxp, one with lighting code and one without, just install one or the other not both at once as they'll clash
The oxp has a script.js that will spawn 6 of the example cubes nearby when you launch from a station, they're like_shipped to asteroids so will appear on the scanner as grey lollypops and will also appear in the demoships parade on the 'load commander y/n' title screen