OpenGL GLSL方块纹理ID值莫名异常的技术求助
问题
近期用C++结合GLFW开发基于OpenGL的体素游戏,已完成区块系统,现需为方块添加纹理。实现逻辑为:向顶点数组中每个方块添加纹理ID(1=圆石,2=泥土等),在着色器中读取该ID以应用对应纹理。
仅使用圆石方块时运行正常,但使用泥土方块时出现异常。
顶点着色器代码
#version 330 core layout (location = 0) in vec3 aPos ; layout (location = 1) in vec3 aNormal ; layout (location = 2) in vec2 aTexCoords ; layout (location = 3) in float type ; // 导入纹理ID uniform mat4 model ; uniform mat4 view ; uniform mat4 projection ; out vec3 Normal ; out vec3 FragPos ; out vec2 TexCoords ; out float BlockType ; void main(){ gl_Position = projection * view * model * vec4(aPos, 1.0f) ; FragPos = vec3(model * vec4(aPos, 1.0f)) ; Normal = aNormal; TexCoords = aTexCoords ; BlockType = type ; // 传递到片元着色器 // 曾尝试传递常量值: // BlockType = 2 ; // 确认问题不来自顶点数组,但结果无变化,问题出在传递之后 }
片元着色器代码
#version 330 core #define NR_POINT_LIGHTS 1 #define NB_TEXTURES_TYPE 3 out vec4 FragColor ; struct Material { sampler2D diffuse[NB_TEXTURES_TYPE]; // 存储纹理的采样器数组 sampler2D specular[NB_TEXTURES_TYPE]; float shininess; }; in vec2 TexCoords; in vec3 FragPos; in float BlockType ; uniform Material material; // 导入纹理的uniform void main(){ // 将ID转为整数用于数组索引 int btype = int(BlockType) ; if(btype > 0){ btype -= 1 ; // 因为C++中0代表空气,这里数组从0开始,所以ID减1对应索引 } FragColor = vec4(vec3(texture(material.diffuse[btype], TexCoords)), 1.0) ; // 输出纹理颜色 // 曾尝试直接用常量索引: // FragColor = vec4(vec3(texture(material.diffuse[1], TexCoords)), 1.0) ; // 此时泥土纹理显示正常 }
怀疑方块纹理ID在顶点着色器传递到片元着色器的过程中莫名改变,请求排查原因。
排查与解决方案
问题核心是float类型的纹理ID在光栅化阶段被插值导致精度丢失:顶点着色器输出的float变量会在片元着色阶段被插值计算,若一个面的不同顶点携带不同纹理ID,片元得到的是插值后的非整数值,转成int后会偏离原始ID,导致纹理索引错误。
方案1:改用整数类型传递纹理ID
直接用整数类型传递ID,OpenGL会禁用插值,保证片元拿到的是原始整数值:
修改后的顶点着色器
#version 330 core layout (location = 0) in vec3 aPos ; layout (location = 1) in vec3 aNormal ; layout (location = 2) in vec2 aTexCoords ; layout (location = 3) in int type ; // 改为int类型 uniform mat4 model ; uniform mat4 view ; uniform mat4 projection ; out vec3 Normal ; out vec3 FragPos ; out vec2 TexCoords ; out int BlockType ; // 输出也改为int类型 void main(){ gl_Position = projection * view * model * vec4(aPos, 1.0f) ; FragPos = vec3(model * vec4(aPos, 1.0f)) ; Normal = aNormal; TexCoords = aTexCoords ; BlockType = type ; // 直接传递整数ID }
修改后的片元着色器
#version 330 core #define NR_POINT_LIGHTS 1 #define NB_TEXTURES_TYPE 3 out vec4 FragColor ; struct Material { sampler2D diffuse[NB_TEXTURES_TYPE]; sampler2D specular[NB_TEXTURES_TYPE]; float shininess; }; in vec2 TexCoords; in vec3 FragPos; in int BlockType ; // 改为int类型输入 uniform Material material; void main(){ int btype = BlockType ; if(btype > 0){ btype -= 1 ; } FragColor = vec4(vec3(texture(material.diffuse[btype], TexCoords)), 1.0) ; }
C++端顶点属性配置
使用glVertexAttribIPointer而非glVertexAttribPointer来配置整数类型的顶点属性:
// 假设Vertex结构体中type是int类型 glVertexAttribIPointer(3, 1, GL_INT, sizeof(Vertex), (void*)offsetof(Vertex, type)); glEnableVertexAttribArray(3);
方案2:禁用float变量插值(兼容旧逻辑)
如果必须保留float类型,在顶点着色器输出和片元着色器输入时添加flat修饰符,强制禁用插值:
// 顶点着色器 flat out float BlockType; // 片元着色器 flat in float BlockType;
这样片元会取该面某个顶点的原始ID值,不会产生插值误差,但不如整数类型直观可靠。
内容的提问来源于stack exchange,提问作者Thomas Boishu
相关产品推荐
相关产品推荐

