Ray Tracing: Rendering a Triangle

News (August, 31): We are working on Scratchapixel 3.0 at the moment (current version of 2). The idea is to make the project open source by storing the content of the website on GitHub as Markdown files. In practice, that means you and the rest of the community will be able to edit the content of the pages if you want to contribute (typos and bug fixes, rewording sentences). You will also be able to contribute by translating pages to different languages if you want to. Then when we publish the site we will translate the Markdown files to HTML. That means new design as well.

That's what we are busy with right now and why there won't be a lot of updates in the weeks to come. More news about SaP 3.0 soon.

We are looking for native Engxish (yes we know there's a typo here) speakers that will be willing to readproof a few lessons. If you are interested please get in touch on Discord, in the #scratchapixel3-0 channel. Also looking for at least one experienced full dev stack dev that would be willing to give us a hand with the next design.

Feel free to send us your requests, suggestions, etc. (on Discord) to help us improve the website.

And you can also donate). Donations go directly back into the development of the project. The more donation we get the more content you will get and the quicker we will be able to deliver it to you.

6 mns read.

Basic Maths Tools

Before we learn about techniques to solve the ray-triangle intersection test, let's first look at some of the tools we will be using to solve this problem. As we said earlier, the triangle's vertices define a plane. Every plane has a normal which can be seen as a line perpendicular to the plane's surface. Considering that we know the coordinates of the triangles vertices (A, B and C), we can compute the vectors AB and AC (which are simply the lines going from A to B and A to C). To do so, we simply subtract B from A and C from A.

To compute the normal of the plane (which, logically, is the same as the triangle's normal since the triangle lies in the plane) we simply compute the cross product of AB and AC. Remember, these two vectors lie in the same plane since they connect the triangle's vertices. The vector resulting from this cross product (denoted N) is the normal we are looking for (figure 1). These operations are summarized in this pseudocode example:

Vec3f v0(-1, -1, 0), v1(1, -1, 0), v2(0, 1, 0); Vec3f A = v1 - v0; // edge 0 Vec3f B = v2 - v0; // edge 1 Vec3f C = cross(A, B); // this is the triangle's normal normalize(C);
$$ \begin{array}{l} A = v1 - v0 = (1--1, -1--1, 0) = (2, 0, 0)\\ B = v2 - v0 = (0--1, 1--1, 0) = (1, 2, 0)\\ C = A \times B = \\ C_x = A_y*B_z - A_z*B_y = 0\\ C_y = A_z*B_x - A_x*B_z = 0\\ C_z = A_x*B_y - A_y*B_x = 2*2 - 0*1 = 4\\ \end{array} $$

If we normalize C we get the vector (0, 0, 1) which as you can see, is parallel to the positive z-axis which is what we expect since the vertices v0, v1 and v2 lie in the xy-plane.

Coordinate System Handedness

The order in which a triangles' vertices are defined affects the orientation of the surface's normal. V0, V1, and V2, created in a counter-clockwise order (CCW), produce a normal denoted N. If the vertices are created in a clockwise order (CW), the normal which we compute from the cross product of the two edges (A=V1-V0 and B=V2-V0) would then point in the opposite direction to the first computed N.

Figure 1: vector A and B can be computed from V1 - V0 and V2 - V0 respectively. The normal of the triangle, or vector C is the cross product of A and B. Note that the order in which the vertices were created determines the direction of the normal (in this example vertices have been created counter-clockwise).

When you create a triangle in Maya (which is using a right-hand coordinate system), if you turn CCW as you create the triangle's vertices, then the x-axis (V0V1) becomes A, the y-axis (V0V2) becomes B and C, the cross product AxB, is parallel to the z-axis (figure 2). But if create vertices in clockwise order, then C will point along the negative z-axis (creating vertices in CW order and computing N from the cross product AxB is the same as computing the cross product BxA when the vertices are created in CCW with A=V1-V0 and B=V2-V0).

Figure 2: creating the vertices in CCW or CW changes the direction of the normal.

Now imagine that you create this triangle in a left-hand coordinate system from three vertices which have exactly the same coordinates than in the right-hand coordinate system example (V0 = (0,0,0), V1=(1,0,0) and V2=(0,1,0)). Following the same steps, we can create the vector A (V1-V0) and B (V2-V0) and compute C (from AxB). The result is also equal to (0,0,1) as with the right-hand coordinate system example (the vertices coordinates are the same, therefore the vectors A and B are the same as well as the result of the cross product AxB). Remember though, that by convention, the z-axis in the left-hand coordinate system is pointing in the direction of the screen (when the x-axis is pointing to the right). Again, everything is coherent with the explanations we have given so far about the handedness of coordinate systems. The result of the cross product as we mentioned in lesson on Geometry, is an anti-commutative operation.

Figure 3: triangles created in left- or right-hand coordinate system don't look the same when rendered from the same camera (pointing down the negative z-axiz).

Now the next step is to render each one of these triangles from a camera which is pointing down the negative z-axis. The result of these tests are shown in figure 4. The two resulting images don't look the same even though the triangle's vertices have exactly the same coordinates, which is not what we want. The choice of a coordinate system's handedness should not change the way geometry is seen from the camera's point of view. A person rendering an image probably wants to see the same image of the same triangle independently from the handedness being used. Even though this is not directly related to the topic of ray-triangle intersection test, this is an important problem which you need to know about. Usually the way we solve it is by mirroring the camera along the negative z-axis (both its position and orientation) as shown in figure 4.

Figure 4: to get the same image of the triangle, we need to mirror the camera about the z-axis and flip the triangle's normal.

This is working however you can see that now the direction of the camera and the direction of the normal have changed compared to what we had before mirroring the camera. In figure 3, the camera's direction and the triangle's normal are pointing in opposite directions. But in figure 4, they are now pointing in the same direction. These two vectors (the triangle's normal and the camera direction) play an important role in shading and it is therefore important that we do not change their direction in relation to each other. Therefore the solution to the problem of the flipped triangle is not only to mirror the camera but also to flip the normal direction. Remember though that this is only necessary if you modelled the triangle in a modeling application using a left-hand coordinate system but render it in a renderer using a right-hand coordinate system (or vice-versa which is the case if you generate geometry in Maya and render this geometry in RenderMan which is using a right- and left-hand coordinate system respectively).

The combination of order and direction in which the vertices are specified is called winding.

We now have all the tools at hand to solve the ray-triangle intersection test using simple geometry.