##########################################
Writing a Custom Software Rendering Shader
##########################################


This tutorials illustrates the software shading API of HOOPS Luminate. It defines a simple material, suited exclusively for the CPU and containing three render shaders:

  * an ambient shader in the pre-lighting pass
  * a lighting shader in the lighting pass
  * a reflection shader in the post-lighting pass

The scene is very simple:

  * the familiar ground plane and spot light
  * a cylinder created with the RED::IMeshShape::Cylinder function

********************************
Declaration of a Software Shader
********************************

As explained in the ':doc:`/book/subjects/bk_bm/bk_bm_wcm/bk_bm_wcr/bk_bm_custom_custom_cpu`' page, the software shaders are just simple callbacks. In the tutorial, the ambient shader callback is:

.. code:: cpp

  RED_RC AmbientSoftwareShader( RED::SoftFrameBufferSample&       ioSample,
                              const RED::ISoftRayContext&       iRayContext,
                              const RED::ISoftShaderContext&    iShaderContext,
                              const RED::ISoftRenderingContext& iRenderContext,
                              const RED::Version&               iVersion, 
                              void*                             iUserData )
  {
    // Simply output the color parameter.
    iShaderContext.GetProgramLocal( ioSample._out_color, 0, iRayContext, iRenderContext );
    return RED_OK;
  }


The shading method must be visible in the dll or exe export symbols. This is easily performed by adding a '.def' file to the build with the list of symbols to export. Our '.def' file contains the three symbols for the three shading callbacks:

.. code:: text

  EXPORTS
    AmbientSoftwareShader       @1
    LightingSoftwareShader      @2
    ReflectionSoftwareShader    @3

In addition to this, the shader program itself sent to HOOPS Luminate must point to the export function. The next sample of code is used to bind the ambient software shader:

.. code:: cpp

  RED::ShaderProgramID sshid;
  RED::ShaderString ssh;
  RED::Version version;

  ssh.SoftShaderStart( "WritingCustomSoftwareShader.exe", "AmbientSoftwareShader", version );
  ssh.ShaderEnd();

  RC_TEST( iresmgr->LoadShaderFromString( sshid, ssh ) )

  RED::RenderCode code;
  code.BindChannel( RED_VSH_VERTEX, RED::MCL_VERTEX );

  RC_TEST( ioShader.SetRenderCode( code, RED_L0, RED::HW_SOFT_TRACER ) );
  RC_TEST( ioShader.SetVertexProgramId( sshid, RED_L0, resmgr, RED::HW_SOFT_TRACER ) );
  RC_TEST( ioShader.SetPixelProgramId( sshid, RED_L0, resmgr, RED::HW_SOFT_TRACER ) );


This sequence of code tells HOOPS Luminate that the shader string whose id is 'sshid' is used as vertex / pixel shader for the ``RED::RenderShader`` named 'ioShader' and that for the ``RED::HW_SOFT_TRACER`` hardware platform, we'll load the 'AmbientSoftwareShader' method from the 'WritingCustomSoftwareShader.exe' module.

*******************************
Writing Software Ambient Shader
*******************************

We'll start the tutorial by defining a CPU ambient shading term. Defining the ambient term is important because of the default blending equations that are additive for the ``RED::MTL_LIT`` and ``RED::MTL_POSTLIT passes``. If we don't start by painting the back color of our object, we'll get wrong transparent objects. Our shader produces the following image:

.. figure:: wf_WritingCustomSoftwareShader01.jpg
    :align: center
    
    **The cylinder with ambient shader**

The ambient shading is really simple:

  * there is a binding part targeting the ``RED::HW_SOFT_TRACER`` platform identifier
  * there is a program part that uses the software shading method prototype

Both code samples for the ambient shading are in the previous paragraph.

The color is defined by creating a color parameter during the binding part:

.. code:: cpp

  RED::RenderShaderParameter param( "color", 0, RED::RenderShaderParameter::PSH );
  param.SetValue( RED::Color( 0.0, 0.5, 1.0, 1.0 ) );

  RC_TEST( ioShader.AddParameter( param, RED_L0, RED::HW_SOFT_TRACER ) );

  
This parameter could then be accessed in the shading function using the ``RED::ISoftShaderContext::GetProgramLocal`` method.

.. code:: cpp

  RED_RC AmbientSoftwareShader( RED::SoftFrameBufferSample&       ioSample,
                              const RED::ISoftRayContext&       iRayContext,
                              const RED::ISoftShaderContext&    iShaderContext,
                              const RED::ISoftRenderingContext& iRenderContext,
                              const RED::Version&               iVersion, 
                              void*                             iUserData )
  {
    // Simply output the color parameter.
    iShaderContext.GetProgramLocal( ioSample._out_color, 0, iRayContext, iRenderContext );
    return RED_OK;
  }


As you can see, the ambient shading function is really simple: it just gets the color parameter and copies it into the output color of the ``RED::SoftFrameBufferSample``.

********************************
Writing Software Lighting Shader
********************************

There are differences between the CPU and GPU lighting passes:

  * The GPU lighting pass is cumulative and all shaders in the ``RED::MTL_LIT`` pass are executed once per light
  * The CPU lighting pass is additive and the software shading method is called once

Therefore, the software shading method must do a loop to cumulate the contribution of all lights

.. code:: cpp

  RED_RC LightingSoftwareShader( RED::SoftFrameBufferSample&       ioSample,
                               const RED::ISoftRayContext&       iRayContext,
                               const RED::ISoftShaderContext&    iShaderContext,
                               const RED::ISoftRenderingContext& iRenderContext,
                               const RED::Version&               iVersion, 
                               void*                             iUserData )
  {
    // Access interpolated mesh parameters:
    double hit[4];
    double normal[4];
    iRayContext.GetWCSHit( hit );
    iRayContext.GetWCSNormal( normal, iShaderContext.GetRenderCode(), RED_VSH_NORMAL );

    // Access shading parameters:
    double color[4];
    iShaderContext.GetProgramLocal( color, 0, iRayContext, iRenderContext );

    int nb_samples;
    double ldir[4], lcolor[4], lshadows[4], dot;
    double ldif[4], lsum[4], latt[4];
    double contrib[4], indirect[4];
    const RED::ISoftLight* light;

    // Initialize the output color:
    ioSample._out_color[0] = 0.0;
    ioSample._out_color[1] = 0.0;
    ioSample._out_color[2] = 0.0;
    ioSample._out_color[3] = 0.0;

    // If the lighting can be approximated, do it:
    if( iRayContext.CanApproximateLighting( contrib, indirect, hit, normal, iRenderContext ) )
    {
      ioSample._out_color[0] += contrib[0] * color[0];
      ioSample._out_color[1] += contrib[1] * color[1];
      ioSample._out_color[2] += contrib[2] * color[2];
    }
    else
    {
      // A call to this function is mandatory before accessing the lights for shading:
      RC_TEST( iRayContext.PrepareLights( hit, normal, false, iRenderContext ) );

      // Calculate the lighting for each light:
      while( ( light = iRayContext.GetLight( iRenderContext ) ) != NULL )
      {
        // Perform light culling:
        if( !light->GetAttenuation( latt, iRayContext.GetThreadID() ) )
          continue;

        lsum[0] = 0.0;
        lsum[1] = 0.0;
        lsum[2] = 0.0;
        lsum[3] = 0.0;

        light->GetDiffuseIntensity( ldif );

        // Does the light have to be sampled?
        if( light->NeedSampling() )
        {
          nb_samples = 0;

          // Do the light sampling until their are no more samples.
          bool moresamples = true;
          do
          {
            RC_TEST( light->GetNextWCSSample( moresamples, ldir, lcolor, lshadows, hit, normal, iRayContext, iRenderContext ) );
            if( !moresamples )
              break;

            ++nb_samples;

            // Calculate the sample lighting:
            dot = normal[0] * ldir[0] + normal[1] * ldir[1] + normal[2] * ldir[2];
            if( dot > 0.0 )
            {
              lsum[0] += lshadows[0] * dot * lcolor[0];
              lsum[1] += lshadows[1] * dot * lcolor[1];
              lsum[2] += lshadows[2] * dot * lcolor[2];
              lsum[3] += lshadows[3] * dot * lcolor[3];
            }
          }
          while( true );

          // Normalize the sampling result:
          if( nb_samples )
          {
            lsum[0] /= nb_samples;
            lsum[1] /= nb_samples;
            lsum[2] /= nb_samples;
            lsum[3] /= nb_samples;
          }

          // Store the lighting term:
          lsum[0] = color[0] * ldif[0] * lsum[0];
          lsum[1] = color[1] * ldif[1] * lsum[1];
          lsum[2] = color[2] * ldif[2] * lsum[2];
        }
        else
        {
          // If the light does not need sampling, just calculates the lighting term.
          light->GetWCSLightDir( ldir, hit, NULL );

          dot = normal[0] * ldir[0] + normal[1] * ldir[1] + normal[2] * ldir[2];
          if( dot > 0.0 )
          {
            // Unlike sampled lights, the shadow term need to be get manually:
            iRayContext.GetShadowColor( lshadows, light, iRenderContext, NULL );

            // Store the lighting term:
            lsum[0] = color[0] * ldif[0] * lshadows[0] * latt[0] * dot;
            lsum[1] = color[1] * ldif[1] * lshadows[1] * latt[1] * dot;
            lsum[2] = color[2] * ldif[2] * lshadows[2] * latt[2] * dot;
          }
        }

        // Cumulate the lighting for each light:
        ioSample._out_color[0] += lsum[0];
        ioSample._out_color[1] += lsum[1];
        ioSample._out_color[2] += lsum[2];
      }
    }

    ioSample._out_color[3] = 1.0;

    return RED_OK;
  }



Don't be afraid of the size of the function, it is quite simple. The code is pretty self-explanatory. However, a few remarks of importance have to be raised:

  * It's up to the method writer to choose it's sampling method. In this implementation, we follow the HOOPS Luminate standards: the lights may require sampling or not (``RED::ISoftLight::NeedSampling``), and we have implemented both strategies
  * Light source culling must be performed manually: the engine does not make any assumption here, hence call to the ``RED::ISoftLight::GetAttenuation`` method to early skip the useless sampling
  * The built-in ``RED::ISoftLight::StartSampling`` / ``RED::ISoftLight::GetNextWCSSample`` methods are already taking shadows and light source attenuation into consideration

The lighting function follows the basic lambertian reflectance:

**material_diffuse * light_diffuse * shadows * light_attenuation * dot( light_direction, normal )**

.. figure:: wf_WritingCustomSoftwareShader02.jpg 
    :align: center

    **The cylinder with ambient and lighting shaders**

To obtain the previous image, the ambient color have been changed to full black (no ambient lighting):

.. code:: cpp

  // Get the ambient shader:
  RED::Shader* sh;
  RC_TEST( imat->GetShader( RED::Object::GetIDFromString( "Ambient" ), RED::MTL_PRELIT, sh, RED::LayerSet::ALL_LAYERS, state ) );
  RED::RenderShader* ambient = sh->As< RED::RenderShader >();

  // Get the ambient color parameter and set it black:
  RED::RenderShaderParameter* ambientcolor;
  RC_TEST( ambient->GetParameter( ambientcolor, "color", RED_L0, RED::HW_SOFT_TRACER ) );
  ambientcolor->SetValue( RED::Color::BLACK );


**********************************
Writing Software Reflection Shader
**********************************

We'll complete this tutorial by the addition of a ``RED::MTL_POSTLIT`` shader, which is easy to understand:

.. code:: cpp

  RED_RC ReflectionSoftwareShader( RED::SoftFrameBufferSample&       ioSample,
                                 const RED::ISoftRayContext&       iRayContext,
                                 const RED::ISoftShaderContext&    iShaderContext,
                                 const RED::ISoftRenderingContext& iRenderContext,
                                 const RED::Version&               iVersion,
                                 void*                             iUserData )
  {
    // Accessing context parameters
    double reflect[4];
    iRayContext.GetReflectionColor( reflect );

    // Accessing shading parameters
    double color[4];
    iShaderContext.GetProgramLocal( color, 0, iRayContext, iRenderContext );

    ioSample._out_color[0] = reflect[0] * color[0];
    ioSample._out_color[1] = reflect[1] * color[1];
    ioSample._out_color[2] = reflect[2] * color[2];
    ioSample._out_color[3] = reflect[3] * color[3];

    return RED_OK;
  }


The reflection is easily get using the ``RED::ISoftRayContext::GetReflectionColor`` function. The reflection term is then modulated by the input color.

In addition to the custom reflection shader, a ``RED::RayReflectionShader`` must be added in the ``RED::MTL_RAYTRACE`` material pass in order to setup HOOPS Luminate to handle reflections.

.. code:: cpp

  // Add a RED::RayReflectionShader to configure the software reflections:
  RED::StateShader shstate;
  RED::RayReflectionShader rayreflect;
  RC_TEST( rayreflect.Setup( NULL, RED::Matrix::IDENTITY, RED::MCL_TEX0, RED::MCL_USER0,
                            shstate, 1.0f, true, 0.0f, 1, resmgr ) );

  RC_TEST( imat->RegisterShader( rayreflect, state ) );
  RC_TEST( imat->AddShaderToPass( rayreflect.GetID(), RED::MTL_RAYTRACE, RED::LIST_LAST, RED::LayerSet::ALL_LAYERS, state ) );


.. figure:: wf_WritingCustomSoftwareShader03.jpg 
    :align: center

    **The cylinder with ambient, lighting and reflection shaders**

Finally, we have added a performance shader called ``RED::RayCutoffShader`` in the ``RED::MTL_RAYTRACE`` material pass. Its purpose is to early terminate the ray propagation if the ray's importance for the scene can be neglected after reflections and refractions.

.. code:: cpp

  // Add a performance cutoff shader
  RED::RayCutoffShader raycut( RED::Color( 0.3f ), NULL, RED::Matrix::IDENTITY, RED::MCL_TEX0,
                              RED::Color::WHITE,  NULL, RED::Matrix::IDENTITY, RED::MCL_TEX0,
                              1, NULL,
                              RED::Color::WHITE,  NULL, RED::Matrix::IDENTITY, RED::MCL_TEX0,
                              1, shstate, resmgr, rc );
  RC_TEST( rc );
  RC_TEST( imat->RegisterShader( raycut, state ) );
  RC_TEST( imat->AddShaderToPass( raycut.GetID(), RED::MTL_RAYTRACE, RED::LIST_LAST, RED::LayerSet::ALL_LAYERS, state ) );
