c++openglglsl

How can I fix hard face translation when sampling lower mips of the texture cubemap?


I've got a texture that I want to use as a indirect radiance light.

There is a problem that when I sample this texture's lowest mip the result looks like this:

incorrect

but actually should look like this:

correct

I use a compute shader to generate radiance data, but for debugging case you can use a regular cubemap with mipmaps generated by hardware (glGenerateTextureMipmap) or load it from a file. The result will be the same - hard face translation.

Vertex shader:

#version 460 core

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;

//per instance
layout(location = 2) in vec4 a_modelToWorld0;
layout(location = 3) in vec4 a_modelToWorld1;
layout(location = 4) in vec4 a_modelToWorld2;
layout(location = 5) in vec4 a_modelToWorld3;

layout(location = 0) out vec3 v_worldPosition;
layout(location = 1) out vec3 v_worldNormal;

layout(std140, binding = 0) uniform PerFrame
{
    mat4 cameraModel;
    mat4 viewProjection;
} u_frame;

void main()
{
    mat4 model = mat4(a_modelToWorld0, a_modelToWorld1, a_modelToWorld2, a_modelToWorld3);
    
    vec4 worldPosition = model * vec4(a_position, 1.0f);

    v_worldPosition = worldPosition.xyz;
    v_worldNormal = mat3(model) * a_normal;

    gl_Position = u_frame.viewProjection * worldPosition;
}

pixel shader:

#version 460 core

layout(location = 0) out vec4 f_color;

layout(location = 0) in vec3 v_worldPosition;
layout(location = 1) in vec3 v_worldNormal;

layout(std140, binding = 0) uniform PerFrame
{
    mat4 cameraModel;
    mat4 viewProjection;
} u_frame;

layout(binding = 1) uniform samplerCube u_indirectRadiance;

void main()
{
    vec3 normal = normalize(v_worldNormal);
    vec3 viewDirection = normalize(u_frame.cameraModel[3].xyz - v_worldPosition);
    vec3 reflectionDir = reflect(-viewDirection, normal);

    vec3 color = textureLod(u_indirectRadiance, reflectionDir, 10.0f).rgb;

    f_color = vec4(color, 1.0f);
}

I did some debugging and checked normal, viewDirection, reflectionDir and these vectors look correct: normal, vd, rd

I've compared these values with my DX11 application that gives me correct indirect radiance light and it all looks the same.

As for the problem itself, it's not about the cubemap data because I've checked it. It has correct values.

The problem with the way it's sampled.

code that creates the radiance cubemap and the sampler

GLuint sampler;
glCreateSamplers(1, &sampler);

glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
                            
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

GLuint texture;
glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &texture);
glTextureStorage2D(texture, 11, GL_RGBA32F, 1024, 1024);

//here you can load a texture from a file
//eg. dds
/*

DirectX::TexMetadata meta;
DirectX::ScratchImage image;
//assuming that radianceCubemap.dds is 1024x1024, cubemap and has mipmaps generated correctly
DirectX::LoadFromDDSFile("assets/radianceCubemap.dds", DirectX::DDS_FLAGS_NONE, &meta, image);

GLuint target = GL_TEXTURE_CUBE_MAP;
GLuint format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; //assuming format is DXGI_FORMAT_BC1_UNORM, but this is just an example

for (std::size_t level = 0; level < meta.mipLevels; ++level) {
    for (std::size_t face = 0; face < meta.arraySize; ++face) {
        const DirectX::Image* img = image.GetImage(level, face, 0);
        if (img) {
            glCompressedTextureSubImage3D(texture, level, 0, 0, face, img->width, img->height, 1, format, img->slicePitch, img->pixels);
        }
    }
}
*/

/*
or you can do whatever, it does not matter, just make sure it's a 1024x1024 cubemap with 11 mips
*/

my cubemap:

mips and faces

tex and sampler

as you can see it's all correct

What's wrong with my code? How can I fix it?


Solution

  • In the last mipmap level, each side of the texture is 1x1. As far as I know, filtering across cubemap texture sides is disabled by default. So even if you use linear filtering, you have only 1 texel, that's why you can't get a gradient. If you call glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS) you enable filtering across sides.