# Refinement Method

# Refinement Method

*September, 2009 (Updated May, 2013)*

### Introduction

From the beginning, the RenderMan interface has specified control over
the number of shading calculations for a primitive by a single number:
the shading rate, specified by `RiShadingRate`. Over the years, a
few associated controls have been added (such as relativeshadingrate and RiGeometricApproximation), but in the end these
still amount to specifying a single fixed rate of shading for the
entirety of a primitive. On the other hand, for efficient rendering,
it is often desirable to instead have a changing shading rate -
increasing it in areas where it is anticipated more shading detail is
necessary, and lowering it whenever the shading detail is
sparse. Furthermore, it is desirable to be able to control this
shading rate programmatically.

To this end, PRMan 15 introduces a new shading object method to the
shading pipeline that allows the desired shading rate to be
calculated. The method is named `refinement`, and has the
following signature:

public void refinement(uniform float shadingrate, depthshadingrate; output uniform float shadingratefraction, depthshadingratefraction);

The refinement method is called first in the shading pipeline,
immediately after a grid has been diced, in order to determine whether
the grid is useable as is, or requires further refinement. The inputs
to the refinement method are the current shading rates. Geometry that
has never undergone any refinement will start with a shading rate
equal to the current `RiShadingRate`. The outputs from the
refinement method are multipliers that are applied to the input
shading rates in order to compute the desired shading rate for
refinement. If the outputs computed by the refinement method are both
greater than or equal to 1.0, then no futher refinement is necessary.
(The refinement method cannot be used to "un-refine" or obtain a final
shading rate greater than the current `RiShadingRate`.) Otherwise,
the multipliers are applied to the shading rates and a new grid is
diced, which may result in a split if the maximum grid size has been
reached. The refinement method is applied once again to the new grid
with the new computed shading rate.

It should be evident by this description that it is extremely important that the refinement method "bottom out", otherwise the renderer will be caught in an infinite loop of dicing and refinement. This can be as simple as ensuring that the calculated shading rate never drops below some arbitrary number.

Also note that the refinement method computes `uniform` output
results, not varying. A refinement method that opts to
refine based on a varying quantity will be required to perform
ifever checks. The new `gridmin` and `gridmax` results may help
with this particular problem, as they allow easy computation of the
range of a varying quantity on a grid and return uniform results,
which may then be used as part of a conditional statement to select an
output shading rate fraction.

#### __computesRefinement

If a shader has a refinement method, a `__computesRefinement` parameter
may be set to 0 to indicate that the shader is never going to request
further refinement (i.e. it will never return a shadingratefraction or
depthshadingratefraction value less than one). In this case, the
refinement method will be skipped during dicing. If the
`__computesRefinement` parameter is set to 1 or is not present, then the
refinement method will be run during dicing.

### Applications

#### Curvature Based Dicing

When rendering shadow maps, it is generally the case (particularly when dealing with opaque geometry) that we do not care about the detail of the color, we care about getting the geometric detail correct. If we can guarantee a certain degree of refinement near areas of higher curvature, then there is an opportunity to use a coarser shading rate everywhere else.

This leads to the following shader, which does a very simple-minded
check of normal divergence in the refinement method. Note the use of
the new `gridmin` and `gridmax` shadeops, which are very handy in
checking the range of a varying quantity on a grid. Also note the
check on minimum shading rate, which effectively provides a limit on
the number of refinements that can possibly take place.

class curvature(uniform float minshadingrate = 0.25) { public void surface(output color Ci, Oi) { Oi = Os; Ci = randomgrid() * 0.7 + 0.3 * random(); } public void refinement(uniform float shadingrate, volumeshadingrate; output uniform float shadingratefraction, volumeshadingratefraction) { if (shadingrate <= minshadingrate) { shadingratefraction = 1.0; } else { normal Nn = normalize(N); uniform float minNx = gridmin(xcomp(Nn)); uniform float minNy = gridmin(ycomp(Nn)); uniform float minNz = gridmin(zcomp(Nn)); uniform float maxNx = gridmax(xcomp(Nn)); uniform float maxNy = gridmax(ycomp(Nn)); uniform float maxNz = gridmax(zcomp(Nn)); if (maxNx - minNx > 0.25 || maxNy - minNy > 0.25 || maxNz - minNz > 0.25) { shadingratefraction = 0.5; } else { shadingratefraction = 1.0; } } } }

Applied to a subdivision mesh teapot, we get the following picture.

#### Volumetric Envelope Refinement

Volumetric primitives already have an attribute that expresses the desire to perform refinement near the envelope. We can also recast the exact same functionality in terms of a refinement method:

public void refinement(uniform float shadingrate, depthshadingrate; output uniform float shadingratefraction, depthshadingratefraction) { // If the volume field is less than zero anywhere, then we know // the grid has crossed the volumetric envelope if (gridmin(VolumeField) < 0) { if (shadingrate > 0.25) { if (shadingrate < 0.5) { shadingratefraction = 0.25 / shadingrate; } else { shadingratefraction = 0.5; } } else { shadingratefraction = 1.0; } if (depthshadingrate > 0.25) { if (depthshadingrate < 0.5) { depthshadingratefraction = 0.25 / depthshadingrate; } else { depthshadingratefraction = 0.5; } } else { depthshadingratefraction = 1.0; } } }

While this example is less efficient than the corresponding volume attribute, it should give you an idea as to how one can go about computing an arbitrary quantity in a shader, and based on that refining the shading rate in the volumetric region where the interesting shading takes place.

### Caveats

Using the range of a varying quantity in a refinement method to
determine whether to refine or not relies on `sample
variance`. While often reasonable in practice, using sample variance
to determine whether or not to take more samples may be theoretically
unsound.

Use of the refinement method may suffer from inherent sampling problems: if the initial shading rate is too coarse and the samples fall outside the spike of some high frequency function, the refinement method may decide not to refine, which causes the high frequency function to be missed.

It is vitally important that good antialiasing techniques be used in conjunction with a refinement method, especially when grids of vastly different shading rates are next to each other. It has always been the case that adjacent grids may have different shading rates but this problem can be greatly magnified by arbitrary refinement methods.

Since there is no limit on what they can compute, refinement methods
may end up being computationally expensive by themselves, and should
be used only to avoid some much more expensive computation in the
`surface` method.