Path sampling
In this section we will discuss about path sampling API of Lightmetrica. Implementing a rendering technique means you want to implement path sampling technique and evaluating the contribution and probability that the path is being sampled. Lightmetrica provides an API for path sampling and evaluation which supports typical use-cases to implement various rendering techniques. The purpose of this section is to give a correspondence between the formulation of light transport and the implementation in Lightmetrica.
Note
The math notations in this page is based on a seminal PhD thesis by Veach [1998], although there are slight differences.
Note
This section would be better understood in conjunction with an actual implementation of a renderer.
You can find various built-in renderer implementations in src/renderer
directory.
Notations
Symbol |
Description |
Type (depending on context) |
---|---|---|
\(\mathbf{x}\) |
Position in a scene |
|
\(\omega\) |
Direction |
|
\(\omega_{\mathbf{x} \to \mathbf{y}}\) |
Direction from \(\mathbf{x}\) to \(\mathbf{y}\) |
|
\(dA\) |
Area measure |
|
\(d\sigma\) |
Solid angle measure |
|
\(d\sigma^\bot\) |
Projected solid angle measure |
|
\(\mathcal{M}\) |
Set of points on scene surfaces |
|
\(\mathcal{V}\) |
Set of points in volume |
|
\(\mathcal{S}^2\) |
Unit sphere |
Scene interaction point
lm::SceneInteraction
structure represents a scene interaction associated to a point \(\mathbf{x}\) in the scene. In the code we often name a variable of this type as sp
(scene interaction point).
The structure contains a type of the interaction sp.type
, the geometry information about the point sp.geom
, and the index of the scene node sp.primitive
.
The primitive index is mainly used internally to query the information of the scene from lm::Scene
class. Many of the sampling and evaluation functions under lm::path
namespace use this index.
Scene interaction type
A type of the interaction sp.type
is either of the following:
Notation |
Description |
Code |
---|---|---|
\(\mathbf{x} \in \mathcal{M}_E \subseteq \mathcal{M}\) |
Scene interaction is camera endpoint |
|
\(\mathbf{x} \in \mathcal{M}_L \subseteq \mathcal{M}\) |
Scene interaction is light endpoint |
|
\(\mathbf{x} \in \mathcal{M}_S \subseteq \mathcal{M}\) |
Scene interaction is surface interaction |
|
\(\mathbf{x} \in \mathcal{V}\) |
Scene interaction is medium interaction |
|
Also, some aggregate types are defined mainly for convenience:
Notation |
Description |
Code |
---|---|---|
\(\mathbf{x} \in \mathcal{M}_L \cup \mathcal{M}_E\) |
Scene interaction is endpoint |
|
\(\mathbf{x} \in \mathcal{M}_S \cup \mathcal{V}\) |
Scene interaction is midpoint |
|
Fixing interaction type
lm::as_type()
function casts the scene interaction as a different type.
For instance, this function is useful when you want to enforce the scene interaction as an endpoint type.
Point geometry
lm::PointGeometry
structure represents the geometry information associated to a point in a scene \(\mathbf{x}\).
In the code we often name a variable of this type as geom
.
The following table shows the correspondence between the math notations and members of the structure.
Notation |
Description |
Code |
---|---|---|
\(\mathbf{n}_s(\mathbf{x})\) |
Shading normal at \(\mathbf{x}\) |
|
\(\mathbf{n}_g(\mathbf{x})\) |
Geometric normal at \(\mathbf{x}\) |
|
\((\mathbf{u},\mathbf{v})\) |
Orthonormal tangent vectors at \(\mathbf{x}\) |
|
\(M_{\mathrm{world}}\) |
\(M_{\mathrm{world}} := [\mathbf{u}\; \mathbf{v}\; \mathbf{n}_s(\mathbf{x})]\) |
|
\(M_{\mathrm{local}}\) |
\(M_{\mathrm{local}} := M_{\mathrm{world}}^T\) |
|
Note
Depending on the context, the notations might be omitted. For instance, \(\mathbf{n}\equiv\mathbf{x}_s(\mathbf{x})\) if by context the use of the shading normal for a point \(\mathbf{x}\) is apparent.
Point geometry type
A point geometry has special flags representing specific configuration of the point.
Degenerated point
geom.degenerated
representing the geometry around a point \(\mathbf{x}\) is degenerated, namely, the surface around the point is collapsed to a point or a line. In this case, the normal and tangent vectors are undefined. Notation: \(\mathbf{x}\in\mathcal{S}_{\mathrm{deg}}\).
Note
Points in a volume is always degenerated: \(\mathcal{V}\subseteq\mathcal{S}_{\mathrm{deg}}\).
Infinitely distant point
geom.infinite
representing a virtual point far distant from a surface in certain direction \(\omega\). The point does not represent an actual point associated with a certain position in the scene. Also in this case, the normal and tangent vectors are undefined. Specifically in this case, geom.wo
represents the direction toward the distant point. Notation: \(\mathbf{x}\in\mathcal{S}_{\mathrm{inf}}\). Since the point is characterized by a direction \(\omega\) we sometime denote the point as \(\mathbf{x}(\omega)\in\mathcal{S}_{\mathrm{inf}}\).
Component index
A scene interaction can be associated with a component information,
which is used to differentiate the behavior of the sampling and evaluation related to the interaction.
A component is denoted by a component index, an integer value that specify the index of the component of the interaction.
For instance, this feature can be used to implement sampling and evaluation of multiple component materials.
Furthermore, the handling of perfect specular materials can be implemented using this feature.
In the code, we denote the component index as comp
.
Later we will discuss about the detail of the usage in the API.
Note
The component index is not included in the information accessible as lm::SceneInteraction
structure, since we want to handle the scene information without components being selected, e.g., the intersected surface point via ray casting.
Ray-scene intersection query
Ray-scene intersection query is a basic building block of the rendering technique. Our API supports two types of the ray-scene intersection queries: ray casting and visibility check.
Ray casting
The query is implemented in lm::Scene::intersect()
function.
Ray casting is an operation to find the closest next surface point \(\mathbf{x}_\mathcal{M}(\mathcal{x},\omega) \in \mathcal{M}_S\) along with a direction \(\omega\) from a point \(\mathbf{x}\) within the range of the distance \([t_{\mathrm{min}},t_{\mathrm{max}}]\), where
lm::Scene::intersect()
function returns lm::SceneInteraction
of SurfaceInteraction
type. The underlying geom
contains the information about the intersected point.
Note that \(\mathbf{x}_\mathcal{M}(\mathbf{x},\omega, t_{\mathrm{min}},\infty) \in \mathcal{S}_{\mathrm{inf}}\) if \(d_\mathcal{M}(\mathbf{x},\omega,t_{\mathrm{min}},\infty) = \infty\).
Checking visibility
The query is implemented in lm::Scene::visible()
function.
The function evaluates the visibility function defined as
More precisely, we use the extended version of the function incorporating the properties of point geometry information. The extended visiblity function is defined for \((\mathbf{x}, \mathbf{y})\) where \(\mathbf{x}\) and \(\mathbf{x}\) are not both in \(\mathcal{S}_{\mathrm{inf}}\) as
Local ray/direction sampling
A path construction comprises a combination of local sampling based on the point in a scene, which is important especially when you want to handle the path generation and evaluation implicitly, e.g., when you want to implement path tracing.
Aggregated solid angle measure for direction sampling
The framework defines various direction sampling techniques, many of which take samples from conditional distribution given a point \(\mathbf{x}\). The density function of the conditional distribution is either defined over solid angle measure or projected solid angle measure according to the type of the point geometry.
To generalize the two cases and simplify the interface, we define aggregated solid angle measure \(d\sigma^*\) which alternates the measure by the degeneracy of the point geometry:
Using this measure, we can define the aggregated PDF:
Aggregated throughput measure for primary ray sampling
The primary ray is sampled from the joint distribution. Similarly to the previous case, we want to define the generalized measure to support various use-cases of the primary ray sampling. We categorize the measure by two types according to the type of the endpoint being sampled from the joint distribution.
where \(\mu^*\) is a throughput measure [Veach 1998, Chapter 4.1] defined for the space of rays \(\mathcal{M}\times\mathcal{S}^2\). The second line expands the definition of the aggregated solid angle measure, which enable to support the cases where the ray originated from the degenerated point (e.g., pinhole camera).
Note that in the case of \(\mathbf{x}\in\mathcal{S}_{\mathrm{inf}}\), the position sampling happens on the virtual plane perpendicular to the ray direction, which is reflected by the projected area measure \(dA^\bot\).
Similarly, the joint PDF can be defined as
Component sampling
Sampling:
lm::path::sample_component()
PDF:
lm::path::pdf_component()
The function samples a component index \(j\).
Currently component sampling is only supported for the surface interactions. In other cases the component index is fixed to 0 (with probability 1). The following table shows where the operation is implemented.
Operation |
Implemented in |
---|---|
\(j \sim p_{c,\mathrm{bsdf}}(\cdot\mid\mathbf{x})\) |
|
\(p_{c,\mathrm{bsdf}}(j\mid\mathbf{x})\) |
Primary ray sampling
Sampling:
lm::path::sample_primary_ray()
PDF:
lm::path::pdf_primary_ray()
The function samples a primary ray \((\mathbf{x}, \omega)\).
If \(\mathbf{x}\) and \(\omega\) are independent,
the function is equivalent to evaluating lm::path::sample_position()
and lm::path::sample_direction()
separately.
The following table shows where each operation is implemented.
Operation |
Implemented in |
---|---|
\((\mathbf{x}, \omega) \sim p_{\mu^* L}(\cdot,\cdot)\) |
|
\(p_{\mu^* L}(\mathbf{x}, \omega)\) |
|
\((\mathbf{x}, \omega) \sim p_{\mu^* E}(\cdot,\cdot)\) |
|
\(p_{\mu^* E}(\mathbf{x}, \omega)\) |
Endpoint sampling
Sampling:
lm::path::sample_position()
PDF:
lm::path::pdf_position()
The function samples an endpoint \(\mathbf{x}\).
The following table shows where each operation is implemented.
Operation |
Implemented in |
---|---|
\(\mathbf{x} \sim p_{AL}(\cdot)\) |
|
\(p_{AL}(\mathbf{x})\) |
|
\(\mathbf{x} \sim p_{AE}(\cdot)\) |
|
\(p_{AE}(\mathbf{x})\) |
Direction sampling
Sampling:
lm::path::sample_direction()
PDF:
lm::path::pdf_direction()
The function samples a direction \(\omega\) originated from a current position \(\mathbf{x}\) with the component index \(j\):
The following table shows where each operation is implemented.
Operation |
Implemented in |
---|---|
\(\omega \sim p_{\sigma^* L}(\cdot\mid\mathbf{x})\) |
|
\(p_{\sigma^* L}(\omega\mid\mathbf{x})\) |
|
\(\omega \sim p_{\sigma^* E}(\cdot\mid\mathbf{x})\) |
|
\(p_{\sigma^* E}(\omega\mid\mathbf{x})\) |
|
\(\omega \sim p_{\sigma^* \mathrm{bsdf}}(\cdot\mid\mathbf{x},j)\) |
|
\(p_{\sigma^* \mathrm{bsdf}}(\omega\mid\mathbf{x},j)\) |
|
\(\omega \sim p_{\sigma^* \mathrm{phase}}(\cdot\mid\mathbf{x})\) |
|
\(p_{\sigma^* \mathrm{phase}}(\omega\mid\mathbf{x})\) |
Direct endpoint sampling
Sampling:
lm::path::sample_direct()
PDF:
lm::path::pdf_direct()
The function samples a direction \(\omega\) directly toward an endpoint based on the current position \(\mathbf{x}\). This sampling strategy is mainly used to implement next event estimation.
The following table shows where each operation is implemented.
Operation |
Implemented in |
---|---|
\(\omega \sim p_{\sigma^* \mathrm{directL}}(\cdot\mid\mathbf{x})\) |
|
\(p_{\sigma^* \mathrm{directL}}(\omega\mid\mathbf{x})\) |
|
\(\omega \sim p_{\sigma^* \mathrm{directE}}(\cdot\mid\mathbf{x})\) |
|
\(p_{\sigma^* \mathrm{directE}}(\omega\mid\mathbf{x})\) |
|
Evaluating directional components
Function:
lm::path::eval_contrb_direction()
The function evaluates directional component of path integral \(f_{s\Sigma}(\mathbf{x},j, \omega_i,\omega_o)\), where
where \(\Sigma\in\{ L,E \}\). \(\Sigma\) corresponds to the transport direction, which is necessary to handle non-symmetric scattering described in Chapter 5 of Veach’s thesis. The following table shows where each operation is implemented.
Operation |
Implemented in |
---|---|
\(L_e(\mathbf{x},\omega_o)\) |
|
\(W_e(\mathbf{x},\omega_o)\) |
|
\(f_{\mathrm{bsdf}L}(\mathbf{x},j,\omega_i,\omega_o)\) |
|
\(f_{\mathrm{bsdf}E}(\mathbf{x},j,\omega_i,\omega_o)\) |
|
\(\mu_s(\mathbf{x})\) |
N/A |
\(f_{\mathrm{phase}}(\mathbf{x},\omega_i,\omega_o)\) |
Note
\(\omega_i\) is not used when \(\mathbf{x}\) is endpoint. Also, \(\omega_o\) always represents outgoing direction irrespective to the transport directions, that is, the same direction as the transport direction.
Transforming probability densities
The framework provides the functions to transform density functions according to a different measure.
Solid angle to projected solid angle
lm::surface::convert_pdf_SA_to_projSA()
implements the conversion:
Aggregated solid angle to area
To achieve the transformation of densities from aggregated solid angle to area measure according to the point geometry types transparently, we define the extended geometry term as
lm::surface::geometry_term()
function evaluates the term, but assuming that \(\mathbf{x}\) and \(\mathbf{y}\) are mutually visible (thus \(V(\mathbf{x}, \mathbf{y})=1\)).
lm::surface::convert_pdf_projSA_to_area()
function internally uses this function to implement the conversion of the densities, which allows to convert the densities according to the point geometry type:
where \(\omega = \omega_{\mathbf{x}\to\mathbf{y}}\).
This is especially useful when the conversion function is used in conjunction with the PDF evaluated with lm::path::pdf_*()
function, which evaluates the density with aggregated solid angle measure.
Conversion of aggregated throughput
The joint PDF for the primary ray sampling is also bound to be used for the measure conversion. We can also use the extended geometry term for the conversion:
Note that in the case of \(\mathbf{x}\in\mathcal{S}_{\mathrm{inf}}\), the conversion happens for the projected area measure, since the differential area around \(\mathbf{x}\) are orthogonally projected to the surface around \(\mathbf{y}\):
We also note that in this case the converted measure is not a product area measure \(dA^2\) but \(d\sigma dA\). We denote the converted aggregate measure as \(dA^{2*}\).
Bidirectional path sampling
Some rendering techniques such as bidirectional path tracing are based on bidirectional path sampling, which explicitly manages a structure of a light transport path in the process of sampling and evaluation. In this section, we will introduce the related API for bidirectional path sampling.
Note
Currently bidirectional path sampling of the framework only supports light transport on surfaces, although we have a plan to support volumetric light transport.
Notations
Symbol |
Description |
Type (depending on context) |
---|---|---|
\(\mathbf{x}\) |
Path vertex |
|
\(\bar{x}\) |
Light transport path (or just path) |
|
\(\bar{x}_L\) or \(\bar{y}\) |
Light subpath |
|
\(\bar{x}_E\) or \(\bar{z}\) |
Eye subpath |
|
\((s,t)\) |
Strategy index of bidirectional path sampling |
|
\(s\) |
Number of vertices in light subpath |
|
\(t\) |
Number of vertices in eye subpath |
|
Light transport path
A path \(\bar{x}\) is defined by a sequence of path vertices. We denote the path with the number of vertices \(k\) as \(\bar{x}_k:=\mathbf{x}_0\mathbf{x}_2\dots\mathbf{x}_{k-1}\). We often omit the subscript \(k\) depending on the context.
A path is full path if the path constitutes of a complete light transport path, where \(\mathbf{x}_0\in\mathcal{M}_L\), \(\mathbf{x}_{k-1}\in\mathcal{M}_E\). In the context without ambiguity, we call a full path as merely a path.
A path is subpath if the path starts but not ends its vertices on the endpoints. Note that the subpath always starts from an endpoint, irrespective to the type of the endpoint. If \(\mathbf{x}_0\in\mathcal{M}_L\), the subpath is called light subpath. If \(\mathbf{x}_0\in\mathcal{M}_E\), the subpath is called eye subpath.
In the framework, lm::Path
structure represents a path, which holds a vector .vs
of lm::Vert
representing a path vertex.
A path vertex structure is a tuple of a surface interaction .sp
and an integer .comp
representing the component index associated to the scene interaction.
We denote the component index associated to the vertex \(\mathbf{x}_i\) as \(j_i\).
Note
The order of the vector .vs
depends on the type of the path. If a path represents a full path, the vector always starts from a vertex representing light endpoint and ends with camera endpoint. On the other hand, if a path represents a subpath, the vector starts from an endpoint irrespective to the type of endpoint.
The correspondence between notations and the operations over the path structure is summarized in the following table.
Notation |
Description |
Code |
---|---|---|
\(k\) |
Number of path vertices |
|
\(k+1\) |
Path length |
|
\(\mathbf{x}_i\) |
\(i\)-th path vertex from \(\mathbf{x}_0\) |
|
\(\mathbf{x}_{k-1-i}\) |
\(i\)-th path vertex from \(\mathbf{x}_{k-1}\) |
|
\(\mathbf{x}_i\) |
\(i\)-th path vertex from \(\mathbf{x}_0\) |
|
Note
lm::Path::vertex_at()
or lm::Path::subpath_vertex_at()
returns
a pointer to a path vertex and nullptr
if the index is out of bound,
which is intentional to simplify the implementation.
Sampling subpath
lm::path::sample_subpath()
function samples a subpath up to the given maximum number of vertices max_verts
. The type of subpath can be configured by the argument trans_dir
. In math notations, this process can be written as
where \(l\) is the maximum number of vertices. In fact, each vertex is sampled sequentially
where \(i=2,\dots,(l-1)\) and \(\Sigma\in\{ L,E \}\). Thus the PDF for subpath sampling can be written as
The above equation abstracts the actual sampling process which iteratively samples directions and applies ray casting to find the next intersected points:
Note that the first two vertices are always sampled from the joint distribution. If \(\mathbf{x}_0\) and \(\mathbf{x}_1\) are independent, the sampling process is equivalent to
where \(i=1,\dots,(l-1)\).
Note
The number of vertiecs sampled with lm::path::sample_subpath()
function might not always same as max_verts
due to the early termination of the subpath. The early termination happens for instance when the ray doesn’t hit with any objects before sampling max_verts
vertices.
Note
For simplicity, we don’t use Russian roulette in lm::path::sample_subpath()
function.
You need to define your own subpath sampling function to support that.
Sampling subpath from endpoint
sample_subpath_from_endpoint()
function can continue to sample the path vertices from the last vertex of the existing subpath. If the given path is empty, this function is equivalent to lm::path::sample_subpath()
.
Connecting subpaths
lm::path::connect_subpaths()
function can combine light subpath \(\bar{y}\) and eye subpath \(\bar{z}\) with a given number of vertices \(s\) and \(t\) in each subpath respectively. This process amounts to sampling a full path with the strategy \((s,t)\). If the subpaths are not connectable, the connection process will be failed and the function returns std::nullopt
.
For the strategy index \((s,t)\) where \(s+t\geq 1\), the subpaths are connectable according to the following conditions:
if \(s=0\), \(\mathbf{z}_{t-1}\notin\mathcal{S}_{\mathrm{deg}}\),
if \(t=0\), \(\mathbf{y}_{s-1}\notin\mathcal{S}_{\mathrm{deg}}\),
if \(s=1\), \(\mathbf{y}_0\in\mathcal{S}_{\mathrm{conn}}\) and \(\mathbf{z}_{t-1}\notin\mathcal{S}_{\mathrm{spec}}\) and \(V(\mathbf{y}_{0}, \mathbf{z}_{t-1}) = 0\),
if \(t=1\), \(\mathbf{z}_0\in\mathcal{S}_{\mathrm{conn}}\) and \(\mathbf{y}_{s-1}\notin\mathcal{S}_{\mathrm{spec}}\) and \(V(\mathbf{y}_{s-1}, \mathbf{z}_{0}) = 0\),
if \(s>0\) and \(t>0\), \(\mathbf{y}_{s-1}\notin\mathcal{S}_{\mathrm{spec}}\) and \(\mathbf{z}_{t-1}\notin\mathcal{S}_{\mathrm{spec}}\) and \(V(\mathbf{y}_{s-1}, \mathbf{z}_{t-1}) = 0\).
The vertex \(\mathbf{x}\) is specular if the directional component includes a delta function.
We use the notation \(\mathbf{x}\in\mathcal{S}_{\mathrm{spec}}\) to denote the property of the vertex.
For instance, perfect specular reflection is specular.
A specular vertex needs special treatment since its support cannot be sampled without deterministic selection.
This condition can be checked using lm::path::is_specular_component()
function.
The endpoint \(\mathbf{x}\) is connectable if corresponding positional and directional PDFs can be evaluated independently. We use the notation \(\mathbf{x}\in\mathcal{S}_{\mathrm{conn}}\). For instance, the endpoint is connectable if the first two subpath vertices are independent. This condition can be checked using lm::path::connectable_endpoint()
function.
Given an strategy \(s\), we call the subpaths satisfying the above condition being connectable subpaths by the strategy \(s\) and the connected full path being samplable by the strategy \(s\).
This condition can be checked by lm::Path::is_samplable_bidir()
function,
assuming the visibility function in the definition is assumed to be one.
Note
An endpoint can be connectable even if \(\bar{x}_{\Sigma,0}\) and \(\bar{x}_{\Sigma,1}\) are not independent as long as we can compute the marginals of the joint distribution analytically.
Evaluating measurement contribution
lm::Path::eval_measurement_contrb_bidir()
function evaluates the measurement contribution function defined by
Here, \(c_{s,t}(\bar{y}, \bar{z})\) is called the connection term, which can be evaluated by lm::Path::eval_connection_term()
function.
Note
The original measurement contribution function does not take strategy index
since the scattering term \(f_s\) is assumed to be symmetrical.
This difference comes from the handling of asymmetric scattering in lm::path::eval_contrb_direction()
function.
Evaluating bidirectional path PDF
lm::Path::pdf_bidir()
function evaluates the bidirectional path PDF defined by
Evaluating sampling weight
For convenience, the path structure provides a function lm::Path::eval_sampling_weight_bidir()
to compute sampling weight \(f_{s,t}(\bar{x})/p_{s,t}(\bar{x})\). We assume the input of the function is samplable by the strategy \((s,t)\). Specifically, the sampling weight is defined as
where \(\alpha_L(\bar{y})\) or \(\alpha_E(\bar{z})\) is called subpath sampling weight, which can be computed by lm::Path::eval_subpath_sampling_weight()
function.
Evaluating MIS weight
lm::Path::eval_mis_weight()
function evaluates MIS weight via power heuristic.
Summary of notations
The path sampling interface defines various properties associated to the point to the scene \(\mathbf{x}\). The following table summarizes the notations introduced in this section.
Notation |
Description |
Code |
---|---|---|
\(\mathbf{x} \in \mathcal{M}_E\) |
\(\mathbf{x}\) is camera endpoint |
|
\(\mathbf{x} \in \mathcal{M}_L\) |
\(\mathbf{x}\) is light endpoint |
|
\(\mathbf{x} \in \mathcal{M}_S\) |
\(\mathbf{x}\) is surface interaction |
|
\(\mathbf{x} \in \mathcal{V}\) |
\(\mathbf{x}\) is medium interaction |
|
\(\mathbf{x}\in\mathcal{S}_{\mathrm{deg}}\) |
\(\mathbf{x}\) is degenerated |
|
\(\mathbf{x}\in\mathcal{S}_{\mathrm{inf}}\) |
\(\mathbf{x}\) is infinitely distant point |
|
\(\mathbf{x}\in\mathcal{S}_{\mathrm{spec}}\) |
\(\mathbf{x}\) is specular |
|
\(\mathbf{x}\in\mathcal{S}_{\mathrm{conn}}\) |
\(\mathbf{x}\) is connectable endpoint |
|