Sobol' Quasi-Random Sampling¶
RayON uses a Sobol' low-discrepancy sampler as its default GPU sampler for both the CUDA and OptiX backends. Unlike pseudo-random generators (PCG), Sobol' sequences are constructed to fill space uniformly by design, giving significantly faster convergence of the Monte Carlo integral.
Why quasi-random?¶
Path tracing is fundamentally a Monte Carlo integration problem. Each pixel colour is the result of averaging many light transport samples:
The error of a Monte Carlo estimator based on pseudo-random samples shrinks as \(O(N^{-1/2})\). A quasi-random (low-discrepancy) sequence like Sobol' achieves \(O\!\left(\frac{(\log N)^d}{N}\right)\), which in practice means the same quality at a fraction of the samples.
PCG vs Sobol' — sample distribution¶
The key insight is visible in a 2D scatter plot of the first 256 samples:
Implementation details¶
Direction vectors¶
Sobol' sequences are generated by XOR-ing pre-computed direction vectors \(v_1, v_2, \ldots, v_{32}\) with the bits of the sample index. RayON uses the 128-dimension Joe–Kuo 2010 table (new-joe-kuo-6.21201) stored in 16 KB of CUDA constant memory (sobol_sampler.cuh):
The dimension layout in RayON is:
| Dimensions | Used for |
|---|---|
| 0–1 | Pixel anti-aliasing jitter (x, y) |
| 2–3 | Depth-of-field lens disc (u, v) |
| 4–5 | First-bounce material scatter direction (θ, φ) |
| 6–7 | Russian roulette + second-bounce material |
| 8+ | Subsequent bounces (2 dims per bounce level) |
| ≥ 128 | Falls back to PCG automatically |
Gray-code ordering¶
Rather than evaluating samples in sequential order \(0, 1, 2, \ldots\), RayON uses Gray-code ordering:
Consecutive Gray-code values differ by exactly one bit, so each new sample only requires one XOR with a direction vector — making progressive (interactive/accumulative) rendering very cache-friendly.
Owen scrambling¶
Unscrambled Sobol' sequences show visible structure in 2D (known as "stratification artefacts"). RayON applies Laine–Karras Owen scrambling — a hash-based reversible bit permutation that:
- Preserves the low-discrepancy property (each scrambled sequence is still a valid \((0,2)\)-net)
- Is different per pixel (based on the pixel coordinate hash)
- Requires no extra memory — computed on-the-fly from a 32-bit seed
__device__ __forceinline__ uint32_t owen_scramble(uint32_t x, uint32_t seed)
{
x = reverse_bits32(x);
x ^= x * 0x3D20ADEAu;
x += seed;
x *= (seed >> 16u) | 1u;
x ^= x * 0x05526C56u;
x ^= x * 0x53A22864u;
return reverse_bits32(x);
}
The per-pixel hash feeds a different scramble seed per dimension:
This ensures that neighbouring pixels produce decorrelated sample sequences, which directly translates to reduced structured noise in the final image.
Per-dimension stratification¶
Each dimension used in path tracing is well-stratified independently. This matters because dimension 0 (AA jitter x) must be uniform even when projected onto the x-axis alone:
GPU integration¶
CUDA path¶
Sobol' state is stored in the 48-byte curandState slot already allocated per-thread — no additional GPU memory is needed. The struct overlaid on the unused bytes is:
struct SobolSamplerState {
uint32_t pixel_hash; // Laine-Karras hash of (pixel_x, pixel_y)
uint32_t sample_idx; // Gray-code index for this sample
uint32_t dim_idx; // Next dimension to consume
uint32_t pcg_seed; // Fallback seed when dim_idx >= SOBOL_MAX_DIM
};
rand_float() in cuda_utils.cuh branches at runtime on the g_use_sobol device flag — no kernel re-compilation needed when switching samplers.
OptiX path¶
The same direction vectors and Owen scrambling are compiled into the PTX ray-generation program (optix_programs.cu). The sampler state is carried per-ray in the PRD (payload):
// In PRDRadiance (optix_params.h)
uint32_t sobol_sample_idx;
uint32_t sobol_dim_idx;
uint32_t sobol_pixel_hash;
The per-launch use_sobol boolean (set via OptixLaunchParams) selects Sobol' or PCG without pipeline rebuilds.
Choosing a sampler¶
# Default (Sobol' — recommended)
./rayon -m 2 -s 512 --scene resources/scenes/default_scene.yaml
# Classic pseudo-random PCG (for comparison or debugging)
./rayon -m 2 -s 512 --scene resources/scenes/default_scene.yaml --sampler pcg
| Flag | Sampler | Convergence | Notes |
|---|---|---|---|
(default) or --sampler sobol | Sobol' | \(O\!\left(\frac{(\log N)^d}{N}\right)\) | Best quality; recommended |
--sampler pcg | PCG | \(O(N^{-1/2})\) | Fallback; unstructured noise |
Both samplers are available on CUDA (-m 2, -m 3) and OptiX (-m 4, -m 5) backends.
Visual comparison¶
The renders below isolate the effect of switching from PCG to Sobol' while keeping MIS disabled — so all noise differences come purely from the sampler choice. Drag the slider to compare.
Caustics chapel (512 SPP)¶
Left: PCG | Right: Sobol' — MIS off, 512 SPP. Sobol' reduces the high-frequency grain on the walls and caustic floor patterns.
Color bleed box (64 SPP)¶
Left: PCG | Right: Sobol' — MIS off, 64 SPP. The Cornell-box walls and soft colour gradients converge faster with Sobol'.
See the full comparison
Four scenes across all configurations are on the Visual Comparisons page.
References¶
- I. Joe and F. Kuo, "Constructing Sobol Sequences with Better Two-Dimensional Projections," SIAM J. Sci. Comput. 30 (2008) 2635–2654
- S. Laine and T. Karras, "Stratified Sampling for Stochastic Transparency," Eurographics (2011)
- B. Burley, "Practical Hash-based Owen Scrambling," JCGT Vol. 9 No. 4 (2020)