Photon Shaders

Photon shaders are used in the photon tracing phase to compute the photon maps that are used to simulate caustics and global illumination. Like shadow shaders, they are specified in materials, and can share the material shader's parameters, or its implementation. Photon shaders need to use the mi_photon_reflection_* and mi_photon_transmission_* functions to reflect or transmit photons. They can also, if desired, use the built-in functions to generate new photon directions: mi_reflection_dir_* and mi_transmission_dir_*.

The following is a simple photon shader example that handles the interaction between a photon and a simple transmitting material. Notice how the incoming energy is modified before the new photon is transmitted; this is according with the fact that photons move in the opposite direction of the rays in the raytracing phase.

struct ptparm {
   miScalar trans; /* fraction of light transmitted */
   miScalar ior;   /* index of refraction */
};

miBoolean ptrans_photon(
   miColor         *result,
   miState         *state,
   struct ptparm   *paras)
{
   miVector        dir;
   miColor         new_energy;
   miScalar        trans;

   mi_refraction_dir(&dir, state, 1.0,
                     *mi_eval_scalar(&paras->ior));

   trans = *mi_eval_scalar(&paras->trans);
   new_energy.r = result->r * trans;
   new_energy.g = result->g * trans;
   new_energy.b = result->b * trans;

   mi_photon_transmission_specular(&new_energy, state, &dir);
   return(miTRUE);
}

This photon shader only handles transmission and it only generates one photon. It is recommended to produce only one photon in a photon shader. If the photon shader supports several types of reflection or transmission, it should use mi_choose_scatter_type to select only one of these per photon interaction such that only one photon is propagated. If several photons are generated in a photon shader, the number of photons in the photon map might grow very quickly without obtaining the necessary quality.

Incoming photons should be stored on materials with a diffuse surface. This is done in the photon shader by calling mi_store_photon.

To benefit from the photon maps (consisting of the photons stored by the photon shaders), the material shaders should include the illumination from the photon maps using mi_compute_irradiance. This is illustrated in the following example of a simple diffuse surface simulated using a material shader and its corresponding photon shader.

pdif_photon is a simple example of a photon shader that stores a photon; pdif is material shader that displays the photon map. Since the pdif_photon shader only computes caustics, it does not reflect the photon off the diffuse surface. The pdif material shader does not compute direct illumination from the light sources and only takes the caustic into account.

struct pdparm {
   miColor diffuse;
};

miBoolean pdif_photon(
   miColor         *result,
   miState         *state,
   struct pdparm   *paras)
{
   mi_store_photon(result, state);
   return(miTRUE);
}

int pdif_version(void) {return(1);}

miBoolean pdif(
   miColor         *result,
   miState         *state,
   struct pdparm   *paras)
{
   miColor         irrad, *diffuse;

   mi_compute_irradiance(&irrad, state);
   diffuse = mi_eval_color(&paras->diffuse);

   result->r = irrad.r * diffuse->r;
   result->g = irrad.g * diffuse->g;
   result->b = irrad.b * diffuse->b;
   result->a = 1.0;
   return(miTRUE);
}

In a scene consisting of a glass lens that creates a caustic on a ground plane, four shaders need to be attached:

Here is a more complete example of a photon shader. It simulates diffuse, glossy, and specular reflection, and can be used for both caustics (both caustic generation and receiving) and global illumination.

miBoolean dgs_material_photon(
    miColor              *energy,
    miState              *state,
    struct dgs_material  *paras)
{
    struct               dgs_material m;
    miColor              color;
    miVector             dir;
    miScalar             ior_in, ior_out;
    miRay_type           type;
    miBoolean            ok;

    /*
     * Make a local copy of the parameters (light
     * sources are not used here)
     */

    m.diffuse  = *mi_eval_color (&paras->diffuse);
    m.glossy   = *mi_eval_color (&paras->glossy);
    m.specular = *mi_eval_color (&paras->specular);
    m.shiny    = *mi_eval_scalar(&paras->shiny);
    m.shiny_u  = *mi_eval_scalar(&paras->shiny_u);
    m.shiny_v  = *mi_eval_scalar(&paras->shiny_v);
    m.transp   = *mi_eval_scalar(&paras->transp);
    m.ior      = *mi_eval_scalar(&paras->ior);

    /*
     * Insert photon in map if this is a diffuse
     * surface.
     */

    if (m.diffuse.r > miEPS ||
        m.diffuse.g > miEPS ||
        m.diffuse.b > miEPS)
            mi_store_photon(energy, state);

    /*
     * Choose scatter type for new photon
     */

    type = mi_choose_scatter_type(state, m.transp,
                                  &m.diffuse,
                                  &m.glossy,
                                  &m.specular);
    /*
     * Shoot new photon: Compute new photon color
     * (compensating for Russian roulette) and shoot
     * new photon in a direction determined by the
     * scattering type
     */

    switch (type) {
                   /* no reflection. or transmission */
      case miPHOTON_ABSORB:
            return(miTRUE);

                   /* specular reflection (mirror) */
      case miPHOTON_REFLECT_SPECULAR:
            color.r = energy->r * m.specular.r;
            color.g = energy->g * m.specular.g;
            color.b = energy->b * m.specular.b;
            mi_reflection_dir_specular(&dir, state);
            return mi_photon_reflection_specular(&color, state, &dir);

                   /* glossy reflection (Ward model) */
      case miPHOTON_REFLECT_GLOSSY:
            color.r = energy->r * m.glossy.r;
            color.g = energy->g * m.glossy.g;
            color.b = energy->b * m.glossy.b;
            if (m.shiny)
                   /* isotropic glossy reflection */
                    mi_reflection_dir_glossy(&dir, state, m.shiny);
            else {     /* anisotropic glossy reflection */
                    miVector u, v;
                    miASSERT(m.shiny_u > 0 && m.shiny_v > 0);
                    anis_orientation(&u, &v, state);
                    mi_reflection_dir_anisglossy(&dir, state,
                                       &u, &v, m.shiny_u, m.shiny_v);
            }
            return(mi_photon_reflection_glossy(&color, state, &dir));

                   /* diffuse (Lamberts cosine law) */
      case miPHOTON_REFLECT_DIFFUSE:
            color.r = energy->r * m.diffuse.r;
            color.g = energy->g * m.diffuse.g;
            color.b = energy->b * m.diffuse.b;
            mi_reflection_dir_diffuse(&dir, state);
            return(mi_photon_reflection_diffuse(&color, state, &dir));

                   /* specular transmission */
      case miPHOTON_TRANSMIT_SPECULAR:
            color.r = energy->r * m.specular.r;
            color.g = energy->g * m.specular.g;
            color.b = energy->b * m.specular.b;
            refraction_index(state, &m, &ior_in, &ior_out);
            miASSERT(ior_in >= 1 && ior_out >= 1);

            return(ior_out == ior_in
                ? mi_photon_transparent(&color, state)
                : mi_transmission_dir_specular(&dir, state,ior_in,ior_out)
                ? mi_photon_transmission_specular(&color, state, &dir)
                : miFALSE);
            }

                   /* glossy transmiss. (Ward model) */
      case miPHOTON_TRANSMIT_GLOSSY:
            color.r = energy->r * m.glossy.r;
            color.g = energy->g * m.glossy.g;
            color.b = energy->b * m.glossy.b;
            refraction_index(state, &m, &ior_in, &ior_out);
            miASSERT(ior_in >= 1 && ior_out >= 1);

            if (m.shiny)             /* isotropic glossy transmission */
                    ok = mi_transmission_dir_glossy(&dir,
                                state, ior_in, ior_out, m.shiny);
            else {                   /* anisotropic glossy transmission */
                    miVector u, v;
                    miASSERT(m.shiny_u > 0 && m.shiny_v > 0);
                    anis_orientation(&u, &v, state);
                    ok = mi_transmission_dir_anisglossy(&dir, state,
                           ior_in, ior_out, &u, &v, m.shiny_u, m.shiny_v);
            }
            if (ok)
            return(ok ? mi_photon_transmission_glossy(&color, state, &dir)
                      : miFALSE);

                   /* diffuse transm. (translucency) */
      case miPHOTON_TRANSMIT_DIFFUSE:
            color.r = energy->r * m.diffuse.r;
            color.g = energy->g * m.diffuse.g;
            color.b = energy->b * m.diffuse.b;
            mi_transmission_dir_diffuse(&dir, state);
            return(mi_photon_transmission_diffuse(&color, state, &dir));

      default:     /* Unknown scatter type */
            mi_error("unknown scatter type in dgs photon shader");
            return(miFALSE);
    }
}

The first thing that happens in dgs_material_photon is that the reflection parameters are evaluated with mi_eval_*. Then the photon is stored if the surface is diffuse and the photon is not directly from the light source. Second, the function mi_choose_scatter_type is called. Based on the transparency and reflection coefficients, it decides whether the photon should be absorbed, reflected diffusely, glossily, or specularly, or refracted diffusely, glossily, or specularly. If absorption is not chosen, mi_choose_scatter_type changes the reflection coefficients of the chosen reflection or refraction type to compensate for the fact that the photon "survived". This is a method known as "Russian Roulette".

If the photon is absorbed, dgs_material_photon simply returns at this point. If the photon should be reflected or refracted, the energy of the new photon is computed, a new direction is computed (using the built-in functions to compute reflection and refraction directions), and the photon is emitted in that direction.

Instead of using Russian Roulette to determine whether the photon should be absorbed or not, one could also emit a new photon for each type of reflection and refraction that has a non-zero reflection coefficient. Each emitted photon should have an energy that is the energy of the incoming photon multiplied by the reflection coefficient for that reflection (or refraction) type. However, this means that each photon emitted from the light source can cause many photons to be stored, some with very insignificant energy. The advantage of using Russian Roulette is that all the stored photons have comparable energies - no storage is wasted saving photons with low energy.

Obviously scene creation is simplified if the material shaders are written such that they also function as photon shaders, and also as shadow shaders, because the same shader can be attached to the respective materials three times, as material shader, photon shader, and shadow shader. If no parameters are specified for the photon and shadow shader references, mental ray will pass the material shader arguments to them, so they need not be duplicated twice. A shader can find the context in which it is called by examining state→type.

Copyright © 1986, 2013 NVIDIA Corporation