On this page 
Overview
Karma is Houdini’s new renderer, designed to supersede Mantra.
A lens shader defines a cvex
function run by the renderer to generate the origin and direction of rays sent out by the ray tracer.
In Karma, a lens shader not only allows you to define the direction and origin of the ray, but the color (tint), the ray’s placement in (shutter) time, and the ray’s clipping range.
Houdini includes a premade Physical Lens lens shader VOP which has parameters for many lens effects, including but not limited to rolling shutter, chromatic aberrations, and tilt/shift. Advanced users can optionally write a completely custom shader function in VEX.
How to
To...  Do this 

Set up a Physical Lens node 

Create a lens shader node from VEX source code 

Create a lens shader node from a VOP network 

Lens shader arguments
Many of the arguments simply pass the values of various camera parameters to your shader.
int ix
The pixel coordinate on the x axis.
int iy
The pixel coordinate on the y axis.
float focal
The focal length of the camera.
float focus
The focus distance of the camera.
float fstop
The fstop of the camera (the N in f/N).
float aperture
The aperture width of the camera.
float orthowidth
The orthographic width of the camera.
vector4 viewport
The camera window, as {xmin, xmax, ymin, ymax}
in NDC (normalized device coordinates).
int seed
A seed value which is specific to the current pixel.
int sampleindex
The number of times this pixel has been processed (sometimes known as pass index).
vector4 datawindow
The data window as a rectangle (in pixel coordinates, not NDC).
float aspect
The aspect ratio of display window (width divided by height).
int xres
The pixel width of the display window.
int yres
The pixel height of the display window.
int isRHS
Whether you should interpret the values passed to your function given in RHS (righthand space). If your shader does not take this argument, Houdini always passes values in lefthand space.
This argument is for better compatibility with Karma, which works in RHS, and better portability.
float &Time
Unit time (from 0.0
to 1.0
) specifying what point in the shutter time the ray was sent.
Outputting a different value than what was passed in is how you achieve rolling shutter and other kinds of motion blur effect.
vector2 &clippingrange
The values passed to your function represent the near and far clipping distances (taken from the camera to which the lens shader is assigned).
Your function can change these values, to change the near/far limits for the ray (the minimum and maximum distances the ray can travel).
With a perspective transform, you should adjust the clipping distances by the Z of the ray direction to ensure consistent distances. For other projections, the interpretation of the clipping range may be different.
vector &P
Set this to the origin of the sent ray in 3D.
vector &I
Set this to the direction of the ray sent ray as a vector.
vector &tint
Set this to the color/contribution of the sent ray.
vector2 &jitter
Set this to any relative jitter (horizontal and vertical) you applied to the pixel. You must output this variable if generating your own x
and y
(see below).
The following arguments are supported for backwards compatibility with existing Mantra lens shader functions. If you are writing a lens shader from scratch for Karma, you should avoid using these arguments and only use the arguments documented above instead.
In Karma, the possible area of dofx
and dofy
, the area of the "aperture" (really, the circle of confusion) is based on the camera’s focal length and fstop.
(The Houdini camera has aperture height and aperture width parameters but this is not a physical camera’s aperture. In many ways it represents the actual sensor dimensions, or you can think of it as a screen of the given dimensions, that you are firing the pixels through, at the focal length away.)
float x
The x coordinate of the current pixel (jittered), fit between the NDC values of the camera’s viewport.
float y
The y coordinate of the current pixel (jittered), fit between the NDC values of the camera’s viewport.
float dofx
The x coordinate of the DOF offset, sampled from a disk the size of the aperture (centered at 0, 0
).
float dofy
The y coordinate of the DOF offset, sampled from a disk the size of the aperture (centered at 0, 0
).
int &valid
After the function is run, set this to 1
or 0
to indicate whether the ray was valid or not. Setting this to 0
is the same as setting the contribution to {0, 0, 0}
Examples
Simple projection example
A simple lens shader that does a projection mapping with depth of field.
This example uses are Mantrastyle arguments. In a Mantra shader, zoom and focus are arguments passed by the user to the shader. In Karma, if focus is not specified, Karma will pass the focus distance from the camera.
cvex simplelens ( // Inputs float x = 0; float y = 0; float Time = 0; float dofx = 0; float dofy = 0; float aspect = 1; // Outputs export vector P = 0; export vector I = 0; // Shader arguments float zoom = 1; float focus = 1; ) { // Set direction vector I = set(x * aspect / zoom, y / zoom, 1.0); // Set position P = set(dofx, dofy, 0); // Focus direction to focus distance away // no matter where P is, a pixel's ray will hit // the focus plane in the same place everytime I *= focus; I = P; }
This example does not use focal length and aperture width to compute the field of view and zoom, instead it uses an explicit zoom parameter.
Standard projection
Since we can have the camera’s information passed to the shader, we can build a projection that matches Karma’s standard projection.
cvex simplelens ( // Inputs float x = 0; float y = 0; float Time = 0; float dofx = 0; float dofy = 0; float aspect = 1; // Camera float focus = 1; float focal = 1; float fstop = 0; float aperture = 1; // note this matches with the horizontalAperture (in meters) // Outputs export vector P = 0; export vector I = 0; ) { // Set direction vector using the camera's properties I = set( x * aperture * 0.5 * aspect / focal, y * aperture * 0.5 / focal, 1.0 ); // Set position P = set(dofx, dofy, 0); // Focus direction to focus distance away // no matter where P is, a pixel's ray will hit // the focus plane in the same place everytime I *= focus; I = P; }
This lens shader should match the regular perspective camera perfectly.
Square bokeh
If you don’t like boring old circular bokeh, you might want a square shaped bokeh.
However, the dofx
and dofy
aren’t easily mapped to a square, so we will generate them ourselves. We have Karma pass us the pixel’s seed
and sampleindex
, then use random_brj to get a uniformly distributed sequence based on them.
cvex simplelens ( // Inputs float x = 0; float y = 0; float Time = 0; // Camera float focus = 1; float focal = 1; float fstop = 0; float aperture = 1; // note this matches with the horizontalAperture (in meters) float aspect = 1; // Random Sequencing int seed = 0; int sampleindex = 0; // Outputs export vector P = 0; export vector I = 0; ) { // Set direction vector using the camera's properties I = set( x * aperture * 0.5 * aspect / focal, y * aperture * 0.5 / focal, 1.0 ); // Get a random dof point in a 01 unit box vector2 dof = random_brj(seed, sampleindex); float diameter = focal / fstop; // get aperture / CoC diameter dof = (dof  0.5) * diameter; // center the unit box and set diameter P = set(dof.x, dof.y, 0); // Focus direction to focus distance away // no matter where P is, a pixel's ray will hit // the focus plane in the same place everytime I *= focus; I = P; }
This is an example of using random_brj to generate uniformly distributed sequences in your lens shader, however there is one thing to remember: if you have multiple random numbers to generate, you don’t want to use the same seed for every random number, but you do want to use a pixelunique seed as your basis for the sequence. In the example above we simply XOR the pixel seed with a constant seed for each random number we want to generate.
Generating X and Y
Below is an example where we generate the x and y completely in the lens shader using /solaris/random_brj.
// simple random seeds for generating jitter and dof #define JITTER_SEED 0x98A208B1 #define DOF_SEED 0xA8B2440D cvex simplelens ( // Inputs int ix = 0; int iy = 0; // Camera float focus = 1; float focal = 1; float fstop = 0; float aperture = 1; float aspect = 1; // Random Sequencing int seed = 0; int sampleindex = 0; // Screen Info vector4 datawindow = 0; vector4 viewport = { 1, 1, 1, 1 }; int xres = 0; int yres = 0; int isRHS = 0; // Outputs export vector P = 0; export vector I = 0; export vector2 jitter = 0; // note this must be returned if generating x and y ) { // Get pixel in reference to the data window int rx = ix + int(datawindow[0]); int ry = iy + int(datawindow[2]); // Must return jitter jitter = random_brj(seed ^ JITTER_SEED, sampleindex); float x = float(rx) + jitter.x; float y = float(ry) + jitter.y; // Map to NDC x = efit(x, 0, float(xres), viewport[0], viewport[1]); y = efit(y, 0, float(yres), viewport[2], viewport[3]); // Set direction vector using the camera's properties I = set( x * aperture * 0.5 * aspect / focal, y * aperture * 0.5 / focal, 1.0 ); // note RHS is assumed // Get a random dof point in a 01 unit box vector2 dof = random_brj(seed, sampleindex); float diameter = focal / fstop; // get aperture / CoC diameter dof = (dof  0.5) * diameter; // center the unit box and set diameter P = set(dof.x, dof.y, 0); // Focus direction to focus distance away // no matter where P is, a pixel's ray will hit // the focus plane in the same place everytime I *= focus; I = P; if (!isRHS) { P.z = P.z; I.z = I.z; } }

This example uses the new Karma arguments, such as the data window, viewport, X and Y resolution, and
isRHS
. 
These arguments let you map a pixel position to the NDC X and Y.

In this example we assume righthand space (RHS) and negate the Z coordinates if
isRHS
is false. 
You must output
jitter
if you do not use thex
andy
parameters. Not outputtingjitter
in that case will result in undefined behavior.
Tinting
Unlike Mantra’s lens shaders, Karma lens shaders can tint the rays, allowing you to create effects such as chromatic aberrations or anaglyphic 3D .
This example makes the camera behave like an anaglyph camera. Typically you would do this by creating two cameras, separated about an eye’s span apart (approximagtely 65mm), color one red, and the other cyan, and overlay their images.
This lens shader can achieve this in a single camera. It sends half the rays out from the left eye position with a tint of cyan, and the other half from the right eye position with a tint of red. (There is a trick to getting this to work, which is the shader needs to focus the rays to actually get the 3D depth of field.)
You can find demo files for this example in $HFS/houdini/help/files/anaglyph.hip.gz
and $HFS/houdini/help/files/anaglyphlens.hda
.
// random seeds to be XOR'd with the given seed #define JITTER_SEED 0x98A208B1 #define COINTOSS_SEED 0xA8B2440D cvex anaglyphlens( // Inputs int ix = 0; int iy = 0; // Camera float focus = 1; float focal = 1; float fstop = 0; float aperture = 1; float aspect = 1; // Random Sequencing int seed = 0; int sampleindex = 0; // Screen Info vector4 datawindow = 0; vector4 viewport = { 1, 1, 1, 1 }; int xres = 0; int yres = 0; int isRHS = 0; // Outputs export vector P = 0; export vector I = 0; export vector tint = 1; export vector2 jitter = 0; // note this must be returned if generating x and y // Shader arguments float dist = 0.065; vector rcolor = { 1, 0, 0 }; int enablecustomleft = 0; vector lcolor = { 0, 1, 1 }; ) { // Get pixel in reference to the data window int rx = ix + int(datawindow[0]); int ry = iy + int(datawindow[2]); // Must return jitter jitter = random_brj(seed ^ JITTER_SEED, sampleindex); float x = float(rx) + jitter.x; float y = float(ry) + jitter.y; // Map to NDC x = efit(x, 0, float(xres), viewport[0], viewport[1]); y = efit(y, 0, float(yres), viewport[2], viewport[3]); // Set direction vector using the camera's properties I = set( x * aperture * 0.5 * aspect / focal, y * aperture * 0.5 / focal, 1.0 ); // note RHS is assumed vector use_lcolor = lcolor; if (!enablecustomleft) use_lcolor = 1  rcolor; // get compliment of right eye color float cointoss = random_brj(seed ^ COINTOSS_SEED, sampleindex); cointoss = fit01(cointoss, 0, 2); if (cointoss < 1) { tint = rcolor; P.x = 0.5 * dist; } else if (cointoss < 2) { tint = use_lcolor; P.x = 0.5 * dist; } // Same focusing as before... I *= focus; I = P; if (!isRHS) { P.z = P.z; I.z = I.z; } }
Rolling shutter
cvex rollingshutter( // Inputs int ix = 0; int iy = 0; // Camera float focus = 1; float focal = 1; float fstop = 0; float aperture = 1; float aspect = 1; // Random Sequencing int seed = 0; int sampleindex = 0; // Screen Info vector4 datawindow = 0; vector4 viewport = { 1, 1, 1, 1 }; int xres = 0; int yres = 0; int isRHS = 0; // Exported Input export float Time = 0; // Outputs export vector P = 0; export vector I = 0; export vector2 jitter = 0; // note this must be returned if generating x and y // Shader arguments float blurriness = 0.05; // amount of blurriness (01) ) { // Get pixel in reference to the data window int rx = ix + int(datawindow[0]); int ry = iy + int(datawindow[1]); // Must return jitter jitter = random_brj(seed ^ JITTER_SEED, sampleindex); float x = float(rx) + jitter.x; float y = float(ry) + jitter.y; // Map to NDC x = efit(x, 0, float(xres), viewport[0], viewport[1]); y = efit(y, 0, float(yres), viewport[2], viewport[3]); // Set direction vector using the camera's properties I = set( x * aperture * 0.5 * aspect / focal, y * aperture * 0.5 / focal, 1.0 ); // note RHS is assumed // Implement rolling shutter: // Map the ray's time position based on the ray's position in the y axis float time_offset = fit(y, viewport[2], viewport[3], 0, 1); // Time is random number between 0 and 1, by limiting it we limit the blurriness // to a smaller portion of the shutter length Time = Time * blurriness + time_offset; // Same focusing as before... I *= focus; I = P; if (!isRHS) { P.z = P.z; I.z = I.z; } }