Search code examples
c#openglopentk

OpenGL - How to create and bind cubemap arrays?


After exceeding texture unit limits I have decided to use cubemap array textures.

For the purpose of testing, I render the contents of cubemaps as skyboxes. Thing is, OpenGL ignores when I bind a cubemap array, and instead renders the skybox using the previously bound cubemap.

GL.BindTexture(TextureTarget.TextureCubeMap, shadowMaps[5].cubeMapTexture);
GL.BindTexture(TextureTarget.TextureCubeMapArray, shadowMapArray.cubeMapHandle); // Ignored

I'm assuming this is the default behaviour for an incorrectly created cubemap array. As far as I can tell, I've done everything correctly: Framebuffer complete.

So, what is the correct way of creating and binding cubemap arrays?

public class CubeMapArray
{
    public static int size = 512;
    public static int layers = 8; // number of cube maps in array

    public int FBO_handle;
    public int cubeMapHandle;
    public int cubeMapDepthHandle;

    // Constructor //
    public CubeMapArray()
    {
        // Create the FBO
        GL.GenFramebuffers(1, out FBO_handle);

        // Create and bind the CubeMap array
        GL.GenTextures(1, out cubeMapHandle);
        GL.BindTexture(TextureTarget.TextureCubeMapArray, cubeMapHandle);

        // Allocate storage space
        GL.TexImage3D(TextureTarget.TextureCubeMapArray, 0, PixelInternalFormat.Rg16, size, size, layers * 6, 0, PixelFormat.Red, PixelType.Float, IntPtr.Zero);

        // Set the suitable texture parameters
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureWrapR, (int)TextureWrapMode.ClampToEdge);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureBaseLevel, 0);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureMaxLevel, 0);

        // Create and bind the CubeMap depth array
        GL.GenTextures(1, out cubeMapDepthHandle);
        GL.BindTexture(TextureTarget.TextureCubeMapArray, cubeMapDepthHandle);

        // Allocate storage space
        GL.TexImage3D(TextureTarget.TextureCubeMapArray, 0, PixelInternalFormat.DepthComponent, size, size, layers * 6, 0, PixelFormat.DepthComponent, PixelType.UnsignedByte, IntPtr.Zero);

        // Set the suitable texture parameters
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureWrapR, (int)TextureWrapMode.ClampToEdge);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureBaseLevel, 0);
        GL.TexParameter(TextureTarget.TextureCubeMapArray, TextureParameterName.TextureMaxLevel, 0);

        // Attach cubemap texture as the FBO's color buffer
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, FBO_handle);
        GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, cubeMapHandle, 0);
        GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, cubeMapDepthHandle, 0);

        // Error check
        var errorcheck = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
        Console.WriteLine("CUBEMAP ARRAY: " + errorcheck);

        // Bind default framebuffer
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
    }
}

}

The shaders are simple enough, and surprisingly, work binding a regular cubemap.

Vertex

#version 330

in vec3 texCoord;
out vec4 fragColor;
uniform samplerCube cubeMapArray[16];

void main (void) 
{
    fragColor = texture(cubeMapArray[0], texCoord);
}

Fragment

#version 330

uniform mat4 modelMatrix; 
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

in vec3 in_position;
out vec3 texCoord;

void main() 
{
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_position, 1.0);
    texCoord = in_position;
}

Solution

  • You need to use the corresponding sampler type in your shader code. Cube map arrays are available in OpenGL 4.0 and later, so you will also need to increase the GLSL version to 400 or later.

    The shader code will then look something along these lines:

    #version 400
    ...
    uniform samplerCubeArray cubeMapArray;
    ...
    fragColor = texture(cubeMapArray, vec4(texCoord, 0));
    

    Note that the array layer for sampling is specified as the 4th component of the texture coordinates.