GLSL Geometry shader to replace glLineWidth

喜欢而已 提交于 2020-04-14 04:11:48

问题


I'm trying to write a geometry shader to replace glLineWidth behavior. I want to draw lines with a customizable width (doing this with a uniform suffices for now). The lines should always have the same thickness, regardless of the camera projection or distance to where the lines are.

Based on a lot of googling, I've come up with the following geometry shader:

#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();
}

For completeness, here is the vertex shader:

#version 330

in vec4 a_position;

void main() {
    gl_Position = a_position;
}

... and my fragment shader:

#version 330

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

void main() {
    fragColor = u_color;
}

I can't get the math to work in all situations. With an orthogonal camera, the above works fine:

But with a perspective camera, the problem is that the line is not a fixed size. It gets bigger and smaller relative to how far away the object is.

I expected the line the be the same size using a perspective camera as well. What am I doing wrong?


回答1:


I managed to fix it by taking into account the viewport size, and scaling my r using that. I do not know if this is the most efficient way to solve this problem (I am by no means a math head), but it does work.

In the code below, I now do all the work in screen space rather than camera/view space, and I use the u_viewportInvSize vec2 (which is 1/viewportSize) to scale my desired radius!

#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 * p1.w), 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) {
        gl_Position = coords[i];
        EmitVertex();
    }
    EndPrimitive();
}



回答2:


I'm not an expert, but having done this before, I'll offer my insight.

I'm assuming your gl_Positions are straight from the vertex shader which was calculated using a projection matrix. That means that their w component is the "clip-space position" of the point; this is what is used by the pipeline to give the projection its effect (further away things are smaller). So it needs to be taken into account.

Fortunately, the only thing you need to do is multiply your offset with it.

coords[0] = p1 + offset1 * p1.w;
coords[1] = p1 - offset1 * p1.w;
coords[2] = p2 + offset2 * p2.w;
coords[3] = p2 - offset2 * p2.w;

This should give you the effect you want.




回答3:


Geometry shader aren't known for being speedy. using a geometry shader is a performance killer, so it should only be suggested if all other options are off the table. A possible solution without using a geometry shader is presented in the answer to OpenGL Line Width.


Anyway if you want to use a geometry shader, then transform the vertex coordinate by the model view projection matrix xin the vertex shader:

#version 330

in vec4 a_position;
uniform mat4  u_modelviewprojection_matrix;

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

Compute the normalized device coordinate in the geometry shader, by Perspective divide:

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;

The normalized device space is a cube with the left, bottom, near of (-1, -1, -1) and right, top, back of (1, 1, 1).

Compute the vector between the points the of the lines. Scale by the viewport size to take into account the aspect ratio of the viewport. Finally get the Unit vector to the line:

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

Compute the hal thickness offset vector normal to the line and transform it to normalized device space. This is done by scaling by the inverse aspect ratio and multiplying by 2:

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

Add the offset vector to the normalized device coordinates and "undo" the perspective divide:

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();

That can be further optimized and leads to the following Geometry shader

#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 - p1.xy) * 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();
}

Fragment shader

#version 330

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

void main()
{
    fragColor = u_color;
}


来源:https://stackoverflow.com/questions/54686818/glsl-geometry-shader-to-replace-gllinewidth

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!