Using VEX and SOPs to make a LookAt Setup

After using the LookAt Shelf tool in Houdini several times, I wanted to explore and recreate the same method in SOPs, VOPs and VEX. In this post I explored the same setup created both in VOPs as well as VEX and other potential methods in similar situations involving copy stamp or instances.

Example #1: Using a Circle SOP to control a Character LookAt in VOPs and VEX

The basic idea is to create a controller circle or a point that the Eye or Character Input can look at or change it's rotation towards. This would involve calculating the position of the controller and position of the character object. The easiest way to find the position of the Controller Circle would be to use the available "Center" parameter on it. However there may be cases where you receive data from others and want to use that as your LookAt controller, for example an alembic cache from animation that represents a proxy of another character or moving prop to LookAt etc. In that case you can find the centroid using the "centroid" expression.

centroid("../YOUR_REF_CACHE", D_X)


Create an Add SOP and turn on the single point, in the "Point0" axis you can put the above expression. Another way is to append a Pack SOP to the input controller after which you can append an Add SOP and switch on "Delete Geometry But Keep Points". This is a lazy technique I have used often to find the center of any object, however if you use an alembic cache, you can get the center from it by changing the settings.

VOP Method:

In this example I have simply channel referenced the Center Parameter from the Controller Circle to the Add SOP Point0 Parameter. In the case of the Eye or Character, I have used a Pack SOP and an Add SOP method as described before. This gave single points that that were located at the center of each object. In both VOPs as well as VEX we will be using this as the "Second" and "Third" Input.

Input 1: Character Geometry

Input 2: Centroid Single Point of Character Geometry

Input 3: Centroid Single Point of Controller Circle Geometry

Use similar Inputs for the Attribute Wrangle SOP in the VEX Method.

I will breakdown the VOP Network into sections to make it easier to understand the flow.

Using ideas given by several users on

I was able to follow along with some of the ideas and adapt it to my own workflow.

The idea is to use two VOPs output matrix and multiply them. Put down an Align VOP to "Computes a matrix representing the rotation around the axes normal to two vectors by the angle which is between the two vectors." as stated in Houdini Help. I promoted the "To" input parameter for the user to define an axis and used (0, 0, 0) for the "From". In this case (0, 0, -1) worked best for me to Align it to the Controller's rotation, however each case is different so be ware. Another thing to note is something user "tamte" pointed out. He rightly pointed out "well, there is directly Look At VOP which will compute rotation matrix from given direction and up vector be aware of Align VOP and dihedral functions for this as they don't care about up vector and therefore you may be experiencing flipping". I would like to try more tests on this as it can cause problems later.

The second matrix to compute is a LookAt VOP which "Computes a 3×3 rotation matrix to orient the z-axis along the vector (to - from) under the transformation." again this uses a "To" and "From" but also has an additional parameter of "Up" which allows us to input an UpVector. This is optional depending on the situation but by default I input (0, 1, 0) into the parameter. If an up vector is specified, it will determine the roll. For the "From" and "To" we will input the Point Position from the Centroid Single Point of Character Geometry and the Centroid Single Point of Controller Circle Geometry respectively. There are multiple ways to calculate this value. In this case I used an Import Point Attribute VOP as it was the fastest way however you promote two parameters as well and use the Centroid Expression referencing the points or a Point Expression as well. This is entirely a workflow thing and so it does not matter how you attain the value.

Once we have the two matrices we can simply use a Multiply VOP to compute the product of the two. This will work as out transformation matrix for the later however we must first extract out only the rotation value. Right now the matrix contains a set of translation, rotation, scale and shear. The easiest way to do this is to use a Extract Transform VOP. This immediately breaks out the matrix in the correct order and output each vector correctly. You can try to individually break the matrix in separate floats or vectors however it is a waste of time and unnecessary, making the network complicated and messy. Input the "rot" output into a Transformation Matrix VOP "rotate" parameter. For the the position use the original Point Position from the input geometry in Input 1. This will immediately snap the geometry to the correct rotation facing the Controller Curve.

VEX Method:

Let's take at the same methodology but this time in VEX. VEX offers multiple vexpressions than can do the trick and recreate the same structure of nodes in code form. This time instead of VOP use an Attribute Wrangle with the same inputs, however keep in mind that vex starts from Input "0" and not "1". Therefore Input 1 in VOPs will now become "0", Input 2 is "1" and Input 3 is "2". This important when importting attributes using expressions like "point", "prim", "detail" etc which use input numbers as reference location.

VEX Code Text format:

01. vector pos = @P; 02. vector pos2 = point(1, "P", 0); 03. vector pos3 = point(2, "P", 0); 04. vector upvector = chv("upvector"); 05. vector axis = chv("axis"); 06. matrix3 matx; 07. matrix3 matx2; 08. vector scale = {1, 1, 1}; 09. vector pivot = {0, 0, 0};


11. //align 12. matx = dihedral( { 0, 0, 1 }, axis );

13. //lookat function 14. matx2 = lookat(pos2, pos3, upvector);


16. //transform 17. vector rot = cracktransform(0, 0, 1, {0, 0, 0}, matx * matx2);

18. 19. //@N = normalize(pos3 - pos2); 20. @P *= maketransform(0, 0, pivot, rot, scale, pivot);

Let us break the code down in smaller snippets.

01. vector pos = @P; 02. vector pos2 = point(1, "P", 0); 03. vector pos3 = point(2, "P", 0);

Here I am only importing the Point Position Attribute from each input using the point expression however in this case I want the value of the single point input to be applied on all points on the input geometry. Therefore I have used point 0 in the expression instead of @ptnum.

point(1, "P", 0);


Let us now create the Align VOP first in vex. The axis can be a user defined variable so that any artist can change it depending on the character easily, if required depending on the cache incoming. To create the align use a dihedral() vexpression. The dihedral "Computes the rotation matrix or quaternion which rotates the vector a onto the vector b. Computes the rotation matrix which rotates the vector a onto the vector b."

To know about the Dihedral Concept there are multiple YoutTube videos and Blogs available to explain this. I found really helpful. CG Wiki has some concepts on lookat and dihedral as weel on this page

05. vector axis = chv("axis");

06. matrix3 matx;

11. //align 12. matx = dihedral( { 0, 0, 1 }, axis );

Now that we have made our first matrix, let's move onto the LookAt VOP in vex. This is relatively simple as there is an existing lookat() vexpression. Similar to the axis variable we used earlier, let's create a upvector that we can make user defined. The lookat() takes in 3 inputs, vector "a" and "b" that will be "from" and "to" and an upvector.

lookat(a, b, upvector);


04. vector upvector = chv("upvector");

07. matrix3 matx2;

13. //lookat function 14. matx2 = lookat(pos2, pos3, upvector);

Finally we can multiply the two matrices together and input into a cracktransform(). The cracktransform() is the vex equivalent of the Extract Transform VOP we used earlier.

"The function uses the given transform and rotation orders (trs and xyz) , the given pivot point (pivot) and optional pivot rotation (pr) to calculate the return value. The specifications for the trs and xyz parameters can be found in $HFS/houdini/vex/include/math.h."

cracktransform(0, 0, 1, { 0, 0, 0}, matx);


ORDER_OF_TRANSDFORMATION : Scale, Rotate, Translate is 0 and so on. I used the VOP menu as a reference point to under the order and value.

ORDER_OF_ROTATION : Rx, Ry, Rz is 0 and so on. I used the VOP menu as a reference point to under the order and value.

VECTOR_TO_OUTPUT : The vector to output, translate, rotate, scale etc. As per the Houdini help the order is "Depending on the value of c, returns the translate (c=0), rotate (c=1), scale (c=2), or shears (c=3) component of the transform (xform)."

PIVOT : If your geometry is not on the origin, I would input the centroid of the geometry wherever it it in world space. This will assure that your lookat rotates exactly on the center of your input geometry.

INPUT_MATRIX : The matrices cocktail we have created from the dihedral() and lookat().

16. //transform 17. vector rot = cracktransform(0, 0, 1, {0, 0, 0}, matx * matx2);

The final step would be to recreate the Transform Matrix VOP using a maketransform() vexpression. We can input the rot vector from line 17 into this expression to rotate the geometry. maketransform() "Builds a 3×3 or 4×4 transform matrix. maketransform(int trs, ...) builds a general 4×4 transform matrix given an order of transformations (trs), an order for rotations (xyz), a vector representing the translation (t), rotation (r), scale (s) (and optionally a pivot (p), pivot rotatation (pr), and shears (shears)). The specifications for the trs and xyz parameters can be found in $HFS/houdini/vex/include/math.hmaketransform(vector zaxis, yaxis, ...) builds either a 3×3 transform matrix or a 4×4 transform matrix. The matrix will be constructed so that the z-axis will be transformed to the z-axis specified with the given up vector (yaxis). Thus, maketransform({0,0,1}, {0,1,0}) will result in an identity matrix. The version which returns a 4×4 transform will apply the translation to the 4×4 matrix. This function is very similar to the lookat function. The vectors passed in are not normalized meaning that scales should be preserved in construction of the transform." as per Houdini Help. The maketransform() will output a matrix which can be multiplied by the current Point Position to manipulate the geometry. Since my pivot value and translation value were the same I used the "pivot" variable in both inputs, however keep in mind that if you have a pivot not a {0, 0, 0}, please create two different variables.

maketransform(0, 0, {0, 0, 0}, rot, {1, 1, 1}, {0, 0, 0});


20. @P *= maketransform(0, 0, pivot, rot, scale, pivot);

Example #2: Using Normals and to create a Multiple LookAts

In this example we simply find the difference between the Controller Circle single point and the Input Geometry single point. Just like before, the inputs remain the same.

VEX Code Text format:

01. vector pos = point(1, "P", 0); 02. vector pos2 = point(2, "P", 0); 03. 04. @N = normalize(pos2 - pos);

As before, I have imported the Point Position of the single point from the Controller Circle and the Input Object. This will act a centroid of sort to detect the point to look at. Finally, I simply found the difference between the two and normalized it to avoid strange flipping or other issues. This is an extreme common trick used in production on several small elements, FX, procedural models etc. It is a quick and easy solution. The Normals will now LookAt the Controller Circle, If you copy stamp or instance any geometry on it, it will face the correct direction.

Example #3: Using Cross Product to create a Multiple LookAts

In this Example we will use Normals again to control the direction the geometry however we will use Cross Product to do this.

This is a really massive concept and requires good understanding of the use of Cross Product. It is not possible to easily explain such a concept in one post, however there are several YouTube videos that will explain it much better than I could ever.

One the easiest explanations I have found was in this YouTube video

Once you have a thorough understanding of cross product, this code will make more sense. I have seen many artist use this technique to create fields, effects etc without understanding the base concepts.

VEX Code Text format:

01. vector target = point(1, "P", 0); 02. vector normal = @N; 03. @N = normalize(cross(cross(normal, target), normal));

The idea to make the Normals follow the Controller Circle without looking through the surface geometry.

cross(a, b);

cross(INPUT_1, INPUT_2)

Let's breakdown line 3. Step 1 is to cross() once, @N = cross(normal, target); If you look at the result in the viewport you will see the result exactly as the video said following arrow result will always be 90 degrees from the two original input arrows. C = B * A. This can be observed in the viewport. Keep in mind that input matters so in this case I put the original Normals as "a" and the target location (single Point Position or Centroid of Controller Circle) as "b".

We want the Normals to face exactly in the direction of the Controller Circle, to do this we can cross() the earlier result again. @N = normalize(cross(cross(normal, target), normal)); This will snap the Normals again as before and make them point towards the controller. It is important to note this time I put the result as Input "a" and the original Normals as Input "b". The order of inputs is important for the cross() vexpression otherwise it will face in the inverted direction.

C = B * A

C = -A * B

In Conclusion there are several ways of creating a kind of LookAt in Houdini which can be used in multiple ways for FX, Modelling, Animation Rigs etc. The same method can useful in Flocking Systems and Crowds as well. The idea behind making it in SOPs is to be able to manipulate it further and give full control to the Artist. There are several other tricks like the "orient" which can particularly be useful in such cases.

Featured Posts
Recent Posts
Search By Tags
Follow Us
  • Facebook Basic Square
  • Twitter Basic Square
  • Google+ Basic Square