New Photon Mapping Features

New Photon Mapping Features

April 2012

1   Introduction

PRMan 17 introduces several improvements to and enhanced uses for photon rendering. These include single-pass photon map generation, transient (in memory) photon maps, photon-guided indirect diffuse illumination, a new RSL shader interface for photon emission, motion-blurred photon maps, and volume photon maps. Additionally, photon tracing is now multi-threaded.


2   Single-pass Photon Map Generation

In versions prior to PRMan 17, photon maps had to be generated in a separate pass. This special execution mode was specified with the photon hider:

Hider "photon" "emit" 500000   # photon map generation pass

This meant that if a photon map was generated and then used for rendering the same scene, the RIB file had to be re-parsed and the scene had to be constructed again. Now we can generate a photon map in a pre-pass while only parsing and constructing the scene once. This is specified with an option in the RIB file:

Option "photon" "emit" 500000   # single-pass photon map generation

After parsing the RIB file, PRMan will emit and trace the specified number of photons and then render the scene. Requesting the emission of 0 photons disables the pre-pass.

Note that for backward compatibility, the original hider syntax still works; it simply traces the photons and then skips the render. The speed benefits from the new multi-threaded photon tracing will still apply.


3   Transient (in memory) Photon Maps

Instead of writing the photon map to a file and then reading it back in, it is now possible to keep the photon map in memory. This is done with the following option:

Option "photon" "string lifetime" "transient"   # "transient" or "file"

(Default is "file", i.e., the old behavior.) Here's a RIB file example with both single-pass photon map generation and transient photon map:

Format 512 512 1
Display "singlePassCaustic" "it" "rgba"
PixelSamples 4 4
Projection "perspective" "fov" 20
Transform [-0.707107 -0.408248 -0.57735 0
           0 0.816497 -0.57735 0
           0.707107 -0.408248 -0.57735 0
           0 0 8.66025 1]

# Emit 500000 photons, store them in a transient caustic photon map
# called 'spcaustic.ptc'.  The caustic() function looks up in the
# photon map.
Option "photon" "emit" 500000   # single-pass photon map generation
Option "photon" "string lifetime" "transient"   # "transient" or "file"
Attribute "photon" "causticmap" "spcaustic.ptc"

WorldBegin
  LightSource "shadowspot" 1 "from" [5 5 0] "to" [0 0 0] "intensity" 25
    "coneangle" 0.25 "string shadowname" "raytrace"
  LightSource "causticlight" 2 "color filter" [0.2 0.2 0.2]

  Attribute "visibility" "transmission" 1
  Attribute "visibility" "specular" 1
  Attribute "visibility" "diffuse" 1

  Attribute "photon" "shadingmodel" "matte"
  Surface "matte"
  Polygon "P" [-10 0 10  10 0 10  10 0 -10  -10 0 -10]

  Attribute "photon" "shadingmodel" "chrome"
  Color 0.02 0.62 0.98
  Surface "aachrome" "color specularcolor" [0.02 0.62 0.98]
  Rotate -90 1 0 0
  Cylinder 1 -0.1 0.5 360
  Orientation "inside"
  Cylinder 0.99 -0.1 0.5 360
WorldEnd

Running this RIB file generates the following image of a caustic:

images/figures.newPhotonMapping/singlePassCaustic.jpg

Caustic from a single-pass, transient photon map


4   Separate Control of Photon Scattering Depths

The max diffuse and specular reflection depth for photons can now be controlled separately from the corresponding depths for rays during rendering. The new attributes are:

Attribute "photon" "maxspeculardepth" [-1]
Attribute "photon" "maxdiffusedepth" [-1]

These are similar to the corresponding attributes in the "trace" namespace, but for photons. Their default values are -1, which means use the corresponding "trace" values (which have default values of 2 and 1, respectively). This is for backwards compatibility: the "trace" max depth parameters used to control photon max depth as well (back when these were always in separate passes). Also added another photon depth control, this one for storing photons:

Attribute "photon" "minstoredepth" [0]

This gives the possibility of e.g. not storing photons directly from the light source -- necessary if we want to compute direct illumination by other means (usually ray tracing) and not double-count direct illum.


5   Photon-guided Indirect Diffuse Illumination

The indirectdiffuse() function is used to compute global illumination from diffuse surfaces. It shoots a bunch of rays and averages their colors. Although the implementation contains some clever optimization tricks, such as adaptive hemisphere sampling and irradiance interpolation (using irradiance gradients), the rays are shot "blindly" - we do not know where the brightest surfaces are until after we have shot the rays.

This blind sampling leads to noisy results when the illumination is very uneven, for example due to a bright spotlight or a sunbeam hitting the floor in an otherwise dark room. In such cases, we have to shoot a lot of rays to avoid visible noise, resulting in higher rendering times.

If only we knew where the brightest surfaces are, then we could shoot more rays toward them. Well, it turns out that we can know that: enter photon maps. If we find the nearest photons impinging on the surface near the shading point, and look at their incident directions (i.e., which direction each photon came from: the _incidentdir vector), that provides us with an excellent idea of where most of the light is coming from. The photon directions act as a kind of "oracle" giving us guidance on where to shoot most of the indirect diffuse rays. This is actually a very old idea, from the infancy of the photon mapping method - the idea was first proposed by Henrik Wann Jensen in 1995 (see References).

The name of the photon map that indirectdiffuse() should use can be specified in two ways: 1) with a new parameter to indirectdiffuse() called "photonmap". 2) With the Attribute "photon" "globalmap". If both are present the parameter will take precedence.

In order to accommodate this new use of photon maps, all photon maps now have two new variables: float _incidenttype and float _diffusedepth. _incidenttype indicates which type of scattering (or emission) the photon came from:

0 = unknown scatter type,
1 = a photon directly from a light source,
2 = specularly scattered photon,
3 = diffusely scattered photon,
4 = photon scattered by a volume (currently unused).

_diffusedepth indicates how many diffuse bounces the photon has been through.

Here's an image rendered "the old-fashioned way", without photon guidance:

images/figures.newPhotonMapping/photonGuidedIndirectdiffuseOff.jpg

indirectdiffuse() without photon-guided rays

And here is the same scene rendered with a transient photon map file guiding the indirect diffuse rays:

images/figures.newPhotonMapping/photonGuidedIndirectdiffuse.jpg

Photon-guided indirect diffuse rays

As it can be seen, there is significantly more noise in the original image, but the rendering times for the two images are nearly identical: 16 seconds versus 18 seconds. To get the same noise reduction without photon guiding would require tracing many more rays, incurring much higher render time.

To gain more intuition about the photon guidance, here is an image showing the photon directions:

images/figures.newPhotonMapping/incidentPhotonDirections.jpg

Incident photon directions

The photon density is highest in brightly illuminated areas, and in other areas most of the the incident photon directions point toward these bright areas.

Here is the RIB file that was used to compute the two images above:

FrameBegin 1
  Format 512 512 1
  ShadingInterpolation "smooth"
  PixelSamples 4 4
  Display "photonGuidedIndirectdiffuse" "it" "rgba"
  Projection "perspective" "fov" 30
  Translate 0 0 5

  # Emit 50000 photons, store them in a transient global photon map
  # called 'transientpmguides.gpm'.  The indirectdiffuse() function
  # uses the photon map to guide ray directions.
  Option "photon" "emit" 50000
  Option "photon" "string lifetime" "transient"   # "transient" or "file"
  Attribute "photon" "globalmap" "transientpmguides.gpm"

  WorldBegin
    LightSource "spotlight" 1 "from" [-0.5 0.999 -0.5] "to" [-0.5 0 -0.5]
      "float coneangle" 0.2 "intensity" 40
    LightSource "spotlight" 2 "from" [0 0.75 0.5] "to" [1 0.75 0.5]
      "float coneangle" 0.2 "intensity" 10

    Attribute "visibility" "transmission" 1
    Attribute "visibility" "specular" 1
    Attribute "visibility" "diffuse" 1

    Attribute "photon" "shadingmodel" "matte"

    Surface "indirectsurf"
      "float samples" 256 "float maxvariation" 0

    # Matte box
    AttributeBegin
      Attribute "identifier" "string name" "box"
      Polygon "P" [ -1 1 -1  -1 1 1  -1 -1 1  -1 -1 -1 ]   # left wall
      Polygon "P" [ 1 -1 -1  1 -1 1  1 1 1  1 1 -1 ]   # right wall
      Polygon "P" [ -1 -1 1  1 -1 1  1 -1 -1  -1 -1 -1 ]   # floor
      Polygon "P" [ -1 1 -1  1 1 -1  1 1 1  -1 1 1 ]   # ceiling
      Polygon "P" [ -1 1 1  1 1 1  1 -1 1  -1 -1 1 ]   # back wall
    AttributeEnd

    # Left teapot
    AttributeBegin
      Attribute "identifier" "string name" "left teapot"
      Translate -0.35 -0.999 0.35
      Scale 0.18 0.18 0.18
      Rotate -30 0 1 0
      Rotate -90 1 0 0
      Geometry "teapot"
    AttributeEnd

    # Right teapot
    AttributeBegin
      Attribute "identifier" "string name" "right teapot"
      Color [0.8 0.8 0.8]
      Translate 0.35 -0.999 -0.35
      Scale 0.18 0.18 0.18
      Rotate 30 0 1 0
      Rotate -90 1 0 0
      Geometry "teapot"
    AttributeEnd

  WorldEnd
FrameEnd

(Comment out the line Attribute "photon" "globalmap" "transientpmguides.gpm" and the "string photonmap" "transientpmguides.gpm" parameter to turn off photon-guided indirectdiffuse().)


6   Shader Interface for Photon Emission

Previously, photon emission could be controlled two ways: by automatic analysis and evaluation of a standard light source shader, and by specifying a point cloud that photons should be emitted from.

We have added a third method to control photon emission: a new shader method explicitly for the purpose of emitting photons from light sources:

generatePhoton(output point origin; output vector direction; output color power; output float pdf);

This method generates one photon at a time. The convention is that the assigned power should be the power of the entire light source. PRMan will then divide the power by the appropriate number of photons before emitting the photon. Here is a simple example of the generatePhoton() function for a rectangle/disk/sphere light:

public void generatePhoton(output point origin; output vector direction;
                           output color power; output float pdf)
{
  // Use stdrsl_AreaSampler to generate a photon origin, direction,
  // texture sample coordinates, and light source area
  stdrsl_AreaSampler sampler;
  float ss, tt, lightarea;
  sampler->generatePhoton(shape, sides,
                          origin, direction, ss, tt, lightarea);

  color lc = intensity * lightcolor;
  if (mapname != "") {
    color tex = texture(mapname, ss, tt, ss, tt, ss, tt, ss, tt);
    lc *= tex;
  }

  // Compute power of light source and assign pdf
  power = lc * lightarea;
  pdf = intensity * lightarea;
}

This function uses a stdrsl_AreaSampler convenience function, also called generatePhoton(), to generate a random photon origin and direction from the area light source, as well as texture coordinates and light source area:

public void generatePhoton(string shape; float sides;
                           output point origin; output vector direction;
                           output float ss, tt, lightarea)
{
  uniform vector xdir = vector "shader" (1,0,0);
  uniform vector ydir = vector "shader" (0,1,0);
  uniform vector zdir = vector "shader" (0,0,1);
  uniform float xscl = length(xdir);
  uniform float yscl = length(ydir);
  uniform float radius = 0.5; // disk and sphere have radius 0.5, not 1
  point pos;
  point rnd[1] = 0;

  // Generate texture coordinates and random point on area light
  randomstrat(0, 1, rnd);
  ss = rnd[0][0];
  tt = rnd[0][1];
  if (shape == "rect") {   // compute point on rectangle
    pos = ((ss-0.5), (tt-0.5), 0);
  } else if (shape == "disk") {   // compute point on disk
    float angle = M_TWOPI * ss;
    float r = radius * sqrt(tt);
    pos = (r*cos(angle), r*sin(angle), 0);
  } else if (shape == "sphere") {   // compute point on sphere
    float angle = M_TWOPI * ss;
    float z = 2*tt - 1;
    float r = sqrt(1 - z*z);
    pos = radius * (r*cos(angle), r*sin(angle), z);
  }
  pos = transform("shader", "current", pos);
  origin = pos;

  // Generate random direction
  if (shape == "sphere") {   // uniform distribution over sphere
    float angle, z, r;
    randomstrat(0, 1, rnd);
    angle = M_TWOPI * rnd[0][0];
    z = 2*rnd[0][1] - 1;
    r = sqrt(1 - z*z);
    direction = vector(r*cos(angle), r*sin(angle), z);
  } else {   // shape == "rect" or "disk": cosine distribution
    float angle, r, r2, z;
    randomstrat(0, 1, rnd);
    angle = M_TWOPI * rnd[0][0];
    r2 = rnd[0][1];
    r = sqrt(r2);
    z = sqrt(1-r2);
    if (sides == 2 && random() < 0.5) z = -z;   // flip to back side
    xdir = normalize(xdir); // (in shader space)
    ydir = normalize(ydir);
    zdir = normalize(zdir);
    direction = r*cos(angle)*xdir + r*sin(angle)*ydir + z*zdir;
  }

  // Compute area of light source
  lightarea = xscl * yscl;   // area in "current" space
  if (shape == "sphere")
    lightarea *= PI;
  else if (shape == "disk")
    lightarea *= PI/4;
  if (sides == 2) lightarea *= 2;
}

7   Shader Interface for Photon Scattering (Work in Progress)

The new shader interface for photon emission is phase one of our on-going overhaul of PRMan's photon shading system. We are working on adding a similar shader interface for photon scattering. This will be in a future release of PRMan.

For now, photon scattering can only be controlled by the build-in shading models ("matte", "glass", ...) and by point clouds or brick maps of brdf scattering coefficients.


8   Motion Blurred Photons

In most cases, it is perfectly fine to emit and trace the photons at shutter open time, and smear the shading result within the shutter interval --- as is the usual shading strategy for REYES rendering.

However, there are cases where the photon times need to be taken more accurately into account. Here are three images of a simple scene, two of which have motion blur:

images/figures.newPhotonMapping/caustic_static.jpg

Static scene

images/figures.newPhotonMapping/caustic_mb1.jpg

Moving caustic caster (sphere moving right)

images/figures.newPhotonMapping/caustic_mb2.jpg

Moving caustic receiver (ground plane moving down)

Here is the rib file used to generate the second image above:

FrameBegin 1

  # Description of the viewer: image and camera options
  Format 400 300 1
  PixelSamples 8 8
  ShadingInterpolation "smooth"
  Display "caustic_mb1" "it" "rgba"   # render image to 'it'
  Projection "perspective" "fov" 22
  Translate 0 -0.5 8
  Rotate -40  1 0 0
  Rotate -20  0 1 0

  Option "photon" "emit" 10000000
      Option "photon" "string lifetime" "transient"
  Attribute "photon" "causticmap" "caustic_mb1.cpm"
  Attribute "trace" "bias" 0.001

  Shutter 0.0 1.0   # emit photons at random times within this interval

  WorldBegin

    Attribute "trace" "samplemotion" 1

    # (PlausibleArealight does not have spotlight capability, so use
    # old-school spot light)
    LightSource "spotlight_rts" 1
      "from" [-7 3 -5] "to" [0 0 0]
      "intensity" 100 "coneangle" 0.2
      "float samples" 25

    Attribute "visibility" "transmission" 1   # the objects cast shadows
    Attribute "visibility" "specular" 1
    Attribute "visibility" "diffuse" 1

    # Ground plane
    AttributeBegin
      Attribute "photon" "shadingmodel" "matte"
      Surface "plausibleMatte"
        "float diffuseGain" 1.0
        "float indirectDiffuseSamples" 0
        "string causticMap" "caustic_mb1.cpm"
        "float photonEstimator" 1000
      Scale 3 3 3
      Polygon "P" [ -1 0 1  1 0 1  1 0 -1  -1 0 -1 ]
    AttributeEnd

    # Moving sphere
    AttributeBegin
      Attribute "photon" "shadingmodel" "glass"
      Surface "plausibleGlass"
        "float minSpecularSamples" 25 "float maxSpecularSamples" 25
      Color 1 1 0
      MotionBegin [0.0 1.0]
        Translate -1.1 0.5 -0.2
        Translate -0.9 0.5 -0.2
      MotionEnd
      Sphere 0.5  -0.5 0.5  360
    AttributeEnd

    # Box
    AttributeBegin
      Attribute "photon" "shadingmodel" "chrome"
      Surface "plausibleConductor"
        "float roughness" 0.0001
        "float minSpecularSamples" 25 "float maxSpecularSamples" 25
      Translate 0.8 0.0 -0.8
      Rotate -15  0 1 0
      # plausibleConductor needs st for tangent
      Polygon "P" [ 0 0 0  0 0 1  0 1 1  0 1 0 ]   # left side
        "s" [0 0 1 1] "t" [0 1 1 0]
      Polygon "P" [ 1 0 0  1 0 1  1 1 1  1 1 0 ]   # right side
        "s" [0 0 1 1] "t" [0 1 1 0]
      Polygon "P" [ 0 1 0  1 1 0  1 0 0  0 0 0 ]   # front side
        "s" [0 0 1 1] "t" [0 1 1 0]
      Polygon "P" [ 0 1 1  1 1 1  1 0 1  0 0 1 ]   # back side
        "s" [0 0 1 1] "t" [0 1 1 0]
      Polygon "P" [ 0 1 0  1 1 0  1 1 1  0 1 1 ]   # top
        "s" [0 0 1 1] "t" [0 1 1 0]
    AttributeEnd

  WorldEnd
FrameEnd

9   Photon Mapping for Volumes

We have several new built-in shading models for volume photon scattering. They are: "isotropic", "rayleigh", "hazymie", "murkymie", "henyeygreenstein:g=...", and "henyeygreenstein:g1=...,g2=...,r=...".

For volumes, we treat each photon stored in the photon map as a beam instead of a point. This makes better use of the information in the photon map. (For details, please see the paper by Jarosz et al. listed below.) In PRMan version 17, each photon has a (unnormalized) vector pointing to where it came from. These can be visualized in ptviewer by switching the Render Style to Vector (_incidentdir).

For photon map lookups in volumes, we use radiance estimates that locate the nearest photon beams passing by the lookup point (rather than the nearest photon point positions). The photonmap() function switches to volume lookups when the normal is (0,0,0). For anisotropic volumes, the photonmap() function needs a new parameter to compute the radiance estimates correctly: incident direction I (pointing from viewing position to the shading point P).

Let's start with a simple homogeneous volume with isotropic scattering:

images/figures.newPhotonMapping/volumebox_homo_iso.jpg

Homogeneous volume, isotropic scattering

In this image, the direct illumination is computed with ray tracing, and the indirect illumination with photon map lookups. Here is the rib file used to generate the image above (note how the new photon depth attributes are used):

Option "statistics" "endofframe" 2 "xmlfilename" "volumebox_stats.xml"
Option "limits" "radiositycachememory" 1000000   # 1GB radiosity/opacity cache

FrameBegin 1
  Format 512 512 1
  ShadingInterpolation "smooth"
  PixelSamples 8 8
  Display "volumebox_homo_iso.jpg" "it" "rgba"
  Projection "perspective" "fov" [25]
  Translate 0 0 5

  Option "photon" "emit" 1000000
  Option "photon" "string lifetime" "transient"
  Attribute "photon" "globalmap" "volumebox_homo_iso.vpm"

  WorldBegin
    Attribute "visibility" "int transmission" [1] # for shadow ray extinction
    Attribute "visibility" "int specular" [1]   # for reflections
    Attribute "trace" "maxspeculardepth" 1
    Attribute "trace" "maxdiffusedepth" 0   # no diffuse rays
    # Up to 5 specular and diffuse bounces for photons
    Attribute "photon" "maxspeculardepth" 5
    Attribute "photon" "maxdiffusedepth" 5
    Attribute "photon" "minstoredepth" 1   # don't store direct photons

    # Area light
    AttributeBegin
      Translate 0 0.99 0
      Scale 0.05 1 0.05
      AttributeBegin
        Rotate 90 1 0 0
        LightSource "plausibleArealight" "arealight"
          "float sides" [1] "float intensity" [5000] "string shape" ["rect"]
          "float minSamples" [9] "float maxSamples" [16]
      AttributeEnd
      AttributeBegin
        Surface "constant"
        Attribute "visibility" "int transmission" [0] "int photon" [0]
        Polygon "P" [-0.5 0 -0.5  0.5 0 -0.5  0.5 0 0.5  -0.5 0 0.5]
      AttributeEnd
    AttributeEnd
    Illuminate "arealight" 1

    # Volume (homogeneous extinction, isotropic scattering)
    AttributeBegin
      Attribute "photon" "shadingmodel" "isotropic"
      ShadingRate 16
      Color 0.6 0.6 0.8   # blueish tint
      Opacity 0.5 0.5 0.5
      # Compute direct illum with ray tracing; indirect illum with photon beams
      Surface "photonVolumeHomo"
        "string photonMap" "volumebox_homo_iso.vpm"
        "float lookupPhotons" 200
      Volume "box" [-1 1  -1 1  -0.4 0.4] [2 2 2]   # box
    AttributeEnd

    # Left teapot
    AttributeBegin
      Attribute "photon" "shadingmodel" "chrome"
      Surface "plausibleConductor"
        "float roughness" 0.001
        "float minSpecularSamples" 9 "float maxSpecularSamples" 9
      Translate -0.35 0 0.1
      Scale 0.15 0.15 0.15
      Rotate -30 0 1 0
      Rotate -90 1 0 0
      Geometry "teapot" # (has no bottom; lid doesn't close)
    AttributeEnd

    # Right teapot
    AttributeBegin
      Attribute "photon" "shadingmodel" "matte"
      Surface "plausibleMatte"
      Color 1 0 0   # red
      Translate 0.35 -0.0 -0.1
      Scale 0.15 0.15 0.15
      Rotate 30 0 1 0
      Rotate -90 1 0 0
      Geometry "teapot" # (has no bottom; lid doesn't close)
    AttributeEnd

  WorldEnd
FrameEnd

And here is the shader:

// photonVolumeHomo:
// A plausible volume material with homogeneous extinction.
// Uses a simple isotropic scattering phase function.
class photonVolumeHomo (
  varying float density = 1;
  uniform color albedo = color(1,1,1);
  uniform string photonMap = "";
  uniform float lookupPhotons = 50;
  uniform string lightCategory = "";
  uniform float __computesOpacity = 1;
  )
{
  // Member variables
  stdrsl_ShadingContext m_shadingCtx;
  stdrsl_IsotropicVolume m_diffuse;
  stdrsl_Fresnel m_fresnel;

  public void opacity(output color Oi) {
    Oi = density * Os; // homogeneous extinction
  }

  public void diffuselighting(output color Ci, Oi) {
    color diffColor = albedo * Cs;
    m_diffuse->init(diffColor);

    Ci = directlighting(this, getlights("category",lightCategory),
                        "integrationdomain", "sphere");

    if (photonMap != "") {
      Ci += diffColor *
            photonmap(photonMap, P, normal(0), "estimator", lookupPhotons);
    }

    Oi = density * Os;
    Ci *= Oi;
  }

  public void evaluateSamples(string distribution;
                              output __radiancesample samples[])
  {
    if (distribution == "diffuse")
      m_diffuse->evalDiffuseSamps(m_shadingCtx, m_fresnel, samples);
  }
}

A variation on this theme is to compute all illumination with photon maps. The only changes required for this is to change the Attribute "photon" "minstoredepth" from 1 to 0, and replace the line

Ci = directlighting(this, getlights("category",lightCategory),
                  "integrationdomain", "sphere");

with

Ci = 0;.

Rendering this gives an image that is very similar to the previous image; the main difference is that the edges of the volume shadow are a bit more blurry. (Emitting more photons makes the edges sharper.)

Finally, here's a more fancy example with a turbulent inhomogeneous volume with anisotropic scattering:

images/figures.newPhotonMapping/volumebox_inhomo_aniso.jpg

Inhomogeneous volume, anisotropic scattering

In this image, both direct illumination and indirect illumination is computed with photon map lookups. The only changes in the rib file are:

1) Change the name of the photon map from volumebox_homo_iso.vpm to e.g. volumebox_inhomo_aniso.vpm. 2) change minstoredepth from 1 to 0. 3) Change the shadingmodel from "isotropic" to e.g. "henyeygreenstein:g=0.5". 4) Change the shader from photonVolumeHomo to photonVolumeTurbulence with the following parameters:

Surface "photonVolumeTurbulence"
  "string photonMap" "volumebox_inhomo_aniso.vpm"
  "float lookupPhotons" 200
  "float baseopa" 0.1 "float amplitude" 0.7 "float freq" 5
  "float octaves" 5

Here is the photonVolumeTurbulence shader:

// photonVolumeTurbulence:
// A plausible volume material with turbulent variation of the extinction.
// Uses anisotropic scattering phase function as defined by the shading model
// attribute.
class photonVolumeTurbulence (
  varying float density = 1;
  uniform color albedo = color(1,1,1);
  uniform string photonMap = "";
  uniform float lookupPhotons = 50;
  uniform float baseopa = 0.0;
  uniform float amplitude = 1;
  uniform float freq = 10.0;
  uniform float octaves = 3;
  uniform float lacunarity = 2;
  uniform float gain = 0.5;
  uniform float __computesOpacity = 1;
  )
{
  // Member variables
  stdrsl_IsotropicVolume m_diffuse;

  // Perlin's turbulence function ("Advanced RenderMan" page 253)
  // Aka. "1/f noise" if gain = 1/lacunarity
  private float
  turbulence(point pp; uniform float octaves, lacunarity, gain) {
    point ppo = transform("object", pp);
    varying float sum = baseopa, amp = amplitude, snoise;
    uniform float i;

    for (i = 0; i < octaves; i += 1) {
      snoise = 2*noise(freq*ppo) - 1; // signed noise
      sum += amp * abs(snoise);
      amp *= gain;
      ppo *= lacunarity;
    }

    return sum;
  }

  public void opacity(output color Oi) {
    Oi = turbulence(P, octaves, lacunarity, gain);
  }

  public void diffuselighting(output color Ci, Oi) {
    color diffColor = albedo * Cs;
    m_diffuse->init(diffColor);
    Ci = 0;

    if (photonMap != "") {
      Ci = diffColor *
           photonmap(photonMap, P, normal(0), "estimator", lookupPhotons,
                     "incdir", I);
    }

    Oi = turbulence(P, octaves, lacunarity, gain);
    Ci *= Oi;
  }
}

10   References

  • Henrik Wann Jensen. Importance driven path tracing using the photon map. Rendering Techniques '95 (Proceedings of the Eurographics Workshop on Rendering 1995), pp. 326-335.
  • Henrik Wann Jensen and Per H. Christensen. Efficient simulation of light transport in scenes with participating media using photon maps. Proc. SIGGRAPH 1998, pp. 311-320.
  • Mike Cammarano and Henrik Wann Jensen. Time dependent photon mapping. Rendering Techniques '02 (Proceedings of the Eurographics Workshop on Rendering 2002), pp. 135-143.
  • Wojciech Jarosz, Derek Nowrouzezahrai, Iman Sadeghi, and Henrik Wann Jensen. A comprehensive theory of volumetric radiance estimation using photon points and beams. ACM Transactions on Graphics, 30(1), 2011.