Jump to content

  • Log In with Google      Sign In   
  • Create Account


Like
20Likes
Dislike

Virtual Texture Terrain

By Josh Klint | Published Jul 14 2013 12:59 AM in Graphics Programming and Theory
Peer Reviewed by (Josh Vega, Vilem Otte, Hodgman)

leadwerks opengl terrain megatexture

The Leadwerks 2 terrain system was expansive and very fast, which allowed rendering of huge landscapes. However, it had some limitations. Texture splatting was done in real-time in the pixel shader. Because of the limitations of hardware texture units, only four texture units per terrain were supported. This limited the ability of the artist to make terrains with a lot of variation. The landscapes were beautiful, but somewhat monotonous.

With the Leadwerks 3 terrain system, I wanted to retain the advantages of terrain in Leadwerks 2, but overcome some of the limitations. There were three different approaches we could use to increase the number of terrain textures.
  • Increase the number of textures used in the shader.
  • Allow up to four textures per terrain chunk. These would be determined either programmatically based on which texture layers were in use on that section, or defined by the artist.
  • Implement a virtual texture system like id Software used in the game Rage.
Since Leadwerks 3 runs on mobile devices as well as PC and Mac, we couldn't use any more texture units than we had before, so the first option was out. The second option is how Crysis handles terrain layers. If you start painting layers in the Crysis editor, you will see when "old" layers disappear as you paint new ones on. This struck me as a bad approach because it would either involve the engine "guessing" which layers should have priority, or involve a tedious process of user-defined layers for each terrain chunk.

A virtual texturing approach seemed liked the ideal choice. Basically, this would render near sections of the terrain at a high resolution, and far sections of the terrain at low resolutions, with a shader that chose between them. If done correctly, the result should be the same as using one impossibly huge texture (like 1,048,576 x 1,048,576 pixels) at a much lower memory cost. However, there were some serious challenges to be overcome, so much so that I added a disclaimer in our Kickstarter campaign basically saying "this might not work"..

Previous Work


id Software pioneered this technique with the game Rage (a previous implementation was in Quake Wars). However, id's "megatexture" technique had some serious downsides. First, the data size requirements of storing completely unique textures for the entire world were prohibitive. Rage takes about 20 gigs of hard drive space, with terrains much smaller than the size I wanted to be able to use. The second problem with id's approach is that both games using this technique have some pretty blurry textures in the foreground, although the unique texturing looks beautiful from a distance.


Attached Image: gf8800_trailer_1.jpg


I decided to overcome the data size problem by dynamically generating the megatexture data, rather than storing it on the hard drive. This involves a pre-rendering step where layers are rendered to the terrain virtual textures, and then the virtual textures are applied to the terrain. Since id's art pipeline was basically just conventional texture splatting combined with "stamps" (decals), I didn't see any reason to permanently store that data. I did not have a simple solution to the blurry texture problem, so I just went ahead and started implementing my idea, with the understanding that the texture resolution issue could kill it.

I had two prior examples to work from. One was a blog from a developer at Frictional Games (Amnesia: The Dark Descent and Penumbra). The other was a presentation describing the technique's use in the game Halo Wars. In both of these games, a fixed camera distance could be relied on, which made the job of adjusting texture resolution much easier. Leadwerks, on the other hand, is a general-purpose game engine for making any kind of game. Would it be possible to write an implementation that would provide acceptable texture resolution for everything from flight sims to first-person shooters? I had no idea if it would work, but I went forward anyway.

Implementation


Because both Frictional Games and id had split the terrain into "cells" and used a texture for each section, I tried that approach first. Our terrain already gets split up and rendered in identical chunks, but I needed smaller pieces for the near sections. I adjusted the algorithm to render the nearest chunks in smaller pieces. I then allocated a 2048x2048 texture for each inner section, and used a 1024x1024 texture for each outer section:


Attached Image: terrain.jpg


The memory requirements of this approach can be calculated as follows:

1024 * 1024 * 4 * 12 = 50331648 bytes
2048 * 2048 * 4 * 8 = 134217728
Total = 184549376 bytes = 176 megabytes

176 megs is a lot of texture data. In addition, the texture resolution wasn't even that good at near distances. You can see my attempt with this approach in the image below. The red area is beyond the virtual texture range, and only uses a single low-res baked texture. The draw distance was low, the memory consumption high, and the resolution was too low.


Attached Image: grid.jpg


This was a failure, and I thought maybe this technique was just impractical for anything but very controlled cases in certain games. I wasn't ready to give up yet without trying one last approach. Instead of allocating textures for a grid section, I tried creating a radiating series of textures extending away from the camera:


Attached Image: terr2.jpg


The resulting resolution wasn't great, but the memory consumption was a lot lower, and terrain texturing was now completely decoupled from the terrain geometry. I found by adjusting the distances at which the texture switches, I could get a pretty good resolution in the foreground. I was using only three texture stages, so I increased the number to six and found I could get a good resolution at all distances, using just six 1024x1024 textures. The memory consumption for this was just 24 megabytes, a very reasonable number. Since the texturing is independent from terrain geometry, the user can fine-tune the texture distances to accommodate flight sims, RPGs, or whatever kind of game they are making.


Attached Image: goodterrain.jpg


The last step was to add some padding around each virtual texture, so the virtual textures did not have to be completely redrawn each time the camera moves. I used a value of 0.25 the size of the texture range so the various virtual textures only get redrawn once in a while.

Advantages of Virtual Textures


First, because the terrain shader only has to perform a few lookups each pixel with almost no math, the new terrain shader runs much faster than the old one. When the bottleneck for most games is the pixel fillrate, this will make Leadwerks 3 games faster. Second, this allows us to use any number of texture layers on a terrain, with virtually no difference in rendering speed. This gives artists the flexibility to paint anything they want on the terrain, without worrying about budgets and constraints. A third advantage is that this allows the addition of "stamps", which are decals rendered directly into the virtual texture. This allows you to add craters, clumps of rocks, and other details directly onto the terrain. The cost of rendering them in is negligible, and the resulting virtual texture runs at the exact same speed, no matter how much detail you pack into it. Below you can see a simple example. The smiley faces are baked into the virtual texture, not rendered on top of the terrain:


Attached Image: Image1.jpg


Conclusion


The texture resolution problem I feared might make this approach impossible was solved by using a graduated series of six textures radiating out around the camera. I plan to implement some reasonable default settings, and it's only a minor hassle for the user to adjust the virtual texture draw distances beyond that.

Rendering the virtual textures dynamically eliminates the high space requirements of id's megatexture technique, and also gets rid of the problems of paging texture data dynamically from the hard drive. At the same time, most of the flexibility of the megatexture technique is retained.

Having the ability to paint terrain with any number of texture layers, plus the added stamping feature gives the artist a lot more flexibility than our old technique offered, and it even runs faster than the old terrain. This removes a major source of uncertainty from the development of Leadwerks 3.1 and turned out to be one of my favorite features in the new engine.



About the Author(s)


Josh Klint is the founder of Leadwerks Software, an advisor for the IGDA Sacramento chapter, and was a speaker at the GDC 2013 mobile summit.

License


GDOL (Gamedev.net Open License)




Comments

Nice article, but why there are still texture repetitions/tiling visible on every shot in a article that tries to solve this ? :)

It's called programmer art. biggrin.png  A competent artist can apply dirt and rocks anywhere they want to break up the detail.  There's also a lot of tricks we can do to improve this, but they're not in the prototype we have running now.

Article approved, and +1 for article on virtual texturing! Definitely, any source on implementation of (sparse) virtual textures (megatextures, or call it however you like ... all in all it's just a clever use of Level-of-Detail and for better performance, texture atlases) is greeted by me...

 

Anyway I'll give few points here, as I've already spent a lot of time with megatextures (I'm although not developing for mobile devices, only for PCs).

 

1) Breaking terrain to chunks is okay, but, I'm using as small chunks, that you don't need to futher "break" the chunk for single "chunk" of virtual texture, which should have like 128x128 or 256x256 pixels. So terrain chunks are quite small, plus I need at least diffuse, normals and displacement channel for virtual texture, compressed using (S3TC) block compression.

 

2) These small chunks are loaded as pages into large texture (I use 2048x2048 for 128x128 chunks) this means max. 256 pages. Of course in this case a page-table is a must.

 

3) Calculating which pages I need in the memory is the hardest part. Using the thing you described earlier is good, but keeps a lot of redundant pages in memory generally.

I stayed with ID Soft presentation and used redundant rendering pass + readback (I'm gotta try to use compute shader here though - just to see whether it'll be faster) to determine which pages I need. Dynamic reflections are although a bit of problem, as I don't see high detail textures sometimes (but as I use normal mapped surfaces for reflections it really isn't visible).

Or you can just guess the pages you need by simple heuristics. I've had quite good results with brain-dead computation of visible tiles with frustum culling, and "mip-level" determination by distance to camera... not as good as rendering pass, but definitely less expensive.

 

4) Using paging instead of using separate textures (like you used, at least thats what I got from the article) makes your texture filtering harder. You though have all the lower mip-levels in memory, and determining mip level is easy. Linear filtering needs a little more computation (for texture boundaries) ... and anisotropic is a bit harder. There was a discussion on Mollyrocket about "texture filtering of virtual textures" - read here if you're interested https://mollyrocket.com/forums/viewtopic.php?t=826

 

I appologize for long comment, but I think these points might be mentioned in comments. smile.png

 

EDIT: Just to add a bit of math... virtual textures are not always the best solution, especially for large open worlds (even though, they might be viable and good - uniqueness rules). Let's say you have 2km x 2km terrain, you want 64 pixels per meter (which is good looking), you need 128000x128000 texture to cover it, assuming you use BC1 for color, BC5 for normals (reconstructing 3rd component0 and BC4 for height map, you need 3.8 GB for color, 7.6 GB for normals and 3.8 GB for displacement, assuming normals and displacement can be at half resolution (quarter size), gets you to 3.8 GB for color, 1.9 GB for normals and 0.95 GB for displacement , summed up to 6.65 GB for just terrain. Which is usable... but larger resolution is just meh to distribute with game.

Vilem, we dynamically generate the megatexture data instead of paging it from the hard drive.  This makes it scale extremely well for large worlds.


Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS