使用texelFetch的OpenGL 2D瓦片渲染代码异常,求排查
2D瓦片地图渲染:texelFetch始终返回0导致瓦片显示异常
代码实现
C++渲染器代码
#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); }
顶点着色器
#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); }
片段着色器
#version 440 core uniform sampler2DArray atlasSampler; in vec3 TexCoords; out vec4 FragColor; void main() { FragColor = texture(atlasSampler, TexCoords); }
问题描述
代码目标是通过存储瓦片索引的2D纹理(每个像素对应一个瓦片ID),结合纹理数组渲染2D瓦片地图。目前网格渲染正常,但所有瓦片均显示索引为0的纹理,texelFetch始终返回0。
已确认:
- 瓦片索引纹理的缓冲区已填充0-23的随机值,数据正确。
- 手动硬编码索引值时,纹理数组能正确显示对应瓦片,说明
sampler2DArray配置正常。 - 此前成功使用过
texelFetch实现类似功能。
问题排查与修复
1. 纹理格式不匹配(核心问题)
创建瓦片索引纹理时,内部格式使用GL_RED(默认8位无符号整数),但传入的是32位无符号整数数据:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, ..., GL_UNSIGNED_INT, ...);
这会导致数据被截断为8位,且texelFetch返回的r分量是归一化到0.0-1.0的浮点数(比如原ID为5,会变成5/255≈0.0196,而非5.0),纹理数组采样时会取索引0的纹理。
修复步骤:
- 修改纹理创建代码,使用32位无符号整数内部格式:
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, info.tilemapSizeX, info.tilemapSizeY, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, m_worldTextureBytes.get());
- 顶点着色器中使用无符号整数采样器:
uniform usampler2D masterTileTexture;
- 保持
texelFetch的转换逻辑不变,因为usampler2D返回的是无符号整数,转float后得到正确的索引值:
float(texelFetch(masterTileTexture, tilexy + chunkOffset, 0).r)
2. 瓦片坐标计算错误
顶点着色器中tilexy的行列计算颠倒:
ivec2 tilexy = ivec2(int(whichTile / chunkSize.y), int(whichTile % chunkSize.y));
若块是按X列数×Y行数排列,正确计算应为:
ivec2 tilexy = ivec2(whichTile % chunkSize.x, whichTile / chunkSize.x);
错误的计算会导致访问纹理的错误区域,甚至超出范围触发GL_REPEAT回到0索引。
3. 移除不必要的Mipmap
瓦片索引纹理不需要Mipmap,建议删除glGenerateMipmap(GL_TEXTURE_2D);,避免采样时的潜在干扰。
内容的提问来源于stack exchange,提问作者Jim Marshall
相关产品推荐
相关产品推荐

