You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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轴)对屏幕空间位置的影响,最终在视角变化后偏移方向出现偏差。

解决方案:在屏幕空间计算偏移

正确的做法是将顶点转换到屏幕空间,计算线的垂直偏移后,再转换回裁剪空间输出。具体步骤:

  1. 将裁剪空间顶点转换为NDC空间(除以w)
  2. 将NDC空间转换为屏幕空间(基于视口尺寸)
  3. 在屏幕空间计算线的方向和垂直偏移向量
  4. 将偏移后的屏幕空间顶点转换回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 10:18:18