Reputation: 5510
I wrote a geometry shader to compute the intersection contour of a mesh and a plane, but at the intersection points there are sometimes 1 pixel wide gaps between two lines.
The shader first computes the signed distances of the triangle vertices to the plane. It then checks if two distances have a different sign to determine if there is an intersection with an edge. If so, it emits a vertex at the intersection, which is computed as the weighted average between the edge points.
#version 330
layout(triangles) in;
layout(line_strip, max_vertices = 3) out;
out vec3 vertexPosition;
uniform vec3 planePos;
uniform vec3 planeNormal;
uniform mat4 mvpMatrix;
uniform vec2 screenSize;
void intersection(in vec4 a, in float distA, in vec4 b, in float distB)
{
if (sign(distA) * sign(distB) <= 0.0f && !(sign(distA) == 0 && sign(distB) == 0))
{
float fa = abs(distA);
float fb = abs(distB);
float fab = fa + fb;
vec4 ptIntersection;
// Don't divide by zero.
if (fab < 0.001)
ptIntersection = (a + b) * 0.5;
else
ptIntersection = (fa * b + fb * a) / fab;
gl_Position = mvpMatrix * ptIntersection;
vertexPosition = gl_Position.xyw;
EmitVertex();
}
}
void main()
{
vec4 a = gl_in[0].gl_Position;
vec4 b = gl_in[1].gl_Position;
vec4 c = gl_in[2].gl_Position;
float distA = dot(a.xyz - planePos, planeNormal);
float distB = dot(b.xyz - planePos, planeNormal);
float distC = dot(c.xyz - planePos, planeNormal);
intersection(a, distA, b, distB);
intersection(b, distB, c, distC);
intersection(c, distC, a, distA);
}
I know it's kind of cheap, as I've ignored the special case where all three points lie on the plane. The !(sign(distA) == 0 && sign(distB) == 0)
makes sure that if two points lie on the plane, no vertex will be emitted for that edge. So if all three lie on the plane, there will be no output. But I guess that's not necessarily a bad thing. What I like about it is that there is no crazy branching, and I'd like to keep it that way if possible.
So I'm wondering: Why do I see these gaps? Let's say there are two triangles (a,b,c) and (c,b,d). a and b are above the plane, c and d below. For the first triangle, the shader generates the intersection with (b,c), for the second the intersection with (c,b). Assuming that the addition of two floats is commutative, then the intersection
function is symmetrical wrt the inputs, so the results should be the same. Why do I still see these gaps?
Upvotes: 2
Views: 1383
Reputation: 5510
Here's the final code, might be useful to someone.
#version 330
layout(triangles) in;
layout(line_strip, max_vertices = 3) out;
uniform vec4 plane;
uniform mat4 mvpMatrix;
void emitIntersection(in vec4 a, in float distA, in vec4 b, in float distB)
{
if (sign(distA) * sign(distB) <= 0.0f && !(sign(distA) == 0 && sign(distB) == 0))
{
float fa = abs(distA);
float fb = abs(distB);
gl_Position = mvpMatrix * ((fa * b + fb * a) / (fa + fb));
EmitVertex();
}
}
void main()
{
float dist[3];
for (int i=0; i<3; i++)
dist[i] = dot(gl_in[i].gl_Position, plane);
// Find the smallest i where vertex i is below and vertex i+1 is above the plane.
ivec3 ijk = ivec3(0, 1, 2); // use swizzle to permute the indices
for (int i=0; i < 3 && (dist[ijk.x] > 0 || dist[ijk.y] < 0); ijk=ijk.yzx, i++);
emitIntersection(gl_in[ijk.x].gl_Position, dist[ijk.x], gl_in[ijk.y].gl_Position, dist[ijk.y]);
emitIntersection(gl_in[ijk.y].gl_Position, dist[ijk.y], gl_in[ijk.z].gl_Position, dist[ijk.z]);
emitIntersection(gl_in[ijk.z].gl_Position, dist[ijk.z], gl_in[ijk.x].gl_Position, dist[ijk.x]);
}
Upvotes: 2
Reputation: 698
The answer lies in the specification of the Bresenham line rasterization algorithm that the OpenGL specification gives. Quote from the OpenGL 3.3 Spec, section 3.5.1:
Rasterizing the line segment starting at pa and ending at pb produces those fragments f for which the segment starting at pa and ending on pb intersects Rf, except if pb is contained in Rf [diamond region centered on the fragment].
With the effect that the endpoint is not rasterized at all if two neighboring lines of yours unfortunately run in opposite directions (i.e. end in the same point) and that endpoint is contained in said diamond around the pixel center. Thus you will see a noticeable gap.
As you are already using geometry shaders, you can of course (with a bit of additional calculation) emit triangles to make a "true" wide line.
Upvotes: 3