Site Tools

Brazil Rendering System Sampling

Brazil r/s for Rhino Wiki pages
Robert McNeel & Associates
Summary: This page discusses sampling in the Brazil render engine, sampling in general, and guidelines for proper setups.

What is sampling?

Real life is continuous. So is a 3D scene. Even a finite surface suspended in space and lit by a spotlight has an infinite number of different colors. In actuality, atoms terminate this regress, but Rhino is not aware of atoms so we cannot count on them in this case. When we render an image, we do not have infinite accuracy since we cannot represent details smaller than one pixel (though it is possible to suggest details smaller than one pixel, but that is a story for another day). The obvious solution is to measure the color at the center of every pixel in the rendering, and then assume that this color is a fair average of the entire pixel. Called sampling, this creates an approximation of a continuous data source by taking measurements at specific intervals:

The pink rings represent the 2D projection of our continuous 3D model, and the dots represent the samples. Notice the samples are not aligned perfectly in a grid. This is done to avoid unwanted aliasing artifacts of (near) vertical and horizontal lines, which occur quite frequently in CG. Brazil's initial sample distribution is in fact far cleverer than this. (These images images show a jittered-stratified grid, whereas Brazil uses a low-discrepancy Best-Candidate sampling. These are just the names of algorithms involved – you do not have to remember them. For now assume that every sample is at least near the pixel center. Every sample which intersects the rings is made black, others are red. If we now have to reconstruct an image based on these samples, we can hardly do better than:

which isn't a good approximation. Large areas of white are filled with black and large areas of pink are still visible. One solution is to shorten our sample interval and measuring four points where we used to measure only one. This solution is a good one, but unfortunately the number of samples increases with the square of the accuracy gain. It will take longer and longer (much, much longer) to compute the whole image as we increase the accuracy. Making a single measurement is a very expensive operation so we want to take as few samples as possible. A solution is to use an adaptive sampling algorithm, which only introduces more samples when increased accuracy is expected to pay off. If Sample A, for example, intersects our pink rings, but its neighbor B does not, we can safely assume that the rings ends somewhere between A and B. We can then sample a few more times between to increase our local accuracy:

This already results in a far more accurate approximation of the continuous input, without a doubling of the number of samples. So the thin lower part of the leftmost ring is still ignored because it happened to fall between two samples during the coarse pass and thus we had no idea there was something in between. This is a common problem with renderers such as Brazil which use adaptive sampling. The only way to solve it is to increase the accuracy of the initial pass.

Sampling controls in Brazil

Brazil offers several settings to control the sampling behavior. First, you can set the rendering's minimum and maximum sampling accuracy. These numbers are integers and they show the exponent of the number of samples per pixel:

Sampling settings
Sample value Value meaning Samples per pixel Result
-3 2-3 One sample every 8 pixels Extreme low quality, very blurry
-2 2-2 One sample every 4 pixels Very low quality, useful for judging lighting conditions across a rendering
-1 2-1 One sample every 2 pixels Low quality, useful for judging local lighting conditions, good for previews
0 20 One sample every pixel This effectively removes any anti-aliasing artifacts from the image
1 21 Two samples every pixel Lowest anti-aliasing value, lines will still be fairly jagged
2 22 Four samples every pixel Pretty good anti-aliasing, especially on low-contract thresholds
3 23 Eight samples every pixel Very good anti-aliasing, only use higher settings to counter sampling artifacts

Let's look closer at how minimum and maximum resolution cooperate to give high-quality images in a reasonable amount of time. The following images are all renderings from the same scene. It's a simple plane with a procedural texture on it, meaning the lines are infinitely accurate (they will never become pixelated). The maximum sampling resolution for each image is +3 (8 samples per pixel). Our eyes cannot distinguish a higher quality, though you can make Brazil sample 256 points per pixel.

brazilsampling_min3plus3.jpg brazilsampling_min2plus3.jpg brazilsampling_min1plus3.jpg
{-3, +3} {-2, +3} {-1, +3}
brazilsampling_0plus3.jpg brazilsampling_plus1plus3.jpg brazilsampling_plus2plus3.jpg
{0, +3} {+1, +3} {+2, +3}

Even though the potential rendering quality is high, there are major sampling artifacts when we begin with a low quality sampling resolution. As the first image shows, Brazil is trashing about like a headless chicken when it is not allowed to perform an accurate enough initial sampling pass. The result is very anti-aliased (because of the +3 maximum sampling) but the super-pixel elements are all messed up. When we start to increase the minimum quality, the moire patterns start to disappear and the image becomes more and more accurate. At {+1, +3} we finally have a completely accurate representation of our texture. {+1, +3} is a relatively high sampling resolution, though you should expect to use similar domains for any production quality rendering. Since the black stripes become very thin in the top half of the rendering (they become thinner than a single pixel), we need multiple samples per pixel for the minimum sampling resolution for Brazil to detect them as continuous entities…

Adaptive filters

Since you can specify two different sampling resolutions in Brazil, you also have to specify when you want to use the more accurate one. By default, Brazil will sample the whole image at the lowest sampling resolution, then refine (adaptive sampling) the sample grid when two neighboring samples meet the specified threshold settings. Assume we have a simple scene with two objects, a groundplane and a single lightsource plus skylight. If we set the minimum sampling resolution to -2 (one sample every 4 pixels) and the maximum to 2 (four samples per pixel) we can expect the following results without adaptive sampling:

brazilsampling_noadaptive.jpg braziladaptivesamplinghighquality.jpg
The entire image is sampled at -2 resolution The entire image is sampled at +2 resolution

Now, Brazil has four different settings that let you set up adaptive thresholds. I typically use all four of them, but then I'm rarely under an extreme deadline so I can afford to spend a few minutes extra on a rendering. These are:

  1. Object Edge
  2. Normal
  3. Z-Depth
  4. Contrast

Object Edge adaptation

When sample A intersects our groundplane, and the neighboring sample B intersects the blue glass, we know that somewhere in between there must be a transition from groundplane to glass. We can use such a threshold to trigger a refinement event:

brazilsampling_objectedgeshow.jpg brazilsampling_objectedge.jpg
All the pixels (red) where the Object Edge refinement was performed. The resulting image. As you can see, the edges are smooth, but all the sharp lines on the interior of the glasses are still chunky and rough.

Surface Normal adaptation

Instead of checking for different objects, the Normal adaption compares the surface normal vectors of samples A and B. If these differ sufficiently then a refinement event is triggered:

brazilsampling_normaladaptiveshow.jpg brazilsampling_normal.jpg
All the pixels (red) where the Normal refinement was performed. The resulting image. This refinement algorithm found the near edge of the green glass, but it was unable to pick up on the far edge. Since the top edge of a glass is fairly level the normal vectors all point upwards. The groundplane also has vertical normals and there was thus insufficient difference. It is already clear that Object Edge and Normal refinement complement each other very nicely.

Z-Depth adaptation

Yet another geometric property that can be evaluated is the difference in distance between |Camera, A| and |Camera, B|. When sample A is very close and B is very far, it's probably safe to assume there's a lot happening in between:

brazilsampling_zdepthshow.jpg brazilsampling_zdepth.jpg
All the pixels (red) where Z-Depth refinement was performed. The resulting image. The image on the left already shows the weaknesses of this approach. Near tangent surfaces are easily tagged (such as the upper half of the groundplane) even though this is a completely pointless refinement in this case.

Contrast adaptation

This is the only non-geometric filter available. It relies on the results of the first (coarse) sampling pass and then refines based on relative contrast between adjacent samples. This is probably the most useful filter available in the Brazil sampling engine.

brazilsampling_contrastshow.jpg brazilsampling_contrast.jpg
All the pixels (red) where Contrast refinement was performed. The resulting image. Contrast refinement found almost all sharp edges in the rendering, with the exception of the transition between the blue glass and the shadow.

Grand finale…

brazilsampling_allshow.jpg brazilsampling_all.jpg
If we combine all four filters we get an overlay of refinement grids. The Z-Depth refinement does not contribute greatly in this case and could have been left out. None of the filters found sufficient difference on the interior of the shadow field. Contrast tagged a few pixels, but not enough to sample the entire gradient at a higher resolution. As a result the resolution of the interior of the shadow is -2 which is coarse enough to see the noise of the skylight. We either have to fine-tune the contrast filter to be more sensitive (picking a contrast threshold color closer to black) or increase the resolution of the minimum sample density.
brazil/sampling.txt · Last modified: 2020/08/14 (external edit)