Houdini VOP raytracer part 1
Posted on August 12, 2013
Long time ago, I came up with this crazy idea of creating a ray tracer in Houdini just by using Houdini VOP nodes just for fun. I found that this may give you a very in-depth understanding on how raytracers works, and let you discover few tips and tricks in Houdini.So after a few requests, I have decided to create this tutorial. Starting from the very basic so everyone can understand. The final ray tracer we are going to create, renders picture on a given “Screen”, custom geometry for eg. simple plane object. Exactly same principles apply for shaders so everything what we will create here, you can do in shader contexts. But first, we are going to start from simple examples and basic math.
Diffuse reflection (also known as Lambertian reflection) is basic property of material that defines an ideal diffusely reflecting surface. For eg. Moon reflects light into all directions without specular highlights. Other examples of such materials are chalk and matte paper; in fact, any surface that appears dull and matte.
Lets see how it works. As Houdini viewport calculates diffuse refrection for us by default, this example I will show in shader context and later on we will move into vops.
In the case of perfect diffuse reflection, the intensity of the observed reflected light depends on the cosine of the angle between the surface normal vector and the ray of the incoming light.
But what does it mean?
As you already know, each polygon on 3d geometry, has a “Normal”, that is a perpendicular vector to surface, describing direction which polygon is facing (blue arrows). We can also have normals on points – vectors which angles will be interpolated based on surrounding face normals (red arrows). Renderer on other hand, calculates normal vector on every shading point.
For simplicity of this example, we will work on point normals. By default, when creating geometry in Houdini, usually we do not get point normal vectors calculated. In order to calculate them, we can use “Point” sop node and select “Add Normal” or “Facet” sop node and tick “Post Compute Normals” checkbox.
Usually we want our normals to have same, unified length of 1.0 (“Normalised”) as we care only about their angle.
TIP. Be careful when using transform sop as by default it has "recompute point normals" checkbox on.
[…] cosine of the angle between the surface normal vector and the ray of the incoming light.
We already know how to get our normal vectors, now we need ray of incoming light. In shader context, we have an access to “illuminance loop” (an iterator which calculates all lights in our scene) from which we have an access to Global Variable “L” – Direction from Surface to Light, perfect for our needs. Unfortunately this is not available from SOP context but we will deal with that problem soon .
We have our Normal Vectors and Ray of Incoming light vectors and now we need to calculate cosine angle between them. Why cosine angle ?
If you remember from geometry, Cosine of 0° between two vectors is 1, cos(90°) = 0.
Below you can see how cosine of angle between vectors L and N change within full π rotation.
If we use cosine value as a color luminosity on every shading point, we will get nice diffuse reflection!
Lets start implementing it in Houdini.
First, we will start with simple polygon sphere, calculate point normals and add empty shader. Render itself should give us 100% white, unshaded objects.
Now, lets try creating diffuse in shader context first. By default we have an access to “N” (Normals) from Global parameters. We can get vector “L” (Direction from surface to light) by adding illuminance loop as described above. How do we calculate cosine of an angle in between them ?
This is where basic geometry comes in handy.
A·B = ||A|| ||B||cosθ
The dot product of two Euclidean vectors A and B is equal to magnitude of vector A (denoted by ||A||) times magnitude of vector B times cosineθ. Where θ is the angle between A and B.
In our case, vectors A and B (L and N) are normalised (their magnitude equals 1.0). So
A·B = 1 * 1 * cosθ
It means that by calculating dot product of vectors L and N, we will get cosine of angle in between them.
Still, this is not proper calculation. As you saw above on animated cosine example, we can get negative values from our dot product calculation and we do not want negative colour values. To solve this problem, we can clamp values between 0-1 or by max(0, N·L). Finally, we want to use incoming colour of object (Cf) and multiply it by intensity of our diffuse.
This should be our render result:
If we would use standard lambert, it will give us same result and additionally calculate shadows:
Now lets try getting diffuse in VOP SOP level. We can easily access “N” (point normals) but we need to create “L” vector (Direction from surface to light) by our own. First, we need a light. In VOP SOP case it can be anything, even a single point in space as lighting will be calculated manually (not by illumination loop like in sharer case). In this example I am using single point, origin of scene light. Lets import it “into this Object” and plug it into second input of newly created vopsop node.
Inside vop sop node, we can calculate “L” vector by subtracting position of examined point (Global “P”) from imported position of second input – origin of light. By subtracting those two point positions, we get vector in-between them. It is very important to keep right order of subtraction as it will affect direction of resulting vector. Resulting vector has to be normalised (its magnitude has to be 1.0) and this is our “L” vector.
Now we can continue with dot product calculation as we did in shader context.
And now you have diffuse reflection in VOP context! Later on we will move it into our own custom VOP ray tracer.
Any thoughts/comments/suggestions and donations would be greatly appreciated !