Returning to form?

It’s been over a year since I updated this blog so I should probably tell you what I’ve been up to. While I was working on my main XNA project, Meteor Engine, I have also been unemployed most of the time, just getting by with a bit of freelance web developer work here and there. Last April I got a full time job and it’s been great most of the time, but the company financial difficulties so they made tough choices I wasn’t happy about. Now it’s back on the job hunt for me. With my much improved experience, hopefully my job hunt now will go much quicker than last time.

So while I’m keeping an eye on my own budget, I decided to get back into the C#/XNA programming I was familiar with. This also means I am taking a new angle.

Previously I was focusing a lot on making a graphics engine. Going back where I left off, I was going to be making a shooter game. The blog will shift focus to “I actually will be making some GAEMS!!!” this time around. So my goal is to start with some base code I have and make a finished game from it.

The graphics engine could be a tool I can now add to my arsenal for making a game and it’s very likely I’ll use it here. My journey into developing the game will begin with my next post.

Triplanar normal mapping for terrain

First, before having gotten into terrain normal mapping, I added mouse picking for objects. I have some interactivity now!

screen26-1

This is taken from an XNA code sample, then I modified it so it supports instanced meshes. So now it’s able to pick the exact instances that the ray intersets, and displays their mesh name. It doesn’t do anything other than that for now, but it’s just the first step towards editing objects in the level editor.

Mapping the terrain

The new update was for fixing a problem that’s been bugging me for a few weeks- combining normal mapping with triplanar texturing. It was a tricky affair as the normal maps get re-oriented along three planes so you also have to shift the normals accordingly. After revising how I did my regular normal mapping for other objects, I was able to get correct triplanar normal mapping for the terrain. This goes for both forward and deferred rendering.

I have only two regular textures- the base texture for mostly flat areas, and a blend texture for cliffs in steep areas. My normal map is for the cliff texture, and no normal mapping is applied for the flat areas. You can also set a bump intensity which increases the roughness of the terrain. Naturally, with great roughness comes great respons- less specular highlights. So you would have to tune the specular and roughness so it achieves a good balance. Most of the time terrain, doesn’t need specular lighting, but it’s needed for wet and icy areas.

Bump up the volume

Terrain normals, binormals, and tangents are all calculated on the CPU, which is the ideal way to go as it saves a lot of overhead of doing it every frame. In the vertex shader, the normal, binormal and tangent are transformed to view space and added to a 3×3 matrix.

output.TangentToWorld[0] = mul(normalize(mul(input.tangent, World)), View);
output.TangentToWorld[1] = mul(normalize(mul(input.binormal, World)), View);
output.TangentToWorld[2] = mul(normalize(mul(input.Normal, World)), View);

In the main pixel shader function we must first compute the normal mapping output before it can be contributed to the vertex normal outputs.

PixelShaderOutput PixelTerrainGBuffer(VT_Output input)
{
    // Sample normal map color. 4 is the texture scale
    float3 normal = TriplanarNormalMapping(input, 4);

    // Output the normal, in [0,1] space
    // Get normal into world space

    float3 normalFromMap = mul(normal, input.TangentToWorld);
    normalFromMap = normalize(normalFromMap);
    output.Normal.rgb = 0.5f * (normalFromMap + 1.0f);

    // ... Then output the other G-Buffer stuff
}

The textures are expected to be in the [0, 1] range and TriplanarNormalMapping outputs them to [-1, 1] so they are properly transformed with the TBN matrix. After that we can set the normals right back to the [0, 1] range for the lighting pass. Remember that it outputs to an unsigned format, so if we don’t do this, all values below zero will be lost.

The following function computes triplanar normal mapping for terrains.

float3 TriplanarNormalMapping(VT_Output input, float scale = 1)
{
    float tighten = 0.3679f;

    float mXY = saturate(abs(input.Normal.z) - tighten);
    float mXZ = saturate(abs(input.Normal.y) - tighten);
    float mYZ = saturate(abs(input.Normal.x) - tighten);

    float total = mXY + mXZ + mYZ;
    mXY /= total;
    mXZ /= total;
    mYZ /= total;

    float3 cXY = tex2D(normalMapSampler, input.NewPosition.xy / scale);
    float3 cXZ = float3(0, 0, 1);
    float3 cYZ = tex2D(normalMapSampler, input.NewPosition.zy / scale);

    // Convert texture lookups to the [-1, 1] range
    cXY = 2.0f * cXY - 1.0f;
    cYZ = 2.0f * cYZ - 1.0f;

    float3 normal = cXY * mXY + cXZ * mXZ + cYZ * mYZ;
    normal.xy *= bumpIntensity;
    return normal;
}

Note that where I define the texture lookups, the XZ plane is just set to a normal pointing directly towards the viewer. The X and Y values are in the [-1, 1] range, and Z is by default 1 because it is not used for view-space coordinates. So don’t forget to flip normalized negative values! Then X and Y are multiplied by the bumpIntensity. The default roughness is 1, and a roughness of 0 will completely ignore the normal map for the final output.

A lot of my texture mapping code was adapted from Memoirs of a Texel. Take caution, that if you want to follow that guide, there is a glaring mistake in that code that I noticed only after seeing this GPU Gems example (see example 1-3). You need to clamp your weight values to between 0 and 1 before averaging them out. The blog article doesn’t do this in its code. Otherwise you will get many dark patches in your textures. I fixed this with the saturate() function shown in the above example. This goes for regular texture mapping as well as normal mapping.

Here are some screenshots with the normal mapping in place. The bump intensity is set to 1.8 for a greater effect.

Edit: I’ve used some better textures for testing now. I got some free texture samples at FilterForge.

screen27-4

screen27-3

screen26-4

screen26-2

Normal computation is the same for forward rendering as it is for deferred rendering. The normals as they contribute to lighting would still be in the [0, 1] range in view space.

Done fixing the shadow maps

It’s been a while, hasn’t it? I’ve been pretty busy trying to look for a job, then maintaining my freelance work on top of other things, but I still try to keep my programming skills sharp. This past week I opened up my XNA projects again and they are still fresh in my mind.

Removal of shadow flickering and swimming

In fact, with my mind so cleared up I finally resolved two problems that have plagued my shadow mapping code for so long. The first problem I resolved in an hour- it had to do with the shadow maps “tearing” between the cascade splits. Those portions would either be completely dark or filled with parallel lines. In other words, a glitchy mess. Originally, I went with using different near-far distances to generate each map, but they were misaligned if viewed from certain angles. Such was the case for parallel-split maps. So I decided to just go with traditional cascaded shadow mapping by making the near distance the same for all the splits.

This means that while some pixels are wasted for the farther cascades, it guarantees that there will always be overlap between them. The tearing is now gone! The next problem is a little less distracting but still something I always wanted to fix, and it’s the “swimming” of the texture shadows when the camera moves. The texels always move quickly and noticeably. Fortunately J. Coluna had the solution in his LPP demos, which he in turn took from a website he couldn’t remember. Fair is fair, so I used the code as well.

After a surprisingly few changes to the code, I removed the swimming effect. The camera for each light is set to move in texel increments, so movements stay “snapped” to an invisible grid. Shadows suddenly look a lot cleaner despite still using the same filtering technique.

Cascade shadow blending

One more trick was added to make the shadows look nicer in motion. CSM usually shows visible seams that divide each cascade, and they also pop in and out noticeably when moving the camera farther away. With the shadow maps now overlapping each other, it is possible to remove the seams that occur from changing different levels of shadow detail for each distance. I decided to start the blending at 85% of the distance between the near and far points of each split. This creates a smooth linear transition between the maps. The below image shows the effect of blending the cascades together, each  color representing a different shadow map.


I have uploaded these changes in the latest build of the Meteor engine, available on CodePlex. There may be some more updates on it, but I don’t foresee doing any more notable changes to the shadows for directional lights.

Between this and Bubble Tower, I’m slowly converging the two projects together in a way that makes them more practical. It’s also a great way to test the Meteor Engine in a real project. As you may know, the game uses a screen management system and I wish to keep this engine self-contained in one of the screens. I have been finding more limitations to the engine that are going to be resolved in order to make it work in a more flexible manner with the game code. More on that later.