GLSL几何着色器来替代glLineWidth

6
我正在尝试编写一个几何着色器来替代 glLineWidth 的行为。我想要用可定制的宽度绘制线条(现在使用一个统一变量就足够了)。这些线条应该始终具有相同的厚度,无论相机投影或线条所在位置的距离如何。

基于大量的搜索,我得出了以下几何着色器:

#version 330

layout (lines) in;
layout (triangle_strip, max_vertices = 4) out;

uniform mat4    u_model_matrix;
uniform mat4    u_view_matrix;
uniform mat4    u_projection_matrix;
uniform float   u_thickness = 4; // just a test default

void main()
{
    float r = u_thickness / 2;

    mat4 mv = u_view_matrix * u_model_matrix;
    vec4 p1 = mv * gl_in[0].gl_Position;
    vec4 p2 = mv * gl_in[1].gl_Position;

    vec2 dir = normalize(p2.xy - p1.xy);
    vec2 normal = vec2(dir.y, -dir.x);

    vec4 offset1, offset2;
    offset1 = vec4(normal * r, 0, 0);
    offset2 = vec4(normal * r, 0, 0);

    vec4 coords[4];
    coords[0] = p1 + offset1;
    coords[1] = p1 - offset1;
    coords[2] = p2 + offset2;
    coords[3] = p2 - offset2;

    for (int i = 0; i < 4; ++i) {
        coords[i] = u_projection_matrix * coords[i];
        gl_Position = coords[i];
        EmitVertex();
    }
    EndPrimitive();
}

为了完整起见,这是顶点着色器:

#version 330

in vec4 a_position;

void main() {
    gl_Position = a_position;
}

...和我的片段着色器:

#version 330

uniform vec4 u_color = vec4(1, 0, 1, 1);
out vec4 fragColor;

void main() {
    fragColor = u_color;
}

我无法在所有情况下使数学计算正确。使用正交相机,上述内容可以正常工作:

Orthogonal camera, close by

但是使用透视相机的问题在于,线条并不是固定大小的。它会随着物体离相机的距离而变得更大或更小。

Perspective camera, close by

Perspective camera, far away

我原以为使用透视相机后,线条的大小会保持不变。但是我错了吗?

3个回答

3
我通过考虑视口大小并使用此进行缩放来修复它。我不知道这是否是解决此问题的最有效方法(我绝对不是数学高手),但确实有效。
在下面的代码中,我现在在屏幕空间而非相机/视图空间中完成所有工作,并使用u_viewportInvSize vec2(即1/viewportSize)来缩放我的期望半径!
#version 330

layout (lines) in;                              // now we can access 2 vertices
layout (triangle_strip, max_vertices = 4) out;  // always (for now) producing 2 triangles (so 4 vertices)

uniform vec2    u_viewportInvSize;
uniform mat4    u_modelviewprojection_matrix;
uniform float   u_thickness = 4;

void main()
{
    float r = u_thickness;

    vec4 p1 = u_modelviewprojection_matrix * gl_in[0].gl_Position;
    vec4 p2 = u_modelviewprojection_matrix * gl_in[1].gl_Position;

    vec2 dir = normalize(p2.xy - p1.xy);
    vec2 normal = vec2(dir.y, -dir.x);

    vec4 offset1, offset2;
    offset1 = vec4(normal * u_viewportInvSize * (r * p1.w), 0, 0);
    offset2 = vec4(normal * u_viewportInvSize * (r * p2.w), 0, 0); // changing this to p2 fixes some of the issues

    vec4 coords[4];
    coords[0] = p1 + offset1;
    coords[1] = p1 - offset1;
    coords[2] = p2 + offset2;
    coords[3] = p2 - offset2;

    for (int i = 0; i < 4; ++i) {
        gl_Position = coords[i];
        EmitVertex();
    }
    EndPrimitive();
}

2

几何着色器并不以速度著称。使用几何着色器会降低性能,因此只有在没有其他选择时才建议使用。

如果不想使用几何着色器,可以参考OpenGL Line Width中的答案提供的解决方案。


但是,如果您想使用几何着色器,请在顶点着色器中使用模型视图投影矩阵来转换顶点坐标:

#version 330

in vec4 a_position;
uniform mat4  u_modelviewprojection_matrix;

void main()
{
    gl_Position = u_modelviewprojection_matrix * a_position;
}

通过透视除法在几何着色器中计算规范化设备坐标:

vec3 ndc_1 = gl_in[0].gl_Position.xyz / gl_in[0].gl_Position.w;
vec3 ndc_2 = gl_in[1].gl_Position.xyz / gl_in[1].gl_Position.w;

标准化设备空间是一个立方体,其左侧、底部、近处为(-1, -1, -1),右侧、顶部、远处为(1, 1, 1)。

计算线段上两点之间的向量。通过视口大小来缩放向量以考虑视口的宽高比。 最后获得线段的单位向量

vec2 dir    = normalize((ndc_2.xy - ndc_1.xy) * u_viewportSize);
vec2 normal = vec2(-dir.y, dir.x);

计算沿着线法向的半高度偏移向量,并将其转换为规范化设备空间。这是通过缩放逆纵横比并乘以2来完成的:

vec3 offset = vec3(normal * u_thickness * 0.5 / u_viewportSize * 2.0, 0.0);

将偏移向量添加到标准化设备坐标,并“撤消”透视除法:
gl_Position = vec4((ndc_1 + offset) * gl_in[0].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex(); 
gl_Position = vec4((ndc_1 - offset) * gl_in[0].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex();
gl_Position = vec4((ndc_2 + offset) * gl_in[1].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex();
gl_Position = vec4((ndc_2 - offset) * gl_in[1].gl_Position.w, gl_in[0].gl_Position.w);
EmitVertex();

这可以进一步优化,并导致以下几何着色器

#version 330

layout (lines) in;                              // now we can access 2 vertices
layout (triangle_strip, max_vertices = 4) out;  // always (for now) producing 2 triangles (so 4 vertices)

uniform vec2  u_viewportSize;
uniform float u_thickness = 4;

void main()
{
    vec4 p1 = gl_in[0].gl_Position;
    vec4 p2 = gl_in[1].gl_Position;

    vec2 dir    = normalize((p2.xy/p2.w - p1.xy/p1.w) * u_viewportSize);
    vec2 offset = vec2(-dir.y, dir.x) * u_thickness / 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();
}

片元着色器

#version 330

out vec4 fragColor;
uniform vec4 u_color = vec4(1, 0, 1, 1);

void main()
{
    fragColor = u_color;
}

完整示例:https://github.com/Rabbid76/graphics-snippets/blob/master/example/cpp/opengl/example_shader_geometry_1_line.cpp

这是一个关于IT技术的示例,上面的链接是一个完整的代码示例。

2
我虽不是专家,但我曾经做过这件事,现在我来分享我的见解。
我假设你的gl_Position是直接从顶点着色器中计算出来的,使用了一个投影矩阵。这意味着它们的w分量是该点的“裁剪空间位置”,这是管道用来产生投影效果的(距离远的物体较小)。因此,需要考虑它。
幸运的是,你只需要将你的偏移量与它相乘即可。
coords[0] = p1 + offset1 * p1.w;
coords[1] = p1 - offset1 * p1.w;
coords[2] = p2 + offset2 * p2.w;
coords[3] = p2 - offset2 * p2.w;

这应该能产生你想要的效果:最初的回答。

我已经尝试过了,没有任何区别。请注意,我是在相机/视图空间中进行工作的(我首先乘以模型/视图矩阵,进行数学计算,然后再乘以投影矩阵)。在屏幕空间中进行操作对我来说效果更差。 - foddex
我明白了,我确实错过了那个。不过,我认为你必须在屏幕空间内完成它,因为所需的效果是相对于屏幕的,对吧?无论如何,你都必须想出一些方法使进一步的偏移更大,以使它们看起来相同大小。使用投影位置或视图空间z坐标取决于你。 - kmdreko
谢谢你的提示!我现在意识到我需要缩小我的半径,使其不是以像素为单位,而是相对于视口大小。手动计算并临时修改着色器实际上让它工作得更好!(当然还要乘以w!) - foddex
很高兴能帮上忙! - kmdreko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接