Integrators

Now things are getting more interesting!
The things returned from the shader program has no meaning to a color display, so we need to figure out how to convert the symbolic representation returned from the shader program to something that we can display on a computer screen (or store in an image). Basically all renderers works with floating point numbers, the computer representation of a real number. But that is fine since Open GL can render floating point numbers and the Open EXR image file format can store images as a matrices of floating point numbers.

The first thing we need to do is to figure out what an integrator actually is. I was not able to find an example in the Open Shading Language Specification or in the source code, so this is what a assume it would be (this may change as we go along)? Create a new file, in the source directory called integrator.h:

#ifndef _Integrator_H
#define _Integrator_H

#include <OSL/oslexec.h>
#include <OSL/oslclosure.h>

using namespace OSL;

class Integrator
{
public:
    Integrator(ClosureColor *Ci);

    Color3 eval_reflect(const Vec3 &omega_out,
                        const Vec3 &omega_in,
                        float &pdf);;

private:
    ClosureColor *m_Ci;
};

#endif

Now for the hard part, how do we actuctually extract the data from the ClosuseColor object, stored in the ShaderGlobals data structure member Ci, because this is the variable you would assign a value in a shader program. I found out that is a pointer to partial type, that really is a pointer to one of the following objects. This text is extracted from the library header oslclosure.h:

Do not enter this text section in the file integrator.cpp, this is just an example:

/// The base class ClosureColor just provides the type, and it's
/// definitely one of the three kinds of subclasses: ClosureComponent,
/// ClosureMul, ClosureAdd.
struct ClosureColor {
    enum ClosureType { COMPONENT, MUL, ADD };

    ClosureType type;
};

So we need to cast the pointer to one of these types based on the information in the type field. It appears that the structure is some kind of tree, which leads to the following recusive function (now I have started to create the file integrator.cpp):

static Color3 eval_closure_reflect(const ClosureColor *closure,
                                   const Vec3 &omega_out,
                                   const Vec3 &omega_in,
                                   float &pdf)
{
    Color3 weight;

    switch (closure->type)
    {
        case ClosureColor::MUL:
            weight = ((ClosureMul *) closure)-> weight *
                     eval_closure_reflect(((ClosureMul *) closure)->closure,
                                          omega_out,
                                          omega_in,
                                          pdf);
            break;

        case ClosureColor::ADD:
            weight = eval_closure_reflect(((ClosureAdd *) closure)->closureA,
                                          omega_out,
                                          omega_in,
                                          pdf) +
                     eval_closure_reflect(((ClosureAdd *) closure)->closureB,
                                          omega_out,
                                          omega_in,
                                          pdf);
            break;

        case ClosureColor::COMPONENT:
            weight = eval_component_reflect((ClosureComponent *) closure,
                                            omega_out,
                                            omega_in,
                                            pdf);
            break;
    }

    return weight;
}

The only place where we can expect to get any real data is in the last case clause, based on the type ClosureComponent. We need to create another function to extract the color value from here:

static Color3 eval_component_reflect(ClosureComponent *comp,
                                     const Vec3 &omega_out,
                                     const Vec3 &omega_in,
                                     float &pdf)
{
    ClosurePrimitive *prim = (ClosurePrimitive *) comp->data();

    if (prim->category() == ClosurePrimitive::BSDF) {
        return ((BSDFClosure *) prim)->eval_reflect(omega_out,
                                                    omega_in,
                                                    pdf);
    }

    return Color3(0, 0, 0);
}

So the data() method gives ut the pointer to the class containing the actual data. In this example we are only conserned with surface shaders, so we also check that the return value from the category() method matched the type ClosurePrimitive::BSDF. The BSDFClosure class has a method to calculate the ammount of reflected light that leaves the surface based on the direction of the incomming light omega_in relative to the direction of the surface normal, stored in the ShaderGlobals data structure as N. The surface normal would normally be calculated by the ray tracer on the point of intersection with the shape. For a point light source the direction of the incomming light omgea_in would be obtained by substracting point of intersection, also stored in the ShaderGlobals data structure as P, from the light position.

We also define a initializer method and a function to evaluate the reflected light:

Integrator::Integrator(ClosureColor *Ci)
    : m_Ci(Ci)
{
}

Color3 Integrator::eval_reflect(const Vec3 &omega_out,
                                const Vec3 &omega_in,
                                float &pdf)
{
    return eval_closure_reflect((const ClosureColor *) m_Ci,
                                omega_out,
                                omega_in,
                                pdf);
}

Now we are done editing the integrator class definition in the file integrator.cpp, save the file and close it.

In order to test our integrator, add the following information in the file main.cpp, below the console output line that said Shader executed successfully. I include this line again in the code below, you can search for it in vi by typing the line below followed by an enter press:
/successfully
If you for some reason has entered another line that contains the word successfully in the file main.cpp, just press the n key to search for the next occurance:

        std::cout << "Shader executed successfully" << std::endl;

        // Create integrator needed to calculate actual color for closure color
        Integrator integrator(shaderglobals.Ci);
        float pdf;

        // Call integrator to evaluate actual color based on closure color
        Color3 col = integrator.eval_reflect(Vec3(1, 1, 1), // Outgoing angle
                                             Vec3(1, 1, 1), // Incomming angle
                                             pdf);
        std::cout << "Reflected color: " << col << std::endl;

Note that we only send dummy vectors for the omgea_out and omega_in agguments.

Add the new integrator class implementation file to the 2:nd line of the CMakeLists.txt file;

SET ( vtrace_srcs vrenderer.cpp integrator.cpp )

and compile the program from the project root again, run it on the compiled shader:
# make
# dist/linux/bin/vtrace src/shaders/matte

Hopefully the output will look like this, where 0.31831 is one divided by pi, the lambertian refelction coeficient:

Initialize VRenderer
Shader executed successfully
Reflected color: (0.31831 0.31831 0.31831)

Congratulations you have managed to extract color data information from a closure.

BONUS section:
Try a little bit more advanced shader program, by entering the following text section into a file called matte2.osl and compile it:

surface
matte2
    [[ string description = "Lambertian diffuse material" ]]
(
    float Kd = 1
        [[  string description = "Diffuse scaling",
            float UImin = 0, float UIsoftmax = 1 ]],
    color Cs = 1
        [[  string description = "Base color",
            float UImin = 0, float UImax = 1 ]]
  )
{
    Ci = Kd * Cs * diffuse (N) + 2.0 * diffuse (N);
}

When you run your renderer program on this file you get the following output:

Initialize VRenderer
Shader executed successfully
Reflected color: (0.95493 0.95493 0.95493)

So what is happening here? The recursive function eval_closure_reflect() you defined, calculates sums and products of closure colors!

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License