How do I use a cubemap array in OpenGL to render multiple point lights with shadowmaps?

Viewed 287

I read and implemented the following tutorial: https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows

Now I want to generalize it to render multiple point lights in my scene, how would I go about it? I heard that cubemap arrays could be used for something like this, how do they work exactly? Could someone show me an example?

(I'm answering this question for myself and for the future generations, because there are simply no easily google-able tutorials on how to use cubemap arrays at this point in time.)

1 Answers

If you want to pass multiple cubemap shadowmaps to a shader, and you dont know in advance how many exactly then cubemap arrays are a good solution. They work like a 3D texture, where each layer is a cubemap. In the fragment shader you can create a sampler for them like this:

uniform samplerCubeArray cubeMapArray;

You bind the array texture to it in the following way:

glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, m_PointLightDepthMaps.m_DepthTexture);

You can sample from the samplerCubeArray in the way like you would from a cubemap, but in this case your vector gets a fourth element, which is the layer to sample from. E.g.:

texture(cubeMapArray, vec4(dir, 3)).r

would sample from the fourth cubemap in the array. To create yourself a cubemap array which works as a depth texture you can use the following template:

void Renderer::initCubemapDepthMap(CubemapDepthMap& map)
{
    glGenTextures(1, &map.m_DepthTexture);
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, map.m_DepthTexture);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexImage3D(
        GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_DEPTH_COMPONENT, m_ShadowCubeMapResolution, m_ShadowCubeMapResolution, 6 * m_MaxPointLights + 6 * m_MaxSpotLights, 0,
        GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0);

    glGenFramebuffers(1, &map.m_FrameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, map.m_FrameBuffer);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, map.m_DepthTexture, 0);
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);

    const int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE)
    {
        std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!\n";
        throw 0;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

Note that you need to use GL_TEXTURE_CUBE_MAP_ARRAY as your texture type, and you need to use glTexImage3D. When calling glTexImage3D the number of layers is the number of cubemaps you need times 6, because each cubemap face is allocated like a separate layer. You don't need to take this into account when you sample it in the fragment shader!

When rendering the shadowmap you bind the buffer like you would any other buffer. And when you pass the depth texture to the shader you pass the whole cubemap array, no need to mess with separate layers.

Related