Last time I talked about what an atlas texture is, why we need them, and how I use them. The important thing is that I’m not hand-crafting the atlas every time I add a new texture. I have some code that does all of this for me. It reads all the images in a particular folder, sorts them-large-to-small for efficient packing, and then generates my atlas texture. It also stores the location of each texture within the atlas. So later when I’m busy generating walls and level geometry, I can say:
cell = LibraryAtlas.Lookup (“textureName”);
That gives me the location of the sub-texture within the atlas. Now I can plot uv coords as if I was using a standalone texture and not worry about the atlas at all. That’s immensely important when you’re building polygons with code and you want to keep things as simple as possible.
Last time I shared my atlas texture. Actually, that was just one fourth of the atlas. Sort of? The point is, I don’t just have one atlas texture. I have four.
The Atlas Collection
So how it works is I hand the shader four different textures. These are all atlas textures with the same exact layout of sub-images, but each atlas contains different data. The shader looks at all the different atlases and does some calculations to figure out what the final color will be for this particular pixel.
It’s important to note that a texture is a collection of four channels of data: Red, green, blue, and alphaAlpha just means transparency.. Sure, RGB values are normally used to store color, but since I’m writing this shader myself I can interpret the data however I want. I can use a blue channel in one of the textures to determine how much to tint the base texture pink. To the shader, it’s all just numbers.
So let’s talk about these four different textures.
The Diffuse Map
This is the simplest and most obvious of our four textures. It contains the traditional texture data that most people are familiar with. If this was 1997, then this would be the only textureAside from baked shadow maps, which aren’t worth exploring right now.. But even though I’m planning on using retro-ish assets, I’m not sticking to pure retro technologies.
So we need a few more textures.
The Normal Map
In the really old days, objects looked very primitive. A supposedly round support pillar would have just six sides. Character models would have primitive mitten hands with the fingers painted on the surface. Everything was blocky and chunky in appearance.
But then as technology progressed, we gained the freedom to use more polygons. That flagon of ale didn’t need to be based on a five-sided cylinder. Characters didn’t need to have flat faces with a pyramid for a nose. Instead you could model the eyes, lips, cheekbones, and other features.
But eventually you get diminishing returns and expanding costs if you continue to add detail with polygons. It would be a nightmare to model a wall where every brick, every crack, and every surface imperfection was fully realized in 3D. All of those millions of polygons would multiply the work needed to be done by the rendering system. Drawing the wall would be slower. Lighting the wall would be slower. Casting shadows would be slower. Heck, just loading the level would take forever.
You end up with this horrible system where making a room look 10% more detailed would make it 100x slower to draw. Moore’s Law has done a lot for us, but even Gordon Moore can’t save us from a growth curve this steep.
We don’t need to model walls down to the individual brick. The problem we’re trying to solve is that walls look a little too flat when you shine a light on them. Shine a light on the right side of a brick wall, and you expect to see the right edges of the bricks catch the light, and the left edges go dark. If it doesn’t, then the wall doesn’t feel like a brick wall, it feels like a wall covered in brick-patterned wallpaper.
The solution here is a normal map. Texture maps are intended to hold visual data – pictures of bricks and such. But there’s nothing saying you can’t store positional / directional data in them. So a normal map uses the RGB color channels to store X, Y, Z vectors. Essentially, the normal map says, “When you render this bit of the wall, pretend the wall faces this direction before you do the lighting calculations.” The bricks will be lit as if they’re really protruding from the wall, without us needing to spend hours of modeling work and millions of polygons to make it happen.
When I make a texture map, I also make a 3D relief map to go with it. The relief map tells the atlas generator to pretend that bright pixels protrude from the surfaces and dark pixels are depressions in the surface. From there it just takes a little math to create a normal map.
The Specular Map
(In the image above, you see some images are greyscale and some are red. The grey ones are leftover from the early stages of the project before I decided how all of this was going to work. Just ignore them. We’re interested in the pink / red sections for now.)
Specular lighting is when you get metallic or glossy highlights from a surface. Let’s consider a few different kinds of surface:
- Chalk is a fully matte surface. It’s not glossy at all. You can clean that chalk (or even the board) all day, but you’re never going to see your reflection in it.
- Brass is incredibly reflective. If you’ve got a strong light shining on polished brass or silver, then you’ll get all of these bright glints on the surface. In real life, if you get in close you’ll see those bright spots are actually mirror-like reflections of the light sources around you.
- Stainless steel is fairly shiny, but unlike Brass it doesn’t really create a mirror-like effect. The surface is a little rough, which scatters the light in a disorganized way. You still get those shiny highlights, but they’re no longer precise reflections of the local light sources and are instead smeared out.
So when we’re dealing with specular lighting, we’re essentially dealing with two variables. One is the strength of the specular light, which dictates how bright those shiny spots are. The other is the specular roughness, which controls how much the shiny spots are scattered around.
Essentially, specular lighting is just a cheap way to make fake reflections. We’re basically reflecting just the light sources in the room. At a distance, that looks passably like a reflection. You need to get very close (or have a very large reflective surface) to see that the reflection is just spots of light and doesn’t include any of the details of the space you’re in.
Specular lighting is one of those technologies where the first 10% of your effort gets you 90% of the way to realism, but then you need a huge additional investment of additional programming and computing power to move the needle further. You can do a bunch of extra calculations to make sure the (say) gold surface reflects the appropriate amount of light, and correctly tints the reflection. You can make even better fake reflections with a static cube map. You can get a little closer still if you’re willing to burn a bunch of processing power maintaining a dynamic cube map. Or you can go one step further and implement screen space reflections. If that’s still not real enough for you, then you can go all the way and shoot for a ray tracing solution. Basic specular lighting is cheap to set up, but there’s no upper limit on how much additional power you can expend if you’re trying to simulate reality.
So great. Basic specular lighting is a cheap way to make fake reflections. The problem is that you don’t want everything in a scene to exhibit the same specular properties. Consider Quake 4, where all of the level geometry had the same exact specular properties. That was understandable in 2005 when this stuff was fairly new, but you can’t get away with that now.
As an aside: This creates a bit of a trap for artists. Specular surfaces are inherently more interesting than perfectly diffuse surfaces. Which encourages the artist (or more likely: art director) to really lean into them, even when it’s not appropriate. This probably explains how you end up with weird design choices like Dead Island, where specular abuse makes it look like people are made of dull plastic.
We want metal and glass to be very shiny, we want plastic to be slightly shiny, and we want bricks and dirt to have no shine at all. We want this to be true, even if those different surfaces are all part of the same 3D model with the same texture.
So what we need is a way to control our specular lighting on a per-pixel basis. In my project, this means an extra texture. The blue channel controls how strong the specular lighting is, and the green channel controls how rough it is. Also, I used the red channel to store a copy of the relief map I discussed in the previous section. That’s not currently being used, but I could use that to add support for parallax mapping later on.
The Data Texture
One last texture. For now, I’m only using two channels:
Red: Self-lighting. If the red value is high, then the pixel will self-illuminate. This can be used to make things that glow. This might be a display screen, or some generic sci-fi control panel lights, or whatever.
Green: Paint color. This is a bit weird and unconventional. Right now I color the polygons of each room so that no two rooms are the same color. This color is normally covered up by the texture map, which means you never see it. However, I can use the green channel to tell the shader to multiply the texture color and the polygon color together. This is enormously useful for debugging during development. Later on, I might keep this feature so I can color-code different sections the way the System Shock games colored the walls in the different areas. Something like: Medical, Engineering, Research, Crew, etc.
Blue: Not currently used, although I have plans for it. I’ll put this channel to use later in the project.
Building the Atlas
I have a big pile of loose textures like wall1, computer3, desk5, or whatever. They’re all stored in a specific folder where the game can find them. The atlas code reads in these images and packs them into the diffuse atlas image. Then it calculates the normal map and sticks it into another image. Then it does the same for the specular and data images.
So that’s how my atlas works. Next I think it’s time to build some furniture.
 Alpha just means transparency.
 Aside from baked shadow maps, which aren’t worth exploring right now.
Juvenile and Proud
Yes, this game is loud, crude, childish, and stupid. But it it knows what it wants to be and nails it. And that's admirable.
Punishing The Internet for Sharing
Why make millions on your video game when you could be making HUNDREDS on frivolous copyright claims?
I'm a very casual fan of the series, but I gave Civilization VI a look to see what was up with this nuclear war simulator.
Could Have Been Great
Here are four games that could have been much better with just a little more work.
The Opportunity Crunch
No, brutal, soul-sucking, marriage-destroying crunch mode in game development isn't a privilege or an opportunity. It's idiocy.