|
Houdini Development Toolkit - Version 6.5
Side Effects Software Inc. 2004
|
Mantra
Adding a Light Shader
To add a custom light shader, there are two steps to perform:
- Write a class that implements an light shader algorithm.
- Modify the Houdini interface to include the new shader.
Writing the light shader class
Houdini's rendering library is called the RAY library. Most of the
functions and classes will contain this prefix. In the RAY library,
all light shaders are sub-classed from the RAY_LightShader class.
Each light shader instance has a pointer to a RAY_Light
light. This light contains all sorts of information, like the
position of the light in space, the color of the light, various maps
and other information.
The easiest way to write a new light shader is to take an existing
shader and modify it to implement a different algorithm. An example is
provided here that can be copied and modified as necessary. The
example source computes illumination through a virtual window. The
edges of the window are "blurred".
The sample shader takes as parameters the width and height of the
window panes, as well as a border width and a fuzz width.
of which are optional. The color is specified with the
-c red green blue option while the density is given with
the -a alpha option.
When compiled, the source file will produce a
DSO which should be accessible by
Mantra.
The header file
RAY_LightCustom.h:
#ifndef __RAY_LightCustom__
#define __RAY_LightCustom__
#include <RAY/RAY_LightShader.h>
class RAY_LightWindow : public RAY_LightShader {
public:
RAY_LightWindow();
virtual ~RAY_LightWindow();
virtual int getIllumination(RAY_ShaderFunc &func,
UT_Vector3 &base_light,
UT_Vector3 &light_color,
UT_Vector3 &light_direction);
virtual int hasRayTracedShadows() const;
static RAY_LightShader *construct(CMD_Args &args);
private:
int myUseBlur;
float height; // Height of a pane
float width; // Width of a pane
float border; // Border between panes
float fuzz; // Blurry amount around edge
};
#endif
All fog shaders are subclassed from the RAY_LightShader class. This
class provides the services that interface the shader with the rest of
the RAY library. Most of the code here is necessary and
fills in functions that are required by the RAY_LightShader class.
Note that there is some data in the private part of the class. This is
data that is specific to the shader. The window shader has four floats
which represent the parameters of the window. Any shader specific
data should be placed here.
The key method which needs to be filled out is
getIllumination. The method is passed in a shader function
which can be used to query the state of the surface being illuminated.
There are also some convenience methods which compute shadows for you:
The sample code contains an ifdeffed section of code which performs
the fastShadow computation.
As well the getIllumination method is passed three UT_Vector3
classes. These must be filled out by the shader. The first vector
represents the base color of the light (before any shadows have been
computed). This may be used by some surface shaders. The second
vector represents the illumination which reaches the surface being
shaded. The third vector represents the unit vector of the light to
the point on the surface being shaded.
The source file
RAY_LightCustom.C:
void
RAYaddLightShader()
{
RAY_LightEntry *entry;
entry = new RAY_LightEntry("window",
"Window Shadow",
RAY_LightWindow::construct);
entry->insertIntoList();
}
Mantra recognizes each light shader is stored in a class called
RAY_LightEntry. The DSO function RAYaddLightShader()
should create a RAY_LightEntry class with the correct name
for your shader and then tell the entry to install itself. This lets
mantra know your shader exists.
int
RAY_LightWindow::getIllumination(RAY_ShaderFunc &func,
UT_Vector3 &base_light,
UT_Vector3 &light_color,
UT_Vector3 &light_direction)
{
float distance;
float alpha;
RAY_Octree *shadow_tree;
float blur;
float x, y, tmp;
float dx, dy;
blur = (myUseBlur) ? light->getSpread() / 100 : 0;
// Here we call the base class to compute base light. This includes:
// - Cone lights
// - Attenuation
// - Projection maps
// - Z-Depth shadows
distance = getBaseLight(func, base_light, light_direction);
light_color = base_light;
// We're actually totally occluded here (i.e. outside a cone), so return
if (distance < 0)
return 0;
//
// Now, we test to see the illumination through the window.
//
// First, we compute our position in X & Y.
tmp = dot(light_direction, light->getZAxis());
if (tmp >= 0) return 0; // Behind the window...
tmp = -light->getZoom() / tmp;
x = fabsf(dot(light->getXAxis(), light_direction)) * tmp;
y = fabsf(dot(light->getYAxis(), light_direction)) * tmp;
// Now, compute whether we're in our "window"...
if (x > width || x < border) return 0;
if (y > height || y < border) return 0;
if (x > width - fuzz) dx = x - (width - fuzz);
else if (x < fuzz+border) dx = border+fuzz - x;
else dx = 0;
if (y > height - fuzz) dy = y - (height - fuzz);
else if (y < fuzz+border) dy = border+fuzz - y;
else dy = 0;
light_color *= (cosf(dx/fuzz*M_PI)+1) * 0.5F;
light_color *= (cosf(dy/fuzz*M_PI)+1) * 0.25;
#if 0
// If we want, we could also cast ray traced shadows...
// Here, we're inside our panes, so just cast shadows...
if (shadow_tree = light->getShadowOctree())
{
alpha = func.getFastShadow(shadow_tree, light_direction,
distance, blur);
light_color *= alpha;
if (alpha < 0.001F)
return 0; // No illumination
}
#endif
return 1; // We're illuminating
}
The getIllumination() method is where the actual shading
takes place. This function can be broken down into three distinct
sections:
- Computing the base light. This is done by calling the base
class method getBaseLight. The base class method
will compute all cone lights, attenuation, and projection maps
that the RAY_Light has assigned to it. This is an
optional step. However, this method does compute the light
direction and illumination for you automatically. In some
cases (i.e. where you have your own cone or attenuation
function), it may be desirable to skip this stage and
initialize the light_direction vector yourself. If
the distance returned by getBaseLight is less than 0,
it means that the light has no illumination.
- Compute masking of illumination due to the window pane
algorithm. This is explained below.
- The third stage is ifdeffed out in this example. The most
expensive computation is usually computing shadows. Since we
want our shader to be fast, we don't compute ray traced
shadows. If we wanted, we could use the base class method
getDepthShadow() which uses the z-depth map attached
to the light source to compute soft shadows without
ray-tracing.
If the light contributes no illumination, the return code should be 0
otherwise, the return code should be 1.
The Window Pane Algorithm
The window pane computation is really quite simple.
- Compute the X/Y position relative to the light. These four
lines of code simply project the direction to the surface
onto the lights axes. The zoom factor of the light is taken
into account, meaning that if the user changes the focal
length of the light, the projected image will be altered.
- Determine whether we are inside the pane. Since the window
is symmetric around the origin, we simply take the absolute
value of the X & Y components and determine whether they are
in a single quadrant of the window. If they aren't we return
0, indicating that the light has no illumination at this
point.
- As a final step, we compute the blur on regions near the edge
of the window. We do this by computing a delta and then
using a half cosine curve to blend smoothly between the black
outside and the bright inside. The cosine curves for X & Y
are multiplied together. This code could be written much
more efficiently than is illustrated here.
Modifying the Houdini interface
Light shaders are chosen from within Houdini using a user dialog which
is built from a script. The new custom light shader must now
be added to this dialog script. The
dialog script containing the list of light shaders is found in
hfs/houdini/config/Scripts/MANlight.ds. To add the new shader,
just copy a simple entry like that for the fastShadow shader, and modify
it as required. It may be a good idea to backup the original file before
making changes to it.
The command name should match the shader name used within the
RAY_LightEntry defining the shader.
Here is what the entry for the window shader might look like:
command {
name window
label "Window Pane"
default "window -w 1 -h 1.5 -b 0.02 -z 0.2"
parm {
name width
label "Pane Width"
option -w
type float
size 1
default { 1 }
}
parm {
name height
label "Pane Height"
option -h
type float
default { 1.5 }
}
parm {
name border
label "Window Border"
option -b
type float
default { 0.02 }
}
parm {
name fuzz
label "Blur Size"
option -z
type float
default { 0.2 }
}
}
Table of Contents
Operators |
Surface Operations |
Particle Operations |
Composite Operators |
Channel Operators
Material & Texture |
Objects |
Command and Expression |
Render Output |
Mantra Shaders |
Utility Classes |
Geometry Library |
Image Library |
Clip Library
Customizing UI |
Questions & Answers
Copyright © 2004 Side Effects Software Inc.
477 Richmond Street West, Toronto, Ontario, Canada M5V 3E7