Search code examples
c++opengl3dtexturesmipmaps

Why does mipmapping not work on my 3D texture? (opengl)


So I am creating a terrain and for texturing, I want to use a 3D texture (depth 3) which holds 3 images (512x512) on each z-layer, so that I would be able to use GPU interpolation between these layers based on just one factor: 0/3 = image 1, 1/3 = image 2, 2/3 = image 3, and every value in between interpolates with the next level (cyclic).

This works perfectly as long as I don't enable mip maps on this 3D texture. When I do enable it, my terrain gets the same one image all over unless I come closer, as if the images have shifted from being z-layers to being mip-map layers.

I don't understand this, can someone tell me what I'm doing wrong?

This is where I generate the texture:

glGenTextures(1, &m_textureId);
glBindTexture(GL_TEXTURE_3D, m_textureId);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 512, 512, 3, 0, GL_BGR, GL_UNSIGNED_BYTE, 0);

This is the step I perform for every Z:

glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, Z, 512, 512, 1, GL_BGR, GL_UNSIGNED_BYTE, imageData);

After this, I do:

glGenerateMipmap(GL_TEXTURE_3D);

In the shader, I define the texture as:

uniform sampler3D tGround;

and simply sample it with:

texture(tGround, vec3(texcoord, f));

where texcoord is a 2D coordinate and f is the layer we need, simply based on height at this moment.


Solution

  • There is a way to do something like what you want, but it does require work. And you can't use a 3D texture to do it.

    You have to use Array Textures instead. The usual way to think of a 2D array texture is as a bundle of 2D textures of the same size. But you can also think of it as a 3D texture where each mipmap level has the same number of Z layers. However, there's also the issue where there is no blending between array layers.

    Since you want blending, you will need to synthesize it. But that's easy enough with shaders:

    vec4 ArrayTextureBlend(in vec3 texCoord)
    {
      float frac = fract(texCoord.z);
      texCoord.z = floor(texCoord.z);
      vec4 top = texture(arrayTex, texCoord);
      vec4 bottom = texture(arrayTex, texCoord + vec3(0, 0, 1));
      return mix(top, bottom, frac); //Linearly interpolate top and bottom.
    }