Renderer¶
-
template<IsSpectral TSpectral>
class Renderer¶ Abstract base class for scene renderers.
Renderer provides the interface and common helpers for rendering a SceneView into a FrameBuffer. Derived classes implement specific rendering algorithms.
- Template Parameters:
TSpectral – The spectral type (e.g., RGB, Visible8)
Public Functions
-
Renderer() = default¶
-
virtual ~Renderer() = default¶
-
virtual void render(SceneView<TSpectral> &scene_view, FrameBuffer<TSpectral> &frame_buffer)¶
-
inline void set_samples_per_pixel(int spp)¶
Set the number of samples per pixel: at least 1, or every pixel would be NaN.
-
inline void set_max_bounces(int max_bounces)¶
Set the maximum number of bounces for path tracing: at least 1, the camera ray’s first hit, or every pixel would be black.
-
inline void set_dynamic_sampling(bool dynamic_sample = true)¶
Turn dynamic sampling on or off (the default). On, each pixel is traced with as many of its spp samples as bring its noise under the noise threshold, and at least the minimum, judged with its neighbours’ (see set_noise_threshold()).
-
inline void set_min_samples(int min_samples)¶
Set the minimum samples per pixel for dynamic sampling: at least 2, since one sample has no variance to judge by.
-
inline void set_noise_threshold(float threshold)¶
Set how noisy dynamic sampling leaves a pixel: non-negative and finite (0 never stops early).
A pixel’s noise is the standard error of its mean radiance, relative to that mean: 0.01 leaves it within about 1% of what more samples would give. Pixels are traced in rounds, the minimum number of samples and then twice as many each round, up to spp, and a pixel samples on while it or any of its eight neighbours is noisier than the threshold. A pixel whose samples have all happened to find no light so samples on among neighbours that found some; where no pixel’s samples differ (nothing but sky, or no light at all), the pixels stop after the minimum.
-
inline float noise_threshold() const¶
How noisy dynamic sampling leaves a pixel (see set_noise_threshold()).
-
inline void set_indirect_clamp(float indirect_clamp)¶
Set the indirect lighting clamp threshold: non-negative, or infinite (the default) for none. A negative one would make indirect light negative.
-
inline void set_unresolved_occlusion(bool occlusion = true)¶
Enable or disable occlusion testing of unresolved point sources.
-
void set_unresolved_taper(double fraction_of_read_noise)¶
Set how far an unresolved source is drawn: until the light it puts in a pixel, as the sensor reads it out, falls to this fraction of the read noise.
With the aperture’s diffraction pattern, still sources are drawn at their exact position, pixel by pixel from the PSF’s tables, and moving ones along their path, as its line integral (see PsfTables::draw_line()), out to the distance r from the source or its path beyond which no pixel reads out more than tau, this fraction of the sensor’s read noise as configured (of an electron for a sensor without read noise; the same whether or not noise is simulated, so that a noiseless frame is the mean of noisy ones). From r to 2 r they fade smoothly to nothing. The light left on the sensor beyond that is added evenly over the frame, so that none is lost and no edge appears. A source’s rings are followed only as far as they change a pixel by more than tau; beyond, its light is averaged over them. No pixel is off by more than about 2 tau. A source is always drawn out to where it holds 90% of its light. A source with a PSF set with CameraModel::set_psf() or set_measured_psf(), still or moving, is drawn the same way from the PSF’s grid (see GridPsf), without rings to follow, as is the defocus blur, or a point in focus, without a PSF; where moving, its faint wings are interpolated between pixels where that is off by no more than half of tau (see GridPsf::draw_line()). RGB sensors read out each channel; others, their sum.
The default, DEFAULT_UNRESOLVED_TAPER, ends halos where a square-root stretch of a noiseless frame to 30 electrons cannot show it. Larger values draw fewer pixels, and so take less time; 0 draws every source over the whole frame, which can take a long time.
- Parameters:
fraction_of_read_noise – Non-negative and finite.
- Throws:
std::runtime_error – if it is negative or not finite.
-
inline double unresolved_taper() const¶
-
void set_fft_rounding_tolerance(double fraction_of_read_noise)¶
Sets how much of the sensor’s read noise (of an electron without read noise) the rounding error of convolving bodies with the PSF in single precision may put in a pixel’s readout, as FftConvolver::rounding_error() predicts it, before they are convolved in double precision instead.
Each FFT spreads rounding error over the whole frame in proportion to its brightest pixel, so that in single precision, the dark sky beside a body many full wells bright carries a faint pattern of it, positive and negative. At the default, DEFAULT_FFT_ROUNDING_TOLERANCE, it is lost in the read noise of any one frame, but a square-root stretch of a noiseless frame to 30 electrons can show it. Single precision takes about 25% less memory for a 1920 x 1080 Visible8 frame, and about as long. 0 always convolves in double precision, whose rounding is about 1e-9 of single’s.
- Parameters:
fraction_of_read_noise – Non-negative and finite.
- Throws:
std::runtime_error – if it is negative or not finite.
-
inline double fft_rounding_tolerance() const¶
-
inline void set_fft_planning(FftPlanEffort effort)¶
How hard FFTW plans the transforms that convolve bodies with the PSF (see FftPlanEffort). Estimate, the default, plans at once. Measure finds faster transforms, which convolve a 1920 x 1080 Visible8 frame in about 15% less time, but takes 2 to 5 seconds the first time a size and precision are used in a process: worth it for long sequences of frames.
-
inline FftPlanEffort fft_planning() const¶
See set_fft_planning().
-
inline void set_region_culling(bool enable = true)¶
Enable or disable skipping screen regions that provably contain no geometry.
Enabled by default. Purely an optimization: the image is identical either way.
-
inline void set_region_cull_margin_scale(float scale)¶
Scale the safety margin added to each tile’s direction cone: finite, and taken as 1 if less.
-
inline void set_region_cull_validation(bool enable = true)¶
(CI): Trace culled regions anyway and report any that turn out to contain geometry.
Public Static Attributes
-
static constexpr float DEFAULT_NOISE_THRESHOLD = 0.01f¶
The default noise threshold for dynamic sampling (see set_noise_threshold()).
-
static constexpr double DEFAULT_UNRESOLVED_TAPER = 1e-3¶
How faint, as a fraction of the sensor’s read noise, the light an unresolved source puts in a pixel gets before it is tapered off. See set_unresolved_taper().
-
static constexpr double DEFAULT_FFT_ROUNDING_TOLERANCE = 0.1¶
How much of the read noise the rounding of convolving bodies with the PSF in single precision may put in a pixel’s readout before they are convolved in double precision, by default. See set_fft_rounding_tolerance().
Protected Functions
-
virtual Image<TSpectral> path_trace_(SceneView<TSpectral> &scene_view, FrameBuffer<TSpectral> &frame_buffer)¶
Path trace a scene view into a frame buffer.
Traces camera rays through each pixel, evaluating direct lighting with shadow rays and indirect illumination via recursive path tracing with Russian roulette termination.
The rendering is parallelized over tiles using TBB. Each tile accumulates results from multiple samples per pixel (spp_) into the frame buffer: all of them at once, or, with dynamic sampling, in rounds, as many as each pixel and its neighbours need (see set_noise_threshold()).
- Template Parameters:
TSpectral – The spectral type (e.g., RGB, Visible8)
- Parameters:
scene_view – The scene view containing geometry, lights, and environment
frame_buffer – The frame buffer to render into
-
virtual Image<TSpectral> render_unresolved_(SceneView<TSpectral> &scene_view, FrameBuffer<TSpectral> &frame_buffer)¶
Render unresolved point sources (stars and unresolved objects) into the frame buffer.
Each source is drawn from the PSF’s tables or grid, at its exact position if it stays put during the exposure and along its path as line integrals if it moves, tapered off where its light fades below set_unresolved_taper()’s threshold, with the light beyond spread over the frame.
The rendering pipeline:
Collects all stars and unresolved objects, each with the tables and grid for its blur
Projects sources to screen space, including those outside the image whose light reaches into it, and lists each in every tile its light reaches
Renders the tiles in parallel, each writing only its own pixels of the frame
Spreads the light each left beyond its drawn radius evenly over the frame
- Template Parameters:
TSpectral – The spectral type (e.g., RGB, Visible8)
- Parameters:
scene_view – The scene view containing stars and unresolved objects
frame_buffer – The frame buffer to render into
-
inline std::shared_ptr<CameraModel<TSpectral>> get_camera(SceneView<TSpectral> &scene_view) const¶
-
inline std::vector<PrimitiveBatch<TSpectral>> get_primitives(SceneView<TSpectral> &scene_view) const¶
-
bool build_occupancy_cones_(SceneView<TSpectral> &scene_view, std::vector<DirectionCone> &cones) const¶
Cones subtending each TLAS occupant as seen from the camera.
Build the cones subtending each TLAS occupant, as seen from the camera.
Each occupant’s bounding sphere is inflated by the aperture’s bounding radius before the cone is taken. Rays do not all leave the camera origin when depth of field is on; they leave points on the aperture. Translating a line by at most R keeps it within R of the original, so a line from any aperture point that meets a sphere of radius r meets the sphere of radius r + R when re-based at the origin. Inflating the sphere therefore makes the origin-centred cone cover every aperture ray’s offset exactly. With a finite focus distance those rays are also tilted relative to the pinhole rays; tile_direction_cone_() widens the tile cones by that tilt.
- Returns:
false when culling must be abandoned for this view.
-
DirectionCone tile_direction_cone_(const CameraModel<TSpectral> &camera, float x0, float y0, float x1, float y1) const¶
Cone enclosing every ray direction a tile can produce.
- Parameters:
camera – Camera model supplying the ray directions.
x0, y0, x1, y1 – The tile’s closed pixel rectangle. Jittered samples span exactly this range: pixel x is sampled over [x, x + 1).
Protected Attributes
-
RandomSampler<float> sampler_¶
-
bool occluder_mask_valid_ = false¶
False until path_trace_() has filled occluder_mask_ for the current frame. While false, every unresolved source is ray tested.
-
int spp_ = 10¶
-
int max_bounces_ = 3¶
-
bool unresolved_occlusion_ = true¶
-
double unresolved_taper_ = DEFAULT_UNRESOLVED_TAPER¶
-
FftPlanEffort fft_planning_ = FftPlanEffort::Estimate¶
-
double fft_rounding_tolerance_ = DEFAULT_FFT_ROUNDING_TOLERANCE¶
-
bool region_culling_ = true¶
-
bool region_cull_validation_ = false¶
-
float region_cull_margin_scale_ = 1.0f¶
-
bool dynamic_sampling_ = false¶
-
int min_spp_ = 16¶
-
float noise_threshold_ = DEFAULT_NOISE_THRESHOLD¶
-
float indirect_clamp_threshold_ = std::numeric_limits<float>::infinity()¶
Protected Static Functions
-
struct DirectionCone¶
A set of ray directions, as a cone about a unit axis.
Region culling works at the angular scale of a pixel, which for a narrow field of view is far smaller than an arc cosine of a single-precision dot product can resolve: near 1, the cosine changes too little. So angles are measured in double precision, between directions by atan2 of their cross and dot products (angle_between_()), and between cones by the chord between their axes, which keeps its precision at small angles.
Public Functions
-
inline void set_half_angle(double angle)¶
-
inline bool overlaps(const DirectionCone &other) const¶
Whether two cones share a direction: their axes are no further apart than the sum of their half-angles, compared by the chord between the axes, 2 sin(angle / 2).
-
inline void set_half_angle(double angle)¶