Flat Shading using legacy GLSL on OSX

Flat shading gives geometry an edgy and geometric look and keeps the faces of meshes sharp and clean. Sometimes that's all you want. With commonly used meshes the normals are usually interpolated between vertices which can make flat-shading wasteful. This post discusses how to optimise geometry for flat-shading. It also shows how to get OpenGL to do the right thing when faced with provoking vertices.

By default, vertex normals are interpolated across triangles. A naive approach to flat shading would be to break up a conventional mesh into individual triangles, and duplicate the per-triangle normal across all vertices. But this makes vertex re-use impossible. For example, to render a simple flat shaded box 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


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

  • Source with precompiled binary - Zipball
  • Source without binary - Zipball
  • Or, alternatively, Visit the github repository

Here's how to do it:

Build a mesh 

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.

Sketch of vertices and Normals

E.g. a cube with 8 vertices:

Triangulate the Mesh 

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 your next two triangles with the next index.

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.

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.

Choose a Convention 

When flat shading, GLSL/OpenGL uses one vertex normal as the normal for the whole triangle this is called the provoking vertex. By default, OpenGL uses the last vertex, as GL_LAST_VERTEX_CONVENTION is set. We don't want this, but want to use the first vertex.

Use Extensions 

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.

You can find more information on this extension at: http://www.opengl.org/registry/specs/EXT/gpu_shader4.txt



See Also:

How far back should the screen go?

Posted . 765 words. (~4min reading time)



Earth Normal Maps from NASA Elevation Data

Posted . 908 words. (~5min reading time)



Using ofxPlaylist

Posted . 787 words. (~4min reading time)