Cross Product
You learned earlier how to define normals for simple shapes like cubes and spheres. How do we compute normals for more complex models like dinosaurs, couches, and bicycles? The easiest option is to let the 3D modeling program compute the normals and store them in the file. Then the renderer can read them in and upload them like other vertex properties. If we have a 3D model that doesn't have its normals exported, we can compute them ourselves. A normal is, after all, nothing more than a description of which way a triangle faces. We can derive that information from the vertex positions with some help from a vector operation.
You remember the dot product. It measures the alignment between two vectors by combining the vectors' associated components together:
The dot product is a scalar value that is maximized when \(p_x\) is near \(q_x\), \(p_y\) is near \(q_y\), and \(p_z\) is near \(q_z\). In other words, the dot product is maximized when the two vectors point in the same direction.
Many geometric challenges in computer graphics, including normal generation, can be solved with a different but related vector operation called the cross product. Stick out two of your fingers. The cross product of your two fingers is a third vector that is perpendicular to both, just like a normal.
Various symbols are used to represent the cross product. The standard multiplication symbol is commonly used. Be careful, though, because this symbol is used to mean several different things:
Unlike the dot product, which measures the similarity within each dimension, the cross product measures the dissimilarity between the components across two different dimensions. There are three ways that the x-, y-, and z-dimensions may be paired: x and y, x and z, and y and z. The cross product is therefore a 3-vector, comprised of three dissimilarity measures. Its three components each tell their own story about the two vectors:
- The x-coordinate measures the dissimilarity between the y- and z-coordinates.
- The y-coordinate measures the dissimilarity between the z- and x-coordinates.
- The z-coordinate measures the dissimilarity between the x- and y-coordinates.
The more that the y- and z-coordinates of the two vectors are dissimilar, the bigger the x-coordinate of the cross product will be. Prove this to yourself by dragging around on the red and green vectors in this renderer:
The blue vector is the cross product of the other red and green vectors. What happens to the magnitude of the cross product as the vectors become similar?
Recall that the dot product of two normalized vectors is the cosine of the angle between them. The cross product's magnitude happens to be the sine of the angle. When the angle is 0, the magnitude is 0. When the angle is 90 degrees, the magnitude is 1.
If all we care about is how to compute the product, here is our mathematical definition:
You should take this definition and add a cross
method to your Vector3
class. It accepts a second vector as a parameter and returns the cross product vector.
Explore how the two vectors combine to form the cross product by entering some values for the x-, y-, and z-coordinates of \(\mathbf{p}\) and \(\mathbf{q}\):
Try entering values that such that the components on two of the dimensions of the two operand vectors are the same non-zero values and the components of the third dimension are different. The products of the two similar dimensions will cancel, leaving the third dimension's component in the cross product at 0.
Mnemonic
You don't need to memorize the cross product formula, as you're probably just going to add a cross
method to your Vector3
class. However, consider the following mnemonic if you want remember how to construct it.
Start with a table charting the interplay between the components of the two vectors:
\(q_x\) | \(q_y\) | \(q_z\) | |
---|---|---|---|
\(p_x\) | ? | ? | ? |
\(p_y\) | ? | ? | ? |
\(p_z\) | ? | ? | ? |
Since the cross product is concerned with interactions across dimensions, zero out the interactions within the same dimension:
\(q_x\) | \(q_y\) | \(q_z\) | |
---|---|---|---|
\(p_x\) | 0 | ? | ? |
\(p_y\) | ? | 0 | ? |
\(p_z\) | ? | ? | 0 |
The zeroed interactions are terms included in the dot product, not the cross product.
Starting at the 0 in each row, move rightward, counting to 2 and filling each cell with the current count. Wrap back to the start of the row as needed. The completed table looks like this:
\(q_x\) | \(q_y\) | \(q_z\) | |
---|---|---|---|
\(p_x\) | 0 | 1 | 2 |
\(p_y\) | 2 | 0 | 1 |
\(p_z\) | 1 | 2 | 0 |
The table serves as your guide for computing the components of the cross product. When computing the x-component, we ignore the x-row and x-column, leaving just the bottom-right of the table:
\(q_x\) | \(q_y\) | \(q_z\) | |
---|---|---|---|
\(p_x\) | 0 | 1 | 2 |
\(p_y\) | 2 | 0 | 1 |
\(p_z\) | 1 | 2 | 0 |
The cell marked 1 represents the product \(p_y \times q_z\). The cell marked 2 represents the product \(p_z \times q_y\). The x-component of the cross product \(\mathbf{c}\) is cell 1 minus cell 2:
The y-component is computed by first ignoring the y-row and y-column:
\(q_x\) | \(q_y\) | \(q_z\) | |
---|---|---|---|
\(p_x\) | 0 | 1 | 2 |
\(p_y\) | 2 | 0 | 1 |
\(p_z\) | 1 | 2 | 0 |
And then again subtracting cell 2 from cell 1:
The z-component is computed by first ignoring the z-row and z-column:
\(q_x\) | \(q_y\) | \(q_z\) | |
---|---|---|---|
\(p_x\) | 0 | 1 | 2 |
\(p_y\) | 2 | 0 | 1 |
\(p_z\) | 1 | 2 | 0 |
And then again subtracting cell 2 from cell 1: