Inside the companion
Light Transport
Build a diffuse CPU path renderer and inspect its sampling, geometry, and tests.
Project files 11
Name the sampling contracts
Random state, direction samples, running statistics, bounds, and an owning scene index.
#pragma once
#include <cstddef>
#include <cstdint>
#include <expected>
#include <optional>
#include <span>
#include <string>
#include <vector>
namespace lantern {
struct Error { std::string message; };
struct Vec3 { double x{}, y{}, z{}; };
struct Ray { Vec3 origin; Vec3 direction; }; // Finite, unit direction required.
struct Aabb { Vec3 low; Vec3 high; };
struct Sphere { std::uint32_t id{}; Vec3 center; double radius{}; };
struct Hit { std::uint32_t id{}; double distance{}; };
inline constexpr std::size_t max_spheres = 128;
inline constexpr std::size_t max_samples = 1'048'576;
// Explicit algorithm and owned state: useful reproducibility, not cryptography.
class Rng {
public:
explicit Rng(std::uint64_t seed) noexcept : state_{seed} {}
[[nodiscard]] std::uint64_t next() noexcept;
[[nodiscard]] double uniform() noexcept; // [0, 1), using the high 53 bits.
private:
std::uint64_t state_;
};
struct DirectionSample { Vec3 direction; double pdf{}; }; // Density per steradian.
[[nodiscard]] std::expected<DirectionSample, Error> cosine_hemisphere(double u, double v);
[[nodiscard]] std::expected<double, Error> cosine_integral(std::size_t samples, std::uint64_t seed);
[[nodiscard]] std::expected<double, Error> power_weight(double pdf_a, double pdf_b);
// Linear-sample statistics. Display transforms belong AFTER averaging.
class Moments {
public:
[[nodiscard]] std::expected<void, Error> add(double value);
[[nodiscard]] std::size_t count() const noexcept { return count_; }
[[nodiscard]] double mean() const noexcept { return mean_; }
[[nodiscard]] double sample_variance() const noexcept { return count_ > 1 ? m2_ / static_cast<double>(count_ - 1) : 0; }
private:
std::size_t count_{};
double mean_{}, m2_{};
};
[[nodiscard]] std::expected<bool, Error> box_hit(const Ray& ray, const Aabb& box);
// Owns copied primitives and its index. Construction is transactional; no query
// borrows the caller's span after build() returns. Binary median splits are an
// educational baseline, not SAH, spatial splits, or a measured speed promise.
class SceneIndex {
public:
[[nodiscard]] static std::expected<SceneIndex, Error> build(std::span<const Sphere> spheres);
[[nodiscard]] std::expected<std::optional<Hit>, Error> closest(const Ray& ray) const;
[[nodiscard]] std::expected<std::optional<Hit>, Error> linear_closest(const Ray& ray) const;
[[nodiscard]] std::size_t node_count() const noexcept { return nodes_.size(); }
private:
struct Node { Aabb bounds; std::size_t first{}, count{}, left{}, right{}; };
std::size_t build_node(std::size_t first, std::size_t count);
std::vector<Sphere> spheres_;
std::vector<Node> nodes_;
};
} // namespace lantern
This is the file included with the course. Copy and download give you its complete source.
Taking your work offline? Download all course companions
