Wide lines in geometry shader behaves oddly

2019-06-25 13:15发布

问题:

I'm trying to render arbitrary wide lines (in screen space) using a geometry shader. At first it seems all good, but on certain view position the lines are rendered incorrectly:

The image on the left present the correct rendering (three lines on positive X, Y and Z axes, 2 pixel wide).

When the camera moves near the origin (and indeed near the lines), the lines are rendered like the right image. The shader seems straightforward, and I don't understand what's going on my GPU:

--- Vertex Shader

#version 410 core

// Modelview-projection matrix
uniform mat4 ds_ModelViewProjection;
// Vertex position
in vec4 ds_Position;
// Vertex color
in vec4 ds_Color;

// Processed vertex color
out vec4 ds_VertexColor;

void main()
{    
    gl_Position = ds_ModelViewProjection * ds_Position;

    ds_VertexColor = ds_Color;
}

--- Geometry Shader

    #version 410 core

    // Viewport size, in pixels
uniform vec2 ds_Viewport;
// Line width, in pixels
uniform float ds_LineWidth = 2.0;
// Processed vertex color (from VS, in clip space)
in vec4 ds_VertexColor[2];
// Processed primitive vertex color
out vec4 ds_GeoColor;

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

void main()
{
    vec3 ndc0 = gl_in[0].gl_Position.xyz / gl_in[0].gl_Position.w;
    vec3 ndc1 = gl_in[1].gl_Position.xyz / gl_in[1].gl_Position.w;

    vec2 lineScreenForward = normalize(ndc1.xy - ndc0.xy);
    vec2 lineScreenRight = vec2(-lineScreenForward.y, lineScreenForward.x);
    vec2 lineScreenOffset = (vec2(ds_LineWidth) / ds_ViewportSize) * lineScreenRight;

    gl_Position = vec4(ndc0.xy + lineScreenOffset, ndc0.z, 1.0);
    ds_GeoColor = ds_VertexColor[0];
    EmitVertex();

    gl_Position = vec4(ndc0.xy - lineScreenOffset, ndc0.z, 1.0);
    ds_GeoColor = ds_VertexColor[0];
    EmitVertex();

    gl_Position = vec4(ndc1.xy + lineScreenOffset, ndc1.z, 1.0);
    ds_GeoColor = ds_VertexColor[1];
    EmitVertex();

    gl_Position = vec4(ndc1.xy - lineScreenOffset, ndc1.z, 1.0);
    ds_GeoColor = ds_VertexColor[1];
    EmitVertex();

    EndPrimitive();
}

--- Fragment Shader

// Processed primitive vertex color
in vec4 ds_GeoColor;

// The fragment color.
out vec4 ds_FragColor;

void main()
{
        ds_FragColor = ds_GeoColor;
}

回答1:

Your mistake is in this:

gl_Position = vec4(ndc0.xy + lineScreenOffset, ndc0.z, 1.0 /* WRONG */);

To fix it:

vec4 cpos = gl_in[0].gl_Position;
gl_Position = vec4(cpos.xy + lineScreenOffset*cpos.w, cpos.z, cpos.w);

What you did was: lose information about W, and thus detune the HW clipper, downgrading it from a 3D clipper into a 2D one.



回答2:

Today I've found the answer by myself. I don't understand it entirely, but this solved the question.

The problem arise when the line vertices goes beyond the near plane of the projection matrix defined for the scene (in my case, all ending vertices of the three lines). The solution is to manually clip the line vertices within the view frustum (in this way the vertices cannot go beyond the near plane!).

What happens to ndc0 and ndc1 when they are out of the view frustum? Looking at the images, it seems that the XY components have the sign changed (after they are transformed in clip space!): that would mean that the W coordinate is opposite of the normal one, isn't?

Without the geometry shader, the rasterizer would have be taken the responsability to clip those primitives outside the view frustum, but since I've introduced the geometry shader, I need to compute those result by myself. Do anyone can suggest me some link about this matter?



回答3:

I met a similar problem, where I was trying to draw normals of vertices as colored lines. The way I was drawing the normal was that I draw all the vertices as points, and then use the GS to expand each vertex to a line. The GS was straightforward, and I found that there were random incorrect lines running through the entire screen. Then I added this line into the GS (marked by the comment below), the problem is fixed. Seems like that the problem was because one end of the line is within frustum but the other is outside, so I end up with lines running across the entire screen.

// Draw normal of a vertex by expanding a vertex into a line
[maxvertexcount(2)]
void GSExpand2( point PointVertex points[ 1 ], inout LineStream< PointInterpolants > stream )
{
    PointInterpolants v;

    float4 pos0 = mul(float4(points[0].pos, 1), g_viewproj);
    pos0 /= pos0.w;

    float4 pos1 = mul(float4(points[0].pos + points[0].normal * 0.1f, 1), g_viewproj);
    pos1 /= pos1.w;

    // seems like I need to manually clip the lines, otherwise I end up with incorrect lines running across the entire screen
    if ( pos0.z < 0 || pos1.z < 0 || pos0.z > 1 || pos1.z > 1 ) 
        return;

    v.color = float3( 0, 0, 1 );
    v.pos = pos0;
    stream.Append( v );

    v.color = float3( 1, 0, 0 );
    v.pos = pos1;
    stream.Append( v );

    stream.RestartStrip();
}