Writing Displacement Shaders

Displacement shaders produce Vec3f vector values and can be chained with other displacement shaders. They are very similar to regular Map shaders except rather than having a sample call that produces a color they have a displace call that produces a vector.

Each Displacement shader implements two static functions to support MoonRay’s scalar and vector execution modes. The function prototypes are defined in scene_rdl2’s Types.h and they are DisplaceFunc and DisplaceFuncv.

Displacement shaders inherit two protected function pointer members (mDisplaceFunc and mDisplaceFuncv) which they must set, typically in the constructor.

There are typically 4 files that make up a shader’s source:

  • <ClassName>.cc
  • <ClassName>.ispc
  • <ClassName>.json
  • CMakeLists.txt

The .cc file is written in C++ and contains the class definition, the constructor/destructor, the update() function, and the static scalar DisplaceFunc implementation. The .ispc file is written in ISPC and contains the vector DisplaceFuncv implementation. It is also common for the .ispc source to contain any data structures needed by the shader during rendering. Attributes are declared in the .json file via JSON.

The displace() function

A Displacement shader implements two different displace functions that can be called by the renderer depending on the execution mode. The displace functions are responsible for generating the vector values that are then provided to the client shaders. The displace function is called for every shade point and can therefore be executed several millions of times in a single render which should be kept in mind when writing these functions.

  • DisplaceFunc - (implemented in C++ language)
  • DisplaceFuncv - (implemented in ISPC language)

The update() function

The update() method is called before rendering begins and anytime the shader’s attributes or bindings are modified. Because the above-mentioned displace functions are going to be potentially called millions of times, a shader writer should strive to use the update() method whenever possible to do any heap allocations or potentially expensive operations that do not depend on varying values/state. The results of these operations can then be stored as class members and later retrieved during sampling. It is fairly easy to cause major performance issues by doing something which would not be a big concern in “normal” code, but because it is happening millions of times across many threads it causes a bottleneck. Such “expensive operations” that should be kept in the update() function include: memory allocation/deallocation such as declaring a string, something that causes threads to lock such as querying or updating a container, and the construction of non-trivial types.

Here’s a simplified Displacement shader that produces a displaced normal based on the shading point’s normal.

// NormalDisplacement.cc

#include <moonray/rendering/bvh/shading/State.h>
#include <moonray/rendering/shading/EvalAttribute.h>
#include <scene_rdl2/scene/rdl2/rdl2.h>
#include <string>

#include "attributes.cc"                      // included at the top of every Displacement shader
#include "NormalDisplacement_ispc_stubs.h"    // this is generated by the build system from the ispc source

RDL2_DSO_CLASS_BEGIN(NormalDisplacement, scene_rdl2::rdl2::Displacement)

public:
    NormalDisplacement(const scene_rdl2::rdl2::SceneClass &sceneClass, const std::string &name);
    ~NormalDisplacement();
    
    // inherited from SceneObject
    virtual void update();

private:
    // scalar implementation (DisplaceFunc)
    static void displace(const Displacement *self, moonray::shading::TLState *tls,
                         const State &state, Vec3f *displace);
    
RDL2_DSO_CLASS_END(NormalDisplacement)

NormalDisplacement::NormalDisplacement(const scene_rdl2::rdl2::SceneClass &sceneClass, const std::string &name) :
    Parent(sceneClass, name)
{
    // Here, we set the two inherited function pointers to point
    // to our scalar and vector implementations
    mDisplaceFunc = NormalDisplacement::displace; 
    mDisplaceFuncv = (scene_rdl2::rdl2::DisplaceFuncv) ispc::NormalDisplacement_getDisplaceFunc();
}

NormalDisplacement::~NormalDisplacement()
{
}

void
NormalDisplacement::update()
{
}

void NormalDisplacement::displace(const Displacement *self, moonray::shading::TLState *tls,
                                  const State &state, Vec3f *displace)
{
    const NormalDisplacement* me = static_cast<const NormalDisplacement*>(self);
    
    const float height = evalFloat(me, attrHeight, tls, state);        
    *displace = height * state.getN();
}
// NormalDisplacement.ispc

#include <moonray/rendering/shading/ispc/Shading.isph>
#include "attributes.isph"

static varying Vec3f
displace(const uniform Displacement *   uniform me,
               uniform ShadingTLState * uniform tls,
         const varying State &          state)
{
    const varying float height = evalAttrHeight(me, tls, state);
    return state.mN * height;
}

DEFINE_DISPLACEMENT_SHADER(NormalDisplacement, displace)

NormalDisplacement.json

{
    "name": "NormalDisplacement",
    "type": "Displacement",
    "attributes": {
        "attrHeight": {
            "name": "height",
            "type": "Float",
            "default": "1.0f",
            "flags": "FLAGS_BINDABLE",
            "interface": "INTERFACE_MAP"
        }
    }
}