Search code examples
c++openglglslshadertextures

Multiple cube map textures behaving strangely when amount of textures increases


So I've managed to successfully implement batching into my engine, but have come across some weird behavior with the array of samplerCubes in my fragment shader. The batch renderer works fine with 2 texture units - I can successfully draw cubes with the right textures if I only bind 2 textures, but as soon as I add a 3rd to the mTextures vector, and use glUniform1i for that index in the samplerCube array, the wrong texture is displayed, even though the texture id (TexID) is correct (I checked this in the fragment shader).

The issue appears to be with my understanding of OpenGL as for some reason, the texture what should be displayed by textureCubes[2] (fragment shader uniform) is displayed by textureCubes[1], and textureCubes[2] displays the same texture as textureCubes[0]. What should be displayed by textureCubes[1] just doesn't exist with the 3rd texture bound.

Here's my code:

Main.cpp

#include "Window.h"
#include "Block.h"
#include "BatchRenderer.h"
#include "Shader.h"
#include "Camera.h"

void Submit(gfx::BatchRenderer* renderer, float height) //temporary to test batching
{  
    for (int i = 0; i < 16; i++)
    {
        for (int j = 0; j < 16; j++)
        {
            gfx::Block* block = new gfx::Block(j % 2 == 0 ? (i % 2 == 0 ? gfx::BlockType::DIRT : gfx::BlockType::GRASS) : gfx::BlockType::COBBLE, math::Vec3f(i * 5.0, height, j * 5.0));
            renderer->Submit(block);
        }
    }
}

int main()
{
    gfx::Window* window = new gfx::Window("MineClone", 800, 800);

    gfx::Shader* shader = new gfx::Shader();
    shader->FromFile(GL_VERTEX_SHADER, "Resources/ModelTest.vert");
    shader->FromFile(GL_FRAGMENT_SHADER, "Resources/ModelTest.frag");
    shader->RefreshProgram();

    math::Mat4f projection = math::Mat4f::Perspective(70.0, window->GetWidth() / window->GetHeight(), 0.1, 1000.0);
    gfx::Camera* camera = new gfx::Camera(projection, 0.05, 0.0015);

    gfx::BatchRenderer* renderer = new gfx::BatchRenderer();

    gfx::TextureCube* grass = new gfx::TextureCube("Resources/top.png", "Resources/dirt.png", "Resources/sides.png");
    renderer->Submit(grass);

    gfx::TextureCube* dirt = new gfx::TextureCube("Resources/dirt.png");
    renderer->Submit(dirt);

    gfx::TextureCube* cobble = new gfx::TextureCube("Resources/cobble.png");
    renderer->Submit(cobble);

    Submit(renderer, 0.0);
    Submit(renderer, 5.0);
    Submit(renderer, 10.0);
    Submit(renderer, 15.0);
    Submit(renderer, 20.0);
    Submit(renderer, 25.0);
    Submit(renderer, 30.0);
    Submit(renderer, 35.0);

    while (!window->IsClosed())
    {
        window->Update();
        if (window->GetInputHandler()->IsKeyDown(VK_ESCAPE))
            window->SwitchMouseState();

        if (window->IsSynced())
            camera->Update(window->GetInputHandler());
    
        shader->Bind();
        math::Mat4f projection = camera->GetProjection();
        shader->SetUniform("projection", projection);

        math::Mat4f view = camera->GetView();
        shader->SetUniform("view", view);

        shader->SetUniform("cubeTextures[0]", 0);
        shader->SetUniform("cubeTextures[1]", 1);
        shader->SetUniform("cubeTextures[2]", 2);

        renderer->Render();
        shader->Unbind();
    }

    return 0;
}

BatchRenderer.cpp

#include "BatchRenderer.h"

namespace gfx
{
    BatchRenderer::BatchRenderer()
        : mMesh(NULL)
    {
    }

    BatchRenderer::~BatchRenderer()
    {
        mBlocks.clear();
        mTextures.clear();
        delete mMesh;
    }

    void BatchRenderer::Submit(Block* block)
    {
        MeshData data = block->GetMesh();
        mMeshData.vertices.insert(mMeshData.vertices.end(), 
            data.vertices.begin(), 
            data.vertices.end());

        for (int i = 0; i < 36; i++)
        {
            data.indices[i] += mBlocks.size() * 8;
        }
        mMeshData.indices.insert(mMeshData.indices.end(),
            data.indices.begin(),
            data.indices.end());

        mMesh = Mesh::Make(mMeshData);
        mBlocks.push_back(block);
    }

    void BatchRenderer::Submit(TextureCube* texture)
    {
        mTextures.push_back(texture);
    }

    void BatchRenderer::Render()
    {
        for (int i = 0; i < mTextures.size(); i++)
        {
            mTextures[i]->Bind(i);
        }

        mMesh->Render();

        for (int i = 0; i < mTextures.size(); i++)
        {
            mTextures[i]->Unbind(i);
        }
    }
}

TextureCube.cpp

#include "TextureCube.h"

namespace gfx
{
    TextureCube::TextureCube(std::string paths[6])
        : Texture(TextureEnum::TEXTURE_CUBE)
    {
        glGenTextures(1, &mHandle);
        glBindTexture(GL_TEXTURE_CUBE_MAP, mHandle);

        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        for (int i = 0; i < 6; i++)
        {
            std::vector<byte> pixels;
            lodepng::decode(pixels, mWidth, mHeight, paths[i].c_str());
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
        }

        glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
        glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    }

    TextureCube::TextureCube(std::string path)
        : Texture(TextureEnum::TEXTURE_CUBE)
    {
        glGenTextures(1, &mHandle);
        glBindTexture(GL_TEXTURE_CUBE_MAP, mHandle);

        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        std::vector<byte> pixels;
        lodepng::decode(pixels, mWidth, mHeight, path.c_str());
        for (int i = 0; i < 6; i++)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
        }

        glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
        glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    }

    TextureCube::TextureCube(std::string top, std::string bottom, std::string sides)
        : Texture(TextureEnum::TEXTURE_CUBE)
    {
        glGenTextures(1, &mHandle);
        glBindTexture(GL_TEXTURE_CUBE_MAP, mHandle);

        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        std::vector<byte> topPixels;
        lodepng::decode(topPixels, mWidth, mHeight, top.c_str());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, topPixels.data());

        std::vector<byte> bottomPixels;
        lodepng::decode(bottomPixels, mWidth, mHeight, bottom.c_str());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, bottomPixels.data());

        std::vector<byte> sidesPixels;
        lodepng::decode(sidesPixels, mWidth, mHeight, sides.c_str());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, sidesPixels.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, sidesPixels.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, sidesPixels.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, sidesPixels.data());

        glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
        glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    }

    TextureCube::~TextureCube()
    {
        glDeleteTextures(1, &mHandle);
        mHandle = NULL;
    }

    void TextureCube::Bind(uint32_t slot)
    {
        glBindTextureUnit(slot, mHandle);
    }

    void TextureCube::Unbind(uint32_t slot)
    {
        glBindTextureUnit(slot, 0);
    }
}

ModelTest.vert

#version 450 core

layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec3 offset;
layout (location = 3) in float textureID;

uniform mat4 projection;
uniform mat4 view;

out vec3 TexPos;
out flat float TexID;

void main()
{
    gl_Position = projection * view * vec4(position, 1.0);
    TexPos = position - offset;
    TexID = textureID;
}

ModelTest.frag

#version 450 core

out vec4 FragColour;

in vec3 TexPos;
in flat float TexID;
uniform samplerCube cubeTextures[3];

void main()
{
    vec3 norm = normalize(TexPos);
    int id = int(TexID);
    FragColour = texture(cubeTextures[id], norm);
}

Solution

  • For anyone looking for a solution, it took me a while but I found it (I'm relatively new to OpenGL). The issue was I was setting the texture sampler slots individually using glUniform1i. To fix this, as cubeTextures is an array of samplerCubes, I created a struct for sampler data containing an int array and a length, and set the texture sampler slots using glUniform1iv.