For the “flat shade” aesthetic each of the triangles that make up the world will be shaded evenly. The way the video card creates the nice shades is because of the normal vector of a given surface. This vector basically tells the videocard what direction the light to bounce to create light and dark shades.
Because 3D models are made up of triangles, the best way to ‘hide’ these triangles is to define the normal vector at the vertex (the corners) of each triangle. The videocard can smoothly fade between these vectors across the surfaces and this gives the effect of smooth shading.
However, in my case, I don’t want the smooth shading. I want my models to be simple and look simple as well. So instead of the videocard smoothing out the normals between eacht vertex, I actually want the same normal across the entire face of the triangle. In order to do this, the videocard needs to calculate the normal vector of the entire triangle when drawing a triangle. After doing a bit of research- this requires a geometry shader- the geometry shader has access to the different vertices that make up the triangle.
Unfortunately, Monogame doesn’t support geometry shaders (yet). On a positive note, for now I don’t want to touch shaders yet and use the built in ‘BasicEffect‘ to do the hard work for me.
In order to solve this, I decided to make each triangle a separate entity- this means that no vertices are shared between triangles. This also enables me to set the normals for each vertex in each triangle to the same value. So what I need is a structure for each vertex that holds the position, normal and the color.
Since there is no such structure in the Monogame framework, I made my own like this:
public struct VertexPositionNormalColor
private Vector3 position;
private Vector3 normal;
private Color color;
public VertexPositionNormalColor(Vector3 position, Vector3 normal, Color color)
this.position = position;
this.normal = normal;
this.color = color;
public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
new VertexElement(sizeof(float) * 6, VertexElementFormat.Color, VertexElementUsage.Color, 0)
Now using this structure, the BasicEffect can draw flat shaded geometry as expected!
The downside to this method is that the models in the game use more vertices than one where information of triangle-neighbors is shared.
For now I am not concerned; the models will be much simpeler than most of the modern games (where models with thousands of triangles are no exception). I expect my road sections to use about 8 to 10 triangles, so maybe an entire circuit comes close to one single model in a triple A game.