In this assignment you will write a ray tracer to draw a simple 3D scene. We will use Shadertoy, a website that lets you write shader programs (i.e. programs for doing image-order rendering) in your browser.
Shadertoy
Open Shadertoy and click the “New” link in the top right menu to create a new program. Modify the default code to the following, and click the ▶ button at the bottom-left of the editor to compile and run it.
void mainImage (out vec4 fragColor, in vec2 fragCoord) {
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
// Time varying pixel color
vec3 col = vec3(uv.x, uv.y, 0.5 + 0.5*cos(iTime));
// Output to screen
fragColor = vec4(col,1.0);
}
Looking at this program, we can understand a few things:
The job of the shader program is to take a single pixel’s coordinates (
fragCoord) as input, and give as output the colour (fragColor) to be written to that pixel. Applying this program to all pixels in the image in parallel is done automatically by the GPU.The pixel coordinates range from 0 to the width/height of the image, but it is typical to normalize them to lie between 0 and 1.
The first 3 elements of the output are the red, green and blue components of the output colour, lying between 0 and 1. The last element is the opacity, which should always be set to 1. For example, x increases from left to right, and so does the red component of the displayed colour.
One can also create animations by using the current time (
iTime) to vary the output.
GLSL
Shader programs in Shadertoy are written in GLSL (short for OpenGL Shading Language), a C-like language that runs on the GPU. You should be able to get an idea of how to write GLSL by reading the example code, and using the “?” button at the bottom right of the Shadertoy editor to see a quick reference for the available operations and functions.
Here are some additional notes on the GLSL language that you should know:
There are built-in types not just for integers (
int) and floating-point numbers (float), but also for vectors of different dimensions (vec2,vec3,vec4). The elements of a vector, which arefloats, can be accessed using eitherv[0],v[1], … orv.x,v.y, …Arithmetic operations between two vectors, or between a vector and a scalar, are applied elementwise. The language is very strict about matching argument types: you can only multiply a vector with a
float(e.g.2.0 * v), not anint(e.g.2 * v)!Many standard linear algebra operations like
dot,cross,normetc. are built in.As seen in the example program above, you can declare that a function argument is to be used as input to the function (
in), as output from the function (out), or as both input and output (inout). This way a function can give multiple outputs.
Ray tracing
Shader programs are ideally suited for ray tracing, since we compute the colour of each pixel independently. Some starter code for this is provided here: code_for_assignment.txt. It is a little more complicated than the example code above, but should not be too hard to follow if you have experience with C, C++, or Java. The main function is at the bottom of the file, and we have provided a few helper functions to create the ray for each pixel, and to intersect a ray with a rectangle. However, the program does not do much right now except shoot a ray, fail to hit anything, and return the background colour of the sky. Your task is to make it render a scene with some shapes in it.
Modify the
castRayfunction to perform a ray intersection test with a green rectangle with vertices at (±2, −1, −4±2).Fill in the
raySphereIntersectfunction to check for the intersection of a ray with a sphere. Call it in thecastRayfunction as well, adding a sphere of unit radius centered at (0, 0, −4).Right now, the scene probably looks very flat. Modify the
shadeHitfunction to compute diffuse (Lambertian) shading from a light of unit intensity coming from the direction (1, 1, 1).Add shadow testing to
shadeHit, so that the sphere now casts a shadow on the rectangle. You may assume that the light source is infinitely far away.Optional, easy: Add more objects (in
castRay) and light sources (inshadeHit) to create an interesting scene with multiple spheres and walls.Optional, hard: Change the sphere to a coloured mirror, by tracing a reflected ray from the hit point and multiplying the resulting colour by the sphere’s own colour. Unfortunately GLSL does not allow recursion, so you may have to reorganize the code using a loop.