This blog will take you through the use of shaders in TwoLoc from basic setup to your first working shader (I'm learning as I teach!). It is based on the shader tutorial created by Saad Khattak. The first step is to ensure your base GLSL file is set up properly and ready to use. From there you can actually begin writing the vertex and fragment shaders.
Before you get started with any code, let make sure to set up our resources first. You'll need to make a few different files with different extensions. To start, set up a new .material file in the resources folder. Edit it with notepad as follows:
vertex_program infr3110vs glsl
{
source infr3110vs.glsl
default_params
{
param_named lightPos float4 1.0 1.0 1.0 1.0
}
}
fragment_program infr3110fs glsl
{
source infr3110fs.glsl
}
material matCrate
{
technique
{
pass
{
vertex_program_ref infr3110vs
{
}
fragment_program_ref infr3110fs
{
param_named diffuseMap int 0
}
texture_unit
{
texture CrateTexture.jpg 2d
tex_coord_set 0
}
}
}
}
This may seem like a lot of stuff, but its really quite the simple. The first just sets the name of the vertex shader (and fragment a bit lower down) for the program to look for. Next it creates a fairly default light at the specified coordinates. This is important for later when you calculate the lighting. Finally, a texture unit is created, which tells the program that the CrateTexture will be placed onto the texture coordinates.
In your .cpp file, make sure to tell the program the paths for your resource files, or they will fail to load at all! Also now that you're starting to work on the shaders, keep in mind that if anything doesn't work properly, Ogre will take over and its default shaders will be displayed instead of your own. This is actually a handy way to determine if everything is working as it should.
Here goes the vertex shader (in GLSL).
varying vec3 normal;
varying vec3 vertToLightVec;
varying vec2 UV;
uniform vec4 lightPos;
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
normal = gl_NormalMatrix * gl_Normal;
The 'varying' modifier defines a variable as something that will change with each vertex. This means that every vertex the shader inputs will have its own normal vector, vector to the light, and UV's. Uniform on the other hand, means the variable has been explicitly stated and it will always remain the same; in this case the light we added from the .material file.
Getting into the main function, you must always remember that shaders work with different viewing matrices, and that it is important to get the right one. For lighting purposes, we want each vertex multiplied by the ModelviewProjection matrix to manipulate it properly. Next we do the same thing, but for the vertex normals.
vec4 vertInMVSpace = gl_ModelViewMatrix * gl_Vertex;
vec4 lightPosInMVSpace = gl_ModelViewMatrix * lightPos;
vertToLightVec = vec3(lightPosInMVSpace.xyz - vertInMVSpace.xyz);
UV = vec2(gl_MultiTexCoord0);
}
After that we perform the necessary lighting calculations. We need to get the vertices in Modelview matrix so that the light's 'rays' and reflection off the normals can be calculated properly. Similary, the light coordinates need to be relative to the Modelview matrix. Once everything is in the same coordinate frame, we use simple vector subtraction to get the resultant vector between the light and that particular vertex.
The last line simply sets the UV coordinates to a variable which will be passed through to the fragment shader (so we can apply the texture per fragment, or else it will look weird). That's it for the vertex shader, now we are almost done!
The first part of the fragment shader should look similar to this:
varying vec3 normal;
varying vec3 vertToLightVec;
varying vec2 UV;
uniform sampler2D diffuseMap;
Here we declare variables inside the fragment shader. When you want data to move from vertex to fragment, make sure you declare it with the same variable in each. You'll see that the first three are the same as you have in the vertex shader; these are all passed through. The final variable is an explicitly set texture type. It tells the program that a 2D texture will be projected onto the object, in this case a diffuse texture map.
void main()
{
gl_FragColor = texture2D(diffuseMap, UV);
vec3 normalizedNorm = normalize(normal);
vec3 normalizedVertToLightVec = normalize(vertToLightVec);
To finish up, its a simple matter of applying the texture and calculating the remainder of the lighting. The first line sets the varying UV coordinates to the diffuse map texture, so each fragment will know which colour it is supposed to become.
The next two lines ensure that everything is nicely normalized before proceeding. This is very important as any non-normalized vectors at this point can drastically alter how the light behaves, and will most likely cause artifacts in your scene.
Going back to algebra, we know that getting the dot product between two vectors returns a scalar value. For lighting, this value determines the amount of light each fragment will receive based on the angle it faces relative to the light source. It is clamped between 0 and 1 to ensure the final multiplication doesn't become a weird number.
float diff = clamp(dot(normalizedNorm, normalizedVertToLightVec), 0.0, 1.0);
gl_FragColor = gl_FragColor * diff;
}
This is it! Here we are! One final calculation to end it! This final step takes the colour from every fragment of the texture, and multiplies it by the scalar value we just got to determine how bright (the final colour) every fragment will be. Congratulations on completing your first simple shader.
Keep in mind that from here the possibilities are endless. You can do anything from adding more lights, to changing the hue/saturation of fragments, or even add in a shadow map (that is where it starts getting tricky). I hope you learned as much as I did, and shade on!
No comments:
Post a Comment