Hi There,
In this post I'm going to delve into the wonderful world of blendshapes, mainly because (no offense meant to sideFX) the standard blendshape that comes with Houdini is a bit basic and misses one of the the most useful functions, something that is essential in almost every circumstance you want to use it. Being able to feed a custom map into the blendshape to only effect certain areas.
So, this is a perfect opportunity to show off VEX's power, you can make a blendshape in VEX with just 2 lines of code, including the ability to feed a custom map to effect specific areas.
So Without further ado, here's my quick guide to making awesome, powerful, flexible VEX blendshapes!
Vanilla Houdini Blendshape
Here's the layout, classic grid with a mountain node situation. Plugged into a blendshape node.
blending between the non-mountained grid and the mountain nodes output gives us this. It's great if all you want is an even blend across all points, if you want part of the mesh to blend in a different way to the rest, it's not so good. In fact it's a bit frustrating!
Replicating This in VEX
It's surprisingly easy to replicate this exact behaviour in VEX in fact you can do it with just 2 lines of code! Wow!
So that's it, we run over the points and perform these two simple operations on each point.
First we subtract the position vector (v@P) from the position of the same point from the second input using the `point` command. This gives us the vector from our current position to the target position.
Secondly, we add this new vector on to the `P` (position) of our current point, multiplied by a float value `envelope`. Knowing what we know about vectors if we multiply a vector by a scalar value it maintains the same direction, but alters the magnitude of the vector. So, an `envelope` value of 0 means that no change occurs, an `envelope` value of 1 would push the point all the way to the target position and a value of 0.5 would push it exactly half way.
CODE
vector offset = point(1, "P", @ptnum) - v@P; // work out vector from current position to target position
v@P += offset * chf("envelope"); // add offset vector to current position using custom parameter as envelope
Great stuff, 2 lines of VEX and we completely reproduce the basic functions of the vanilla blendshape node.
Time to power it up a bit!
VEX Paintable Blendshape
So the only addition to the network is a paint node which is overriding the color attribute for a custom float attribute I've called 'blend'
And here's the code, exactly the same as before but instead of using the `envelope` attribute I am using the `blend` attribute that I painted in the paint node. Easy!
CODE:
vector offset = point(1, "P", @ptnum) - v@P;
v@P += offset * f@blend;
That's all good, we've got ourselves a proper paintable blendshape, but why stop here? VEX gives us so much power and flexibility over point attributes that we can assign all kinds of interesting conditions to our blendshaping.
Conditional Blendshape
So what this version is doing is checking to see whether the new target position is pushing the point 'up' or 'down' (caveat here to mention that up and down are reference specific terms and in this specific case refer to the relative displacement along the world y-axis!) then only pushes the point if it is going 'up'.
This is relatively easy to do and requires simply adding an `if` statement into our previous code.
So the only thing I've added here is a simple `if` statement which checks the dot product of the `offset` vector against the world 'up' axis (i define this to be the positive y-axis using set(0,1,0) and only apply the offset if the dot product is greater than 0 (meaning it is pointing 'up')
I've also used the `envelope` parameter meaning I can dial in the displacement or keyframe it if I want, you could just as easily use a painted map, or even a combination of the two.
CODE
vector offset = point(1, "P", @ptnum) - v@P; // work out vector from current position to target position
if(dot(normalize(offset), set(0,1,0)) > 0){ // check if direction of offset vector is pointing 'up' or 'down'
v@P += offset * chf("envelope"); // add offset vector to current position using custom parameter as envelope
}
This is just one example of how we can start assigning conditions to our blendshape to create different effects, I'll include one more example, where we assign secondary effects based on the information we are generating inside our blendshape.
Secondary Effects
So here I am measuring the length (magnitude) of the `offset` vector we are creating, and using it to assign a new colour all points above a certain height.
I've added the blue colour onto the grid before it goes into my blendshape.
I'm also using a 'normal' node to add a normal attribute (N) to the points, this will make sense later, trust me!
So a bit more complex than our previous example but it's basically doing the same sort of stuff, it now not only applies the position offset to the points which will get pushed 'up' it also reads the colour ('Cd') and the point normal ('N') from the second input and assigns those aswell, and finally it checks the length (magnitude) of the offset vector and if that value is above the threshold (defined by the float channel 'snow_line') it colours those points white (1,1,1).
The other main change I have added here is to replace the arbitrary 'up' vector with the point normal, this makes the whole setup a lot more flexible and will allow us to do something pretty cool.
CODE
vector offset = point(1, "P", @ptnum) - v@P;
vector color_offset = point(1, "Cd", @ptnum) - v@Cd;
float offset_dot = dot(normalize(offset), v@N);
if(offset_dot > 0){
v@P += offset;
v@Cd += color_offset;
v@N = point(1, "N", @ptnum);
if(length(offset) > chf('snow_line')){
v@Cd = set(1,1,1);
}
}
By checking the direction of `offset` against the point normal attribute rather than an arbitrary 'up' vector we ensure that the we get the desired effect, even on curved surfaces.
Final thought
So what we've actually done here is something called 'linear interpolation' which is a term you may hear frequently in CG land, it basically describes a mathematical process where we take two data points, in our case position vectors, and draw a straight line between those points. From there we can use a weighting value between 0 and 1 to find any value between the two original points.
Linear interpolation isn't just something that can be used for position data, any data which can be plotted on a graph can be interpolated, for example position data over time can be interpolated in much the same way and is useful for working out the position of points between frames for collision substeps or motion blur. Or keyframes in the animation editor, you can plot 2 keyframes on a single parameter at opposite ends of the time line and Houdini will interpolate between those points to find accurate values anywhere between the two original points.
Finally, there is already a VEX function called `lerp` to perform linear interpolation, and in fact it can be used to create blendshapes in much the same way that we created above.
Here we go, a nice lerp-ified blendshape that is functionally identical to the first VEX blendshape we made above. A nice, clean single line of code, beautiful.
So, I here you ask, why didn't we just do this from the start? Good question,
firstly I wanted to run over the maths behind the process, I think it's really important to not just learn how to apply a function but to try to get to grips with what the function is doing behind the scenes. This is a great example for that as it requires understanding some basic principles of vector maths which hopefully make the whole process less mysterious.
Secondly I wanted to demonstrate that by doing it the 'mathsy' way we get access to useful information that you simply don't get from 'lerping' so by working out the interpolation vector (we called it "offset") we can then find out both the direction of movement (we used that to isolate "upward" motion) and also the magnitude of the movement (we used that to create the snowline effect).
I hope all that makes sense, please feel free to comment on this post I will do my best to reply. And remember......
Don't get mad.
Get vexed!
Comments