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 RiシェーディングRate. 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. ジオメトリ that has never undergone any refinement will start with a shading rate equal to the current RiシェーディングRate. 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 RiシェーディングRate.) 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.

images/figures.refinement_method/curvature_teapot.png

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.