Writing volume shaders

This page describes the options to exchange data between a Softimage object and its volume shader. For more datails on the Arnold volume rendering API, see the Arnold wiki page.

A volume shader gets called as many times as required by the ray as it marches through the media, with the base marching step defined by Step Size.
Some shaders may be data independent, meaning that they compute the absorption emission and scattering values out of a given function (say a noise), and call the respective API calls.
In many case though, shaders will depend on some data set, for instance a voxel grid or a particle cloud.

For the first family of shaders, there is no need to exchange data between the containing object and the volume shader. The values to be compute will usually depend on sg->P, i.e., for volume shaders, the current raymarched point.

For data-dependent shaders, there are two ways to pass data, depending on the object type (polymesh/pointcloud).

Polymeshes

If the original volume object is a Softimage polymesh, no extra data are exported with the box.
The shader writer should provide the optional dataset either by a file (being the file path the main shader's parameter) or by a User Data Blob.
As described in the blob documentation, the shader writer must provide a way to write the data into the blob, and then retrieve it in the shader, by looking up the user data attached to the shader's owner (sg->Op).

Pointclouds

For ICE pointclouds, you can go with blobs as well, but the best way is to exploit ICE attributes.
Together with the pointcloud box, SItoA exports a sub-set of the ICE attributes as user data of the box. So, a volume shader will be able to retrieve those attributes, by looking up the user data of the shader's owner.
So, it works similarly to the blob data, but

  1. The data are written automatically by SItoA into the box user data
  2. There is no data conversion needed. While the blob passes a raw array of bytes, the ICE attributes structure and data types are preserved when writing them to Arnold.

 

A shader must declare the list of attributes he wants to receive, by a special statement line in the output section of its spdl.
Some of the BA shaders (currently unsupported) coming with Softimage have such a line. An example is the following

Meaning that the shader needs PointPosition, Color, Size, ArnoldVolume0 to work properly.

The attributes that you can declare in the shaders are the following: Age, AgeLimit, Color, Id, PointVelocity, Size, StrandColor, StrandPosition, StrandSize, SizeVelocity.
Then, there are 10 extra names reserved: ArnoldVolume0, ArnoldVolume1, ... ArnoldVolume9, that you can use to pass further attributes.

Say for example you need to pass StrandCount (the number of points per strand). What you can do is create a new ICE attribute called ArnoldVolume0, and set it equal (using a Set Data node) to the StrandCount array.
Then, if ArnoldVolume0 is declared in the spdl output, as shown above, you can retrieve the array in the shader.

The following attribute types are supported: boolean, long, float, vector, rgba, matrix4, rotation, string, and strand arrays of boolean, long, float, vector, rgba, matrix4, rotation .

Examples

Let's see an example, using a very stupid shader called particle_density, implemented by particle_density.cpp and particle_density.spdl.
This shader loops the PointPosition (the positions of the points). If the distance between at least one of the points and the current volume point (sg->Ro) is less than a given threshold (the Max Radius parameter), then the point's color is returned, and set as the volume scattering and attenuation value.

To start, we emit a few particles from a grid. The cloud has the needed attributes set by default (PointPosition and Color), and its Step Size set to 0.1.

 

If we want to check what happens at export time, we can dump an ascii .ass file, and under the pointcloud box we read:

What the shader does is loading the attributes (only once) and storing them into the shader local data. This section reads PointPosition, and stores the points into a stl vector:

Then, at evaluating time, the points vector is looked up. If at least one point is closer than max_radius to sg->Ro, its color is returned and used for the volume rendering calls.

This is the result without shadows.

 

A further shader, strand_density, implemented by strand_density.cpp and strand_density.spdl, does the same, but uses the strand position.

If you create some strands and export them to .ass, you'll read:

meaning that there are 8 strands, the first 2 made of 5 points. This shows how the strands arrays are exported as constant arrays of arrays, and each sub-array contains the data of a single strand.

The section of strand_density reading the points then becomes:

  • No labels