Heightmaps, tiles and 3D textures

I have a heightmap in my engine. Each cell of the heightmap has its own tile (texture).

The heightmap render it’s terribly slow because I usually have to bind a texture every 2 triangles. I have thought that using 3D textures like an array of 2D textures, and putting all the 2D tiles in a single 3D texture could be faster. But I have also noticed that 3D textures are terribly slow in some old cards (like geforce 4 MX) or when the texture is big (128x128xX). So the questions are.

  • Is that a good option?
  • What cards can render 3D textures fast?
  • Any other ideas on rendering the surface faster?

Thanx in adcance.

All cards since the Gf 3 support 3D textures. The Gf 4 is slow with 3D textures, but it´s slow in general, so don´t bother about it. The real Gf 4 doesn´t have problems with 3D textures.

Alternatively, you could simply bind different textures to different texture units, but that will make a lot of problems, when you don´t have so many texture units available.

Jan.

Hey, here’s an idea! Why don’t you put all your textures into one big 2d texture, and then use texture coordinates to access each individual texture from the big one? Heck, I might be onto something here…

Originally posted by martinho_:
[b]I have a heightmap in my engine. Each cell of the heightmap has its own tile (texture).

The heightmap render it’s terribly slow because I usually have to bind a texture every 2 triangles. I have thought that using 3D textures like an array of 2D textures, and putting all the 2D tiles in a single 3D texture could be faster. But I have also noticed that 3D textures are terribly slow in some old cards (like geforce 4 MX) or when the texture is big (128x128xX). So the questions are.

  • Is that a good option?
  • What cards can render 3D textures fast?
  • Any other ideas on rendering the surface faster?

Thanx in adcance.[/b]
You can assume that a card renders 3D textures fast if it exports the 3D texture extension (as opposed to supporting them just by the fact that the OpenGL version is >=1.2). This is normally true for any extension added to the core: if the new core functionality is also present as part of the extension string, then it’s hardware accelerated.
Note that the converse may not be true, though (the fact that the card doesn’t support the extension doesn’t mean it’s not hardware accelerated).

Re 3D texture speed, if your texture coordinates are local to one of the slices of the texture (so you render texels from the same 3D texture slice all the time), it should be the same speed as 2D texturing (normally the performance problem with 3D textures comes from the texture coordinates straddling different slices, for texel lookups thrash the texture cache).

Another alternative is to use a texture atlas as knackered recommends (pack all textures inside a bigger texture). As always when using texture atlas, be careful with filtering (bilinear at the borders or anisotropic filtering close to the borders) and when generating the mipmaps.

You may not be able to fit all your textures inside a big one, but you should be able to decrease the number of binds significantly just by packing some in groups. If you terrain is static you can find the best packing as an offline process.

You can even have a given terrain texture in different packings to allow different locallities: if the texture for the river appears in your terrain close to mountains and sand, it may appear in two packings). Note that the texture coordinates for a triangle using the river texture may be different in one and the other packing (that depends on your heuristics when packing textures).

Note that 3D texturing will do the Wrong Thing for your needs if you turn on filtering. Which you really really want to have on (especially MIP mapping for the memory bandwidth savings).

… then use texture coordinates to access each individual texture from the big one? Heck, I might be onto something here…
Yes knackered, you’re on to something.
Mark Harris used that technique for his dissertation on real-time cloud simulation. He calls it texture flattening. See also this thread on http://shadertech.com for a discussion.

  • Klaus

Thanx everybody for the info.

I’ve already implemented the texture atlas, and as evanGLizr said I had problems in the border texels.
Since CLAMP_TO_EDGE is of no use here, I’ve modified texture coords by moving them 1 texel so they don’t directly sample the border texel of each sub texture (by adding or substracting 1/atlasSize to the texture coords).

It looks ok on my geForceFX5600 with either iso or anisotropic filtering.

Is what I do ok?, may I have problems with other cards?

Originally posted by martinho_:
[b]Thanx everybody for the info.

I’ve already implemented the texture atlas, and as evanGLizr said I had problems in the border texels.
Since CLAMP_TO_EDGE is of no use here, I’ve modified texture coords by moving them 1 texel so they don’t directly sample the border texel of each sub texture (by adding or substracting 1/atlasSize to the texture coords).

It looks ok on my geForceFX5600 with either iso or anisotropic filtering.

Is what I do ok?, may I have problems with other cards?[/b]
1 border texel is for sure not going to be enough for anisotropic filtering of more than 2 taps (if your card has adaptive aniso filtering you may need a very skewed/pitched view to notice). A different thing is whether it will be really noticeable, as at least more than half the samples will be from the right texture.

I haven’t given it much thought, but I have the hunch that you should be able to pack one texture on each face of a cubemap and assign texture coordinates in such a way that the biggest texcoord (in magnitude) selects the face to use and the other two are used to bilinearly interpolate the texture as usual.
This may introduce a different artifact, though, because the sampling at the edges of the face may be undefined if the texture coordinates you use to bilinear filter come too close to the texture coordinate you use to select the cube face.

Well, I’ve tried lots of things and I think there is no decent way of doing the atlas.

  • Changing texture coords as I said before doesn’t work with ATI cards, they don’t sample the border texel at all, so surfaces look tiled.

  • Adding border texels to each sub texture desn’t work with mipmapping, even with more than 1 border texels (I tried even with 8). At least with automatic generated mipmaps, I could scale the image manually and it would propably work, by never mixing texels of 2 subtextures.

So to get rid of all those problems, what I finally decided is to pack only variations of the same texture, i.e. if I have 10 different variations for grass I pack them into 1 texture and I have to bind only when changing from grass to other type of surface.