Using the RiCurves Primitive

Using the RiCurves Primitive

March, 1997 (Revised June, 2012)




Beginning with PhotoRealistic RenderMan 3.7, the RiCurves primitive provides a mechanism for specifying 3-D curves. One may think of this primitive as a ribbon or a string of spaghetti. It is efficient to render large numbers of these primitives, so it is ideally suited to modeling such things as hair and grass.


RiCurves( RtToken type, RtInt ncurves, RtInt nvertices[], RtToken wrap, parameterlist );

RiCurvesV( RtToken type, RtInt ncurves, RtInt nvertices[], RtToken wrap,
RtInt n, RtToken tokens[], RtPointer parms[] );

    Draws one or more 3-D ribbons of specified width through a set of control vertices. Multiple disconnected individual curves may be specified using one call to RiCurves. The parameter ncurves is the number of individual curves specified by this command, and nvertices is an array of length ncurves integers specifying the number of vertices in each of the respective curves.

    The interpolation method given by type can be either "linear" or "cubic" (note: not "bilinear" or "bicubic"). Cubic curves interpolate using the v basis matrix and step size set by RiBasis. The u parameter changes across the width of the curve, while the v parameter changes across the length of the curve (i.e. the direction specified by the control vertices).

    Curves may wrap around in the v direction, depending on whether wrap is "periodic" or "nonperiodic". Curves which wrap close upon themselves at the ends and the first control points will be automatically repeated. As many as three control points may be repeated, depending on the basis matrix of the curve.

    parameterlist is a list of token-value pairs where each token is one of the geometric primitive variables or a variable that has been defined with RiDeclare. The parameter list must include at least position ("P" or "Pw") information. The width along the curve may be specified with either a special "width" parameter which is a varying float argument, or a "constantwidth" parameter which is a constant float (one value for the entire RiCurves). Widths are specified in object space units of the curve. If no "width" vector or "constantwidth" value is given, the default width is 1.0 units in object space.

    Since by default an RiCurve generates a flat ribbon, but the control vertices only specify the direction of the "spine", the rotation of the flat ribbon about the spine is ambiguous. If no normals ("varying normal N") are specified in the parameter list, the ribbon will rotate so that it is as perpendicular to the view plane as possible. This is a good way to simulate a thin tube, since the silhouette of the ribbon will match that of the tube. However, if "N" values are supplied, the normals will be used to guide the ribbon so that it stays perpendicular to the supplied normals, thus allowing user-controlled rotation of the ribbon. To summarize, if you supply "N" values, you will get something like grass, and if you do not supply "N" values, you will get something like hair.

    It is also possible to specify that the hair should be intersected as though it is a cylindrical tube (instead of a flat ribbon) by enabling round curves and hair dicing (in this case, user-specified normals should not be supplied):

    Attribute "dice" "int roundcurve" [1] "hair" [1] 
    See the sections below on "Ray Tracing and Curves" and "Shadow Maps and Hair" for additional notes.

    The number of data items required for constant, uniform, varying, or vertex parameters for curves is as follows:


      one single value for the entire RiCurves primitive.
      ncurves values
      sum (nsegsi+1) values for nonperiodic curves,
      sum (nsegsi) values for periodic curves.

    where sum() indicates summing over all the individual curves specified by the RiCurves statement, and:

      nsegsi is the number of segments in curve #i
      = nvertices[i]-1 for linear, nonperiodic curves
      = nvertices[i] for linear, periodic curves
      = (nvertices[i]-4)/vstep+1 for cubic, nonperiodic curves
      = nvertices[i]/vstep for cubic, periodic curves


    Curves type [nvertices] wrap parameterlist


    Curves "cubic" [4] "nonperiodic" "P" [0 0 0 -1 -.5 1 2 .5 1 1 0 -1 ] "width" [.1 .04]

    Curves "linear" [5] "nonperiodic" "P" [0 0 0 3 4 5 -1 -.5 1 2 .5 1 1 0 -1 ] "constantwidth" [0.075]


Using RiCurves for Hair

PhotoRealistic RenderMan is able to render large numbers (millions or more) of curves very efficiently, making this primitive particularly well suited for modeling hair. Since the default behavior (when normals are not supplied) is to generate ribbons that keep their flat side facing the camera, we are creating geometry which has the same silhouette as a hair, but is perhaps hundreds of times more effiencient to render (in both time and memory) than an equivalent surface of circular cross section.

But the hairs are just ribbons, not really round tubes. If we light them using a standard illumination model (like, they will not look like hair. Flat ribbons don't reflect light the same way that a tube does. But we can make the illusion complete by writing a shader for use with the curves which reacts to light as if it was a thin cylinder.

Here is such a shader, with comments that should be self-explanatory:

surface hair (float Ka = 1, Kd = .6, Ks = .35, roughness = .15;
	      color rootcolor = color (.109, .037, .007);
	      color tipcolor = color (.519, .325, .125);
	      color specularcolor = (color(1) + tipcolor) / 2;
    vector T = normalize (dPdv); /* tangent along length of hair */
    vector V = -normalize(I);    /* V is the view vector */
    color Cspec = 0, Cdiff = 0;  /* collect specular & diffuse light */
    float cosang;

    /* Loop over lights, catch highlights as if this was a thin cylinder */
    illuminance (P) {
	cosang = cos (abs (acos (T.normalize(L)) - acos (-T.V)));
	Cspec += Cl * v * pow (cosang, 1/roughness);
	Cdiff += Cl * v;
	/* We multipled by v to make it darker at the roots.  This
	 * assumes v=0 at the root, v=1 at the tip.

    Oi = Os;
    Ci = Oi * (mix(rootcolor, tipcolor, v) * (Ka*ambient() + Kd*Cdiff)
                 + (Ks * Cspec * specularcolor));

Below is an example showing what RiCurve primitives look like, in combination with the shader above:

Ray Tracing and Curves

Starting in PRMan 17.0, curve ray tracing has been re-written to be significantly faster and to use less memory. Hair and fur geometry is intersected more efficiently using an improved bounding volume hierarchy, and now works very efficiently with even a small tessellation cache memory footprint.

When ray tracing hair or fur, it is recommended that round curves and hair dicing be enabled, using:

Attribute "dice" "int roundcurve" [1] "hair" [1] 

Doing so will ensure high quality hair shadowing in any lighting configuration by intersecting against cylindrical hair geometry instead of flat ribbons, resulting in significantly more realistic occlusion.

Also see the Optimized Rendering application note for details on achieving the best performance when ray tracing against curves primitives.

Shadow Maps and Curves

When rendering hair or fur into a shadow map, there are a few additional considerations.

At small, subpixel scales there can be ambiguity over whether a change in the transmission through an area shadow map pixel is due to an opaque object that partially covers the pixel or a transparent object that fully covers the pixel. For areashadow(), we assume coverage. This allows us to preserve antialiasing at the edges of shadows and minimize light leaks through the interior regions. However, the tradeoff for this is that dense, subpixel-sized, opaque geometry that overlaps a pixel at different pixel samples may be confused for being parts of a larger surface. This can produce aliasing and noisy acne in the shadowed areas due to over-occlusion.

To avoid this problem, we recommend that hairs be at least a pixel wide in the shadow map and be made partially transparent to compensate. Effectively, this is a form of pre-filtering for the area shadow map. We support a technique that can apply this adjustment automatically:

    Option "hair" "float minwidth" [1.0]

The value given is a minimum width in pixels. Hair-diced RiCurves and RiPoints that project to a smaller size than this will be widened and reduced in opacity proportionately. We have found 1 to 1.5 to be useful values for area shadow map passes. The minwidth option may also be used for beauty passes but normally will have a lower value. Beware that this option will increase memory use by producing more visible points.

We also recommend that round curves and sigma hiding be off during shadow map passes. The round curves setting will bias the hairs towards the shadow map camera, which may affect self shadowing when rays are traced in other directions for the beauty pass. While the sigma buffer can reduce memory use and help to antialias small geometry for beauty passes, it does so by collapsing samples in depth. This may result in detail missing from the shadow map. It is important to note that sigma should be off on the hider line, not just in the attribute, otherwise widening will not occur.

In short, we suggest the following as typical settings for hair in area shadow maps:

    Option "hair" "float minwidth" [1.0]
    Hider "stochastic" "int sigma" [0]
    Attribute "dice" "hair" [1] "int roundcurve" [0]

The following pair of images shows an example of some thin hairs without and with minwidth set to 1.0 in the shadow pass, respectively: images/figures.areashadow/hair_notwidened.png images/figures.areashadow/hair_widened.png

Known Limitations

Starting in RenderMan 17.0, there is a new, optimized implementation of the curves primitive. However, there are a couple of known limitations with the new implementation:
  • Re-rendering currently works with the new curves primitive implementation only when using the ray traced hider.
  • The new, optimized curves primitive implementation is currently enabled for at most two motion samples.

If you see any problems with the new curves primitive implementation, it is possible to revert to the previous curves primitive implementation by setting an option (Option "hair" "int spatialgrouping" [0]), or by appending the following line to your rendermn.ini file (/prman/hair/spatialgrouping 0).