C sharp:Smooth Shading

From GDWiki

Jump to: navigation, search

Hi, it's me again. I decided to write something about Smooth shading (Gouraud shading), a technique that it's very easy to implement in your application. The results are not amazing but it's a start for an OpenGL beginner like me (and you). It's also the technique I used in my MD2 model loader. The easy part about smooth shading is that it's not dependent of the light position and light properties and the only thing you have to do is to calculate the normals, OpenGL will handle the rest.

In order to implement this you need a basic knowledge of vectors, at least what a vector is and what operations you can do using vectors. Usually a vector is represented by it's origin, direction and length or magnitude. I chose to represent my vector just like a 3D point (vertex) in my application with a little difference:

class Vec3D
    {
        private float x,y,z;
    }
    //it would be usefull to also add custom creators for this class and setters/getters for x,y,z

The difference is that x,y,z doesn't represent coordinates but actual lengths of the vector on each axis. So that means that all my vectors have (0,0,0) origin. Using this representation (carthesian representation) I don't have to actually store the length of the vector, but calculate it using the following formula (basic school vector math):

   length=SQRT(X^2 + Y^2 +Z^2)

so here is my getLength method:

public float getLength()
    {
        return Math.Sqrt(x * x + y * y + z * z);
    }

Ok, now OpenGL doesn't expect vectors when calculating lighting but versors. A versor is a vector with the length=1, it's also called a normal because the process to get the a vector's versor is called normalizing. To normalize a vector you will have to divide the vector's x, y, z by it's length, like in my normalize method below:

public void normalize()//get the versor (magnitude=1)
    {
        this = this / length();
    }

In order for this method to work we need to overload a few operations to work with vectors:

public static Vec3D operator +(Vec3D a, Vec3D b)//add two vectors
    {
        Vec3D Result = new Vec3D(a.x + b.x, a.y + b.y, a.z + b.z);
        return Result;
    }
 
    public static Vec3D operator -(Vec3D a, Vec3D b)//subtract two vectors
    {
        Vec3D Result = new Vec3D(a.x - b.x, a.y - b.y, a.z - b.z);
        return Result;
    }
 
    public static Vec3D operator *(Vec3D a, Vec3D b)//multiply two vectors
    {
        Vec3D Result = new Vec3D(a.x * b.x, a.y * b.y, a.z * b.z);
        return Result;
    }
 
    public static Vec3D operator *(Vec3D a, float x)//multiply vector by scalar
    {
        Vec3D Result = new Vec3D(a.x * x, a.y * x, a.z * x);
        return Result;
    }
 
    public static Vec3D operator /(Vec3D a, float x)//divide vector by scalar
    {
        Vec3D Result = new Vec3D(a.x / x, a.y / x, a.z / x);
        return Result;
    }
 
    public static Vec3D operator %(Vec3D a, Vec3D b)// cross product
    {
        Vec3D Result = new Vec3D(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
        return Result;
    }
 
    public static float operator %(Vec3D a, Vec3D b)// dot product
    {
        return x * x + y * y + z * z;
    }

Now that I know how to normalize a vector I can start to calculate the normal of each vertex in my model. In theory, in order to shade a surface I will calculate the normal of the surface but OpenGL works with vertex normals so:

  1. For each triangle I calculate the normal by using corner A and B to get one coplanar vector and corner A and C to get a second coplanar vector. Then I cross product these two vectors and normalize the resulting vector to get the normal of the triangle.
  2. I add this normal vector to each of the three corner vertexes
  3. I keep track in how many triangles each vertex is involved (connections)
  4. In the end I divide each vertex normal by it's connections number

and now for some pseudo-code (I wont use my actual code because a lot more is involved there, like data structures, MD2 frames, etc):

foreach triangle in triangle pool
       vector_a = corner_A
       vector_b = corner_B //so we have 3 vectors with the origin (0,0,0) and lengths specified by each corner coordinates
       vector_c = corner_C
 
       //create 2 coplanar vectors using these corner vectors
       vector_ca = vector_a - vector_b
       vector_cb = vector_a - vector_c
 
       vector_norm = cross prod of vector_ca and vector_cb
       normalize vector_norm
 
       corner_A_norm = corner_A_norm + vector_norm
       corner_B_norm = corner_B_norm + vector_norm
       corner_C_norm = corner_C_norm + vector_norm
 
       corner_A_connections = corner_A_connections + 1
       corner_B_connections = corner_B_connections + 1
       corner_C_connections = corner_C_connections + 1
    end for
 
    foreach vertex in the vertex pool
       vertex_nromal = vertex_normal / vertex_connections
    end for

This algorithm is widely used and described in a lot of tutorials so if I was not clear enough please feel free to surf the net for better explanations and drop em a line here.

Now that we have the vertex normals we just have to set the light(s) position and properties(using glLightfv function), the object material properties and enable the light using glEnable(GL.GL_LIGHTx). Then use glNormal3f at the time of the drawing to specify each vertex normal. All these things can be found in the OpenGL Red Book explained in detail with examples so I wont handle them here.

So these are a few code samples that can be useful when implementing your own shading functionality. More complex shading can be achieved using OpenGL shader language which I'm not (yet) familiar with, one of them being Phong shading which will calculate the shading per each pixel in a polygon and taking into account the light position.

This should be enough to get you started. Feel free to download my MD2 model loader and look in the source how I implemented this code-wise. Please note that some of the 3D model data files already include pre-calculated normals (like MD2).

DOWNLOAD

Files:Scrn.jpg

This is my MD2 viewer, the code is a mess and the methods I used for the data structures and rendering might not be the brightest but I achieved my goal to learn OpenGL in C# and it will give you an idea how I've complicated my life with OOP and OpenGL. For any comments, suggestions or "your code sucks" messages please do not hesitate to write me an email to balex2ro@yahoo.com.

The source code

Thanks to all the members here writing about the MD2 format, and thanks to the people who wrote the csgl library.

STUFF TO READ

Cheers

Categories