Houdini Development Toolkit - Version 9.0

Side Effects Software Inc. 2007

Compositing Operators

Pixel Function Operations

Pixel function COPs are simple functions that take an input pixel and some parameter values, and produce the corresponding output pixel. Because they don't access other neighbouring pixels, other inputs or planes, they can be highly optimized. Gamma and luminance functions are examples of pixel functions.

Unlike other COPs, you don't override the cook method. Instead, you override the method:
virtual RU_PixelFunction * addPixelFunction(const TIL_Plane *plane,int array_index, float t, int xres, int yres, int thread)
and create a class derived from RU_PixelFunction which implements your function. In addPixelFunction, you can evaluate parameters, so it is not necessary to create a context data for a pixel function COP.

To begin, let's take a look at the important methods in RU_PixelFunction that you may need to override:
class RU_API RU_PixelFunction
{
public:
RU_PixelFunction();
virtual ~RU_PixelFunction();

virtual bool needAllComponents() const;
virtual bool eachComponentDifferent() const;

virtual RUPixelFunc getPixelFunction() const;
virtual RUVectorFunc getVectorFunction() const;
};

Your pixel function will also need to contain any parameters that it needs to run. Instead of creating a Context Data and stashing the parameter values there, you'll be stashing the parameter values in your Pixel Function class. Your addPixelFunction() method should initialize these values.

Pixel functions are divided into two groups - scalar and vector pixel functions. A scalar pixel function does not depend on the other components of the plane - like a gamma() function. A vector pixel function requires the other components of the plane to be used in the calculation - like luminance(). So first, you need to decide which type of function your pixel function is. If it is a scalar function, override:
bool needAllComponents() const { return false; }
Otherwise, if it is a vector function:
bool needAllComponents() const { return true; }
A scalar pixel function should override getPixelFunction() to return a pointer to your static pixel function:
static float	 pixel_function(RU_PixelFunction *func, float val, int comp);
In this function, you can access your pixel function class to read the stashed parameters, you have access to the input pixel value (val), and you know which component you are processing (comp). You then return the result from the function. For example,
float simpleAdd(RU_PixelFunction *func, float val, int comp)
{
cop2_AddFunction *add_parms = dynamic_cast<cop2_AddFunction *>(func);

return val + add_parms->myAddend[comp];
}
RUPixelFunction getPixelFunction() const { return cop2_AddFunction::simpleAdd; }
In addition, scalar pixel functions can also override the virtual method eachComponentDifferent(). If false, then the components can all be processed exactly the same. If true, then the parameters for each component differ, and each component must be processed individually. For example, if you were brightening the color by 2, eachComponentDifferent() should return false. If you were adding the color (0.7,0.5,0.6) to each color, it should return true.

If your function is a vector function, then you need to override getVectorFunction() to return a pointer to your static vector function, which is in the form:
static void	vector_function(RU_PixelFunction *f, float **vals, const bool *scope)
This function is slightly more complicated, since up to 4 components are passed to you at once. vals acts as both the input and the output; it contains the input values, and you write the results back to that array. scope is an array of 4 booleans that tells you which components you can write the result to (if true, write the result to the array; if false, it is not scoped so don't write it). An example:
void saturate(RU_PixelFunction *func, float **vals, const bool *scope)
{
cop2_SaturateFunc *sat_parms = dynamic_cast<cop2_SaturateFunc *>(func);
float amount = sat_parms->myAmount;
float lum_level = 0;

if(vals[0]) lum_level = *vals[0] *0.3;
if(vals[1]) lum_level += *vals[1] *0.6;
if(vals[2]) lum_level += *vals[2] *0.1;

if(scope[0] && vals[0]) *vals[0] = (*vals[0] - lum_level) * amount + lum_level;
if(scope[1] && vals[1]) *vals[1] = (*vals[1] - lum_level) * amount + lum_level;
if(scope[2] && vals[2]) *vals[2] = (*vals[2] - lum_level) * amount + lum_level;
}

RUVectorFunction getVectorFunction() const { return cop2_SaturateFunc::saturate; }
In this example, even though there are up to 4 components, we didn't read or write to the 4th component. It will be passed through untouched. Also note that you need to check if the component has data (for an alpha plane, only vals[0] would be non-null).

For an example of a pixel function COP, see the COP HDK sample COP2_PixelAdd.C.


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 © 2007 Side Effects Software Inc.
477 Richmond Street West, Toronto, Ontario, Canada M5V 3E7