Normal Map Break Down
Posted: Tue Oct 06, 2009 9:50 pm
This is a Normal Map Shader Langauge Tutorial.. I tried to keep it as non technical as I could.
The first thing you need is a Graphic Card that supports shaders.. without shaders.. no normal mapping. and there is nothing you can do about it... You also need to know to make normal maps.. that is beyond the scope of this tutorial.. but basic a grey scale bump map can be converted using plugins in photoshop or other tools, to a normal map.. which is far superior to greyscale bump mapping. There are other more advanced methods.
The second thing you need is the shader files, these are known as
vertex & fragment file types
These are written in a code form format very similar to C
here are the two simple files from my Polaris Class destroyer... These are direct copies of Griffs Coriolis Station.. the undamaged one... They are without any effects other than the normal mapping...
First the Vertex File..
This is a simple as it gets... I'm not going to pretend that I understand everything to the spec what is going on here. But generally speaking the vertex file is as its extension indicates used to manipulate the models Vertexes and also relay information in regard to its position and angle to the camera (the screen) to the fragment file.. (texture manipulation file) it does this for all vertexes.. for reasons of simplicity I'm not going to show you examples of how to manipulate vertexes since this tutorial shows you how to make normal maps appear... But you will need at least this code above in a vertex file to make normal mapping work...
Now before we enter into explanation of the fragment file you need to be aware of howto link these in the shipdata.plist file...
here is the releavent shipdata.plist file entries... for the Polaris Class Destroyer
What I'm doing here is telling Oolite to:
When on this model encountering a texture named back_metal.png then link this texture to:Frame_station.vertex and Frame_station.fragment.
Textures named back_metal.png on this particular model is replaced by the Texture named fx3_Panels_Shiny.png
Add a second texture.. as this is added last, this is not the primary Texture as in it is not shown.. Think of it as an auxiliary texture.. something you want to do something to your primary texture with. In our case we want to use it as a normal map.
Note that there are other ways of doing this but it involves Uniforms.. something I will not address here.
This means Tiling is enabled and note that everything still depends on how you projected the UV map in your model editor. but if you set it to tile 3 times on the X axis... this should now show instead of Clamping the texture to the edges... if you ever tiled a texture and tried to look at it you will note it does not look ok... everything outside the UV map box will be stretched...
Now to the juicy part.. the fragment file..
There is no way around this, some coding experience is required to understand this... but lets take it from the start...
Here we declare two uniforms which we will need namely our two textures... you will need to declare them... or else the code cannot see them... Oolite takes care of linking them.. so
tex0 isfx3_Panels_Shiny.png our diffuse map
tex1 isfx3_Panels_Bump2.png our normal map
these next declarations are must be in declarations..
VTexCoord is the uvmap on this particular face fetched from the vextex shader file which got it from the dat file. We get it from the vertex shader file because we could have manipulated its vertexes...
VeyeVector is the vector at which the user is pointing in space, and therefore the way his eyes is pointing..
vLight0Vector
vLight10Vector
not sure about those. but ofcourse something with lighting in space and in the start screen and buy ship screen... They do got their values from the vertex shader since they are of type varying...
Finally these two declarations are used to control how much the normal and color map is lit up... You can alter their settings as you wish however you can never change them on the fly.. meaning you cannot alter them any other place than here becuase they are declared const meaning they are a constant number that never changes after being declared... the declaration is at compile time constant, and in our case the compile time is when we start up Oolite...
Right... now to the scary stuff... the light function... Ease Up, after this you should never concern yourself about it again.. it just has to be there in order for the normal map and color map to light up correctly
what this function does is that it calculates the light reflection and returns the result.. it is used in 3 cases..
Ship Buy Screen
Oolite start screen
Ingame...
unless you want to do some heavy modifying of the light result you should not alter this... however here is a link if you are interested
http://www.lighthouse3d.com/opengl/glsl ... php?lights
Now to the main part, where it all happens namely MAIN
You always need the function main, Oolite expects it.. and starts to execute it once the game is running and the model is showing in some form...
without it you will probably get an error...
but to try and explain what goes on here...
the first thing you see is all the Normalize functions, now what does that mean.. Without going into to much detail it means bringing our texture face into a correct state so we can manipulate it.
So at this stage you should simply accept that this is so... So copy and paste..
Now here is the stuff we are interested in...
We here declare a vec3 meaning a vector with 3 values. XYZ does not mean Model space XYZ but RGB... you always need to use xyz when using a vec3
But our Normal is set to be equal of our normal map RGB channels and from each are subtracted 0.5. I admit i have no idea why the subtract , but I assume it has to-do with the parallax method used to generate the normal maps..
this:
Is hard for me to explain, but what it is is that the result equals the texture.st where st is the coordinates.. st coordinates are of type float... are basicly XY coordinates but in 3d space. but as we normalize them we can think of them in 2d space... and as XY coordinates...
The texCoord is a vec2 meaning it contains two values
namely s and t and they are floats... and you can manipulate them individualy like this.
s and t us could be called sub-members of texCoord
But on With the Normal mapping Explanation...
next up we got
This is our texture map, our base map so to speak which we are going to manipulate in order go get the normal map projected onto there... what we did was declare it of type vec4. we fetch it via its texture coordinates.. found in the dat file... and retrieved via the vertex shader...
Vec4 submembers are known as RGBA (red green blue alpha) channels
so we can manipulate the the different channels individually just like we did with the s and t member of texCoord by simply writing colorMap.a = 1.0 meaning the entire alphamap will be white.. which we are going to-do later
Next Up
we read
these are two simple declarations of vec4
we are setting all of their values to 0.0 for the diffuse and 0 for the specular..
we are declaring it now because we are going to use it later..
next up we got two things... depending on where the player is...
this calculates the correct lighting depending on where the player is... in the buy ship screen / demo screen or in space screens...
terrible boring so we move on to
Here we set our diffuse light
Diffuse reflection is the reflection of light from an uneven or granular surface such that an incident ray is seemingly reflected at a number of angles.
also known as our normal map
We then grab our normal map with our color map in the next line..
here by mutiplying theire values, obeying mathematical rules..
We finally add the extra lightning and the colorMap
for some reason Griff set the alpha map to be 1.0 = all white. I tried setting it to 0.(no apparent effect). I presume Oolite will or had crashed in the past had the alpha map been non existent... So It was manually added here as a safe guard.
If your texture has an alphamap, and you are using it for something, you should remove this line. (I think )
finally return the result to Oolite
If you are keen on learning more shader langauge, read up griffs examples and/or read here
http://www.lighthouse3d.com/opengl/glsl/index.php?intro
Hope this was of help to you
PS. do not forget to vote...
man this was supposed to be 15 min write up.. it took 2 hours at least...
to bed to bed and let loose the pigeons of peace...
The first thing you need is a Graphic Card that supports shaders.. without shaders.. no normal mapping. and there is nothing you can do about it... You also need to know to make normal maps.. that is beyond the scope of this tutorial.. but basic a grey scale bump map can be converted using plugins in photoshop or other tools, to a normal map.. which is far superior to greyscale bump mapping. There are other more advanced methods.
The second thing you need is the shader files, these are known as
vertex & fragment file types
These are written in a code form format very similar to C
here are the two simple files from my Polaris Class destroyer... These are direct copies of Griffs Coriolis Station.. the undamaged one... They are without any effects other than the normal mapping...
First the Vertex File..
Code: Select all
#ifdef OO_TANGENT_ATTR
attribute vec3 tangent;
#else
const vec3 tangent = vec3(1.0, 0.0, 0.0);
#endif
varying vec2 vTexCoord;
varying vec3 vEyeVector; // These are all in tangent space
varying vec3 vLight0Vector;
varying vec3 vLight1Vector;
void main()
{
// Build tangent basis
vec3 n = normalize(gl_NormalMatrix * gl_Normal);
vec3 t = normalize(gl_NormalMatrix * tangent);
vec3 b = cross(n, t);
mat3 TBN = mat3(t, b, n);
vec3 eyeVector = -vec3(gl_ModelViewMatrix * gl_Vertex);
vEyeVector = eyeVector * TBN;
#ifdef OO_LIGHT_0_FIX
vec3 light0Vector = gl_LightSource[0].position.xyz + eyeVector;
vLight0Vector = light0Vector * TBN;
#endif
vec3 light1Vector = gl_LightSource[1].position.xyz + eyeVector;
vLight1Vector = light1Vector * TBN;
vTexCoord = gl_MultiTexCoord0.st;
gl_Position = ftransform();
}
Now before we enter into explanation of the fragment file you need to be aware of howto link these in the shipdata.plist file...
here is the releavent shipdata.plist file entries... for the Polaris Class Destroyer
Code: Select all
shaders =
{
"back_metal.png" =
{
vertex_shader = "Frame_station.vertex";
fragment_shader = "Frame_station.fragment";
textures =
(
{
name = "fx3_Panels_Shiny.png";
repeat_s = "yes";
repeat_t = "yes";
},
{
name="fx3_Panels_Bump2.png";
repeat_s ="yes";
repeat_t ="yes";
}
);
};
Code: Select all
vertex_shader = "Frame_station.vertex";
fragment_shader = "Frame_station.fragment";
Code: Select all
textures =
(
{
name = "fx3_Panels_Shiny.png";
Textures named back_metal.png on this particular model is replaced by the Texture named fx3_Panels_Shiny.png
Code: Select all
name="fx3_Panels_Bump2.png";
Note that there are other ways of doing this but it involves Uniforms.. something I will not address here.
Code: Select all
repeat_s ="yes";
repeat_t ="yes";
Now to the juicy part.. the fragment file..
Code: Select all
uniform sampler2D tex0;
uniform sampler2D tex1;
varying vec2 vTexCoord;
varying vec3 vEyeVector; // These are all in tangent space
varying vec3 vLight0Vector;
varying vec3 vLight1Vector;
const float kSpecExponent = 1.0;
const float kSpecular = 0.1;
void Light(in vec3 lightVector, in vec3 normal, in vec4 lightColor, in vec3 eyeVector,
in float specExponent, inout vec4 totalDiffuse, inout vec4 totalSpecular)
{
lightVector = normalize(lightVector);
vec3 reflection = normalize(-reflect(lightVector, normal));
totalDiffuse += gl_FrontMaterial.diffuse * lightColor * max(dot(normal, lightVector), 0.0);
totalSpecular += lightColor * pow(max(dot(reflection, eyeVector), 0.0), specExponent);
}
#define LIGHT(idx, vector) Light(vector, normal, gl_LightSource[idx].diffuse, eyeVector, kSpecExponent, diffuse, specular)
void main()
{
vec3 eyeVector = normalize(vEyeVector);
vec2 texCoord = vTexCoord;
vec3 normal = normalize( texture2D(tex1, texCoord).xyz - 0.5);
normal = normalize(normal);
vec4 colorMap = texture2D(tex0, texCoord);
vec4 diffuse = vec4(0.0), specular = vec4(0);
#ifdef OO_LIGHT_0_FIX
LIGHT(0, normalize(vLight0Vector));
#endif
LIGHT(1, normalize(vLight1Vector));
diffuse += gl_FrontMaterial.ambient * gl_LightModel.ambient;
vec4 color = diffuse * colorMap;
// calculate the specular, colour it using the diffuseMap
color += colorMap * 5.0 * specular * kSpecular;
color.a = 1.0;
gl_FragColor = color;
}
Code: Select all
uniform sampler2D tex0;
uniform sampler2D tex1;
tex0 isfx3_Panels_Shiny.png our diffuse map
tex1 isfx3_Panels_Bump2.png our normal map
these next declarations are must be in declarations..
Code: Select all
varying vec2 vTexCoord;
varying vec3 vEyeVector; // These are all in tangent space
varying vec3 vLight0Vector;
varying vec3 vLight1Vector;
VeyeVector is the vector at which the user is pointing in space, and therefore the way his eyes is pointing..
vLight0Vector
vLight10Vector
not sure about those. but ofcourse something with lighting in space and in the start screen and buy ship screen... They do got their values from the vertex shader since they are of type varying...
Finally these two declarations are used to control how much the normal and color map is lit up... You can alter their settings as you wish however you can never change them on the fly.. meaning you cannot alter them any other place than here becuase they are declared const meaning they are a constant number that never changes after being declared... the declaration is at compile time constant, and in our case the compile time is when we start up Oolite...
Code: Select all
const float kSpecExponent = 1.0;
const float kSpecular = 0.1;
Code: Select all
void Light(in vec3 lightVector, in vec3 normal, in vec4 lightColor, in vec3 eyeVector,
in float specExponent, inout vec4 totalDiffuse, inout vec4 totalSpecular)
{
lightVector = normalize(lightVector);
vec3 reflection = normalize(-reflect(lightVector, normal));
totalDiffuse += gl_FrontMaterial.diffuse * lightColor * max(dot(normal, lightVector), 0.0);
totalSpecular += lightColor * pow(max(dot(reflection, eyeVector), 0.0), specExponent);
}
Ship Buy Screen
Oolite start screen
Ingame...
unless you want to do some heavy modifying of the light result you should not alter this... however here is a link if you are interested
http://www.lighthouse3d.com/opengl/glsl ... php?lights
Now to the main part, where it all happens namely MAIN
Code: Select all
void main()
{
vec3 eyeVector = normalize(vEyeVector);
vec2 texCoord = vTexCoord;
vec3 normal = normalize( texture2D(tex1, texCoord).xyz - 0.5);
normal = normalize(normal);
vec4 colorMap = texture2D(tex0, texCoord);
vec4 diffuse = vec4(0.0), specular = vec4(0);
#ifdef OO_LIGHT_0_FIX
LIGHT(0, normalize(vLight0Vector));
#endif
LIGHT(1, normalize(vLight1Vector));
diffuse += gl_FrontMaterial.ambient * gl_LightModel.ambient;
vec4 color = diffuse * colorMap;
// calculate the specular, colour it using the diffuseMap
color += colorMap * 5.0 * specular * kSpecular;
// add in the glowing window lights
//color += LampColor * colorMap.a; // multiplier here to increase glow effect
color.a = 1.0;
gl_FragColor = color;
}
without it you will probably get an error...
but to try and explain what goes on here...
the first thing you see is all the Normalize functions, now what does that mean.. Without going into to much detail it means bringing our texture face into a correct state so we can manipulate it.
So at this stage you should simply accept that this is so... So copy and paste..
Now here is the stuff we are interested in...
Code: Select all
vec3 normal = normalize( texture2D(tex1, texCoord).xyz - 0.5);
But our Normal is set to be equal of our normal map RGB channels and from each are subtracted 0.5. I admit i have no idea why the subtract , but I assume it has to-do with the parallax method used to generate the normal maps..
this:
Code: Select all
texture2D(tex1, texCoord)
The texCoord is a vec2 meaning it contains two values
namely s and t and they are floats... and you can manipulate them individualy like this.
Code: Select all
texCoord.s = 0.5
But on With the Normal mapping Explanation...
next up we got
Code: Select all
vec4 colorMap = texture2D(tex0, texCoord);
Vec4 submembers are known as RGBA (red green blue alpha) channels
so we can manipulate the the different channels individually just like we did with the s and t member of texCoord by simply writing colorMap.a = 1.0 meaning the entire alphamap will be white.. which we are going to-do later
Next Up
we read
Code: Select all
vec4 diffuse = vec4(0.0), specular = vec4(0);
we are setting all of their values to 0.0 for the diffuse and 0 for the specular..
we are declaring it now because we are going to use it later..
next up we got two things... depending on where the player is...
Code: Select all
#ifdef OO_LIGHT_0_FIX
LIGHT(0, normalize(vLight0Vector));
#endif
LIGHT(1, normalize(vLight1Vector));
terrible boring so we move on to
Code: Select all
diffuse += gl_FrontMaterial.ambient * gl_LightModel.ambient;
Diffuse reflection is the reflection of light from an uneven or granular surface such that an incident ray is seemingly reflected at a number of angles.
also known as our normal map
We then grab our normal map with our color map in the next line..
here by mutiplying theire values, obeying mathematical rules..
Code: Select all
vec4 color = diffuse * colorMap;
Code: Select all
color += colorMap * 5.0 * specular * kSpecular;
If your texture has an alphamap, and you are using it for something, you should remove this line. (I think )
Code: Select all
color.a = 1.0
Code: Select all
gl_FragColor = color;
http://www.lighthouse3d.com/opengl/glsl/index.php?intro
Hope this was of help to you
PS. do not forget to vote...
man this was supposed to be 15 min write up.. it took 2 hours at least...
to bed to bed and let loose the pigeons of peace...