RIS C++

Sometime ago, Pixar introduced a new rendering architecture which is meant to rival the established path tracers such as Arnold, Cycles and Maxwell . RIS is a new path tracing renderer which comes along with a traditional Renderman REYES architecture. However, it doesn’t support RSL. Renderman Shading Language was one of the main reasons of Renderman’s success in the animation and VFX industries. The ease and simplicity of RSL allowed studios easily adapt Renderman and create complex custom shading solutions in short time. After two decades of RSL, Pixar had to give up on RSL and go with C++. Once simple and intuitive programming has become a routinely intricate process. A radical departure from RSL to C++ was due to the fact that Pixar wanted to optimize render calculations by relying on a binary compiled language.

To understand all the intricacies of RIS shader development, I conducted a small research in which I developed two similar shaders. The first shader is written in RSL for REYES and another one is written in C++ for RIS. The shaders have completely different code structures, luckily my RSL experience helped me a great deal in creating a C++ RIS shader. The goal of the research is to draw an analogy between different systems.

So here we go. The RSL shader Checker.

REYES_checker

The most artistic rendering of the RSL checker shader.

surface checker(uniform float tile = 2;)
	{
	varying float ss = mod (s*tile,1);
	varying float tt  = mod (t*tile,1);
	varying color s_checkerA = 1-(smoothstep(0.49 ,0.5,ss)-smoothstep(0.99 ,1,ss));
	varying color t_checkerA = 1-(smoothstep(0.49 ,0.5,tt)-smoothstep(0.99 ,1,ss));
	varying color s_checkerB = smoothstep(0.49 ,0.5,ss)-smoothstep(0.99 ,1,ss);
	varying color t_checkerB = smoothstep(0.49 ,0.5,tt)-smoothstep(0.99 ,1,ss);
	varying color checker_resultA = s_checkerA+t_checkerA;
	varying color checker_resultB = s_checkerB+t_checkerB;
	Oi = 1;
	Ci = checker_resultA*checker_resultB;
        }

Checker is a procedural pattern shader . It uses s and t coordinates to derive its colors. The code is simple and minimalistic. Now, lets take a look at the C++ pattern shader. I took time to comment the important parts of the code.

checker

RIS C++ checker. The visual output is almost identical to RSL. If examined carefully, the image has slightly better aliasing.

#include RixPattern.h            //in order to load the libs correctly the .h files should be in angle brackets!
#include RixShadingUtils.h       // I haven't placed the brackets because the text editor doesn't display em.
#include RixShading.h
#include cstring>
#include cmath

//Even though the node will be a part of Maya hypershade,
//I implement the uv tiling functionality inside the pattern
RtFloat mod(RtFloat A, RtFloat B) {                  
	RtFloat result = A - B *floorf(A / B);
	return result;
}


class checker : public RixPattern {                  
public:

	checker(); //constructor
	virtual ~checker() { }
	virtual int Init(RixContext &, char const *pluginpath); //a must have method for any plugin
	virtual RixSCParamInfo const *GetParamTable(); //this is an important method which defines patterns values
	virtual void Finalize(RixContext &) { } //a must have routine
	virtual int ComputeOutputParams(RixShadingContext const *sctx, //the fun part is here
		RtInt *noutputs, 
		OutputSpec **outputs,
		RtConstPointer instanceData,
		RixSCParamInfo const *ignored);
private:
	RixMessages *m_msg;
	RtFloat m_tile;
};
checker::checker() : //constructor
	m_msg(NULL), 
	m_tile(1)
{ }
int checker::Init(RixContext &ctx, char const *pluginpath) {
	m_msg = (RixMessages*)ctx.GetRixInterface(k_RixMessages);
	return (!m_msg) ? 1 : 0;
}
RixSCParamInfo const *checker::GetParamTable() { //here we define pattern's params
	static RixSCParamInfo s_ptable[] = {
		RixSCParamInfo("resultC",k_RixSCColor, k_RixSCOutput), //resultC is an output color
		RixSCParamInfo("tile",k_RixSCFloat, k_RixSCInput), // tile is an input float
		RixSCParamInfo() //empty
	};
	return &s_ptable[0];
}
enum paramIndex { //index the resultC and the tile params
	k_resultC = 0,
	k_tile
};

int checker::ComputeOutputParams(RixShadingContext const *sctx, //here we calculate the pattern
	RtInt *noutputs, //we have one output
	OutputSpec **outputs, //we will fill this pointer later
	RtConstPointer instanceData, //a pointer from instanceData class
	RixSCParamInfo const *ignored) {

	//Declare a pointer for the input
	RtFloat const *tile;
	bool varying = true;
	sctx->EvalParam(k_tile, -1, &tile, &m_tile, varying); // eval the tile param, write it in the pointer

	// Allocate memory for the OutputSpec data structure.
	RixShadingContext::Allocator pool(sctx); //create object which allocates mem
	OutputSpec *outSpec = pool.AllocForPattern(1); //allocate for the OutputSpec class
	*outputs = outSpec;
	*noutputs = 1; //hardcoded as we know that there is only one output

	outSpec[0].paramId = k_resultC; //every outSpec should have the ParamId, value and detail
	outSpec[0].value = NULL; //we live it empty if there is no outgoing connection
	outSpec[0].detail = k_RixSCInvalidDetail; //same goes here
        //Prepare the OutputSpec to the output(s).
	RixSCParamInfo const *paramTable = GetParamTable();
	RixSCConnectionInfo cinfo;
	RixSCType type = paramTable[k_resultC].type;
	sctx->GetParamInfo(k_resultC, &type, &cinfo);
	if (cinfo == k_RixSCNetworkValue) { //if the node has a connection we allocate the memory
		outSpec[0].detail = k_RixSCVarying; 
		outSpec[0].value = pool.AllocForPattern(sctx->numPts);
	}
	RtColorRGB *resultC = (RtColorRGB*) outSpec[0].value; //bind
	if (!resultC) {
		resultC = pool.AllocForPattern(sctx->numPts); //make sure the space is allocated
	}
	RtFloat2 const *uv;
	RtFloat const *uv_width;
	RtFloat2 const default_uv(0, 0);
	//this part is similar to the RSL shader, here we calculate the pattern. 
	//The only part where we can have fun
	sctx->GetPrimVar("st", default_uv, &uv, &uv_width);
	for (int i = 0; i < sctx->numPts; i++) {
		RtFloat tile_u = mod(uv[i].x*tile[i], 1);
		RtFloat tile_v = mod(uv[i].y*tile[i], 1);
		RtFloat s_checkerA = 1 - (RixSmoothStep(0.49, 0.5, tile_u) - RixSmoothStep(0.99, 1, tile_u));
		RtFloat t_checkerA = 1 - (RixSmoothStep(0.49, 0.5, tile_v) - RixSmoothStep(0.99, 1, tile_v));
		RtFloat s_checkerB = RixSmoothStep(0.49, 0.5, tile_u) - RixSmoothStep(0.99, 1, tile_u);
		RtFloat t_checkerB = RixSmoothStep(0.49, 0.5, tile_v) - RixSmoothStep(0.99, 1, tile_v);
		RtFloat resultA = s_checkerA + t_checkerA;
		RtFloat resultB = s_checkerB + t_checkerB;
		RtFloat result_checker = resultA*resultB;
		resultC[i].r = result_checker;
		resultC[i].g = result_checker;
		resultC[i].b = result_checker;
	}
	return 0;
}

RIX_PATTERNCREATE{
	return new checker();
}
RIX_PATTERNDESTROY{
	delete ((checker*)pattern);
}

It is very easy to get scared by the code complexity. It is five times more work compared to RSL equivalent. Pointers, memory allocation, multiple hierarchy API classes and other scary things are hiding in the lines of the code waiting to rip apart a poor guy who decides to enter the labyrinth.

There is a lot of code, but if we look carefully and take out some of its parts we will see that the remaining part is indeed very similar to the RSL shader.

code-mapping

The code of RSL shader is found in the method ComputeOutputParams()

So it is not as scary as it may look at the beginning. When I was saying that RSL checker shader has the pure creative logic and no distractions, I really meant that. RSL asks only what is necessary to get pixels on a screen, while C++ shader has to have a lot more code which should comply with the API’s routines but not necessarily influence the visual output of the shader. In this particular case, RSL was used as a prototype language. I could quickly experiment with the RSL code, get instant feedback in the renderer and once I was happy with the results I transferred the logic to the C++ shader.

It might feel that C++ is too much complicated for such simple thing as the checker shader, however the true benefits of binary compiled language become evident once more complex stuff is required. Raytraced BSDFs benefit a great deal from C++ ability to shade batches of rays and points. However, it is worth to mention that production renderers such as 3delight, Cycles and some versions of Arnold succeeded in bringing RSL and OSL to the new altitudes of performance in the world of path tracing.

When I was conducting my personal research I felt the need to share it, because there is few material about how to develop for RIS. Writing both RSL and C++ shaders helped me to draw an analogy between radically different approaches and break down the C++ shader on the smaller bits which are more comprehensible for an artistic mind. Moreover, I would like to thank Malcolm Kesson for making a tutorial on Cutter and Rix Patterns.

Me when I saw the code of the PxrDisney shader

scream_

Cheers.

 

Leave a Reply

The reply will be processed within a day