Apr 102013
 

Recently the wife and I have been playing through Tomb Raider and Bioshock Infinite, and there is one thing that has really stuck in my mind from those games: how much I hate scouring a world for crates of loot.

Tomb Raider

First things first – the new Tomb Raider is really good. It’s actually the first Tomb Raider game I’ve played (not sure how I managed that) so I don’t know how it compares to previous ones, but it looks great and the animation on Lara is top-notch. The storyline is pretty interesting and very dark, exploring her journey from gentle young woman to bloodthirsty killing machine. Well, the story elements explore her trauma and shock at having to kill to survive, but it obviously doesn’t affect her too much because by the end of the game you’ve murdered several hundred people. Such are the concessions to interesting gameplay I suppose, but it does create a bit of a disconnect.

The were a few moments of intense frustration and boredom for me. These weren’t directly the fault of the game, but from years of conditioning. Most of the time my wife was playing the game and I was watching. Every so often we’d come into a large open area with loads of buildings and things to climb around. After ten minutes of viciously killing the local inhabitants, the next hour was spent meticulously exploring every single corner of every single part of the map, in search of crates of salvage, ancient relics or log books. It must be an obsessive compulsive thing learnt over her previous fifteen years of playing Final Fantasy games.

“OK, we must be done here, are you going to go rescue your friend now?”

“Hang on, there’s another thing over there I think I can get to…”

“You don’t need any more stuff.”

“But… need to get the things…”

“…I’m going to do the washing up, call me when you’re ready…”

The best part of the game in this regard is the Survival Instincts feature. Press a button and everything you can see that can be interacted with glows yellow. Which I feel should have sped up the exploration process a lot more than it actually seemed to.

Overall though, I highly recommend giving Tomb Raider a go. Interesting story, satisfying combat and very pretty.

Bioshock Infinite

I’m currently undecided about Bioshock Infinite, although it would appear that I’m in the minority. We both loved the first game – the art deco styling; the creepy atmosphere; the scary enemies; all came together to make a memorable experience. I don’t remember much about how it actually played, and as the general opinion is that Infinite is basically the same gameplay-wise, I think I may have a case of rose-tinted glasses. Because I’m just not having much fun actually playing the game.

We’re maybe three-quarters of the way through. It looks nice enough (we preferred the aesthetics of the first game, but that’s just personal preference), the storyline is getting quite interesting, and your companion character brings a lot of life to the game when you meet her. But far too often the game falls into that all-too-familiar and ludicrous situation of:

“I need to rescue Elizabeth, she’s been seized by the baddies again. Hmm, she can wait, I need to finished searching all of these bins for loose change and scraps of food. Oh, a few random rooms, I really want to get on with the story but if I don’t thoroughly search them I might miss a big pile of coins or an infusion or a good piece of gear…”

Nothing breaks the flow of a game more for me than having to take time out from saving the world to scavenge for junk, search for trinkets, or steal from random people’s houses. It’s not so bad if it’s just optional extras (e.g. relics and audio extracts in Tomb Raider), but in Bioshock it’s almost the entire progression mechanic. Upgrading your guns and powers requires money, which is primarily obtained from scavenging, and the health/shield/salt upgrade infusions are usually tucked out of the way behind closed doors. All of which means that you’re ill-advised to ignore the quest for loot.

Overall, it’s still a pretty good game I think. It’s very old-school in its mechanics (particularly the combat, which feels really dated), but it’s worth playing to see how a companion character can be done right.

So you should probably get it. But play Tomb Raider first.

Apr 022013
 

Last time I spoke about specular lighting, which combined with diffuse and ambient from the previous article means we now have a good enough representation of real-world lighting to get some nice images.

However, this lighting isn’t very detailed. Lighting calculations are based on the orientation of the surface, and the only surface orientation information we have is the normals specified at each vertex. This means that the lighting will always blend smoothly between the vertices, because the normals are interpolated. Drawing very detailed surfaces in this manner would require very many vertices, which are slow to draw. What would be better would be to vary the lighting across a polygon, and for this we can use normal maps.

Normal maps

This is an example of what a normal map looks like:

Example of a normal map

A normal map looks like a bluey mess, but it makes sense when you understand what the colours mean. What we’re doing with a normal map is storing the orientation of the surface (the surface normal) at each pixel. Remember that the normal is a vector that points directly away from the surface.  A 3D vector is made up of three coordinates (X, Y and Z), and coincidentally our textures also have three channels (red, green and blue). What this means is that we can use each colour channel in the texture to store one of the components of the normal at each pixel.

We need the normal map to work no matter the orientation of the polygon that is using it. So if the normal map is mapped onto the floor, or a ceiling, or wrapped around a complex shape, it still has to provide useful information. Therefore it’s no use encoding the direction of the normal directly in world-space (otherwise you couldn’t reuse the map on a different bit of geometry). Instead, it is encoded in yet another ‘space’ called tangent space. This is a 3D coordinate system where two of the axes are the U and V axes that texture coordinates are specified in. The third axis is the surface normal.

How tangent space related to UV coordinates

Encoding a normal in this space is straightforward. The red channel in the texture corresponds to the distance along the U axis, the green channel is the same for the V axis, and the blue channel is the distance along the normal. The distances along U and V can go from -1 to 1 (as we’re encoding a unit vector), so a texture value of 0 represents -1, and 255 (the maximum texture value if we’re using an 8-bit texture) represents +1. Because a surface normal can never face backwards from the surface, the blue channel only needs to encode distances from 0 to 1.

Now we can understand what the colours in the normal map mean. A pixel with lots of red is facing right, and with little red is facing left. A pixel with lots of green is facing up, and with little green is facing down. Most pixels have a lot of blue, which means they’re mainly facing out along the normal (as you’d expect, as this is the average surface orientation).

Shading with normal maps

So now we have a normal map, and it’s mapped across our object in world space. We can read the texture at each pixel to give us a tangent space normal, but the lighting and view directions are specified in world space. We need to get all of these vectors into the same space, and for this we need a matrix that converts between tangent and world space. Luckily, that’s fairly easy to get.

Matrices

First a quick diversion into rotation matrices. I’ve talked about 4×4 transform matrices for transforming from one 3D space to another, but the top left 3×3 part of the matrix is all you need to perform just the rotation. Because we only want to rotate the normal we don’t need to apply any translation, so we just need a rotation matrix.

Green is the rotation part of a transform matrix. Red is the rotation part, made up of the X, Y and Z axes in the new space.

Rotation matrices between coordinate systems with three perpendicular axes (i.e. the usual ones we use in graphics) have a couple of nice properties. The first is that the columns are just the original axes but transformed by the rotation we’re trying to represent, i.e. the first column is where the X axis would be after the rotation, the second column where the Y axis would be, and the third column where the Z axis would be.

The second nice property is that the inverse of a rotation matrix is its transpose. This means that the rows represent the three axes with the inverse rotation applied. If you’re interested, this is a more in depth explanation of rotation matrices here.

Tangent to world space

So how does this help us? We need to build a rotation matrix to convert between tangent space and world space. The first thing to do is to add a couple more vertex attributes – these are the tangent and the binormal vectors. These are similar to the normal, but they define the other two axes in tangent space. Remember that these are defined by how the UV texture coordinates are mapped onto the geometry. Your modelling package should be able to export these vertex attributes for you.

Now, we need to use these to get the light, view and normal vectors into the same space. In this case we’ll transform the view and light directions into tangent space in the vertex shader (although you could instead transform the normals into world space in the pixel shader, if that makes your shader simpler).

As shown above, the tangent-to-world matrix is just the 3×3 matrix where the columns are the Tangent (X axis in the normal map), Binormal (Y axis) and Normal (Z axis), in that order. To get the world-to-tangent matrix, just transpose it so the rows are Tangent, Binormal and Normal instead:

World to tangent space matrix, made up of the tangent, binormal and normal vectors in world space

Then you can use this to transform your light and view vectors! In case it helps, here’s some vertex shader HLSL code to do all this:

// Transform the normal, tangent and binormal into world space. ModelViewMtx
// will be a 4x4 matrix, so take care not to include the translation.
float4 normalWorld = float4(input.normal, 0.0f);
normalWorld.xyz = normalize(mul(normalWorld, ModelViewMtx).xyz);
float4 tangentWorld = float4(input.tangent, 0.0f);
tangentWorld.xyz = normalize(mul(tangentWorld, ModelViewMtx).xyz);
float4 binormalWorld = float4(input.binormal, 0.0f);
binormalWorld.xyz = normalize(mul(binormalWorld, ModelViewMtx).xyz);

// Build the world-to-tangent matrix (transpose of tangent-to-world).
float3x3 worldToTangentSpace =
    float3x3(tangentWorld.xyz, binormalWorld.xyz, normalWorld.xyz);

// Transform the light and view directions.
output.lightDirTangentSpace = mul(worldToTangentSpace, lightDirWorldSpace);
output.viewDirTangentSpace  = mul(worldToTangentSpace, viewDirWorldSpace);

In the pixel shader you read the normal map the same as any other texture. Remap the X and Y components from (0, 1) range to (-1, 1) range, and then perform lighting calculations as usual using this normal and the transformed view and light vectors.

Here’s my test scene with the normal map on the floor:

And here’s a screenshot with the nicer shading and shadows turned on:

A caveat

One last technical point – the properties of rotation matrices I talked about only hold for purely rotational matrices, between coordinate spaces where the axes are all at right angles. Due to unavoidable texture distortions when they’re mapped to models, this usually won’t be the case for your tangent space. However, it’ll be near enough that it shouldn’t cause a problem except in the most extreme cases. If you renormalise all your vectors in the vertex and pixel shaders then it should all work out alright…

Next time should be a bit simpler when I’ll be talking about environment mapping!