Graphics – MGP – Intro to Shaders

10 06 2010

Intro
Before we can go any further into the project we need to introduce the concept of shaders and the modification of the Fixed Functionality Pipeline.

In OpenGL the normal pipeline looks like that in the image below. Vertices, normals and colours go in one end, are transformed into their final location (depending on camera, matrix transformations etc) and then the colour of each pixel is determined before being put on the screen.

OpenGL Pipeline Simplified

In our use of shaders we are changing two stages of this pipeline by replacing the fixed functionality with little programs of our own. The two areas that we can replace are marked in green in the diagram.

Replacing the FFP
We are going to use GLSL shaders which means they are written in the GLSL language and supplied to the graphics driver as code at runtime. The graphics driver then compiles them into a shader and returns a reference for us to use.

To create the reference we need to follow the steps below:

  1. Create handles for our shaders
  2. Send the source code for each to the graphics driver
  3. Compile the shaders
  4. Check for compilation problems
  5. Tell our shader about the Fragment and Vertex shaders
  6. Link shader
  7. Validate shader
  8. Check for linker errors

The code to do this is as follows:

// references we will need
GLuint shaderHandle, vsHandle, fsHandle;
GLint result;
const GLChar* vsSource = "…";
const GLChar* fsSource = "…";

// compile the vertex shader part
vsHandle = glCreateShader (GL_VERTEX_SHADER_ARB); // Step 1
glShaderSource (vsHandle, 1, &vsSource, NULL); // Step 2
glCompileShader (vsHandle); // Step 3
glGetShaderiv (vsHandle, GL_COMPILE_STATUS, &result); // Step 4
if( result == GL_FALSE )
    ; // ohoh - need to handle the fail case (print what is wrong etc)

// compile the fragment shader part
fsHandle = glCreateShader (GL_FRAGMENT_SHADER_ARB); // Step 1
glShaderSource (fsHandle, 1, &fsSource, NULL); // Step 2
glCompileShader (fsHandle); // Step 3
glGetShaderiv (fsHandle, GL_COMPILE_STATUS, &result); // Step 4
if( result == GL_FALSE )
    ; // ohoh - need to handle the fail case (print what is wrong etc)

// link them together
shaderHandle = glCreateProgram (); // Step 1
glAttachShader (shaderHandle, vsHandle); // Step 5
glAttachShader (shaderHandle, fsHandle); // Step 5
glLinkProgram (shaderHandle); // Step 6
glValidateProgram (shaderHandle);	// Step 7
glGetShaderiv (shaderHandle, GL_LINK_STATUS, &result); // Step 8
if( result == GL_FALSE )
    ; // ohoh - need to handle the fail case (print what is wrong etc)

And that is pretty much it. There is a whole bunch of extra error checking you can do and you can even get and print the compilation errors if you want but the above is the simplest case.

Now that you have a proper shader you need to be able to turn it on and off. How this works is that you can enable a particular shader much like you would lighting and then each draw command submitted until the shader is turned off will use the new functionality.

As such the proper way to use it would be something like:

glUseProgram (shaderHandle); // Turn it on
// Do some drawing
glUseProgram(0); // Turn it off

Writing a (really) simple shader
The above code explains how to create a new shader and how to use to draw but assumes you have some code to submit to the graphics driver. Here we will describe the simplest possible shader and a small variation that lets you use the normal at each point as the colour (for visualization much like the image in the previous graphics post).

If all you want is to replicate the fixed funtionality the following fragment and vertex shaders will do the trick

// Vertex shader
void main ()
{
	gl_Normal = gl_NormalMatrix * normalize(gl_Normal);
	gl_Position = ftransform();
}
// Fragment shader
void main ()
{
	gl_FragColor = gl_FrontColor;
}

This is pretty boring though (and does even less than the normal pipeline) so lets change the fragment shader a bit to use the normal instead of the color.

// Fragment shader
void main ()
{
	gl_FragColor = vec4( gl_Normal.xyz, 1.0);
}

This will use the x, y and z components as the red, green and blue components respectively.

And here is that image again to show where we are at:

Normals

For further reading into what functionality is available when creating these shaders the GLSL spec is the place to go: OpenGL.org





Graphics – MGP – Normals

7 06 2010

Last time we ended up with a solid but flat shaded model of our glass. The next step to making this look more realistic is to include per-pixel lighting. For our purposes we will use Phong Illumination but this (and every other lighting formula) requires that we know the normal at each point on the surface.

A normal is the vector that is perpendicular to the surface. For flat surfaces this is easy to understand but for curved surfaces it can be easier to understand it as the cross product of the two tangents to the surface (in essence the two tangents define a flat surface and the normal is then perpendicular to this). More information on normals can be found over on Wikipedia

For the specific case of our surface of revolution things are a bit easier than this. We have a 2D curve defined as a bezier curve for which we can find the 2D tangent. The tangent is found by solving the derivative of the Bezier equation. i.e we use:

and

Which is easier than it looks and mostly uses the code from the previous post on creating a surface.

But we need the vector that is perpendicular to the surface, not tangential so there is one further step before we can use this. To do this we rotate the vector anti-clockwise by 90 degrees to get the orthogonal vector.

The function itself looks something like this:

-(NSPoint) tangentOnCurve:(double) t
{
	NSPoint ret = NSMakePoint(0,0);
	double bern;
	// iterate over all points adding the influence of each point to the tangent
	for( int i=0; i < [mControlPoints count]; i++ )
	{
		bern = Basis_Derv([mControlPoints count], i, t);
		ret.x += [[mControlPoints objectAtIndex: i] pointValue].x * bern;
		ret.y -=  [[mControlPoints objectAtIndex: i] pointValue].y * bern;
	}
	// we then normalize this result (make sure the vector has a length of 1)
	double len = sqrt( ret.x*ret.x + ret.y*ret.y );
	if( len > 0.0 )
	{
		ret.x = ret.x/len;
		ret.y = ret.y/len;
	}
	return ret;
}

The rotation bit is being handled by lines 9 & 10 where the components are reversed to create the orthogonal vector.

This gives us a vector in 2D which we know is associated to a 2D point that is being rotated around the z-axis to create the surface of revolution. This rotation can be applied to the normal vector as well to create a final 3D vector.

Carying on from last time we need to put these normals into an array and upload them to the graphics card for use. The same as for vertices, to get the normals into graphics memory we can use:

// generate the buffer
glGenBuffers(1,&normalRef); 
// fill the buffer
glBindBuffer(GL_ARRAY_BUFFER, normalRef);
glBufferData(GL_ARRAY_BUFFER, numberVerts * 3 * sizeof(float), &normals, GL_STATIC_DRAW);

And then when drawing there are a couple extra lines (highlighted below):

// let it know which buffers we will be supplying
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
// let it know reference/offsets for the draw array
glBindBuffer(GL_ARRAY_BUFFER, drawRef);
glVertexPointer(3, GL_FLOAT, 0, 0);

// let it know reference/offsets for the normals array
glBindBuffer(GL_ARRAY_BUFFER, normalRef);
glNormalPointer(GL_FLOAT, 0, 0);

// tell it to draw the specified number of vertices
glDrawArrays(GL_TRIANGLE_STRIP, 0, numberVerts);
// turn stuff off again
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

If we were to color the surface using these vectors (a false colouring using the normals x,y,z as the r,g,b colour components) we get an image like the one below:

Normals

Next: Lighting





Photo Blog 108

7 06 2010

Something I have been interested in for a while but never had an impetus to explore is product photography. Recently however someone said they were needing some stuff done and simultaneously a tutorial popped up on one one of the blogs I read (www.lightstalking.com). This was reason enough so I tried it out yesterday and made myself a light tent. It really was quite easy and just consisted of finding some nice diffuser material at the Look Sharp store on Victoria St and one of my storage boxes butchered for the task. The final setup looked like this:

Setup Shot

The two sides and top had been replaced with the semi-transparent material and I had two identical desk lamps shining from the sides. Unfortunately we had no paper big enough to act as the backdrop so used two pieces stuck together. This resulted in a line in the background but it is easy to imagine what it would look like without this…

So now on to the results. I didn’t have much time to play around before we had to head out but here are two examples of what was able to be achieved. It definitely required longer exposures than normal and a tripod to keep things steady. Even on small apertures focus was an issue this close to I had to pull the camera out a bit and then crop the final image.

Jewellery Example 1 (f22 2.5s 105mm)

And a second example:

Jewellery Example 2 (f22 1.3s 105mm)

The effect of having nice diffused lighting is obvious and I’m definitely keen to try some more!