For your first question, the basic process should be (simplified version):
// Step1: Ray intersection: calculate the maximum extent for the ray before hitting the surface
bool hit = ray_intersection(scene, ray);
if (!hit) {
// check if the world itself contains scatter medium, if not then we return
}
/** Step2: distance sampling (free-path sampling).
* For homogeneous scattering medium, it's easy to do with transmittance sampling
* We sample a distance traveled before an scattering event
* and evaluate transmittance
*/
MediumInteraction mi;
Spectrum tr;
bool medium_event = ray.medium.distance_sample(scene, mi, tr);
if (medium_event) {
// we are here, if the sampled distance is less than the distance to the closest surface
// Step 3: phase function sampling to upate the ray direction,
// since distance sampling already updates the origin of the ray
ray = ray.medium.phase_sampling(mi);
path_throughput *= tr;
} else {
// Nothing to be said here, it's just basic surface rendering process
// One thing to note is that, transmittance value should be evaluated
// via ray marching (or some other algorithms)
}
// Okay, then we update our ray for the next intersection and scattering event. Loop continues.
There are some details hidden in this pseudo code:
- How do we sample the free path in step 2?
In Dartmouth distance-sampling, page 7, the sampling process is well explained: first find the bin we are in (since we are using fixed steps, it lefts us several bins in the space), and find the exact position in that bin (so, from discrete bins to continuous distance).
- How to evaluate the transmittance?
For homogeneous medium, it's simple and can be done analytically (if, the medium is exponential). For heterogeneous medium, when ray marching is used, we just subdivide the space and evaluate each part then multiply them together.
- Absorption & scattering coefficient
You will find them in most renders as
sigma_a
and sigma_s
, they compose a extinction coefficient sigma_t
. The transmittance of a certain distance is usually:
$$
\exp(-\sigma_t d), \text{ where } d \text{ is the distance}
$$
For the second question, I believe it has been answered: we sample the distance sample (distance traveled before a scattering event) according to the aggregated local transmittance, and we should be able to evaluate the transmittance (approximately) for any given path. So for non-uniform transmittance we can use ray marching here.
For the third question:
No. Actually, for heterogeneous medium (not yet for SSS), some better ways are developed already: delta-tracking (woodcock tracking), residual tracking, etc. They are unbiased and efficient in multiple ways.
For SSS, path tracing can have extremely poor converge in highly scattering medium (high sigma_s
, low sigma_a
) since the energy of the ray won't change much even we have hundreds of scattering events. This is decided by the sampling process, let's take a look at a homogeneous example:
$$
\text{estimator} = \sigma_s\frac{f(x)}{p(x)} = \sigma_s\frac{\exp(-\sigma_t d)}{\sigma_t\exp(-\sigma_t d)} = \frac{\sigma_s}{\sigma_s + \sigma_a} \approx 1
$$
So, a huge amount of scattering events need to be sampled (hence great max_depth
) and the variance is pretty high. There are some interesting works on accelerating Monte Carlo SSS rendering, if you are interested:
- Meng, Johannes, Johannes Hanika, and Carsten Dachsbacher. "Improving the Dwivedi sampling scheme." Computer Graphics Forum. Vol. 35. No. 4. 2016.
- Křivánek, Jaroslav, and Eugene d'Eon. "A zero-variance-based sampling scheme for Monte Carlo subsurface scattering." ACM SIGGRAPH 2014 Talks. 2014. 1-1.
The ideas behind these works are basically: the ray will get lost in the highly scattering medium, so we should guide the ray out or to the emitter as quickly as possible.
I think for SSS, BSSRDF can already be very efficient.