I began developing a simple OpenGL based rendering engine in my spare time, whilst working on my MSc degree at the University of Cape Town. I wanted to learn more about graphics programming, and game development in general. While developing this engine, I learnt a great deal about software design, design patterns, shader programming, optimization and various rendering techniques.
While achieving such results can be much more easily achieved by using a pre-existing engine such as Unity, or Unreal, I think there is valuable insight which can be gained by developing something like this from scratch. I was interested in the technology that makes such engines possible, and I gained much knowledge through this endeavor, while simultaneously greatly improving my programming skills.
A simple demo can be downloaded here (but please keep in mind that I haven’t looked at it in quite a while, and it in no way reflects my current skillset).
Features
GPU Geometry Clipmaps
Geometry clipmaps is an extremely efficient solution for terrain level-of-detail, with the GPU variant computing all updates to the mesh on the GPU. Geometry clipmaps uses nested regular grids to represent terrain data. The coarseness of the grids varies depending on the distance from the viewer. Textures are created from a base heightmap, which correspond to the vertices’ positions in the heightmap. The vertices are then displaced according to these values in the vertex shader, with a blending area around clipmap borders, to ensure that the mesh stays watertight. You can refer to this page for more information on the technique.
Cascaded Shadow Maps
Cascaded shadow maps is a technique which aims to solve the problem of insufficient resolution in shadow maps. For large scenes, the view frustum is very large, which means that fitting a normal shadow map to the view frustum results in very low resolution shadows. Additionally, large portions of the shadow map fall outside of the view frustum, and are thus wasted. Cascaded shadow maps solves these issues by fitting multiple shadow maps to the view frustum. Thus, higher resolution shadow maps are used closer to the camera (where it really matters). In order to avoid a sudden jump in shadow resolution, the shadow maps are allowed to overlap slightly. The shadowing values can then be blended across these borders.
Water Simulation
I have implemented a simple animated water technique. The technique renders water by drawing a simple plane. First, two rendering passes are used to generate reflection and refraction textures, by clipping all geometry below or above the plane correspondingly. Then the plane is drawn, with the reflection and refraction lookups being offset according to a normal map. Realistic filtering of light is applied to the refraction, based on the depth of the water. A Fresnel term is used to calculate the amount of refraction and reflection each fragment should exhibit, based on the angle of the surface normal in comparison to the angle of the camera. The plane is drawn using the GPU geometry clipmaps technique, so that at a later stage I can extend the technique with vertex displacement, in order to generate larger, realistic waves.
Parallax Occlusion Mapping
Parallax occlusion mapping is a technique which adds 3D definition to textured surfaces. This is achieved by ray tracing across a heightfield. A vector from the camera to the fragment is transformed into texture space. The ray is then sampled across the surface, until it collides with the heightfield. An offset is then calculated from the length of the ray, and the texture coordinates are offset accordingly. The technique adds a lot of definition to surfaces, making them look and feel far more realistic.
Screen Space Ambient Occlusion
Screen space ambient occlusion is a technique which adds realism to a scene, by simulating the blocking of ambient light by nearby surfaces. A kernel is used to randomly sample points around the position. If the points are behind the geometry, we assume that the point is occluded, and it adds to the occlusion value. The kernel is randomly rotated for each fragment to remove banding artefacts, which otherwise occur when the same kernel is used for each fragment. This results in noise, which is then removed by applying a blur to the result. I have implemented the Crysis technique, which is well known to suffer from a few shortcomings. I am planning to improve upon the technique once I move over to a deferred pipeline, as then I will readily have access to the surface normals, which helps to improve the results substantially.











