This post will talk on how to build reflection matrix along a given axis using simple projection equations. This matrix could be useful in a variety of gameplay mechanics, from simple physics used to bounce bullets off the walls to complex ray tracing techniques to bounce rays off the meshes from a light source. The image below gives an overview of what we are trying to achieve.
Variables in play
These are the following variables in play:
b = incident vector
b’ = reflected vector
q = axis along with reflection should happen
p = projection vector of b along q
For input, we would just need the single axis, which is a normalized vector. It should be normalized to make the calculations easier and to not have the resultant matrix modify the magnitude of vectors that operate on it. For those short on time and couldn’t read through the explanation, here are the equations:
Reflection Matrix: I – 2 qqT Reflection vector: b’ = b – (2 qqT) b
There’s also a code snippet at the bottom.
Reflection Matrix generation
Given the incident vector(b), we want to reflect on the plane, which is defined by the normal vector q, to produce the reflected vector(b’). We restrict the axis vector (q) to be normalized so that qTq will be equal to 1 and make calculations simpler. To give a general idea, we need to add/subtract a mystery vector from b to land at vector b’. This vector happens to be the projection(p) of vector b on our given axis vector q. On adding -p to b will put us on the plane perpendicular to q. Adding on more -p to it will give us our reflection vector.
The image below should summarize all the equations in play that are going to be translated to the code.
//NOTE: This code is not optimized.
// Mat3x3 and Vec3 are my custom structures
inline Mat3x3 makeReflectionMatrix(const Vec3& axis)
{
Mat3x3 refMat = Mat3x3::Identity();
Vec3 normAxis = axis;
normAxis.normalize();
for(int i = 0; i < 3; ++i)
{
for(int j = 0; j < 3; ++j)
{
float val = refMat.get(i, j); // get ith row and jth col element
val = val - (2 * normAxis.buffer[i] * normAxis.buffer[j]);
refMat.set(i, j, val);
}
}
return refMat;
}
Conclusion
Hope this helped someone in some capacity. As always, please do visit again ๐
This post is an attempt to elucidate and demystify the “World to Local Matrix”. This is one concept that you would have to face some time or the other during your game development adventures. Typically game engines expose a helper function like “InverseTransformMatrix” or something else to make this easy. BUT! it’s important to understand what’s going on behind the scenes to fully appreciate it. I will not be going into the actual calculation of the inverse matrices in this post, but rather how to visualize them when trying to transform a point from world to local space. A short recap helps to establish a few things:
Basis vectors are a set of vectors that define a coordinate space. (1, 0) and (0, 1) represent X and Y axes for 2D coordinate space.
A point in a coordinate space is a linear combination of basis vectors.
World space has basis vectors that sit in the Identity matrix.
This diagram should demo the idea of points being combination of basis vectors. It’s vital that this piece of information is completely understood. If we want to represent a point, no matter the basis, it’s going to be a linear combination of the basis vectors under consideration.
A New Basis
Now that we have established that a point is a linear combination of basis vectors, let’s setup a new 2D basis that’s rotated from World space basis by 45 degrees and scaled by 4. This is how our new basis would look and it’s matrix representation:
Going by the rules set above that a point is a linear combination of basis vectors, (1, 0) in local space of A’ would be (4, 4) in the world space (space of A). This is basically the X-Axis of our new basis.
The vector (4, 4) is one unit in our new basis along X’
The vector (-4, 4) is one unit in out new basis along Y’
X’ and Y’ and effectively scaled up. Their magnitude is no longer 1.
X’ and Y’ are rotated by 45 degrees from the default basis vectors.
I’ve intentionally chosen slightly weird vectors to act as our new basis. Following properties can be observed in out new basis. Let’s take another example and try to work out the local position. Note: The bracket notation is used to represent column matrices/vectors
Let P = (4, 0) be a point in world-space and we want to represent that in local space of A’.
Let P’ = (a, b) be the point we require in local-space of A’.
Since P’ should be a linear combination of vectors of A’, we can represent it as follows
a (4, 4) + b (-4, 4) = (4, 0)
On solving the above equation and back-substitution, we get a = 0.5 and b = -0.5.
It’s that simple and those are the values that we want. (a, b) represent a point in the local-space of A’, whose world-space counterpart is (4, 0) !! When represented in matrix notation, we get a familiar local-to-world matrix representation. Except in this case, the unknown is on the left hand side and we are solving 2 equations to obtain unknowns.
On moving things around a little bit, we land in a more convenient format for calculating (a, b) as shown in picture above. This is basically multiplying the World-Space point with the inverse matrix. Let’s make sense of the inverse matrix next!
The Inverse
What does it mean to represent a point in Local-Space? The new basis that I chose is quite odd. It’s X-Axis is (4, 4) and Y-Axis is (-4 ,4) when represented in World-Space coordinates. We want some way to represent that (4, 4) as (1, 0) in the new basis and (-4 ,4) as (0, 1). This shouldn’t be magical, but rather should happen with what we have. As it happens, we only have the basis vectors to work with.
By linear combination of basis vectors of A’, we must transform X’ (4, 4) to (1, 0)
By linear combination of basis vectors of A’, we must transform Y'(-4, 4) to (0, 1)
In doing this, we would have our target representation where (4, 4) is transformed to (1, 0) and (-4, 4) is transformed to (0, 1).
Expanding on the example in the paragraph above, I’ve worked a part of it by hand. This is just to demo the idea of the inverse matrix. NOTE that, I’ve done it using a hacky approach as we know both the Local and World spaces of couple of points. There are obviously better approaches to calculating the inverse. This is basically our World to Local Matrix!!
This is a bit mind-bending, but try to think of Inverse matrix representing a different coordinate space with the basis vectors as the ones we’ve obtained after calculation above. Let us represent this new basis by T.
In this new basis, (4, 4) in local space of T is (1, 0) in world space.
In this new basis, (-4, 4) in local space of T is (0, 1) in world space.
This transformation essentially scales and rotates the world-space basis vectors. They are scaled and rotated such that (4, 4) is transformed to (1, 0) and (-4, 4) is transformed to (0, 1). AND, This is the transformation we needed and we reduced it to a simple local-to-world matrix calculation. BUT! in this case, the point being transformed is in world-space rather than in local-space of T!! If you can swallow this, you’ve pretty much mastered inverse! Whack your head around this a little bit. Draw some picture. Make a mess! If you think about it, transforming (4, 4) with T will give (1, 0), which represents a single unit it X-Direction inside our basis A’.
Therefore, inverse is basically a set of basis vectors that have the offset required to transform A to Identity. Once we have the matrix that maintains this offset, any point in world-space when transformed with T(inverse of A) will represent local-space of A’.
I hope this post helped someone and check back later to see what’s new. That’s all for now ๐
Let’s start with something simple. We want to rotate a vector along x-axis at a certain angle. This is quite trivial to compute and you just need knowledge of rudimentary trigonometry to compute the resulting coordinates of vector. Let’s see if we can wrap around head around vector rotation intuition.
Let’s kick it up a notch and try to rotate Y-Axis in positive direction to get a new vector. This too can be easily computed with a few simple calculations. But! since we are considering counter-clockwise rotation to be positive(Left-Handed System), in our example, X’ should receive a negative value. How can we reason about this?
Here’s what’s happening: In out Left-Handed system, positive rotation around Z-Axis happens from +X -> +Y -> -X -> -Y -> +X. As we are rotating our Y-Axis, there’s an additional offset of PI/2 that needs to be added to rotation. This gives: X’ = Cos(90 + A) = Cos(90)Cos(A) – Sin(90)Sin(A) = -Sin(A) Y’ = Sin(90 + A) = Sin(90)Cos(A) + cos(90)Sin(A) = Cos(A) Now we see a negative sign showing up in the computation of X’. So, we ended up having to add an offset that would let us reach Y-Axis from X-Axis and then add the actual rotation that we want Y-Axis to rotate.
Viewing Through Y-Axis
This is great, but here’s another way of thinking about it: Let’s take a peek at how rotation looks from Y-Axis’ perspective. We are still in Left-Handed space and our Z-Axis hasn’t changed. In Y’s view, it is the positive axis that starts the rotation and on rotating counter-clockwise by 90 degrees, it reaches -ve X-Axis. Upon rotating some angle, it arrives at (YL, XL). This is what the Y-Axis sees and these coordinate are what we get in the local space of Y-Axis. Now that we have local coordinates, we just need a transformation matrix to compute the actual position in world space. Refer this for a refresher on local to world computation.
This matrix is quite easy to write as we just swapped X-Axis with Y-Axis and Y-Axis with -ve X-Axis.
0
-1
1
0
Transformation(Column-Major)
As an example, let’s rotate a vector along Y-Axis by 45 degrees. – (YL, XL) are (1/sqrt(2), 1/sqrt(2)) – On applying the transformation matrix to the vector above, we get (-1/sqrt(2), 1/sqrt(2)). These are indeed the world space coordinates that we desire.
In this post, let’s try to understand the geometric intuition behind the Local To World Matrix and try to make sense of matrix multiplication a bit better through a simple illustration. – Why exactly are we multiplying row ‘i’ of matrix A with column ‘j’ of matrix B to get an element ‘Rij‘?
This post is going to talk a lot. So, please bear with it. ๐
Matrix Multiplication: Geometric Intuition
For a refresher on matrix multiplication, I recommend going though this article. In summary, multiplication of matrix can be interpreted as Dot product of row ‘i’ of matrix A with column ‘j’ of matrix B to get the element ‘Rij‘. If you ever wondered why exactly multiplications are done this way and what’s the geometric interpretation behind it, this article is for you! ๐
Here are the assumptions I’ve defined: – All the matrices and vectors in this article are represented using Column-Major notation. – Our basis vectors for “World-Space” coordinate system are each represented by a column in a square matrix. Let this matrix be represented by ‘W‘ (Default Basis). – Let’s assume a new set of 2D basis vectors(not necessarily orthonormal). If a point is represented relative to this new basis, we are representing the point in local space (New Basis). – A 2D point P(2, 3) as an example.
If we look at a generic multiplication, we can see that the result is a “linear combination” of rows of matrix A with the weights coming from matrix B.
Now, if we replace the matrix A with our Default Basis and perform the multiplication, we end up at 2 units to right and 3 units up. Understanding what’s happening here is vital to making sense of geometric interpretation of matrix multiplication. On expanding multiplication, we get following equations: – (1)(2) + (0)(3) = 2 = P’.X – (0)(2) + (1)(3) = 3 = P’.Y Everything is beautiful in our Default Basis. Our Y-axis vector doesn’t have an X component to add weight to P’.X! Similarly, X-Axis doesn’t have Y-Component to add any weight to P’.Y! As a result, we cleanly end up at the location we expect to.
Now, what if the basis vectors are not so clean(like our ‘New Basis’)? Another intuitive point to note is that this “New Basis” is represented in terms of our Default Basis. If we apply the same matrix multiplication to this new basis and the same point, we get this: (1)(2) + (-1)(3) = -1 = P’.X (1)(2) + (1)(3) = 5 = P’.Y What exactly is happening here? What does this multiplication imply? When doing the same calculation with Default Basis, we started and ended with a same point. Now, with our New Basis, the point (2, 3) represents the location in New Basis’ coordinate system. When the matrix transformation is applied, we are transforming the point from New Basis’ space to Default Basis’ coordinate space.
Local To World Space Intuition:
– To get to point (2, 3) in any coordinate space, we move 2 units right and 3 units up. – We have represented New Basis in terms of Old basis. In terms of default basis, 2 units right in New basis is represented by: (1, 1) * 2 = (2, 2). In terms of default basis, 2 units up is represented by: (-1, 1) * 3 = (-3, 3). Sum of both these vectors gives our World-Space location: (-1, 5) !!! We simplified the issue from confusing matrices to simple vector addition ๐ In the end, matrices are just glorified representation of linear combination of Basis Vectors !!
There’s one last thing to think about. Why is the matrix multiplication a dot product of rows and columns? If we look at mapping points in our default basis, P.X and P.Y can be viewed as projections on X(1,0) and Y(0,1) axes respectively.
Now, what vector should we project on to get the World-Space coordinates of a local position in New Basis? – Looking at matrix multiplication, we have P’.X = (X’.X*P.X) + (Y’.X * P.Y). X components of both X’ and Y’ contributes to the final result P’.X in world space. – A new vector with (X’.X, Y’.X) will be the one that we should project on to get P’.X. – This can be thought of as vector obtained through the addition of (X’.X, 0) and (0, Y’.X). – The final vector obtained implies that: X component of X’ is added only along world’s X axis. X component of Y’ is only added along world’s Y axis. – It’s the same for P’.Y.
A matrix transformation is very simple to learn, but the intuition behind it is quite magical. I apologize if what you read seemed redundant. Hope this makes you a bit wiser than yesterday! ๐