Top: Contents
PTLib is a flexible path tracing library, thought to be as performant as possible and yet vastly configurable at compile-time. The module is organized into a host library of parallel kernels, PTLib, and a core module of device-side functions, PTLibCore. The latter provides functions to generate primary rays, process path vertices, sample Next-Event Estimation and emissive surface hits at each of them, and process all the generated samples. In order to make the whole process configurable, all the functions accept three template interfaces:
- a context interface, holding members describing the current path tracer state, and providing two trace methods, for scattering and shadow rays respectively. The basic path tracer state can be inherited from the PTContextBase class. On top of that, this class has to provide the following interface:
struct TPTContext
{
FERMAT_DEVICE
void trace_ray(
TPTVertexProcessor& vertex_processor,
const uint32 nee_vertex_id);
FERMAT_DEVICE
void trace_shadow_ray(
TPTVertexProcessor& vertex_processor,
const uint32 nee_vertex_id,
const uint32 nee_sample_id);
};
Note that for the purpose of the PTLibCore module, a given implementation is free to define the trace methods in any arbitrary manner, since the result of tracing a ray is not used directly. This topic will be covered in more detail later on.
- a user defined vertex processor, determining what to do with each generated path vertex; this is the class responsible for weighting each sample and accumulating them to the image. It has to provide the following interface:
struct TPTVertexProcessor
{
FERMAT_DEVICE
uint32 preprocess_vertex(
const TPTContext& context,
const float cone_radius,
const uint32 prev_vertex_info);
FERMAT_DEVICE
void compute_nee_weights(
const TPTContext& context,
const uint32 prev_vertex_info,
const uint32 vertex_info,
uint32& out_vertex_info);
FERMAT_DEVICE
void compute_scattering_weights(
const TPTContext& context,
const uint32 prev_vertex_info,
const uint32 vertex_info,
const uint32 out_comp,
uint32& out_vertex_info);
FERMAT_DEVICE
void accumulate_emissive(
const TPTContext& context,
const uint32 prev_vertex_info,
const uint32 vertex_info,
FERMAT_DEVICE
void accumulate_nee(
const TPTContext& context,
const uint32 vertex_info,
const bool shadow_hit,
};
- a user defined direct lighting engine, responsible to generate NEE samples. It has to provide the following interface:
struct TDirectLightingSampler
{
FERMAT_DEVICE
uint32 preprocess_vertex(
const uint32 pixel,
const uint32 bounce,
const bool is_secondary_diffuse,
const float cone_radius,
FERMAT_DEVICE
uint32 sample(
const uint32 nee_vertex_id,
const float z[3],
float* light_pdf,
FERMAT_DEVICE
void map(
const uint32 prev_nee_vertex_id,
const uint32 triId,
float* light_pdf,
FERMAT_DEVICE
void update(
const uint32 nee_vertex_id,
const uint32 nee_sample_id,
const bool occluded);
};
- You'll notice this is just a slight generalization of the Light interface, providing more controls for preprocessing and updating some per-vertex information. At the moment, Fermat provides two different implementations of this interface:
- The most important functions implemented by the PTLibCore module are:
template <typename TPTContext>
FERMAT_DEVICE
TPTContext& context,
const uint2 pixel,
template <typename TPTContext, typename TPTVertexProcessor>
FERMAT_DEVICE
TPTContext& context,
TPTVertexProcessor& vertex_processor,
const bool shadow_hit,
const uint32 vertex_info = uint32(-1),
const uint32 nee_vertex_id = uint32(-1),
const uint32 nee_sample_id = uint32(-1));
template <typename TPTContext, typename TPTVertexProcessor>
FERMAT_DEVICE
TPTContext& context,
TPTVertexProcessor& vertex_processor,
const uint32 bounce,
const uint2 pixel,
const uint32 prev_vertex_info = uint32(-1),
const uint32 prev_nee_vertex_id = uint32(-1),
- Note that all of these are device-side functions meant to be called by individual CUDA threads. The underlying idea is that all of them might call into TPTContext trace() methods, but that the implementation of the TPTContext class might decide whether to perform the trace calls in-place, or rather enqueue them. The latter approach is called "wavefront" scheduling, and is the one favored in Fermat, as so far it has proven most efficient.
Wavefront Scheduling
- PTLib implements a series of kernels to execute all of the above functions in massively parallel waves, assuming their inputs can be fetched from some TPTContext -defined queues. In order for these wavefronts kernels to work, it is sufficient to have the TPTContext implementation inherit from the prepackaged PTContextQueues class, containing all necessary queue storage. Together with the kernels themselves, the corresponding host dispatch functions are provided as well. These are the following:
template <typename TPTContext>
TPTContext context,
template <typename TPTContext, typename TPTVertexProcessor>
const uint32 in_queue_size,
TPTContext context,
TPTVertexProcessor vertex_processor,
template <typename TPTContext, typename TPTVertexProcessor>
const uint32 in_queue_size,
TPTContext context,
TPTVertexProcessor vertex_processor,
- The last key function provided by this module is the one assembling all of the above into a single loop, or rather, into a complete pipeline that generates primary rays, traces them, shades the resulting vertices, traces any generated shadow and scattering rays, shades the results, and so, and so on, until all generated paths are terminated:
template <typename TPTContext, typename TPTVertexProcessor>
TPTContext& context,
TPTVertexProcessor& vertex_processor,
- In the next chapter we'll see how all of this can be used to write a very compact path tracer.
- 'Modern Hall, at sunset', based on a model by NewSee2l035
Next: The Path Tracer (Revisited)