BSDF plugin

../../_images/bsdfplugin.jpg

Example of a procedural BSDF plugin

With the SDK, you may create new BSDFs types that will extend the base types included in Ocean.

Class header

To make a new BSDF type, you need to subclass IBsdfPlugin defined in the bsdfplugin.h SDK header, and implement a set of pure virtual functions.

The example below shows the declaration of a simple BSDF type named “test”:

#include <oceansdk/bsdfplugin.h>

class TestBsdf : public Ocean::Sdk::IBsdfPlugin
{
public:
     TestBsdf() : e(1000.0f), R0(0.2f) { }
     ~TestBsdf() { }

     Vec3<float> samplePdf(const Ocean::Sdk::IShaderContext * context,
                           const Vec3<float> & k1t,
                           Ocean::Sdk::ISampler * sampler) const;

     float pdf(const Ocean::Sdk::IShaderContext * context,
               const Vec3<float> & k1t, const Vec3<float> & k2t) const;

     float decay(const Ocean::Sdk::IShaderContext * context,
                 const Vec3<float> & k1t) const;

 void getValues(const IShaderContext * context,
          IStokesContext * stokes,
          const Vec3<float> & kLt, const Vec3<float> & kEt) const;

     const char * typeName() const { return "test"; }
     const char * prettyTypeName() const { return "SDK Test"; }
     Ocean::Sdk::IBsdfPlugin * clone() const { return new TestBsdf(*this); }

     void getParameters(Ocean::Sdk::IParamList * /*iParamList*/) { }
     bool setParameter(const char * /*parameterName*/,
                       const Ocean::Sdk::IParamValue & /*value*/)
     { return false; }

     void addChildren(Ocean::Sdk::IAddChildContext * /*iChildAdd*/) { }
     bool prepare(const Ocean::Sdk::IPrepareContext * /*iPrepare*/)
     { return true; }

private:
     float e;
     float R0;
};

Class implementation

getValues function

Documentation : IBsdfPlugin::getValues()

This is the main BSDF function, which computes BSDF values for a pack of wavelengths.

We implemented below a classical Blinn-Phong metallic BSDF, with an exponent of 1000, and a Schlicks approximation to the Fresnel term, with normal reflectance of 0.2:

void TestBsdf::getValues(const Ocean::Sdk::IShaderContext * context,
                      IStokesContext * stokes,
                      const Vec3<float> & kLt,
                      const Vec3<float> & kEt) const
{
     uint32_t n = context->numWavelengths();
 float * sptr[4];
 stokes->getStokesPointers(sptr);

     //Check if reflection or transmission
     if(kLt[2]*kEt[2]>0.0f)
     {
             //Reflection
             float exponent = 1000.0f;

             //Halfway vector
             Vec3<float> ht = (kLt+kEt).normalized();
             float cosL = std::abs(kLt[2]);
             float cosE = std::abs(kEt[2]);

             //Schlicks approximation
             float omz = 1.0f-std::min(cosL, cosE);
             float fr = R0 + (1.0f-R0)*omz*omz*omz*omz*omz;

             float glossy = fr * (exponent+1)*0.125f*M_1_PIf
                               * std::pow(std::abs(ht[2]),exponent)
                             / ( (kLt|ht) * std::max(cosL, cosE) );

             for(uint32_t i=0;i<n;++i)
             {
                     sptr[0][i] *= glossy;
             }
     }
     else
     {
             //Transmission
             for(uint32_t i=0;i<n;++i)
             {
                     sptr[0][i] = 0;
             }
     }
}

samplePdf function

Documentation : IBsdfPlugin::samplePdf()

This function samples an environment direction and returns the pdf.

We sample the Blinn-Phong reflection model with the classical halfway vector sampling approach:

Vec3<float> TestBsdf::samplePdf(const Ocean::Sdk::IShaderContext * /*context*/,
                            const Vec3<float> & k1t,
                            Ocean::Sdk::ISampler * sampler) const
{
    //Sample glossy reflection
    Vec3<float> ht = sampler->sampleUnitHemisphereF();
    float r2 = sq(ht[0])+sq(ht[1]);
    if(r2>0 && ht[2]>0)
    {
            ht[2] = std::pow(ht[2], 1.f/(e+1));     //ht[2]>0
            float t = std::sqrt((1.0-sq(ht[2]))/r2);        //r2>0
            ht[0] *= t;
            ht[1] *= t;
    }

    //Symmetric of k1t by ht
    return ht * ht.dotProduct(k1t)*2 - k1t;
}

pdf function

Documentation : IBsdfPlugin::pdf()

This function must return the pdf corresponding to the sampling function IBsdfPlugin::samplePdf()

We use the pdf corresponding to the sampling function above:

float TestBsdf::pdf(const Ocean::Sdk::IShaderContext * /*context*/,
                    const Vec3<float> & k1t, const Vec3<float> & k2t) const
{
     //Check if reflection or transmission
     if(k1t[2]*k2t[2]>0.0f)
     {
             Vec3<float> ht = (k1t+k2t).normalized();
             return  (e+1)*0.125f*M_1_PIf * std::pow(std::abs(ht[2]), e) / (k1t|ht);
     }
     else
     {
             return 0;
     }
}

decay function

Documentation : IBsdfPlugin::decay()

This function must return the probability to recurse the ray-tracing (Russian Roulette factor). It should ideally be equal to the energy conservation factor for the given incident vector.

float TestBsdf::decay(const Ocean::Sdk::IShaderContext * context, const Vec3<float> & k1t) const
{
     //Schlicks approximation
     float omz = 1.0f-std::fabs(k1t[2]);
     float fr = R0 + (1.0f-R0)*omz*omz*omz*omz*omz;
     return fr;
}

prepare function

Documentation : IBsdfPlugin::prepare()

To get the wavelength in IBsdfPlugin::addRadianceValues, we need to get the simulation wavelength range during initialization. This is done in this function:

bool TestBsdf::prepare(const Ocean::Sdk::IPrepareContext * iPrepare)
{
     wlMin = iPrepare->wavelengthMin();
     wlMax = iPrepare->wavelengthMax();
}

Result

The image at the top of this page is the result of using this BSDF in our test scene.

Material definition:

<material type="generic" name="previewmaterial">
     <bsdf type="sdktest/test" name="bsdf"/>
</material>