Search code examples
c++openglglslglm-math

Why is this OpenGL code using texelFetch not working?


I've written this code to render a 2d map of square tiles:

#define TILE_NUM_INDICES 6

inline static u32 GetRandomIntBetween(u32 min, u32 max) {
    return (u32)rand() % (max - min + 1) + min;
}

static void GetRandomTileMap(u32* map, u32 size) {
    for (int i = 0; i < size; i++) {
        u32 r = GetRandomIntBetween(0, 23);
        map[i] = r;
    }
}
NewRenderer::NewRenderer(const NewRendererInitialisationInfo& info)
    :m_tileShader("shaders\\TilemapVert2.glsl", "shaders\\TilemapFrag2.glsl"),
    m_worldMapSize(info.tilemapSizeX, info.tilemapSizeY),
    m_tilemapChunkSize(info.chunkSizeX, info.chunkSizeY),
    m_windowWidth(info.windowWidth),
    m_windowHeight(info.windowHeight)
{
    using namespace std;
    const u32 mapsize = info.tilemapSizeX * info.tilemapSizeY;
    m_worldTextureBytes = make_unique<u32[]>(mapsize);
    GetRandomTileMap(m_worldTextureBytes.get(), mapsize);
    glGenTextures(1, &m_worldTextureHandle);
    glBindTexture(GL_TEXTURE_2D, m_worldTextureHandle);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // GL_NEAREST is the better filtering option for this game
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, info.tilemapSizeX, info.tilemapSizeY, 0, GL_RED, GL_UNSIGNED_INT, m_worldTextureBytes.get());
    glGenerateMipmap(GL_TEXTURE_2D);

    glGenVertexArrays(1, &m_vao);
}

void NewRenderer::DrawChunk(
    const glm::ivec2& chunkWorldMapOffset,
    const glm::vec2& pos,
    const glm::vec2& scale,
    float rotation,
    ArrayTexture2DHandle texArray,
    const Camera2D& cam
) const
{
    m_tileShader.use();
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(pos, 0.0f));
    model = glm::rotate(model, glm::radians(rotation), glm::vec3(0.0f, 0.0f, 1.0f));
    model = glm::scale(model, glm::vec3(scale, 1.0f));

    m_tileShader.setMat4("vpMatrix", cam.GetProjectionMatrix(m_windowWidth, m_windowHeight));
    m_tileShader.setMat4("modelMatrix", model);
    m_tileShader.SetIVec2("chunkOffset", chunkWorldMapOffset);
    m_tileShader.SetIVec2("chunkSize", m_tilemapChunkSize);
    m_tileShader.setInt("masterTileTexture", 0);
    m_tileShader.setInt("atlasSampler", 1);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_worldTextureHandle);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D_ARRAY, texArray);

    glBindVertexArray(m_vao);
    glDrawArrays(GL_TRIANGLES, 0, m_tilemapChunkSize.x * m_tilemapChunkSize.y * TILE_NUM_INDICES);

}

(Vertex shader)

    #version 440 core
/*
cpp setup:
create a big index buffer
*/
layout (location = 0) in vec2 pos;
layout (location = 1) in vec2 uv;
out vec3 TexCoords;

uniform mat4 vpMatrix;
uniform mat4 modelMatrix;
uniform ivec2 chunkOffset;
uniform ivec2 chunkSize;
uniform sampler2D masterTileTexture;

#define TILE_NUM_VERTS 4
#define NUM_TILE_INDICES 6
void main()
{

    // vertices and indices that make up two triangles (a quad)
    // ie one tile in the map
    vec4 vertices[TILE_NUM_VERTS] = vec4[TILE_NUM_VERTS](
        vec4(0.5f,  0.5f,   1.0f, 1.0f),
        vec4(0.5f, -0.5f,   1.0f, 0.0f),
        vec4(-0.5f, -0.5f,  0.0f, 0.0f),
        vec4(-0.5f,  0.5f,   0.0f, 1.0f)
    );
    int indices[NUM_TILE_INDICES] = int[NUM_TILE_INDICES](
        0, 1, 3,   // first triangle
        1, 2, 3    // second triangle
    );

    // cycle through indicies
    int index = indices[int(gl_VertexID % NUM_TILE_INDICES)];

    // get base vertex
    vec4 baseVertex = vertices[index];

    // which tile in the map is being drawn?
    int whichTile = gl_VertexID / NUM_TILE_INDICES;

    // transfrom into x y coords of tile in the chunk
    ivec2 tilexy = ivec2(int(whichTile / chunkSize.y), int(whichTile % chunkSize.y));

    // translate base vertex by tilexy
    baseVertex.xy += vec2(tilexy);

    // set the z coord of the tex coords passed based on what tile is here
    // in the master tile map.

    // based on shader output all steps up to here are successful, a grid is drawn.
    // The problem is the texelFetch is not working, it's always the same tile drawn.
    TexCoords = vec3(
        baseVertex.zw,
        // changing this to different hard coded values does change what tile is drawn as expectd so sampler2DArray is setup correctly
        float(texelFetch(masterTileTexture, tilexy + chunkOffset, 0).r)); 

    gl_Position = vpMatrix * modelMatrix * vec4(baseVertex.xy, 0.0, 1.0);

}

(Frag shader)

#version 440 core

uniform sampler2DArray atlasSampler;
in vec3 TexCoords;
out vec4 FragColor;

void main()
{
    FragColor = texture(atlasSampler, TexCoords);
}

The idea is that it will be used to draw chunks of a large texture, each pixel of which represents a tile. The basic premise seems to work, a grid of tiles is drawn, however the texelFetch line in the vertex shader does not seem to be working, or the texture containing the tile indices is not set up properly as it is only ever the tile with index 0 that is drawn.

To test it I've tried to make a texture which contains random values for the tile index texture, debugging the code I can see that random values are inserted into the texture buffer.

I've used texelFetch before in a shader and it's worked and as far as I can tell I am using it right.

Can anyone spot what is wrong with my code?


Solution

  • glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, info.tilemapSizeX, info.tilemapSizeY, 0, GL_RED, GL_UNSIGNED_INT, m_worldTextureBytes.get());
    

    This creates a texture in a normalized fixed-point format. When you read it in the shader (through texelFetch) the value is always going to be between 0 and 1, thus sampling the 0th layer from the array texture.

    OpenGL 4.4 supports integer texture formats, which is what you should use here. Replace the first GL_RED with GL_R8UI, GL_16UI or GL_R32UI, whichever is more appropriate, and the second GL_RED with GL_RED_INTEGER. E.g.:

    glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, //<---
        info.tilemapSizeX, info.tilemapSizeY, 0, GL_RED_INTEGER, //<---
        GL_UNSIGNED_INT, m_worldTextureBytes.get());
    

    Additionally you have to change the sampler2D in the shader to a matching integer sampler type. For the above internal format, the matching sampler would be usampler2D:

    uniform usampler2D masterTileTexture;
    

    EDIT: Also you have to set the MAG filter to GL_NEAREST, since it's the only one that's supported:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    

    (MIN filter could also be GL_NEAREST_MIPMAP_NEAREST.)