OpenGL 4.3几何Shader实现线宽:相机移动后偏移方向异常求助
问题描述
使用OpenGL 4.3实现自定义线宽的几何Shader时,仅第一帧显示正常,移动鼠标改变相机视角后,线的偏移方向出现错误(相机矩阵会在循环中更新到Shader)。相关Shader代码如下:
顶点Shader
#version 330 core layout (location = 0) in vec3 aPos; uniform mat4 projection_view_model; void main() { gl_Position = projection_view_model * vec4(aPos, 1.0); }
片段Shader
#version 330 core out vec4 FragColor; uniform vec4 uniform_fragment_color; void main() { FragColor = uniform_fragment_color; }
原几何Shader
#version 330 core layout (lines) in; layout(triangle_strip, max_vertices = 4) out; uniform float u_thickness ; uniform vec2 u_viewportSize ; in gl_PerVertex { vec4 gl_Position; } gl_in[]; void main() { vec4 p1 = gl_in[0].gl_Position; vec4 p2 = gl_in[1].gl_Position; vec2 dir = normalize((p2.xy - p1.xy) * u_viewportSize); vec2 offset = vec2(-dir.y, dir.x) * u_thickness*100 / u_viewportSize; gl_Position = p1 + vec4(offset.xy * p1.w, 0.0, 0.0); EmitVertex(); gl_Position = p1 - vec4(offset.xy * p1.w, 0.0, 0.0); EmitVertex(); gl_Position = p2 + vec4(offset.xy * p2.w, 0.0, 0.0); EmitVertex(); gl_Position = p2 - vec4(offset.xy * p2.w, 0.0, 0.0); EmitVertex(); EndPrimitive(); }
问题原因
原代码直接在裁剪空间计算偏移,裁剪空间的坐标是透视除法前的结果(xyzw),其中w分量会随相机视角变化而改变,导致偏移的缩放比例错误;同时没有考虑深度(z轴)对屏幕空间位置的影响,最终在视角变化后偏移方向出现偏差。
解决方案:在屏幕空间计算偏移
正确的做法是将顶点转换到屏幕空间,计算线的垂直偏移后,再转换回裁剪空间输出。具体步骤:
- 将裁剪空间顶点转换为NDC空间(除以w)
- 将NDC空间转换为屏幕空间(基于视口尺寸)
- 在屏幕空间计算线的方向和垂直偏移向量
- 将偏移后的屏幕空间顶点转换回NDC,再转换回裁剪空间
修改后的几何Shader
#version 330 core layout (lines) in; layout(triangle_strip, max_vertices = 4) out; uniform float u_thickness; uniform vec2 u_viewportSize; in gl_PerVertex { vec4 gl_Position; } gl_in[]; void main() { // 1. 裁剪空间转NDC(透视除法) vec4 ndc1 = gl_in[0].gl_Position / gl_in[0].gl_Position.w; vec4 ndc2 = gl_in[1].gl_Position / gl_in[1].gl_Position.w; // 2. NDC转屏幕空间([-1,1] -> [0, viewportSize]) vec2 screen1 = ((ndc1.xy + 1.0) / 2.0) * u_viewportSize; vec2 screen2 = ((ndc2.xy + 1.0) / 2.0) * u_viewportSize; // 3. 计算屏幕空间的线方向和垂直偏移 vec2 lineDir = normalize(screen2 - screen1); vec2 perpDir = vec2(-lineDir.y, lineDir.x); // 垂直方向 vec2 offset = perpDir * (u_thickness / 2.0); // 半宽偏移 // 4. 偏移后的屏幕坐标转NDC,再转裁剪空间 vec2 offsetScreen; // 第一个顶点:p1 + offset offsetScreen = screen1 + offset; vec4 ndcOffset1 = vec4((offsetScreen / u_viewportSize) * 2.0 - 1.0, ndc1.z, 1.0); gl_Position = ndcOffset1 * gl_in[0].gl_Position.w; EmitVertex(); // 第二个顶点:p1 - offset offsetScreen = screen1 - offset; ndcOffset1 = vec4((offsetScreen / u_viewportSize) * 2.0 - 1.0, ndc1.z, 1.0); gl_Position = ndcOffset1 * gl_in[0].gl_Position.w; EmitVertex(); // 第三个顶点:p2 + offset offsetScreen = screen2 + offset; vec4 ndcOffset2 = vec4((offsetScreen / u_viewportSize) * 2.0 - 1.0, ndc2.z, 1.0); gl_Position = ndcOffset2 * gl_in[1].gl_Position.w; EmitVertex(); // 第四个顶点:p2 - offset offsetScreen = screen2 - offset; ndcOffset2 = vec4((offsetScreen / u_viewportSize) * 2.0 - 1.0, ndc2.z, 1.0); gl_Position = ndcOffset2 * gl_in[1].gl_Position.w; EmitVertex(); EndPrimitive(); }
额外说明
- 偏移量使用
u_thickness / 2.0,因为线宽是总宽度,需要向两侧各偏移半宽 - 保留原顶点的z值和w值,确保深度测试正常工作
- 视口尺寸
u_viewportSize需要正确传递(例如:窗口的宽高,单位为像素)
内容的提问来源于stack exchange,提问作者PetrasVestartasEPFL
相关产品推荐
相关产品推荐

