In this post I am going to be talking about non affine transformations using matrices. First I am going to talk about how to do transformations around an arbitrary axis and origin, then discuss non affine transformations and lastly talk about using vertex attributes to combine several transformations together.
In case you haven’t read the first part of this post the Matrices for Tech Artists, a Cheat Sheet, go and read that now, because you would need it to understand what I am saying here.
As usual, you will find the code used here on my Github: https://github.com/IRCSS/Non-Affine-Transformation-Matrices
Different Axis or Origin
In the previous post, I mentioned a series of affine transformations. The linear ones were scaling, rotation and skewing, and the affine one was those plus translation in homogeneous coordinate systems. That was fun. The issue was, everything was with respect to the origin. What if you wanted to rotate the mesh not around the origin, but around an arbitrary point or axis?
The way to do that, is to transform the space first so that the point of interest is on the origin, and the axis of interest aligns with one of the three primary axis. If you have read the previous post, this might already sound familiar. We need to transform every vertex of the mesh to a space which has those properties. If we could construct a matrix, which has such a column space with those properties, we can first transform our vertices in this space using multiplication, do whatever transformations we want, and then transform the vertex back to world space for correct viewing using the inverse of the matrix we constructed.
Let’s go though this step by step. First, just the origin.
Consider the following scenario. In the scene below, the origin is marked using the xyz arrows. We would like to rotate the mesh, but not around the origin, but around the head of the statue. Let’s see what happens first if we simply rotate around the origin. I am not going to go over the rotation matrix since I covered it last time, this is the rotation around x axis. Code
This is not what we want. We actually want to rotate it around the head. The simplest solution is this: let’s say the head is 20 units above the origin. What if we moved every vertex in the mesh by 20 units down, rotated the mesh with our matrix, then moved it back up again by 20. By the time it gets to rotation, our head will be sitting on the origin, so rotating with our matrix will be rotating not just around the origin but also the head. Since we don’t really want to move the mesh, we move the mesh back up with 20 units again, to compensate for the transformation we did before rotation.
This method is nice, but remember as I said in the previous post, expressing this translation as a matrix has the advantage that we can combine it with other transformations in one matrix. So let’s build a matrix that does what I stated above.
What we need is translation from the previous post. The code.
Nice, now we are rotating around the head. This is important to understand, so look over the figure 1 until you do. The idea is really simple. You can look at it as either, you move the mesh down so that the point we want to rotate around sits on the origin, rotate the mesh, and move the mesh back up OR you move the space/world so that the origin lays on the head, rotate, and move the space back. Again numerically is all the same, it is all a question of frame of reference for us.
Here comes the concept of the matrix’s Inverse. I named 2 matrices there T2Head (Translation to head) and TfromHead (Translation from head). Each matrix is the inverse of the the other. Meaning whatever the matrix T2Head does to your vector the TfromHead does the inverse. They cancel each other out. In this case it is easy to understand the inverse. You moved 20 units down, you move 20 units up. When you combine bunch of matrices together, it won’t be as easy.
In the scene below, I added a fourth axis colored magenta. This one is the axis we actually wish to rotate around. This axis can of course be represented in a vector form. Let’s call it c1, c1 has the value (-1, 0, -1).
Let’s keep everything else the same. We still want to rotate around the head. But this time not around the global x axis, but around c1, we have to follow the same steps we did for rotation around an arbitrary point.
The rotation matrix we are using, rotates around the x axis. Let’s assume we find a matrix called R2Axis. This matrix rotates the space so, that the x axis aligns with the vector (-1,0,-1). You can also look at it in terms of the column space spanned by the columns of R2Axis matrix. The space is such where the first of the three basis (the first column) is the vector (-1,0,-1). After multiplying each vertex of our mesh with R2Axis, we apply our normal rotation around x. With this setup we are rotating around the x axis as usual, but also around vector (-1, 0, -1), since they are the same. We just have to apply the inverse of R2Axis after we are done with our rotation around x axis, to compensate for the between steps we did with R2Axis. Code.
Let’s go over figure -2- calculations. As stated, we need to create a matrix which has (-1,0,-1) as the first basis for its column space. So the first column. How do we go around constructing it? This space needs to have some properties. The new space should be a rotated space compared to the identity matrix, but just that. This means, there shouldn’t be any skewing, or scaling. From this property alone, we can easily construct our matrix. If you remember from the previous post, if the columns of the matrix are normalized, there could not be a scaling involved, since the basis has a length of one, vectors multiplied by them wouldn’t be scaled. So having normalized basis means we are left with, skewing, rotation, and mirroring/reflection. What if the bases were orthogonal to each other on top of that? Then the bases could not contribute to eachother in the calculation, so no skewing.
So we need to construct an orthogonal matrix. There are many different ways to do this. Typically, your maths library of choice already has functions for this. If you want to calculate this yourself, and you already have 3 non orthogonal vectors that show the general directions the basis should be at, you can use linear algebra to construct orthonormal bases from them. I won’t go over the method in detail since I won’t be using it here, but the general idea is that one axis at the time, you project the axis on top of each other, and subtract away from them the overlapping parts (same calculations as finding the error vector from a projection).
I don’t care about the other two axis though. I don’t care if they are pointing up or down. So I just need to find any other two vectors that are orthogonal to my axis of rotation and each other. The first vector I deduce simply by looking at the vector (-1, 0., -1). I need a vector which has a dot product of 0 with this vector. Since I have a 0 as one of the components, y in this case, finding this vector is very easy. Take any number for y, and leave x and z as zero. It doesn’t matter which number you take for y, the dot product will always be zero as far as x and z are zero. Now we need to address the last step to construct the matrix. How can we be sure that the matrix we construct is rotating and not mirroring/reflecting? The answer is simple, you should not switch between right and left hand coordinate systems through the transformation. Mathematically, the determinant of your matrix shouldn’t switch signs if multiplied with our matrix. I find it easier to just visualize it though. If we are flipping the x axis from (1,0,0) to (-1,0,-1) then we have to flip the y to (0, -1, 0) to ensure we remain in a right handed coordinate system.
So having two of the three axis, we simply take the cross product of the two and get our third. Then we normalize each axis individually, and we are done, we have our matrix. What about the inverse? This matrix is an orthogonal one, its inverse is always its transpose. Why? That is beyond this article, if you don’t want to spend a hundred hours learning linear algebra, you need to take some stuff at face value. Again your library of choice will give you inverses. If you want to implement it yourself for three by three matrices, using the cofactors or the big formula is quite fast, for bigger matrices elimination is the faster way to go.
You didn’t need to do all this work. Above we almost finished implementing the model matrix for a local space we defined ourselves. The only thing we didn’t do was scale. Good news is you now know how to create the transform.localToWorld and transform.WorldtoLocal matrix in Untiy. In future, you can simply put an empty gameobject where you want your rotation center to be, and rotate it so that the axis are how your want them to be, and pass on the localToWorld and WorldToLocal matrix of the empty gameobejct to the shader to imitate everything we did above.
Non Affine Transformations
Finally more juicy stuff. A non affine transformations is one where the parallel lines in the space are not conserved after the transformations (like perspective projections) or the mid points between lines are not conserved (for example non linear scaling along an axis).
Let’s construct a very simple non affine transformation. We are going to rotate around x as we have so far, but this time, we are going to increase the amount of the rotation, based on the y component of the vertex position. This gives you a bending effect. Code.
You can add several different rotations together, lerp between different matrices, apply overlapping sinus waves etc.. I am not going to go over this in more detail since I already covered the basis you need to experiment with this yourself. Here are some stuff you can do with this. Have a look at the entire thread, there are some random transformations and effects.
Another ones are the inception effects I did a while back, you can see a bunch of them here: https://medium.com/realities-io/making-the-inception-effect-in-unity-3d-with-few-lines-of-code-fb9667d4786f
More of that you can also find here: https://tempv02-final.itch.io/mausebunker-bend
Vertex Attribute and Transformation Matrices
So let’s go back to the statue of the wandering man. Let’s say we are happy with how the transformation looks, but don’t want the staff to bend with the body. The issue at the moment is that our transformation is a function of the y component of the vertex position. This will apply a global effect everywhere on the mesh.
This is where vertex attributes come in play. We could pre calculate for example the geo distance between our center of rotation and each vertex and use that instead for the rotation amount. Or we could pre bake pretty much anything we want and pass it as a vertex object uniform. I tend to avoid doing pre calculations like that which deal with a large number of objects in Unity’s c#. The cache misses make it painfully slow, for those things I recommend writing a simple c++ program.
Just to demonstrate what this can do, I included polybrush from the asset store in the project. Polybrush let’s you paint the vertices with a color in editor. You can read this data in the vertex shader, and for example lerp between different transformation matrices.
We will use the lack of the color red in the channel to turn of the bending for the ground and the staff. Plus some other crazy random stuff added on top. Code here.
That’s about it. I covered the majority of the stuff required to start doing your own crazy stuff. Hope you enjoyed reading and you can follow me on via my Twitter IRCSS.