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:
As you can see, the rectangles are a little too small. Here is how I calculated them.
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.
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?