Friday, May 31, 2013

Polygon Surface to Blobby Conversion in Render Time 
using Python and Renderman Interface Filters

     RenderMan Pro Server 14 introduces prman_for_python, a Python plugin module that allows us to build Pixar's RenderMan into our Python applications. With the plugin module "prman_for_python.so" comes a simple Python wrapper module, "prman.py". Together they make accessing the power of prman as easy as python. 

    Prman_for_python can be used to write/append RIB files and to render pixels directly from Python. We can even express RenderMan procedural primitive plugins and Ri filters (rifs) directly in Python.

prman.py

         prman.py represents the highest level layer of the bridge to prman. prman.py is a Python module that exports a combination of procedures, constants, and class definitions. You must first import the module to gain access to prman. Python offers a number of methods to ensure a module import succeeds.

RI Layer


     The Ri layer is at the heart of prman and embodies the RenderMan Interface. Ri Filter plugins can be expressed with Python. This makes it trivial to manipulate the Ri stream at the mouth of the renderer or simply to filter RIB files. 

     For this project i am using python rifs to convert a polygon surface into volumetric blobbys. 

Polygon Surface to Volumetric Blobbies 


video


Breakdown

      First i created a particle simulation in houdini and used "Particle Fluid Surfacer" node to create the polygon surface for the simulation. Once i got the simulation i rendered it as a rib sequence. 



     The code first reads all the rib files in the folder and converts it blobby geometry. Then it goes through all the files for the second time and renders the blobby surface as volume primitives.

Polygon Surface To Blobby ( rif_polymeshToBlobby.py)

     Using the "PointGeneralPolygon" module, this script goes through the rib files and converts it into blobby surface.



import prman
scale = .25
class Rif(prman.Rif):
def PointsGeneralPolygons(self, numpolys, numloops, indices, params):
opcodes = []
numblobs = len(params['P'])/3
for n in range(numblobs):
opcodes.append(1001)
opcodes.append(n * 16)
opcodes.append(0) # blending code
opcodes.append(numblobs)# blend all blobs
for n in range(numblobs):
opcodes.append(n)

common = (scale,0,0,0,0,scale,0,0,0,0,scale,0)
transforms = (scale,0,0,0,0,scale,0,0,0,0,scale,0)
xyz = params['P']
numxyz = len(xyz)
for n in range(0, numxyz, 3):
pos = (xyz[n], xyz[n+1], xyz[n+2])
if n == 0:
transforms = common + pos + (1,)
else:
transforms = transforms + common + pos + (1,)
params = {}
strs = ('',)
self.m_ri.Blobby(numblobs,opcodes,transforms, strs, params)

Blobby To Volume Primitives (rif_blobby.py)

     Using the "Blobby" module, this script goes through the rib files and converts it into volume primitives. It basically appends the file and adds the number '8' as the first entry in the opcode, which renderman interprets as a blobby surface.


import prman
class Rif(prman.Rif):
def Blobby(self, numblobs, opcodes, xyz, strs, params):
opcodes = (8,) + opcodes
self.m_ri.Blobby(numblobs, opcodes, xyz, strs, params)


BatchRiHoudini.py

     The following piece of code reads the directory where all the rib files are saved  and makes use of the modules listed above for the conversion process. It also creates the batch render script, which renders the images to the location specified in the rib file. 

import ribops, os, sys, subprocess, prman, rif_polymeshToBlobby, rif_blobby

def testSingleRib(ribpath):
ri = prman.Ri()
ri.Option("rib", {"string asciistyle": "indented"})
rif = rif_polymeshToBlobby.Rif(ri)
prman.RifInit( [rif] )
temprib = os.path.join('/home/sragha20/mount/Chops/blobbyTest/test', 'temp.rib')
ri.Begin('-')
prman.ParseFile(ribpath)
ri.End()

def main():

hostdir = '/home/sragha20/mount/Chops/blobbyTest/test'
files = os.listdir(hostdir)
ribs = []
for f in files:
if os.path.isdir(f):
continue
if f.endswith('.rib'):
ribs.append(f)
# Specify the batch render script - it could be saved anywhere
batchPath = os.path.join(hostdir, 'batchRender')
job = ribops.Job('/home/sragha20/mount/Chops/blobbyTest/test')
ri = prman.Ri()
ri.Option("rib", {"string asciistyle": "indented"})
rif = rif_polymeshToBlobby.Rif(ri)
rif1 = rif_blobby.Rif(ri)
prman.RifInit( [rif])
for rib in ribs:
rib = os.path.join(hostdir, rib)
print rib
parent = os.path.dirname(rib)
name = os.path.basename(rib)   
tmpRib = os.path.join(parent, 'tmp_' + name)
ri.Begin(tmpRib)
prman.ParseFile(rib)
ri.End()
os.remove(rib)
os.rename(tmpRib,rib)
prman.RifInit([rif1])
for rib in ribs:
rib = os.path.join(hostdir, rib)
print rib
parent = os.path.dirname(rib)
name = os.path.basename(rib)   
tmpRib = os.path.join(parent, 'tmp_' + name)
ri.Begin(tmpRib)
prman.ParseFile(rib)
ri.End()
os.remove(rib)
os.rename(tmpRib,rib)
print 'done'
# Create the script
job.makeBatchRenderScript(hostdir, batchPath, ribs)
# Make is executible
os.chmod(batchPath, 0777)
# Spawn a child process that will run the script
args = ['sh', batchPath]
subprocess.Popen(args, stdout=subprocess.PIPE)


if __name__ == "__main__":
main()




Python Scripting

Data Expansion in Houdini  using Python & Renderman Interface Filters

   This page demonstrates the use of a procedural primitive, or helper app as they are often called, to generate an arbitrary number of points in order to enhance the appearance of a particle simulation.

   For this project i want to create a procedural Tornado/Dust Devil in Houdini and do data expansion in render-time  I am going to concentrate only on the tornado/dust devil network for now. 

Reference Footage


My Render


video

Houdini playblast of the initial Tornado Network

Following is the procedural network i created in Houdini which can be used to create a basic tornado/dust devil simulation.

video

My next step was to figure out a way to use Data Expansion in houdini since i want to avoid big simulation time. 

Script implementation for the Tornado network :

video
Original Houdini Particles Count : 5600         After applying the script : 1680000

Python Script

 Following is the code. It basically goes through all the points in the scene and creates a rib file for each point. The built-in procedural RiProcRunProgram will run an external “helper program,” capturing its output and interpreting it as RIB input. This helper program will add the extra particles during the render time.

The following code entered in "Python Source Editor".

import math,os,re,sys,prman,random
def main():
ri = prman.Ri()
ri.Option("rib", {"string asciistyle": "indented"})
ap_script = '/usr/bin/python/home/sragha20/mount/stuhome/vsfx705/python/addPoints.py'
ap_num = str(hou.node("/obj/proxy").parm("num").eval())
ap_dist = str(hou.node("/obj/proxy").parm("dist").eval())
ap_dia = str(hou.node("/obj/proxy").parm("dia").eval())
ap_freq = str(hou.node("/obj/proxy").parm("freq").eval())
inputs = ap_num + ' ' + ap_dia + ' ' + ap_freq + ' ' + ap_dist + ' ' + '3'
items = [ap_num, ap_dia, ap_freq, ap_dist]
pointnode = hou.node('/obj/pop/point1')
pointgeo = pointnode.geometry()
pos = pointgeo.iterPoints()
hou.setFrame(hou.frame())
path = '/home/sragha20/mount/project4/ribs/particle_%04d.rib' % int(hou.frame())
ri.Begin(path)  # set rendertarget to ri.RENDER to render pixels
for i in range(len(pos)):
tmp = pointgeo.iterPoints()[i]
tmp1 = tmp.floatListAttribValue("P")
ri.TransformBegin()  
ri.Translate(tmp1[0], tmp1[1], tmp1[2])
angle = random.uniform(-180.0, 180.0)
ri.Rotate(angle, random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0))
ri.Procedural( (ap_script, inputs), (-1,1, -1,1, -1,1), "ProcRunProgram")
ri.TransformEnd()
ri.End()
if __name__ == "__main__":
main()

Noise Function (Addpoints.py)

import sys, random, math
from pnoise import pnoise
inputs = sys.stdin.readline()
 while inputs:
items = inputs.split()
pixels = float(items[0])
num = int(items[1])
dia = float(items[2])
freq = float(items[3])
size = float(items[4])
sed = int(items[5])
random.seed(sed)
print 'Points "P" ['
for n in range(num):
x = random.random() * freq * random.uniform(0.8, 1.3)
y = random.random() * freq * random.uniform(0.8, 1.3)
z = random.random() * freq * random.uniform(0.8, 1.3)
  x = pnoise(x,y,z) * size
y = pnoise(y,x,z) * size 
z = pnoise(z,x,y) * size  
print '%1.3f %1.3f %1.3f' % (x,y,z)
print '] "constantwidth" [%1.3f]' % dia
sys.stdout.write('\377')
sys.stdout.flush()
inputs = sys.stdin.readline()

       Once the script was entered in source editor, I made a Renderman Rop and referenced the function in "Pre-Frame Script" field. 

                           hou.session.main()

       "hou" is a built-in python procedure which gives you interactive access to all the contexts in houdini. "main" is the function.