3
$\begingroup$

I am learning GLSL and want to raytrace a sphere. I am rendering rectangles which sit between the camera and sphere, and do a ray-sphere intersection test in the fragment shader. It almost works:

enter image description here

As you can see, the rectangles are a little too small. Here is how I calculated them.

enter image description here

Point $C$ is the centre of the rectangle, a distance $R$ from the radius of the sphere. Taking basis vectors $i,j$ I can construct the corners as $C+(\pm i, \pm j)$

Point $C$ is a fraction $1-\frac{R}{||V||}$ along the vector $V$ from the camera.

I can direct $i$ and $j$ given the vector $V$ and the "up" vector.

How long should $i$ and $j$ be? By similar triangles, they should have length $R\times (1-\frac{R}{||V||})$.

Here is the code (I'm new to GLSL so please ignore bad habits, for now I just want to get the geometry right).

A vertex array containing four $(\pm 1, \pm 1, 0)$ vectors forms a triangle strip.

The vertex shader:

#version 300 es
in  vec2 position;
out vec3 vs_fs_position;

uniform vec3 i;
uniform vec3 j;
uniform vec3 centre;

uniform mat4 pvm; // projection * view * model

void main ()
{
    vec3 p = centre + position.x * i + position.y * j;

    vs_fs_position = p; // world space coordinate sent to fragment

    gl_Position = pvm * vec4 (p, 1); // to screen space
}

The fragment shader

#version 300 es
precision mediump float;
out mediump vec4 out_colour;
in  mediump vec3 vs_fs_position;

uniform mediump vec3 camera_position;
uniform mediump vec3 camera_to_sphere;
uniform float radius;

void main ()
{
    vec3 direction = vs_fs_position - camera_position;

    float A = dot (direction, direction);
    float B = 2.0 * dot (direction, camera_to_sphere);
    float C = dot (camera_to_sphere, camera_to_sphere)
            - radius * radius;

    float det = B * B - 4.0 * A * C;

    if (det < 0.0)
        out_colour = vec4 (0.0, 0.0, 0.0, 1.0);
    else
        out_colour = vec4 (0.5, 0.5, 0.5, 1.0);

}

Some C++ to draw it

shader .camera_position = m_camera_position;

const float EPSILON = 0.0;

for (auto & s : SPHERES)
{
    auto R = s .position - m_camera_position;

    float ratio = 1.0 - (s .radius + EPSILON) / length (R);

    float width = ratio * s .radius;

    auto & up = m_camera_up;

    const float W = width * (1 + EPSILON);

    glm :: vec3 i = normalize (cross (up, R)) * W;
    glm :: vec3 j = normalize (cross (R,  i)) * W;

    shader .i = i;
    shader .j = j;

    shader .centre = s .position;
    shader .radius = s .radius;

    shader .camera_to_sphere = s .position - m_camera_position;

    glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
}

If it runs with EPSILON=0.5, I get this output.

enter image description here

Theoretically, I should be able to get a perfect fit with EPSILON=0, that way I can be confident it will work at all scales/positions.

What's wrong with my calculation of the width parameter?

$\endgroup$
3
$\begingroup$

How long should $i$ and $j$ be? By similar triangles, they should have length $R\times (1-\frac{R}{\lVert V\rVert})$.

I believe this is where you went wrong. If you draw the 2D version of this on a sheet of paper, it will be obvious that the edge of your triangle which touches the side of your square goes through the sphere. More so as the camera comes closer.

That line needs to be tangent to the sphere. So your large similar triangle will have its right angle at the contact point with the sphere, not at the sphere's center as you did. If my trigonometry is not too rusty, this gives

$\frac{R}{\sqrt{\lVert V\rVert^2-R^2}} \times \left(\lVert V\rVert-R\right)$

$\endgroup$
  • $\begingroup$ Thanks. I asked a purer version of this question at math stackexchange and got an equivalent answer which gives another hint at how to solve this. $\endgroup$ – spraff Feb 19 '18 at 22:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.