Custom RIS Flame Plugin




With the release of Renderman 19, Pixar has included their RIS rendering system. RIS has the ability to import OpenVDB files- allowing Houdini simulations to be incorporated into a Maya scene. However, as with many first releases, RIS - specifically the PxrVolume Shader - isn't a very friendly tool for artists.

I created a Maya plugin for RIS that can be used by artists for shading flame effects. The node uses vdb attributes in order to drive the look.






Exporting/Importing VDBs


First, a brief overview of exporting and importing VDBs from Houdini to Maya. First, create a nice pyro simulation.


After creating a pyro simulation, the vdbs are converted and written out from inside the "import_pyro_build" node. Immediately after the "import_pyrofields" node, the simulation is prepared to be written as a VDB with a "convert_vdb" node. In the attributes of this node, there are a few settings that need to be changed. Use 'Convert to VDBs', and set the VDB Class to 'Convert SDF to Fog'. Also, under the 'Group' dropdown, choose the density and temperature attributes (as well as any you may want to import into Maya, such as heat). This part is absolutely necessary for the shader in Maya to work, so make sure your simulation has these attributes.

Finally, write out the vdbs using a "file" node and playing through the simulation.

To import the vdbs into Maya with the RIS renderer, use the OpenVDB button on the Renderman shelf. Once the vdbs are read in through the OpenVDBRead, more attributes are accessable.





Warning: Rendering hotkeys don't work with these OpenVDBs as of Winter 2015. If you hit 'R' to render in 'it', your vdb's will magically disappear. You need to ALWAYS actually click the render button. If they do go away, just save the scene and reopen it to re-enable rendering.



Pixar Flame Setup


By default, Maya attatches a PxrVolume Material to the VDB, with a density attribute attatched through a PixarPrimvar. This doesn't do much for a flame simulation, to be honest.

The folks over at Pixar have set up a very effective variation on this default setup in order to achieve a flame shader. This setup requires two seExpression nodes in order to manipulate the values coming out of the density Primvar. These SeExpressions are then used to drive PxrVolume attributes 'density float' (using the density result) and 'emit color' (using the incandescence result).


Result







The issue comes when an artist needs to change the look development of their flame shader. With the SeExpression equations, it's extremely difficult to manipulate the how the attributes from the vdb effect the attributes of the volume shader. It's possible of course to change the values, but difficult to predict the results.



The "eo_flames" Plugin




The "eo_flames" plugin can be used to replace the SeExpressions with a single node that has more user-friendly controls. At it's core, it's Pixar's code bundled into a usable interface. The default values of "eo_flames" are also the ones used by Pixar. The hypershade setup is similar as well: Primvars are attached into "eo_flames". The 'result_Density' and the 'result_Incandescence' outputs in turn drive the PxrVolume's attributes 'density float' and 'emit color' respectively. The one recommendation I have that deviates from Pixar's setup is the use of a second vdb attribute, 'temperature', which I find has a nicer effect.



Also, I have (with some help) created SplineByDist, a RIS plugin that has the ability to ramp between colors on the flame shader. This ramp can even be driven by vdb attributes such as temperature. Find the breakdown at 'RIS Spline Plugin'.



Render Settings


By default, the renderer is really noisy and grainy. While part of this may have to do with quality of the vdb import, a lot has to do with the RIS render settings.

Indirect Quality at 6 vs. default


In the Sampling tab in RIS's Render Settings, the quality can be improved through the max/min sampling relationship. If you already have a setup you've used in the past that you like, that's fine. For my scene, I used a value of 0 for the minimum number of samples and brought the maximum samples up to 1024.

In my scene, there are no lights. The only illumination in the scene is coming from the flame itself. This means that the indirect lighting sampling is responsible for the quality of the render. Under the Sampling tab for the Render Settings, increase the indirect lighting sampling. I used around 6, with 8 being about the highest I'd go before the rendering quality didn't outweigh the render time. Because I had no other lights in my scene, I turned the direct lighting settings to 0 in order to improve render times.



Comparisons



Pixar's method v. "eo_flames"


If the code is essentially the same, what explains the difference in render output?


Several things. The over exposed look of Pixar's shader is caused by setting the color's value over 1. This can be achieved within the “eo_flames” plugin's color attribute, but it doesn't happen automatically. The second is the way that the code calculates bias in the .cpp file differs from the SeExpression editor. I tried a number of different definitions for the 'bias' function, including Pixar's own explanation of the SeExpression editor's expression, but the look never quite matches perfectly.

In the end, I decided on Pixar's SeExpression definition, although I have left the other 'options' within the comments of the .cpp file.

Despite the differences in the look, the attributes of SeExpression can be changed to more closely match Pixar's.

//Function pxslFloatBias creates a 'bias' - different algorithms determine
//how the bias is calculated, which changes the flame's shape.
// The ones I didn't like I left, but commented out. 
  
float pxslFloatBias(  double attribute,  double bias ) {
    //float tb = pow(attribute, 1/bias); //gamma bias
   //float tb = pow(attribute, -log(bias +.5 )/log(2) );
   //float tb = bias / ((1/max(attribute,1e-4) - 2) * (1 - bias) + 1);
   float tb = pow(attribute, -log(bias)/log(0.5) );
  
return tb;
    }



Pixar's Method v. "eo_flames" with altered attribute settings




Attributes


Breaking down the individual attribute sliders in “eo_flames” is tricky to visualize, since many of theses controls rely on each other. This means it's not really possible to show the difference with single renders.

Density Parameters:
The Density parameters control the effect of the density output, which drives the PxrVolume's density float attribute.


Primvar Density Input:

Plugin for PixarPrimvar calling 'density'

Density Bias:

adjusts the curve that defines the density values from the Primvar. Generally, the higher the number the more details are included. Too high and the vdb's bounding box is visible

Density Scale:

similar to how the density scale works in the PxrVolume's attributes, it controls the overall presence/brightness of the vdb volume

Density Min:

the lowest amount of density that the expression will account for. Smaller numbers mean wispier details are more present.

Density Midpoint:

This changes the shape of the curve from the min density to the max density. Must be between the two values.

Density Max:

The highest amount of density the expression will account for. Primvar values above this number are clamped to the density max.

Emitter Parameters:
These controls effect the emit color attribute within the PxrVolume. Generally, I drive these values with temperature, but heat and density also work.


Primvar Input:

to plugin an unspecified input from a VDB attribute as called by a PixarPrimvar (eg. temperature)

Emit Bias

adjusts the curve that defines the density values from the Primvar. Generally, the higher the number the more details are included.

Emitter Min:

Controls the lowest temperature value from the VDB that will emit light/color. The lower values increase brightness, higher values increase details. Balances out with the 'emit bias' control.

Emitter Max:

the highest/brightest intensity of the flame shader

Emitted Color:

The color of the flames. Here is where the value can be increased as well

Color Scale:

increases the intensity of the color by raising the RGB values to above 1.


Loading the Plugin to Maya


Custom Hypershade Plugins in RIS are made up of two files. The first is a .args file that creates a UI for the node. The main calculations are done in a .cpp file. They are published in Renderman through the Renderman_for_Maya.ini file that Maya calls the specified path names on start-up. The node ID needs to be unique, and stated within the .args file as well.

The majority of the .cpp file is to call on the user defined values that were created in the .args file, and can be ignored. Other than that, the important parts are the definition of 'bias' and the conversion of Pixar's SeExpression into RIS's implementation of C++.

The .cpp file also needs to be compiled before it will work within Maya.



The .args file used to define the User-defined variables for the node:


<args format="1.0">
    <page name="Parameters" open="True">
        <page name = "Density Parameters" open="True">
            <param name="primvar_density" label="Primvar Density Input" type="float" default="0" input="True" min="0" max="1" widget="default"/>
            <param name="density_bias" label="Density Bias" type="float" default="0.815" input="True" min="0" max="1" widget="default"/>
            <param name="density_scale" label="Density Scale" type="float" default="0.9" input="True" min="0" max="1" widget="default"/>
            <param name="density_midpoint" label="Density Midpoint" type="float" default="0.15" input="True" min="0" max="1" widget="default"/>
            <param name="density_min" label="Density Min" type="float" default="0.136" input="True" min="0" max="1" widget="default"/>
            <param name="density_max" label="Density Max" type="float" default="0.857" input="True" min="0" max="1" widget="default"/>
        
        </page>
        <page name = "Emitter Parameters" open="True">
            <param name="primvar_temp" label="Primvar Input" type="float" default="0" input="True" min="0" max="1" widget="default"/>
            <param name="emit_bias" label="Emitter Bias" type="float" default="0.8" input="True" min="0" max="1" widget="default"/>
            <param name="emit_min" label="Emitter Min" type="float" default="0.143" input="True" min="0" max="1" widget="default"/>
            <param name="emit_max" label= "Emitter Max" type="float" default="0.857" input="True" min="0" max="1" widget="default"/>
            
            <param name="emit_color" label="Emitted Color" type="color" default="1 0.3888 0.1851" input="True" widget="color" tag="color and pattern"/>
            <param name="color_scale" label="Color Scale" type="float" default="20" input="True" min="0" max="1" widget="default"/>
    
        </page>
    </page>
<output name="result_Incandescence"  tag="color|vector|normal|point|pattern"/>
<output name="result_Density"  tag="float|pattern"/>
  
  
    <typeTag>
        <tag value="pattern"/>
    </typeTag>
    <rfmdata nodeid="1053606" classification="rendernode/RenderMan/pattern"/>
</args>

The C++ code used to determine the output of the node based on Pixar's equation:


//------------------------------------------------------------------------------
  
    // Assign values to the output(s).
    for(int i = 0; i < ctx->numPts; i++) {
        
        //Density values:
        //determine the bias of the density
        float bias =   pxslFloatBias( primvar_density[i], density_bias[i] );
        float density_result;
        
        //determines the output of density - connected to PxrVolumes "density float" attribute
        density_result = (bias < density_midpoint[i]) ? (density_scale[i] * RixSmoothStep(bias, density_min[i],  density_midpoint[i])) :
                           (density_scale[i] * (1.0 - RixSmoothStep(bias,  density_midpoint[i], density_max[i])));
                
        //Emit color (incandescence) values:  
        float incan_bias =   pxslFloatBias( primvar_temp[i], emit_bias[i] );
        
        RtColorRGB scaled_color = emit_color[i] * color_scale[i];
        
        RtColorRGB incandescence = (1.0 - RixSmoothStep( incan_bias, emit_min[i], emit_max[i]) )*scaled_color;
    
        result_Density[i]= density_result;
        result_Incandescence[i] = incandescence;
        
        } 
    return 0;
    }
  
  
//-----------------------------------------------------------------------------


Copy Code


If you want to view the code behind the 'eo_flames' plugin:

View Code - eo_flames.args

View Code - eo_flames.cpp\

If you want to download the 'eo_flames' and 'SplineByDistance' plugins for Windows, Mac, or Linux:

Download .zip Here

I'd love to hear back about your results!