Home       About Me       Contact       Downloads       Part 71    Part 73   

Part 72: Texturing Landscape

November 23, 2012

The last month has been pretty worthless, with very few hours put into the project. I finally have the summary code working correctly though, complete with texturing. The results are in Figure 1:

Fig 1: Textured quad landscape

This is the real code, not any kind of mock-up. You can look at a full resolution version here. (1.18 meg) I've actually kicked up the resolution a bit from the last version I showed you. This runs at about 175 fps on my machine, and uses 50 meg of display memory. I will probably continue to fiddle with the resolution issues. And since displays vary so much, I'll need to make the actual resolution an option.

I'm not 100% satisfied with this, but it's good enough to go on with. There have been a few issues with this code.

Texture Packing

Fig 2: A chunk texture

The terrain is made of quads, and in each quad we have bricks. I average the full brick texture to get a 1 by 1 pixel summary. These are used to make the final texture of the quad.

All the quads are packed into a single OpenGL texture for the chunk, using a "level-oriented" strip packing algorithm -- the first algorithm described here. I still need to play with this a bit (or try the other algorithms) to see if I can get better quad packing, but it will do for now. The results look like Figure 2.

The entire chunk is drawn as a single call with this as the texture, giving pretty good performance. The Figure 1 image is 108 chunks.

In addition to improving the packing of quads, I should also eliminate hidden data off the edges of chunks. Several of those large strips in the first row are the sides of the chunk, which will never be seen.

I also need to create distant single-pixel equivalents to the non-cube shapes like flowers, rail tracks, etc. Right now, it's just using the first color in the texture of the shape to make the summary, which can be very wrong.

Aliasing

The big problem with my texturing is that since a brick is only one pixel, I can't turn on mip-mapping or any kind of averaging in the texturing. That would just ruin the texture for my purposes. Instead, I'm using GL_NEAREST on the texture filtering. This aliases quite a bit as you move. It's no worse than Minecraft, but not as nice as McrView.

Small or thin features are also a problem. The summaries are created with a bounding box in each cell. Although there are never more than 32 by 32 by 32 cells in a chunk, the box in a cell could be as small as a single brick. In Figure 1, you can see the elevated walkways in the distance (look at the black lines near the gray cube, center right of the image.) As these get more distant, they flicker as you move.

I think the only solution for this is some kind of whole-screen anti-aliasing, but I need to read more about AA techniques. I don't intend to do anything on this now.

Quad Boundaries

Texturing individual bricks this way has another problem -- the coordinates of pixels within a quad can round into the next quad in the texture. Since there's no relationship between adjacent texture patterns (they are packed by size), this meant lots of edge artifacts in the output. See Figure 3:

Fig 3: Wrapping between quads

The solution to this was a custom shader. Vertexes include the bounds of the quad, and the fragment shader can clamp each pixel to the bounds of the quad texture.

Brick codes vs. RGB

My first design for this code created the textures in the summaries, based on the full resolution versions of the brick textures. I figured I could then take a high resolution version of the bricks in a quad and scale that down, averaging adjacent pixels.

Unfortunately, this locks the summaries to a particular set of brick textures. For a single user, this wouldn't be a huge problem. After you changed the texture pack, it would just have to recalculate all the summaries (though that's pretty slow on a large world!)

In a multi-user environment, it's more of an issue. The server is going to have the summaries in its database, and send them to the clients as users move around. If the summaries are based on the texture pack, all users must use the same textures.

I didn't really want that, so I changed the code to save brick ids in the summaries, not actual texture pixels. The clients lay out the final textures and create them from the texture pack in use on that client.

The cost of doing this is that adjacent bricks don't blend, increasing the aliasing issues. However, the full texture approach wasn't going to be practical anyway:

  • Using a full resolution (or even 4 by 4 versions) brick textures and scaling them was too expensive. There are 10 levels of summary, and each one has to be updated when you change a brick.

  • Adjacent quads would not have been averaged anyway, just pixels within a quad. I need a better solution for anti-aliasing.

  • Keeping RGBA values in the textures is twice as much memory as 16-bit brick ids.

Resolution Issues

I now have a grid of chunks in three dimensions. All chunks are 32 by 32 by 32 cells. Nearest the eye, the code will be using the full bricks as in McrView. All the distant landscape is drawn with summary chunks, where bricks are single pixels in a texture. Cells range from size=2 out to size=1024 bricks across.

There are parameters here:

  • I can change the resolution of the chunks with distance. My current code tries to keep the size (on the screen) of each cell constant with distance. So at twice the distance, cells are twice as large.

    I'm not sure if this is the best algorithm. One consideration is what happens when you move -- if chunks are redivided and reloaded frequently, you'll get a lot of "popping" of the landscape. Another issue is that on a flat landscape like the Figure 1 Minecraft data, you can't really see much of the distant scenery unless you get high up.

    It might be better to have a non-linear algorithm that gives higher resolution summaries up close and less in the distance.

  • I can change the resolution of nearby textures. Right now, I'm using 1 pixel per brick. I could use a 2 by 2 or 4 by 4 pattern for the nearest (size=2) summaries. I tried this though and didn't see a huge amount of difference. See Figure 4. Notice the texture of the trees at bottom-right.

    Fig 4: Texture resolution - 1x1, 2x2 and 4x4

  • I can change the resolution of distant textures. At size=2, I'm using 1 pixel per brick. At size=4, I increase the texture resolution to keep a single pixel per brick, even though the cells are twice as large. After that distance, I continue to use 128 pixels per 32 cells.

    Since there are so few distant chunks, I could increase their resolution a bit. This costs more display memory and more storage on disk, but doesn't slow down rendering. It does preserve tiny details out to greater distances.

    I can't really tell if it's worth it on these sample landscapes, since they are so flat. Details get lost in all the noise. It might make a difference for buildings on mountains or on the surface of an asteroid.

That's what I have so far. I still need to rebuild the old rendering code with instancing and quads instead of individual faces. Then integrate it all into a single demo.

Home       About Me       Contact       Downloads       Part 71    Part 73   

blog comments powered by Disqus