如何结合GL_TEXTURE_2D_ARRAY与stb_image实现正确的精灵动画?
纹理数组实现精灵动画的问题排查与正确实现
问题定位
你的问题核心出在CPU端的纹理裁剪逻辑,着色器部分无致命错误但可优化。具体错误点:
- 精灵图Tile的内存偏移计算错误:原代码假设所有Tile在内存中是连续块排列,但实际精灵图是行优先的网格排列(比如6x6的图,第6个Tile是第二行第一列,而非连续内存的第6块),直接按连续块计算偏移会导致纹理采样错位。
- 错误设置
GL_UNPACK_IMAGE_HEIGHT:该参数用于3D纹理的像素解包,你的源图是2D精灵图,设置它会干扰正常的像素解包逻辑。
修正方案
1. 修复纹理裁剪逻辑
重新计算每个Tile的内存偏移,按网格行列推导起始地址,同时移除不必要的解包参数:
int width, height, depth; stbi_set_flip_vertically_on_load(true); // 注意修正路径中的转义错误,示例改为正确的相对路径 byte_t* buffer = stbi_load(R"(./fire.jpg)", &width, &height, &depth, STBI_rgb_alpha); if (buffer == nullptr) { std::cerr << "Could not read texture" << std::endl; return EXIT_FAILURE; } GLuint texture_id; const GLenum target = GL_TEXTURE_2D_ARRAY; SAFE_CALL(glGenTextures(1, &texture_id)); SAFE_CALL(glBindTexture(target, texture_id)); SAFE_CALL(glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0)); SAFE_CALL(glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 1)); SAFE_CALL(glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); SAFE_CALL(glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); SAFE_CALL(glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); SAFE_CALL(glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); const GLsizei tile_w_count = 6, tile_h_count = 6; const GLsizei total_tiles = tile_w_count * tile_h_count; const GLsizei tile_w = width / tile_w_count, tile_h = height / tile_h_count; std::cout << "Texture WxH: " << width << "x" << height; std::cout << ", Tile WxH: " << tile_w << "x" << tile_h << std::endl; SAFE_CALL(glTexStorage3D(target, 1, GL_RGBA8, tile_w, tile_h, total_tiles)); // 仅设置2D纹理解包所需的行长度,移除不必要的UNPACK_IMAGE_HEIGHT SAFE_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, width)); for (GLsizei i = 0; i < total_tiles; ++i) { // 计算当前Tile在网格中的列、行索引 GLsizei col = i % tile_w_count; GLsizei row = i / tile_w_count; // 推导Tile在源buffer中的起始字节偏移:行偏移*每行字节数 + 列偏移*单像素字节数 size_t offset = (row * tile_h * width + col * tile_w) * depth; SAFE_CALL(glTexSubImage3D( GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, tile_w, tile_h, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer + offset )); } // 记得释放stbi加载的内存 stbi_image_free(buffer);
2. 优化片元着色器
原着色器的四舍五入操作多余(整数+0.5后floor仍为原整数),简化边界钳制逻辑:
#version 460 core in vec2 tex_coords; out vec4 frag_color; uniform sampler2DArray texture_0; uniform int current_frame; uniform int total_frames; float actual_layer() { // 直接将整数帧索引转为float,同时钳制在有效范围内 float frame = float(current_frame); return clamp(frame, 0.0, float(total_frames - 1)); } void main() { frag_color = texture(texture_0, vec3(tex_coords, actual_layer())); }
3. 额外注意事项
- 确保精灵图的宽高能被
tile_w_count和tile_h_count整除,否则tile_w/tile_h的计算误差会导致纹理错位。 - 若启用mipmap,需调用
glGenerateMipmap(GL_TEXTURE_2D_ARRAY),但你的代码仅分配了1级mipmap,无需额外操作。 - 检查纹理文件路径是否正确,避免因路径错误导致纹理加载失败。
内容的提问来源于stack exchange,提问作者oiushder
相关产品推荐
相关产品推荐

