FumeFX to RenderMan - Maya Integration

The following is a transcription of one of my independent projects while I was a student at SCAD.  The original page is still available here.  Unfortunately, I haven't had much time or inclination to further develop this as I haven't really done anything with an fx simulation in a couple of years.  Perhaps some of the information here is useful to someone!

INTEGRATING WITH MAYA - PASSES

Based on the unweildy nature of the previous steps, I decided to investigate adding my own passes to the RenderMan Pass Settings Tree in Maya. I started this process by examining the Slim templates for the All Purpose Material Subsurface Scattering component and the Base Volume. After analyzing the Slim template code, I isolated the relevant TCL code to create these render passes. Like any of these experiments, a long series of deeper and deeper searches into the RenderMan Slim templates and TCL header files finally illuminated what actually happens with the TCL code in the main Slim template. I will not spend any time going over the basics of Slim template creation, but the interesting bit of the Slim template is posted below.

parameter float Disable {
   label {Disable Bake}
   subtype switch
   default 1
   msghandler {
      SetValue {
         set atl sss
         set ch _radiance_t
         set sty volumeScatter 
         set prop %obj
         set app [$prop GetAppearance]
         set map [[$app GetProperties -name *Crew*] GetValue]
         set d [$prop GetValue]
         set bakedFileProp [$app GetProperties -name *BakedFile*]
         set ptcFileProp [$app GetProperties -name *PtcFile*]
         set bakePassIdProp [$app GetProperties -name *BakePassId*]
         set bakePassClassProp [$app GetProperties -name *BakePassClass*]
         $bakedFileProp SetValue "\[bakemap -atlas $atl -chan $ch -map $map
                                 -style $sty -disable $d\]"
         $ptcFileProp SetValue "\[ptcfile $atl $map $sty\]" 
         $bakePassIdProp SetValue "\[bakepassid $atl $map $sty\]"
         $bakePassClassProp SetValue "\[bakepassclass $sty\]"
      }
   }
}
slimattribute string Crew {
   default {$SHADERGROUP}
}
parameter string CurrentPassId {
   provider variable locked
   default {$PASSID}
}
parameter string BakePassId {
   default {}
   provider variable locked
}
parameter string BakePassClass {
   default {}
   provider variable locked
}
parameter string PtcFile {
   default {}
   provider variable locked
}
parameter string BakedFile {
   default {}
   provider variable locked
}

The basic element of interest here is the msghandler SetValue. This TCL method runs when the value of the associated parameter changes. In this case, it was a checkbox for enabling or disabling the point cloud bake. When the point cloud is enabled, parameters are retrieved about the set of objects with the shader applied ("Crew") and the current Pass ID, and four TCL functions are set in strings that will create the relevant render passes in the Pass Settings dialog. I have not isolated the area yet where these are finally executed, but I am fairly certain these are evaluated by some TCL code that is executed at render-time. This code is mostly borrowed from the BaseVolume material.

Now that RenderMan will happily create the additional passes, I have control over the various associated render settings (shading rate, number of bounces, etc.). Next, I added conditionals to my shader code to only bake the map if the prepass was the current pass, and only use the point cloud result in the final render pass. These are not accessed in the standard way and are instead accessed via the various hidden parameters set in the previous code block.

//BakedFile and Disable are set as shader params
if(BakedFile != "" && Disable != 1) {
	color indirLight = 0;					
	texture3d(BakedFile, P, N, "_indirectdiffuse", indirLight);
	sAccum += indirLight * Intensity;
}

//Disable, CurrentPassId, BakePassId, PtcFile are all set as shader params
if(Disable != 1 && rdepth == 0 && CurrentPassId == BakePassId) {
	float area = area(P, "dicing");
	if(OI != color(0) && PtcFile != "") {
		bake3d(PtcFile, "_area,_radiosity,_extinction,Cs", P, N,
			"interpolate", 1,
			"_area", area, "_radiosity", CI, 
			"_extinction", OI, "Cs", CI);
}

I am extremely happy with how this smooths the process of using the shader and am much clearer (though still finding the last few details) on how these more complex Slim templates interface with RenderMan for Maya.

INTEGRATING WITH MAYA - AOVS

The last major feature I hoped to add was support for AOVs. Having a small amount of experience with standard RSL shader AOVs, I thought this would take only a couple of minutes to implement with simple output varying parameters. While it was not at all this simple, I was able after all to find a fairly elegant way to implement AOVs.

//A simple RSL shader with an AOV
surface hasAOV ( output varying color extraChan = 0 ) {
	//will create an AOV with the surface colored red
	extraChan = color(1,0,0);
}

First, a bit of background about how Slim shader templates work. A Slim template is a bunch of TCL defining input and output parameters followed by a block of RSL code. Inside this RSL code, values are assigned to output parameters and at first glance everything appears exactly like a regular RSL shader. The actual process is much more complicated than that, but can be described in brief. The RSLFunction defined in a Slim template is implemented in the resulting .sl file as just that - a function. The actual surface shader code calls the function and assigns the output parameters to the actual shader outputs. First, a very simple Slim template:

slim 1 extensions cutter {
extensions fundza cutr {
	template shadingmodel HasColor {
		collection shadingmodel result {
			access output
			display hidden
			parameter color CI {
				access output
				}
			parameter color OI {
				access output
				}
			}
		RSLFunction {
		void cutrHasColor (
			output color CI;
			output color OI;
			)
		{
		OI = Os;
		CI = Os * color(1,0,0);
		}
} } } }

This template simply creates an RSL function called cutrHasColor and sets the OI and CI values (no AOVs yet). The resulting .sl code (reduced):

surface HasColor () {
	void cutrHasColor ( output color CI;
			output color OI; )
	{
		OI = Os;
		CI = Os * color(1,0,0);
	}
	
	color tmp2;
	color tmp3;
	
	cutrHasColor(tmp2, tmp3);
	Ci = tmp2;
	Oi = tmp3;
}

A close look at this .sl file, created by Slim, shows that the RSLFunction block from Slim is just carried over as a function inside the shader. The shader is called and the output values are assigned to the correct required output values of the surface shader (Ci and Oi). This is all well and good, but I discovered quickly that if I tried simply adding a few more output parameters to the result collection of the Slim template and function, they didn't translate over to my final .sl code. It would still just set Ci and Oi. A bit more research led me to the concept of Visualizers.

Visualizers are a type of dynamic shader template, where that last bit of RSL is defined. In this case, the overall template has a shadingmodel type. This relates to ashadingmodel visualizer, which defines two outputs, OI and CI, and plugs them into Oi and Ci of the final RSL shader. There are many built in visualizers, and one of particular interest is the shadingmodel_aov visualizer. This visualizer references a built-in Pixar .h file with a list of predefined AOVs and runs some TCL loops to define all of the RSL code for their use. This file can be modified, but lives within the install directory and is quite inconvenient to work with. However, when Malcolm told me about this header file, I did some more digging and decided on a different approach by defining my own visualizer inside my Slim template.

Basically I wanted my template to have three AOVs - Fire, Smoke, and Indirect. This way the user in post would have control over the color of the particular element as well as the intensity of the indirect component. Ultimately, my output collection for the Slim template now needed 5 outputs instead of the standard two. As I mentioned, just adding them causes no errors, but did not hook them to the final RSL code in any meaningful way. I added the following visualizer to my code:

slim 1 extensions kevin_george {
extensions kgeorge kdg {
	template visualizer shadingmodel_kdg {
		collection shadingmodel_kdg shadingmodel {
			detail mustvary
			parameter color CI {
				detail mustvary
				default {1 1 1}
			}
			parameter color OI {
				detail mustvary
				default {1 1 1}
			}
			parameter color Fire {
				detail mustvary
				provider connection
				default {0 0 0}
			}
			parameter color Smoke {
				detail mustvary
				provider connection
				default {0 0 0}			
			}
			parameter color IndirFire {
				detail mustvary
				provider connection
				default {0 0 0}			
			}
		}
		
		RSLDeclare output varying color Fire 0
		RSLDeclare output varying color Smoke 0
		RSLDeclare output varying color IndirFire 0	
		
		RSLMain {
			generate
			output "Ci = [getvar CI];"
			output "Oi = [getvar OI];"
			output "Fire = [getvar Fire];"
			output "Smoke = [getvar Smoke];"
			output "IndirFire = [getvar IndirFire];"
		}
	}

This section is including a lot of code, but the above basically defines the parameters of a new template 'type' called shadingmodel_kdg that expects five output parameters instead of the two. Additionally, the RSLDeclare statements will add the the actual ouput varibles to the .sl file, and the 5 commands in RSLMain write the final values to the .sl file.

Still unwilling to cooperate, RenderMan did not recognize my new template type as attachable, so I was no longer able to attach my shader to geometry. To tell RenderMan that this new type is attachable, the slim.ini file must be modified with the following command:

SetPref SlimAttachableMap(shadingmodel_kdg) surface

With this final step complete, I was able to assign my shader and output AOVs with RenderMan for Maya.

More valuable information can be found in the RMS 3 documentation under the section Slim -> Templates -> Templates: Advanced Topics [3].

FUTURE DEVELOPMENTS

While I accomplished much of my desired goal, I still would like to extend this shader to be a bit more universal by learning and utilizing the same channel names as would be used with Maya Fluids. This extension should make it easier for me to integrate my shader with Maya Fluid simulations as well, instead of being totally reliant on my custom primvar channel names.

On the last day of the project, I found a huge oversight on my part - temparature is really not the best method to determine where fire resides. I was using this approach because FumeFX does not allow the user access to the actual 'fire' parameter. However, this does not consider that for very hot fire, there will be very hot convection areas above the flames that are not visible. I have determined that I can deal with this issue by adapting my code to also export the fuel channel and look forward to the next version of the shader.

I would also like more control over the color and opacity - preferably through some means to set a color gradient for each of these parameters for fire and smoke to dial in and art direct the final look.

Originally, I planned to use the vector velocity data from the FumeFX simulation as a sort of motion vectors, but I did not have time to implement this feature. I'm not certain it's as straightforward as exporting a motion vector pass for compositing, but I also plan to explore ways to use this velocity data in future versions.

REFERENCES

RenderMan Pro Server App Note - Volume Rendering
Stefan-Boltzmann Law
RenderMan Studio: Slim Templates - Advanced Topics
Production Volume Rendering - Siggraph 2011 Course Notes