face
Ben Esler
VFX Artist

RenderMan: Dynamic Secondary Geometry

This breakdown covers using Pixar's custom mel procedures (part of RenderMan for Maya) to create secondary geometry at "render-time". Initially the end goal was to create a tree using the secondary geometry. During the development a terrarium became more interesting. The geometry that was generated at render-time is the majority of the moss.

Process

All the moss geometry was created in Houdini. It has many options to introduce noise and randomness, generating several variants of each type of moss quickly. The Maya project directory is located on a network drive and can be slow. To resolve this issue during look development when Houdini exports the geometry it also exports a lower polygon version.

houdiniSop

Three files are used to instance .rib archives at render time for RenderMan in Maya. These files are generate a artist friendly UI on a Transform Node. When the artist renders it, RenderMan checks for a “Post Transfrom MEL”, which is referenced in the UI. This script uses the attributes inside of the UI to read in archiveswithin a select folder per vertex. These archives have the ability to have random positional and rotation offsets. Along with the ability to have a uniform scale and remove a percentage of the archives.

ArchiveOnMeshUI
mayaProject/
└── renderman/
└── ribarchives/
└── moss_1/
└── high/
│ └── moss_1_01.rib
│ └── moss_1_02.rib
│ └── moss_1_03.rib
└── low/
└── moss_1_01.rib
└── moss_1_02.rib
└── moss_1_03.rib
pxrSeExpr

The shaders used for each moss look for a custom Pixar Attribute. By default it is set to vertex_id and brought into a Pixar Se Expression node. Which allows the artist to pre-select 4 colors that will be used as the Diffuse Color. This adds random color variation that is controllable. There is still one section of moss that uses displacement. This is because it is more of a muddy type of moss that isnt leafy.

# ArchivesOnMesh.rman
rman "-version 1" {
Declare param {string aaom_ribsDir} {
label "Archive Ribs Folder"
description "Must be a folder in the project directory."
}
Declare param {int aaom_useHigh} {
label "High Poly"
description "No description."
subtype switch
}
Declare param {int aaom_useNorm} {
label "Align to Normals"
description "No description."
subtype switch
}
Declare param {float aaom_rotOff} {
label "Random Rotation Offset"
subtype slider
range {0 180}
description "Randomly rotates object +/- of value. If using normals it will rotate around the normal."
}
Declare param {float aaom_posOff} {
label "Random Position Offset"
subtype slider
range {0 1}
description "Randomly moves object +/- of value. If using normals it will move around the normal."
}
Declare param {float aaom_scale} {
label "UniformScale"
subtype slider
range {0.001 100}
description "Scales Rib uniform."
}
Declare param {float aaom_removePercent} {
label "Remove %"
subtype slider
range {0 1 .001}
description "Removes Ribs based off the percentage removed."
}
Declare param {string aaom_shadingID} {
label "Shading Identifier"
description "Custom attribute for shader department."
}

}

// Generated by Cutter v7.7.8 at 4:14:23 on the 10.12.2017.
// The source document on which this mel script is based is,
// "/i-drive/Savannah/Home/Student/maya/projects/RfM_mel/ArchiveOnMesh.rman"
// Cutter software by Malcolm Kesson (all rights reserved).
//
// Post Transform User Interface (UI) Mel Script
//
global proc ArchiveOnMeshUI()
{
string $selected[] = `ls -sl`;
int $i;

for( $i=0; $i < size($selected); $i++ )
{
string $attr = `rmanGetAttrName "postTransformScript"`;
string $transformName = $selected[$i];

// "Connect" to the mel script that calls
// Pixar's custom Ri mel procedures.
rmanAddAttr $transformName $attr "ArchiveOnMeshRI";

$attr = `rmanGetAttrName "aaom_ribsDir"`;
$ribArchDir = "";
rmanAddAttr $transformName $attr $ribArchDir;

$attr = `rmanGetAttrName "aaom_useHigh"`;
rmanAddAttr $transformName $attr "0";

$attr = `rmanGetAttrName "aaom_useNorm"`;
rmanAddAttr $transformName $attr "1";

$attr = `rmanGetAttrName "aaom_rotOff"`;
rmanAddAttr $transformName $attr "0";

$attr = `rmanGetAttrName "aaom_posOff"`;
rmanAddAttr $transformName $attr "0";

$attr = `rmanGetAttrName "aaom_scale"`;
rmanAddAttr $transformName $attr "1";

$attr = `rmanGetAttrName "aaom_removePercent"`;
rmanAddAttr $transformName $attr "0";

$attr = `rmanGetAttrName "aaom_shadingID"`;
rmanAddAttr $transformName $attr "vertex_id";

$attr = `rmanGetAttrName customShadingGroup`;
rmanAddAttr $transformName $attr "";
renderManUpdateAE;
renderManShaderUpdateAE "";
rmanUpdateAE;
}
}

// Generated by Cutter v7.7.8 at 4:14:49 on the 10.12.2017.
// The source document on which this mel script is based is,
// "/i-drive/Savannah/Home/Student/maya/projects/RfM_mel/ArchiveOnMesh.rman"
// Cutter software by Malcolm Kesson (all rights reserved).
//
// Post Transform Mel Script
//
global proc ArchiveOnMeshRI() {
// Get the name of the transform node
string $tformNode = `rman ctxGetObject`;

// The node may hava a number in its name that we can use to set the
// random number generator
int $nodeNumber = `match "[0-9]+" $tformNode`;
if($nodeNumber != "") {
seed(int($nodeNumber));
}

// Bounding box is only relevant if the transform node is not a group.
string $children[] = `listRelatives -children $tformNode`;
string $shapeNode = $children[0];
float $bb_width = -1, $bb_height = -1, $bb_depth = -1;
if(size($children) == 1) {
$bb_width = `getAttr ($shapeNode + ".boundingBoxSizeX")`;
$bb_height = `getAttr ($shapeNode + ".boundingBoxSizeY")`;
$bb_depth = `getAttr ($shapeNode + ".boundingBoxSizeZ")`;
}

string $attr;
$attr = `rmanGetAttrName "aaom_ribsDir"`;
string $aaom_ribsDir = `getAttr($tformNode + "." + $attr)`;
$aaom_ribsDir = `workspace -q -rd`+"renderman/ribarchives/"+$aaom_ribsDir;

$attr = `rmanGetAttrName "aaom_useHigh"`;
int $aaom_useHigh = `getAttr($tformNode + "." + $attr)`;

$attr = `rmanGetAttrName "aaom_useNorm"`;
int $aaom_useNorm = `getAttr($tformNode + "." + $attr)`;

$attr = `rmanGetAttrName "aaom_rotOff"`;
float $aaom_rotOff = `getAttr($tformNode + "." + $attr)`;

$attr = `rmanGetAttrName "aaom_posOff"`;
float $aaom_posOff = `getAttr($tformNode + "." + $attr)`;

$attr = `rmanGetAttrName "aaom_scale"`;
float $aaom_scale = `getAttr($tformNode + "." + $attr)`;

$attr = `rmanGetAttrName "aaom_removePercent"`;
float $aaom_removePercent = `getAttr($tformNode + "." + $attr)`;

$attr = `rmanGetAttrName "aaom_shadingID"`;
string $aaom_shadingID = `getAttr($tformNode + "." + $attr)`;

// Use of Pixar's custom RenderMan Studio procedures begins here.

// To use the shader of a "Custom Shading Group that can be
// assigned to the transform node of your geometry.
vector $posVerts[], $normVerts[];
getVertices($tformNode, $posVerts);
getNormals($tformNode, $normVerts);
int $isRemoved[];
int $remove = size($posVerts)*$aaom_removePercent;

int $i;
//Loops $isRemoved indexs to set to True based off $ssom_removePercent
if(`size($posVerts)`){
$isRemoved[`size($posVerts)`-1] = 0;
for($i=0; $i<$remove; $i++){
int $removeIndex = 1;
while($removeIndex){
int $index = rand(0,size($posVerts));
$removeIndex = $isRemoved[$index];
$isRemoved[$index] = 1;
}
}
}

//Gets Rib Archives
string $ribsHigh[] = `getFileList -folder $aaom_ribsDir -filespec "high/*.rib"`;
string $ribsLow[] = `getFileList -folder $aaom_ribsDir -filespec "low/*.rib"`;
//vector $ribBound[];
for($i=0; $i<size($ribsHigh); $i++){
$ribsHigh[$i] = $aaom_ribsDir+"/high/"+$ribsHigh[$i];
$ribsLow[$i] = $aaom_ribsDir+"/low/"+$ribsLow[$i];
//$ribBound[$i] = <<boundingBoxSizeX, boundingBoxSizeY, boundingBoxSizeZ>>
}

RiArchiveRecord("structure", "RLF Inject SurfaceShading");
for($i=0; $i<size($posVerts); $i++){
seed(int($nodeNumber)+$i); //must set seed else results change depending on if point is removed
vector $posOffset = <<`rand ($aaom_posOff*-1) $aaom_posOff`, `rand ($aaom_posOff*-1) $aaom_posOff`, `rand ($aaom_posOff*-1) $aaom_posOff`>>;
vector $rotOffset = <<`rand ($aaom_rotOff*-1) $aaom_rotOff`, `rand ($aaom_rotOff*-1) $aaom_rotOff`, `rand ($aaom_rotOff*-1) $aaom_rotOff`>>;
float $rotOffsetNorm = `rand($aaom_rotOff*-1) $aaom_rotOff`;
int $rib = int(rand(0, size($ribsHigh)));
if ($isRemoved[$i] == 0) {
RiAttributeBegin();
RiAttribute "user" "int vertex_id" $i;
vector $pos = $posVerts[$i];
float $rot[] = aimY($normVerts[$i]);
RiTranslate($pos.x, $pos.y, $pos.z);
if($aaom_useNorm){
RiTranslate($posOffset.x, 0.0, $posOffset.z);
RiRotate($rot[1], 0,0,1);
RiRotate($rot[0], 1,0,0);
RiRotate($rotOffsetNorm, 0,1,0);
}
else{
RiTranslate($posOffset.x, $posOffset.y, $posOffset.z);
RiRotate($rotOffset.x, 1, 0, 0);
RiRotate($rotOffset.y, 0, 1, 0);
RiRotate($rotOffset.z, 0, 0, 1);
}
RiScale($aaom_scale, $aaom_scale, $aaom_scale);
if($aaom_useHigh){
RiReadArchive($ribsHigh[$rib]);
}
else{
RiReadArchive($ribsLow[$rib]);
}
RiAttributeEnd();
}
}
}

// A utility to create a rib path for an archive to be saved
// in the project directories "data" folder.
global proc string getArchivePath(string $nodename) {
string $projpath = `workspace -q -rootDirectory`;
string $datapath = $projpath + "data/";
string $scenename = `file -q -sceneName -shortName`;
int $frame = `currentTime -q`;
string $fstr = "0" + $frame;
if($frame < 10)
$fstr = "000" + $frame;
else if($frame < 100)
$fstr = "00" + $frame;
$ribpath = $datapath + $nodename + "." + $fstr + ".rib";
return $ribpath;
}

import os
node = hou.pwd()
inputs = node.inputs()

def inGeo(input):
try:
geo = inputs[input].geometry()
except:
print 'No connection on input %n' % input
return geo

def export():
print('exporting')
high = inGeo(0)
low = inGeo(1)

mayaDir = "$HOME/maya/"
mayaProjDir = hou.evalParm('mayaProjDir')
asset = node.parent()
assetDir = "%sprojects/%srenderman/ribarchives/%s/" % (mayaDir,mayaProjDir,asset)
version = hou.evalParm('version')

for dir in ('high', 'low'):
dirToCheck = "%s%s" %(assetDir, dir)
if not os.path.exists(dirToCheck):
os.makedirs(dirToCheck)

highPath = "%shigh/%s_%02d.rib" %(assetDir, asset, version)
lowPath = "%slow/%s_%02d.rib" %(assetDir, asset, version)

high.saveToFile(highPath)
low.saveToFile(lowPath)

export()

Conclusion

This project turned out better than I expected. The lighting and mood was what I was aiming towards. I am not sure if placing Rib Archives like this is the most effecient. When I went to render it took an exteremly long time to build the archive that would be used to render. The render time itself was relatively quick. Again this was most likely from pulling files from the network. It was a good learning experience for using RImel and using RenderMan.

Site programmed in Python with Django framework