Inside the companion
Light Transport
Build a diffuse CPU path renderer and inspect its sampling, geometry, and tests.
Project files 11
Check light before trusting the image
Analytic radiance, known pixels, bounded work, and diffuse-renderer failure cases.
#include "diffuse.hpp"
#include <cmath>
#include <iostream>
#include <limits>
#include <stdexcept>
namespace {
std::size_t checks{};
void require(bool okay,const char* message) { ++checks; if (!okay) throw std::runtime_error(message); }
bool near(double a,double b) { return std::abs(a-b)<1e-10; }
bool near(lantern::Vec3 a,lantern::Vec3 b) { return near(a.x,b.x)&&near(a.y,b.y)&&near(a.z,b.z); }
double dot(lantern::Vec3 a,lantern::Vec3 b) { return a.x*b.x+a.y*b.y+a.z*b.z; }
}
int main() {
try {
using namespace lantern;
for (const auto n:std::array<Vec3,4>{{{0,0,1},{0,0,-1},{1,0,0},{0,0.6,0.8}}}) {
const auto f=frame_from_normal(n);
require(f&&near(dot(f->tangent,f->normal),0)&&near(dot(f->bitangent,f->normal),0)&&
near(dot(f->tangent,f->bitangent),0)&&near(dot(f->tangent,f->tangent),1)&&
near(dot(f->bitangent,f->bitangent),1),"frame is orthonormal including both poles");
require(near(to_world(*f,{0,0,1}),n),"local north maps to the stated normal");
}
require(!frame_from_normal({0,0,0})&&!frame_from_normal({0,0,2}),"nonunit frames rejected");
std::array<Surface,1> surfaces{{{{7,{0,0,0},1},{0.6,0.3,0.15},{}}}};
const auto constant=DiffuseScene::build(surfaces,{2,1,0.5});
require(constant.has_value(),"constant-environment scene builds");
for (std::uint64_t seed=0;seed<64;++seed) {
Rng rng{seed}; const auto t=constant->trace({{0,0,3},{0,0,-1}},rng,1);
require(t&&near(t->radiance,{1.2,0.3,0.075})&&t->count==2&&t->end==PathEnd::environment,
"every one-bounce sample equals reflectance times constant environment");
}
Rng rng{47};
const auto zero=constant->trace({{0,0,3},{0,0,-1}},rng,0);
require(zero&&near(zero->radiance,{})&&zero->count==1&&zero->end==PathEnd::depth_limit,"zero scatters at nonemissive surface is black");
const auto miss=constant->trace({{0,0,3},{0,0,1}},rng,0);
require(miss&&near(miss->radiance,{2,1,0.5}),"zero scatter miss still sees environment");
require(!constant->trace({{0,0,3},{0,0,-2}},rng,1),"invalid initial ray rejected");
require(!constant->trace({{0,0,3},{0,0,-1}},rng,9),"path work cap rejected");
surfaces[0].sphere.radius=8; surfaces[0].reflectance={0.5,0.5,0.5}; surfaces[0].emission={0.1,0.2,0.3};
const auto enclosure=DiffuseScene::build(surfaces,{16,16,16});
const auto bounded=enclosure->trace({{0,0,0},{0,0,1}},rng,4);
require(bounded&&bounded->count==5&&bounded->end==PathEnd::depth_limit&&
near(bounded->radiance,{0.19375,0.3875,0.58125}),"enclosure adds five known geometric-series emission terms and no environment");
const auto maximum=enclosure->trace({{0,0,0},{0,0,1}},rng,8);
require(maximum&&maximum->count==9&&maximum->end==PathEnd::depth_limit&&
near(maximum->radiance,{0.199609375,0.39921875,0.598828125}),"maximum eight scatters use exactly nine owned visits and known emission terms");
surfaces[0].reflectance={0,0,0};
const auto emitter=DiffuseScene::build(surfaces,{});
const auto absorbed=emitter->trace({{0,0,0},{0,0,1}},rng,4);
require(absorbed&&near(absorbed->radiance,{0.1,0.2,0.3})&&absorbed->count==1&&absorbed->end==PathEnd::absorbed,"black emitter adds emission before termination");
surfaces[0].reflectance.x=1.1;
require(!DiffuseScene::build(surfaces,{}),"energy-gaining reflectance rejected");
require(!DiffuseScene::build({}, {std::numeric_limits<double>::infinity(),0,0}),"nonfinite environment rejected");
const auto empty=DiffuseScene::build({}, {0.1,0.2,0.3});
const auto image=render_diffuse(*empty,{4,3,2,1,47});
require(image&&image->linear.size()==12,"owned image has validated extent");
for (const auto color:image->linear) require(near(color,{0.1,0.2,0.3}),"every empty-scene pixel has analytically known radiance");
require(!render_diffuse(*empty,{0,3,2,1,47})&&!render_diffuse(*empty,{512,512,128,1,47}),"invalid dimensions and camera-ray budget rejected");
const auto scene=study_scene(); require(scene.has_value(),"study scene is valid");
const auto first=render_diffuse(*scene,{8,6,2,4,47}), second=render_diffuse(*scene,{8,6,2,4,47});
bool equal=first&&second;
if (equal) for (std::size_t i=0;i<first->linear.size();++i) equal &= near(first->linear[i],second->linear[i]);
require(equal,"fixed image/settings replay on this implementation");
std::cout<<checks<<" diffuse bridge checks passed.\n";
return 0;
} catch (const std::exception& e) { std::cerr<<e.what()<<'\n'; return 1; }
}
This is the file included with the course. Copy and download give you its complete source.
Taking your work offline? Download all course companions
