2
votes

I need to calculate projection of point on specific line segment in shader (OpenGL ES 2).
Here is how I test the algorithm:
I draw simple triangle with points A(0, 0.5), B(1, -0.5), C(-1, -0.5).
I calculate projection of every point on line segment AC.
I draw points with a projection in the middle of a line segment AC in blue. And the remaining points in green.
I expect to get a green triangle with a blue line perpendicular to the side AC. But blue line is not perpendicular to AC.
I check projection formula in code with drawing on canvas and got expected result.
What's my mistake?

Result of shader: enter image description here

Vertex shader:

uniform mat4 matrix;
attribute vec4 position;
varying vec4 vPosition;

void main()
{
    vPosition = matrix * position;
    gl_Position = matrix * position;
}

Fragment shader:

precision mediump float;

varying vec4 vPosition;

void main()
{

    vec2 P = vPosition.xy;
    vec2 A = vec2(0.0, 0.5);
    vec2 B = vec2(-1.0, -0.5);
    vec2 AP = P - A;
    vec2 AB = B - A;
    vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB;

    if(projection.x > -0.51 && projection.x < -0.49 && projection.y > -0.01 && projection.y < 0.01) {
        gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
    } else {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    }
}
1

1 Answers

3
votes

You didn't consider the rectangular aspect of the of the window. When the normalized device coordinates in the range [-1, 1] are mapped to the viewport rectangle (see glViewport) then the triangle gets stretched. This causes that angles of 90 degree are not maintained.

Add a uniform variable to the fragment shader which contains the width and height of the viewport:

uniform vec2 u_resolution;

Calculate the aspect ratio:

float aspect = u_resolution.x / u_resolution.y;

Of course you can initialize the variable float aspect, by a constant value, too.
e.g. float aspect = 16.0/9.0;

Correct the coordinates of the points A, B and P according to the aspect ratio:

vec2 P = vPosition.xy;
vec2 A = vec2(0.0, 0.5);
vec2 B = vec2(-1.0, -0.5);

A.x *= aspect;
B.x *= aspect;
P.x *= aspect;

And consider the aspect ration when evaluating the result projection:

vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB;
projection.x /= aspect;

The final fragment shader may look like this:

precision mediump float;

varying vec4 vPosition;

uniform vec2 u_resolution;

void main()
{
    float aspect = u_resolution.x / u_resolution.y;
    vec2  as = vec2(aspect, 1.0);

    vec2 P = as * vPosition.xy;
    vec2 A = as * vec2(0.0, 0.5);
    vec2 B = as * vec2(-1.0, -0.5);

    vec2 AP = P - A;
    vec2 AB = B - A;

    vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB / as;

    if(projection.x > -0.51 && projection.x < -0.49 && projection.y > -0.01 && projection.y < 0.01) {
        gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
    } else {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    }
}