0

I have a triangle which I pass through a vertex and fragment shader. I want to draw per texel a correction vector u (encoded in r and b channel) that points onto the closest edge (or even one pixel further) in the coordinate space of the vertices (not texture coordinates): u is the distance between the texel (center?) and the edge.

This texture is later used as a look up table for particles to calculate a reaction. I will use u for a position correction and determining the reflection vector. This already works for hardcoded values. The texel depending u is the last piece. The shader must be OpenGL ES friendly.

For each vertex v which I pass to the vertex shader I (if it helps) could also pass the normal of each connected edge and the center of the triangle.

Any idea how this can be done?

enter image description here

Sebastian Barth
  • 4,079
  • 7
  • 40
  • 59
  • 1
    This might help: https://stackoverflow.com/questions/18035719/drawing-a-border-on-a-2d-polygon-with-a-fragment-shader. The basic idea will be similar (getting barycentric coordinates), but I'm not sure how to get the world space distances from them. – BDL Feb 09 '22 at 09:33

1 Answers1

2

A signed distance from a point P to a segment (A,B) is given by:

d = area(A, B, P) / length(B - A)

where

area(A, B, C) = (B.x - A.x)*(C.y - A.y) - (B.y - A.y)*(C.x - A.x)

Thus the distance you're looking for is given by:

dist0 = area(P, B, C) / length(C - B)
dist1 = area(A, P, C) / length(A - C)
dist2 = area(A, B, P) / length(B - A)
minimum distance = min(dist0, dist1, dist2)

The remaining question is how to map this into the GPU pipeline.

One way is to calculate vec3 dist at each vertex of a triangle, then let OpenGL interpolate it across:

dist at A = vec3( area(A, B, C)/length(C - B), 0, 0 )
dist at B = vec3( 0, area(A, B, C)/length(A - C), 0 )
dist at C = vec3( 0, 0, area(A, B, C)/length(B - A) )

Then in the fragment shader you only need to calculate the minimum of the interpolated dist components:

in vec3 dist; // interpolated from the above
minimum distance = min(dist.x, min(dist.y, dist.z))

Alternatively, if you already have the barycentric coordinates available (e.g. through the NV_fragment_shader_barycentric extension), you can piggyback on that. Notice that the barycentric coordinates formula is identical to the above, up to the scaling factors:

bary0 = area(P, B, C) / area(A, B, C)
bary1 = area(A, P, C) / area(A, B, C)
bary2 = area(A, B, P) / area(A, B, C)

Thus, if bary is already available, we only need to scale it by:

dist0 = bary0 * area(A, B, C) / length(C - B)
dist1 = bary1 * area(A, B, C) / length(A - C)
dist2 = bary2 * area(A, B, C) / length(B - A)

Since the scaling factors are constant across the triangle, this can shave off a few FLOPs.


EDIT: I missed that you asked for the actual vector rather than just the distance. The concept remains the same though: you'll need to pass either the normals or the vertices into the shader and based on the computed distances (using either of the above methods), select the normal of the closest edge, scale, and return it. Let's say that n0, n1, n2 are the normals opposite to vertices A, B, C correspondingly; then you'd do the following in the fragment shader:

in vec3 dist; // interpolated from the above
in vec2 n0, n1, n2; // normals
...

vec2 result;
if(dist.x < min(dist.y, dist.z))
    result = dist.x*n0;
else if(dist.y < dist.z)
    result = dist.y*n1;
else
    result = dist.z*n2;
Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • I've implemented it 2h ago after reading the comment of @BDL, right before your commit using just basic tools. It works and is quiet fast. Your answer points out some cool functions I wasn't aware of. To squeeze some FLOPS out I want to try to incorporate your variants. It sounds simpler than what I do. Especially because I calculate the foot of perpendicular from C to AB and you seem (I'm bad at math) do it different. What I don't get is the term barycentric: Is this the line from point to the center of the a line or a point to the closest point on the line (as I calculate)? – Sebastian Barth Feb 10 '22 at 21:49
  • Also the calculation of the perpendicular vector is currently done on the CPU, so that I have to do it only once. To get it updated I just apply the same transformation matrix which I use on the vertex also on the perpendicular vector. This works fine. Ignoring the extensions for a moment: I'm unsure what part of your first variant runs on the CPU and what on the vertex shader. Could you clarify this in your answer? At the moment I have no idea how this could be moved to the vertex shader. Also I assume doing it once plus transformation per frame is faster than doing everything on every frame. – Sebastian Barth Feb 10 '22 at 21:57
  • 1
    @SebastianBarth See [Barycentric coordinates](https://en.wikipedia.org/wiki/Barycentric_coordinate_system). The "distances to each edge" aren't barycentric coordinates, but they are a scaling transformation thereof. ------- Fragment shader only selects the minimum distance. Everything else would be precomputed. Vertex shaders aren't good at that because they don't quite have the information about other vertices. So instead you'd do that on the CPU, compute, or [NV_mesh_shader](https://www.khronos.org/registry/OpenGL/extensions/NV/NV_mesh_shader.txt). – Yakov Galka Feb 11 '22 at 02:44