GLSL顶点着色器输入疑问:片段着色器两种长度计算结果为何不同?
代码与现象
顶点缓冲区定义
GLfloat _vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f };
绘制设置代码
glViewport(0, 0, w, h); glEnableVertexAttribArray(0); glDisable(GL_BLEND); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, _vertices); glDrawArrays(GL_TRIANGLES, 0, 6);
顶点着色器
#version 320 es layout(location = 0) in vec2 vertPos; out vec2 fragPos; out float theLength; void main() { fragPos = vertPos; theLength = length(fragPos); gl_Position = vec4(vertPos.xy, 1.0, 1.0); }
片段着色器(非预期结果版本)
#version 320 es precision mediump float; in vec2 fragPos; in float theLength; out vec4 fragColor; void main() { vec4 color = vec4(0.0); float d = length(fragPos); fragColor = vec4(d, 0.0, 0.0, 1.0); }
现象对比
- 使用上述片段着色器时,输出图形呈现横向拉伸的红色渐变,边缘颜色深浅不均,不符合预期。
- 将片段着色器中
fragColor = vec4(d, 0.0, 0.0, 1.0);替换为fragColor = vec4(theLength, 0.0, 0.0, 1.0);后,输出为均匀红色正方形,符合预期。
疑问
为何两种长度计算方式会产生不同结果?在SDL中使用GLSL着色器,是否SDL后台自动实现了几何着色器?
两种情况结果不同的核心原因是顶点插值与坐标空间的差异,和SDL无关——SDL不会在后台添加几何着色器。
具体分析
theLength的计算逻辑
顶点着色器中,theLength是在每个顶点的NDC(标准化设备坐标)上计算的长度。你的顶点坐标是NDC的四个角点:(-1,-1)、(1,-1)、(-1,1)、(1,1),每个顶点的length(vertPos)结果都是√2≈1.414。
这个值传递到片段着色器时,会在三角形内部做线性插值(因gl_Position.w=1.0,透视校正插值退化为线性插值)。所有片段的theLength最终都会被插值为1.414,而颜色分量会被自动 clamp 到[0,1]区间,所以最终显示为纯红色。d = length(fragPos)的计算逻辑
片段着色器中的fragPos是顶点着色器输出的vertPos经过插值后的NDC坐标。当你的窗口宽高比不是1:1时,NDC坐标的x轴和y轴对应窗口的像素范围不同:比如窗口宽是高的2倍,NDC的x=1对应窗口最右侧,y=1对应窗口最上方,但视觉上x方向的跨度是y方向的2倍。
此时length(fragPos)计算的是片段NDC坐标到原点的距离,这个距离会随片段位置变化:中心区域距离近(颜色偏暗),边缘区域距离远(颜色偏红),再加上窗口宽高比的拉伸,最终呈现出横向拉伸的渐变效果。
总结
你预期的均匀红色,是默认片段颜色一致,但d = length(fragPos)会因窗口宽高比和片段位置产生变化;而theLength因所有顶点的计算值相同,插值后片段值统一,所以颜色均匀。
内容的提问来源于stack exchange,提问作者gorogoro

