Shading: diffuse, normal and specular mapping

Yesterday I started making use of Giuseppe's XNA lessons about vertex and pixel shaders, texturing and lighting. The models of the game are now nicely rendered using techniques with great names such as Normal mapping, specularity mapping and so on...

First of all I'd like to give a short introduction about shading in modern games (I'm quite a newbie in this area, but Wikipedia and Google help out quite a lot).

Shading: using the programmable pipeline

Originally, early 3D applications and games had to execute all polygonal operations on the CPU and used the graphical card mostly to present the result of such operations on the screen. Then, someday, the first graphical cards supporting hardware transform and lighting (T&L) were released, capable of executing all operations on polygons much faster than the CPU (these operations include the transformation of the vertices from "object space" to "world space", that is translating, rotating and scaling them, then compute the lighting and the final color of each pixel on the screen).

Since DirectX 8 or so, programmers now have the possibility of using the programmable shading pipeline of the GPU: that means that the operations executed by the graphical adapter (the Transform and Lighting steps above) could be replaced by more complex programs (called shaders), therefore enabling a whole range of creativity and graphical power. The programmable units that execute this shaders can be programmed by using a set of instructions, defined by the shader model (1.0, 1.1, 2.0, 3.0 and the latest 4.0). Each shader version provides new instructions and supports more complexity.

With DirectX 10 the fixed pipeline has been completely ditched and shaders are a must. Different types of shaders exist:

  • Vertex shaders: a program that takes each single vertex of the model and transforms it. Since the vertex format is customizable, you can potentially put every kind of information into your vertices and do every kind of operation, but usually the required steps are simply the transformation from "object space" to "world space".
  • Pixel shaders: is called for each pixel the model is rendered on. You get interpolated vertex data as input and have to output a color. Lighting composition and texturing is usually done here.
  • Geometry shaders: available in DirectX 10, they allow you to generate new geometry "on the fly" before the pixel shader is called.

Using all this shader-goodness...

XNA simply wraps the power of DirectX 9 and provides you full access to the shaders by using the High Level Shader Language (similar to C). How are we going to use it in order to realistically render our meshes and light them correctly? Phong shading to the rescue!

Sphere model with normals

This is how a simple sphere looks in Blender. The mesh is composed of triangles of vertices, each having several information: position, normal and texture coordinates. Normals are vectors orthogonal to the surface's tangent plance (pointing outwards). This vector can be used to compute the realistic effect of light on the model's surface. We simply need the viewers position and we're ready to implement Phong shading. By writing all lighting computations into the pixel shader we'll have (another big word) Per-pixel Lighting (that is, lighting effects are computed for each pixel of the model, and not only for each vertex). The effect is great even for models with few polygons.

Each pixel's color is composed adding "diffuse" color (the base color of the model), "light" color, "specular" color (the highlight given by the Phong light model) and the shading computed using normals.

More detail using textures

Different types of maps All the information above (diffuse color, specularity and normals) can be provided by textures instead of the vertices. Using textures for diffuse color is actually quite normal, but with normal mapping and specularity mapping we can achieve great realism, actually increasing the rendered image's detail without increasing the number of polygons.

On the right, you'll see an example of diffuse map, normal map and specularity map for the planet Earth.

The normal map contains colors that represent the direction of the normal vector at each pixel of the map.

Game update

OK, after this long (hopefully not too boring) excursus about rendering and shading techniques, let's apply all this stuff on the little game I'm developing:

Updated screenshot

As you can see, the Earth is beautifully shaded: normal mapping gives us shadows and highlights on mountains and valleys, specularity mapping makes sure that only oceans reflect white specular light and the diffuse texture... well, makes it look like good ol' earth.  :)

The space ship is lit using the per-pixel phong lighting model and a diffuse texture. Oh, and the background is a cubic sky dome, but that's for the post of tomorrow!