Ponies & Light

Flat shading of Meshes using OpenGL / GLSL on OSX with openFrameworks

Flat shading of Meshes using OpenGL / GLSL on OSX with openFrameworks

A walk-through on how to flat-shade a mesh using a phong shader on OSX, with example source code for openFrameworks

If you are pressed for time, jump to the meat of how to activate GLSL flat shading in OS X

Flat shading gives you an edgy and geometric look and keeps the faces of your meshes sharp and clean. Sometimes that’s desirable. The tricky thing with a commonly used mesh is that the normals are usually interpolated between vertices, and in order to render faces with the correct flat normals, you’d have to push the geometry for every face to the mesh individually. Rendering a simple box thus means using 6 * 6 vertices, meaning you have lots of duplicate vertex positions and a mesh that is a mess.

We want to keep the vertex count low, and vertices unique. We achieve this by telling the shader that we don’t want to use interpolated normals, but want to use the normal of one special vertex as the normal for the whole triangle. This vertex is called a Provoking Vertex.

Note that future versions of GLSL ( > version 140 ) will have the ‘flat’ attribute available by default, and that this tutorial has been written as a lament to OS X’s currently wilful OpenGL implementation.

This tutorial is best read if you download the example, look at its source and then compare the following walk-through.

This is how the example app should look like:

Files

This tutorial comes with a working example project (see screenshot above). Place this into your openFrameworks apps/dev directory:

Recipe

0. Build a mesh using vertices and indices.

Start numbering at the bottom left, front face. Then go counter-clockwise. When done with the front face, start bottom left, back face; go counter-clockwise, too. This numbering scheme will help you, should you happen to want to extrude along the z-axis sometime in the future.

image Make a drawing on paper and number your vertices. It helps. (N.b. normals in blue; The origin of the coordinate system is at the centre of the cube.)

E.g. a cube with 8 vertices:

ofVec3f vertices[] = {

        ofVec3f(-1, -1,  1),                // front square vertices
        ofVec3f( 1, -1,  1),
        ofVec3f( 1,  1,  1),
        ofVec3f(-1,  1,  1),
        
        ofVec3f(-1, -1, -1),                // back square vertices
        ofVec3f( 1, -1, -1),
        ofVec3f( 1,  1, -1),
        ofVec3f(-1,  1, -1),
};

1. Triangulate the Cube using Indices

by convention begin with the lowest index. Start with that index to draw both triangles that form one quad of the box. To do this, draw a diagonal from your current starting index to the opposing vertex to get two triangles. List the 3 indices for each triangle. Winding is anti-clockwise when listing a triangle facing the camera, and clockwise when listing a triangle which is hidden. When done, move to the next face. Start drawing with the next index.

ofIndexType indices[] = {
        // -- winding is counter-clockwise (facing camera)
        0,1,2,                // pos z
        0,2,3,

        1,5,6,                // pos x
        1,6,2,

        2,6,7,                // pos y
        2,7,3,

        // -- winding is clockwise (facing away from camera)
        3,4,0,                // neg x
        3,7,4,

        4,5,1,                // neg y
        4,1,0,

        5,7,6,                // neg z
        5,4,7,
};

Note that pairs of triangles use the same vertex as the starting vertex. This will be our provoking vertex, the vertex whose normal is the normal for the whole triangle. Provoking vertices work by telling GLSL that either the first or the last vertex of a triangle holds the normal for the whole triangle. Since both triangles sit on the same face, they can share a normal and a provoking vertex by starting with the same index.

2. Add Normals

For every vertex, you’d need one normal, the normal should be the perpendicular to the triangle that begins with your vertex index. Look at the indices to find the triangle in your drawing. When you have found the triangle, you should see from the drawing which face it is part of, and thus get the required normal.

ofVec3f normals[] = {
        ofVec3f( 0,  0,  1),
        ofVec3f( 1,  0,  0),
        ofVec3f( 0,  1,  0),
        ofVec3f(-1,  0,  0),
        ofVec3f( 0, -1,  0),
        ofVec3f( 0,  0, -1),
        ofVec3f( 1,  0,  0), // can be anything, will not be used
        ofVec3f( 1,  0,  0), //  -- " --
};

3. Set up OpenGL to use Flat Shading in your App

Note that the default OpenGL behaviour for provoking vertices is GL_LAST_VERTEX_CONVENTION, which will use the last vertex of every triangle as the provoking vertex for flat shading. We don’t want this, but want to use the first vertex.

myShader.begin();

glShadeModel(GL_FLAT);
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
myMesh.draw();
glShadeModel(GL_SMOOTH);

myShader.end();

4. Set up the Shader to allow the “flat” Variable Attribute

GLSL will allow flat attributes from version 140 on, but OSX’s default OpenGl version 2.0 coming with GLSL 120 doesn’t support flat varyings in shaders out of the box.

We thus need to activate an extension to get some modern niceties from our shader processor, and then we can use the “flat” specifier for our varying.

More information on this extension can be found at http://www.opengl.org/registry/specs/EXT/gpu_shader4.txt

// vertex shader
#extension GL_EXT_gpu_shader4 : require

flat varying vec3  normal;        // note 'flat varying'

void main(){
        normal = gl_NormalMatrix * gl_Normal;
        gl_Position = ftransform();
}

// fragment shader

#extension GL_EXT_gpu_shader4 : require

flat varying vec3  normal;        // note 'flat varying'

void main(){
        vec3 N = normalize(normal); 

        // voila, we can use our flat normal! This will colour our fragment with the current normal for debug purposes.
        gl_FragColor = vec4((N + vec3(1.0, 1.0, 1.0)) / 2.0,1.0);
}

Contact:

Unit 3, 410 Hackney Road,
E2 7AP London, UK