055 - Physically Based Terrain Rendering

February 23, 2020

I ended last week in the middle of writing the new renderer.

At the time ideas were settling into place and things weren't entirely stable - so I found myself doing a lot of thinking and code sketching around how to organize and design the renderer based on my requirements.

Today the renderer is largely stable from a code structure perspective and adding new aspects has been boiling down to fitting them into the existing patterns instead of needing to carefully think through what I'm trying to accomplish.

I'm happy with where it's at structurally even though I'm sure it will evolve over time as I have more requirements and learn more.

Rendering Meshes

I started the week working on rendering non skinned meshes and then once that was working I added support for skinned meshes.

Skinned mesh Rendering a skinned letter "F" mesh with a principled shader.

I'm using a physically based lighting model. I'm sure that I'll tweak and experiment with the look closer to release - but for now I have enough to keep moving forwards.

I incorporated the concept of levels of detail from the beginning. I only actually implement a single level of detail at the moment - but it should be straightforwards to add a couple more in the coming months.

One nice thing about the test suite is that when I'm staring at a still image that I'm supposed to mark as the correct passing case I'm finding myself paying very close attention to just about every pixel.

This has led to me catching several issues in my shaders that I might not have otherwise noticed. These mostly boiled down to silly oversights and slightly incorrect math.

It also led me to taking the time to add face weighted normals to my mesh preparer.

Face weighted normals Rendering the same normal mapped 1,000 vertex mesh with and without face weighted normals.

Rendering Terrain

After getting the non-skinned and skinned mesh shaders working I moved on to handling terrain rendering.

I did a lot of reading around different terrain rendering techniques and level of detail techniques.

Even though I took in a lot of information I wasn't really able to find the "one correct way" - but that's usually the name of the game when it comes to graphics programming.

So I eventually decided that I was researching too much and needed to just start and adjust over time as I better understand my own use case. I whipped out my notebook to plan out an implementation and got started.

Eventually I moved on to my typical post-notebook thinking place - my IDE comments.


Thinking through the terrain implementation When I'm thinking through how to implement something I'll use one of a few different approaches - depending on the topic. One of these approaches is writing out a comment explaining what I'm trying to do - illustrated in this screenshot. This tends to help me realize what I know and don't know and gives me a more targeted sense of what I still need to research.


I got started by writing a function to generate the geometry for my terrain at any detail level.

/// A 1x1 chunk with one subdivision.
///
/// This means that there are two rows of triangles - so we need to use degenerate triangles
/// in order to render our triangle strips.
///
/// We accomplish this by duplicating the last vertex in the row that's ending and the first
/// vertex in the new row that's beginning.
///
/// ```text
///      Pos 6         Pos 7         Pos 8
///    Index 9       Index 11      Index 13
///       ┌┬────────────┬┬────────────┐
///       │└─┐          │└─┐          │
///       │  └─┐        │  └─┐        │
///       │    └─┐      │    └─┐      │
///       │      └─┐    │      └─┐    │
///       │        └─┐  │        └─┐  │
///       │          └─┐│          └─┐│
///    Pos 3   ────   Pos 4   ─────   Pos 5
/// Index 1,7,8    Index 3,10      Index 5,6,12
///       │  └─┐        │  └─┐        │
///       │    └─┐      │    └─┐      │
///       │      └─┐    │      └─┐    │
///       │        └─┐  │        └─┐  │
///       │          └─┐│          └─┐│
///       └────────────┴▶────────────┴▶
///     Pos 0         Pos 1         Pos 2
///    Index 0       Index 2       Index 4
#[test]
fn indices_1x1_tile_chunk_1_subdivision() {
    let indices = generate_terrain_chunk(1, 1).indices;

    #[rustfmt::skip]
    let expected_strip = vec![
        0, 3, 1, 4, 2, 5,
        5, 3, // Degenerate triangles
        3, 6, 4, 7, 5, 8
    ];

    assert_eq!(indices, expected_strip);
}

After that I got working on rendering the terrain.

Here are some screenshots I took as I made progress working on the terrain renderer.

Getting started on the terrain renderer. First terrain render. Using a very simple TerrainRenderJob. Accidentally used TRIANGLES instead of TRIANGLE_STRIP. Will continue to iterate from here.


Terrain basic physically based lighting Working on one aspect of the terrain shader at a time. This go around I added physically based lighting. Surface normals and face tangents will be calculated in the terrain shader - but here they're hard coded.


Tiling textures blended with a blendmap Have our color textures tiling using a blendmap. They're simple test textures - but the exact same code will work for our real textures.


Terrain heightmap working Sampling a heightmap in the vertex shader and also computing the normal, tangent and bitangent vectors in the vertex shader.


Right now the roughness is hard coded and the tangent space normal vector is also hard coded - but replacing those with a roughness and normal map will be trivial.

Other Progress

  • Made some changes to blender-mesh to add a method to interleave vertex data. Haven't pushed these changes up yet - will cut a new blender-mesh release when I wrap up this game client refactor.

  • I started going through a Substance Designer tutorial as I'll be needing to get good at creating high quality PBR (physically-based rendering) textures quickly.

Next Week

I'm going to kick off the week by finishing up the terrain renderer - which will boil down to creating a quick roughness map and normal map and verifying that we're sampling them correctly.

After that I'm going to continue pushing on finishing this large refactor of the game client. There are a few main things left to do - most of which are already in progress.

The one big unstarted piece is changing the asset build and deploy process to generate individual files for our meshes and armatures instead of lumping them together in one file. This will play nicely with our new on demand asset loader.

I want to get all of this stuff out of the way this week so that I can spend March focused on implementing actual gameplay. Going to try and make a big splash this week.


Cya next time!

- CFN