计算机图形学笔记(四):光线追踪与全局光照
光线追踪绝对是图形学里最有意思的部分了!第一次看到自己写的代码渲染出带反射、折射的真实画面时,那种成就感真的无法言喻。虽然算法理解起来不难,但要写出高效的实现还是有很多细节需要注意的。
目录
- 光线追踪基础理论 - 渲染方程与Whitted光线追踪
- 光线-几何体相交算法 - 数学推导与数值稳定性
- 空间加速数据结构 - BVH与KD-Tree构建算法
- 蒙特卡洛方法与积分 - 重要性采样与方差减少
- 全局光照与路径追踪 - 路径追踪算法实现
光线追踪基础理论
16.1 光线追踪的物理与数学基础
16.1.1 几何光学的数学框架
几何光学的基本假设
1. 直线传播假设: 在均匀介质中,光沿直线传播。这是光线追踪算法的核心假设。
2. 独立性假设: 不同光线之间不发生相互作用,可以独立计算每条光线的贡献。
3. 几何尺度假设: 光的波长 远小于场景中物体的特征尺度 ,即 。
光线的参数化表示
数学定义: 三维空间中的光线可以用参数方程表示:
其中:
- :光线起点(origin)
- :光线方向向量(direction),通常为单位向量
- :参数范围
工程实现:
struct Ray { Vector3f origin; // 起点 o Vector3f direction; // 方向 d(单位向量) float t_min = 1e-4f; // 最小参数(避免自相交) float t_max = 1e30f; // 最大参数
// 计算光线上的点 Vector3f at(float t) const { return origin + t * direction; }
// 检查参数是否在有效范围内 bool is_valid_t(float t) const { return t >= t_min && t <= t_max; }};16.1.2 渲染方程的递归形式
渲染方程的数学推导
积分形式的渲染方程:
递归形式的推导: 入射辐射度 实际上是从方向 射向点 的光线在其起始点的出射辐射度。
设光线与场景的下一个交点为 ,则:
代入渲染方程得到递归形式:
光线追踪的数学本质
积分方程的求解: 渲染方程是一个Fredholm积分方程,光线追踪通过递归求解这个积分方程。
Neumann级数展开: 渲染方程可以展开为无穷级数:
其中 是传输算子:
物理解释:
- :直接光照(0次反射)
- :一次反射光照
- :二次反射光照
光线追踪的收敛性
收敛条件: 当传输算子 的谱半径小于1时,Neumann级数收敛:
这在物理上对应于能量守恒条件:反射的总能量小于入射能量。
16.2 Whitted光线追踪算法
16.2.1 Whitted算法的数学模型
算法的理论基础
Whitted模型的简化假设:
- 完美镜面反射:只考虑镜面方向的反射
- 完美透射:只考虑折射方向的透射
- 点光源:光源被建模为点光源
- 递归终止:通过深度限制终止递归
反射定律的数学推导
反射向量计算: 给定入射向量 和表面法向量 ,反射向量 为:
推导过程: 设入射向量为 ,分解为法向分量和切向分量:
- 法向分量:
- 切向分量:
反射时切向分量不变,法向分量反向:
Snell定律与折射
Snell定律:
其中 是两种介质的折射率, 是入射角和折射角。
折射向量的向量形式:
其中:
全内反射条件: 当 时发生全内反射。
Whitted算法的完整实现
Vector3f whitted_ray_tracing(const Ray& ray, const Scene& scene, int depth) { // 递归终止条件 if (depth <= 0) return Vector3f::Zero();
// 光线与场景求交 Intersection hit = scene.intersect(ray); if (!hit.happened) return scene.background_color;
// 初始化颜色为自发光 Vector3f color = hit.material->emission;
// 直接光照计算 for (const auto& light : scene.lights) { Vector3f light_dir = light->sample_direction(hit.position);
// 阴影测试 Ray shadow_ray(hit.position + EPSILON * hit.normal, light_dir); if (!scene.is_occluded(shadow_ray, light->distance(hit.position))) { // 计算直接光照贡献 Vector3f light_color = light->intensity / light->distance_squared(hit.position); Vector3f brdf_value = hit.material->evaluate_brdf(-ray.direction, light_dir, hit.normal); float cos_theta = std::max(0.0f, hit.normal.dot(light_dir));
color += brdf_value * light_color * cos_theta; } }
// 镜面反射 if (hit.material->is_reflective() && depth > 0) { Vector3f reflect_dir = reflect(ray.direction, hit.normal); Ray reflect_ray(hit.position + EPSILON * hit.normal, reflect_dir);
Vector3f reflected_color = whitted_ray_tracing(reflect_ray, scene, depth - 1); color += hit.material->reflectance * reflected_color; }
// 折射/透射 if (hit.material->is_transparent() && depth > 0) { float eta = hit.front_face ? (1.0f / hit.material->ior) : hit.material->ior; Vector3f refract_dir = refract(ray.direction, hit.normal, eta);
if (refract_dir.squaredNorm() > 0) { // 检查是否发生全内反射 Vector3f offset = hit.front_face ? (-EPSILON * hit.normal) : (EPSILON * hit.normal); Ray refract_ray(hit.position + offset, refract_dir);
Vector3f refracted_color = whitted_ray_tracing(refract_ray, scene, depth - 1); color += hit.material->transmittance * refracted_color; } }
return color;}
// 反射向量计算Vector3f reflect(const Vector3f& incident, const Vector3f& normal) { return incident - 2.0f * incident.dot(normal) * normal;}
// 折射向量计算Vector3f refract(const Vector3f& incident, const Vector3f& normal, float eta) { float cos_i = -incident.dot(normal); float sin_t2 = eta * eta * (1.0f - cos_i * cos_i);
if (sin_t2 >= 1.0f) { return Vector3f::Zero(); // 全内反射 }
float cos_t = std::sqrt(1.0f - sin_t2); return eta * incident + (eta * cos_i - cos_t) * normal;}### 16.2.2 项目中的实现框架
**基于GAMES101 Assignment5-6的结构**:```cppVector3f Scene::castRay(const Ray &ray, int depth) const { if (depth > this->maxDepth) { return Vector3f(0.0, 0.0, 0.0); }
Intersection intersection = Scene::intersect(ray); if (!intersection.happened) { return this->backgroundColor; }
Material *m = intersection.m; Object *hitObject = intersection.obj; Vector3f hitColor = this->backgroundColor; Vector3f hitPoint = intersection.coords; Vector3f N = intersection.normal; Vector2f uv = intersection.uv;
switch (m->getType()) { case REFLECTION_AND_REFRACTION: { Vector3f reflectionDirection = reflect(ray.direction, N); Vector3f refractionDirection = refract(ray.direction, N, m->ior);
Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ? hitPoint - N * EPSILON : hitPoint + N * EPSILON; Vector3f refractionRayOrig = (dotProduct(refractionDirection, N) < 0) ? hitPoint - N * EPSILON : hitPoint + N * EPSILON;
Vector3f reflectionColor = castRay(Ray(reflectionRayOrig, reflectionDirection), depth + 1); Vector3f refractionColor = castRay(Ray(refractionRayOrig, refractionDirection), depth + 1);
float kr = fresnel(ray.direction, N, m->ior); hitColor = reflectionColor * kr + refractionColor * (1 - kr); break; } case REFLECTION: { float kr = fresnel(ray.direction, N, m->ior); Vector3f reflectionDirection = reflect(ray.direction, N); Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ? hitPoint - N * EPSILON : hitPoint + N * EPSILON; hitColor = castRay(Ray(reflectionRayOrig, reflectionDirection), depth + 1) * kr; break; } default: { // 漫反射材质的直接光照计算 Vector3f lightAmt = 0, specularColor = 0; Vector3f shadowPointOrig = (dotProduct(ray.direction, N) < 0) ? hitPoint + N * EPSILON : hitPoint - N * EPSILON;
for (auto& light : this->get_lights()) { Vector3f lightDir = light->position - hitPoint; float lightDistance2 = dotProduct(lightDir, lightDir); lightDir = normalize(lightDir); float LdotN = std::max(0.f, dotProduct(lightDir, N));
// 阴影测试 auto shadow_res = trace(Ray(shadowPointOrig, lightDir), this->get_objects()); bool inShadow = shadow_res && (shadow_res->t * shadow_res->t < lightDistance2);
lightAmt += inShadow ? 0 : light->intensity * LdotN; Vector3f reflectionDirection = reflect(-lightDir, N); specularColor += powf(std::max(0.f, -dotProduct(reflectionDirection, ray.direction)), m->specularExponent) * light->intensity; }
hitColor = lightAmt * m->Kd * m->diffuseColor + specularColor * m->Ks; break; } }
return hitColor;}16.3 光线-物体相交算法
16.3.1 光线-球体相交
数学推导: 球体方程: 光线方程:
代入得到:
展开:
整理得到二次方程:
其中:
- (假设d是单位向量)
代码实现:
bool intersect_sphere(const Ray& ray, const Vector3f& center, float radius, float& t_near, float& t_far) { Vector3f oc = ray.origin - center; float a = ray.direction.dot(ray.direction); float b = 2.0f * oc.dot(ray.direction); float c = oc.dot(oc) - radius * radius;
float discriminant = b * b - 4 * a * c; if (discriminant < 0) return false;
float sqrt_discriminant = std::sqrt(discriminant); t_near = (-b - sqrt_discriminant) / (2.0f * a); t_far = (-b + sqrt_discriminant) / (2.0f * a);
if (t_near > t_far) std::swap(t_near, t_far);
return t_near >= ray.t_min && t_near <= ray.t_max;}16.3.2 光线-三角形相交
Möller-Trumbore算法:
bool intersect_triangle(const Ray& ray, const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, float& t, float& u, float& v) { Vector3f edge1 = v1 - v0; Vector3f edge2 = v2 - v0; Vector3f h = ray.direction.cross(edge2); float a = edge1.dot(h);
if (a > -EPSILON && a < EPSILON) return false; // 光线平行于三角形
float f = 1.0f / a; Vector3f s = ray.origin - v0; u = f * s.dot(h);
if (u < 0.0f || u > 1.0f) return false;
Vector3f q = s.cross(edge1); v = f * ray.direction.dot(q);
if (v < 0.0f || u + v > 1.0f) return false;
t = f * edge2.dot(q);
return t > EPSILON && t >= ray.t_min && t <= ray.t_max;}16.3.3 光线-平面相交
数学推导: 平面方程: 光线方程:
代入: 解得:
代码实现:
bool intersect_plane(const Ray& ray, const Vector3f& point, const Vector3f& normal, float& t) { float denom = normal.dot(ray.direction); if (std::abs(denom) < EPSILON) return false; // 光线平行于平面
t = normal.dot(point - ray.origin) / denom; return t >= ray.t_min && t <= ray.t_max;}光线-几何体相交算法
17.1 包围盒相交算法
17.1.1 轴对齐包围盒(AABB)的数学理论
AABB的数学定义
轴对齐包围盒: AABB是一个与坐标轴平行的长方体,可以用两个对角顶点定义:
光线-AABB相交的数学推导
光线参数方程:
平面相交计算: 对于每个坐标轴 ,光线与两个平面 和 的相交参数为:
近远平面确定:
相交条件: 光线与AABB相交当且仅当:
数值稳定性考虑
除零处理: 当 时,光线平行于第 轴:
- 如果 或 ,则无相交
- 否则,,
优化的AABB相交实现
struct AABB { Vector3f min_point, max_point;
AABB() : min_point(Vector3f::Constant(INFINITY)), max_point(Vector3f::Constant(-INFINITY)) {}
AABB(const Vector3f& min_p, const Vector3f& max_p) : min_point(min_p), max_point(max_p) {}
// 计算表面积(用于SAH) float surface_area() const { Vector3f extent = max_point - min_point; return 2.0f * (extent.x() * extent.y() + extent.y() * extent.z() + extent.z() * extent.x()); }
// 计算体积 float volume() const { Vector3f extent = max_point - min_point; return extent.x() * extent.y() * extent.z(); }
void expand(const Vector3f& point) { min_point = min_point.cwiseMin(point); max_point = max_point.cwiseMax(point); }};
bool intersect_aabb_robust(const Ray& ray, const AABB& box, float& t_min, float& t_max) { t_min = ray.t_min; t_max = ray.t_max;
for (int i = 0; i < 3; ++i) { if (std::abs(ray.direction[i]) < 1e-8f) { // 光线平行于第i轴 if (ray.origin[i] < box.min_point[i] || ray.origin[i] > box.max_point[i]) { return false; } } else { float inv_dir = 1.0f / ray.direction[i]; float t1 = (box.min_point[i] - ray.origin[i]) * inv_dir; float t2 = (box.max_point[i] - ray.origin[i]) * inv_dir;
if (t1 > t2) std::swap(t1, t2);
t_min = std::max(t_min, t1); t_max = std::min(t_max, t2);
if (t_min > t_max) return false; } }
return true;}17.1.2 有向包围盒(OBB)
OBB定义:
struct OBB { Vector3f center; Vector3f axes[3]; // 三个正交轴 Vector3f half_extents; // 沿各轴的半长度
AABB to_aabb() const { Vector3f extent = Vector3f::Zero(); for (int i = 0; i < 3; ++i) { extent += axes[i].cwiseAbs() * half_extents[i]; } return AABB(center - extent, center + extent); }};17.2 复杂几何体相交
17.2.1 光线-网格相交
网格相交算法:
class TriangleMesh {private: std::vector<Vector3f> vertices; std::vector<Vector3i> triangles; AABB bounding_box;
public: bool intersect(const Ray& ray, Intersection& hit) { if (!intersect_aabb(ray, bounding_box)) return false;
bool hit_found = false; float closest_t = ray.t_max;
for (const auto& triangle : triangles) { Vector3f v0 = vertices[triangle[0]]; Vector3f v1 = vertices[triangle[1]]; Vector3f v2 = vertices[triangle[2]];
float t, u, v; if (intersect_triangle(ray, v0, v1, v2, t, u, v) && t < closest_t) { closest_t = t; hit.t = t; hit.position = ray.at(t); hit.normal = (v1 - v0).cross(v2 - v0).normalized(); hit.uv = Vector2f(u, v); hit_found = true; } }
return hit_found; }};17.2.2 光线-隐式曲面相交
隐式曲面定义:f(x, y, z) = 0
牛顿迭代法求交:
bool intersect_implicit_surface(const Ray& ray, std::function<float(Vector3f)> f, std::function<Vector3f(Vector3f)> gradient, float& t) { float t_current = ray.t_min; const int max_iterations = 100; const float tolerance = 1e-6f;
for (int i = 0; i < max_iterations; ++i) { Vector3f point = ray.at(t_current); float value = f(point);
if (std::abs(value) < tolerance) { t = t_current; return true; }
Vector3f grad = gradient(point); float denominator = grad.dot(ray.direction);
if (std::abs(denominator) < tolerance) break;
t_current -= value / denominator;
if (t_current < ray.t_min || t_current > ray.t_max) break; }
return false;}空间加速数据结构
18.1 BVH(层次包围盒)
18.1.1 BVH的数学理论基础
层次包围盒的数学原理
核心思想: BVH(Bounding Volume Hierarchy)通过递归地将几何对象分组并用包围盒包围,构建一个二叉树结构,从而实现对光线-场景相交测试的加速。
数学基础: 设场景中有 个几何对象 ,BVH将其组织成一个二叉树 ,满足:
- 包围性质:每个内部节点的包围盒包含其所有子节点的包围盒
- 分离性质:叶子节点包含的几何对象在空间上相对集中
- 平衡性质:树的深度尽可能小,理想情况下为
相交测试的复杂度分析
朴素方法: 对于每条光线,需要与所有 个对象进行相交测试,时间复杂度为 。
BVH加速: 利用包围盒的层次结构,平均时间复杂度降低到 :
- 如果光线与节点的包围盒不相交,则可以跳过整个子树
- 只有当光线与包围盒相交时,才需要递归测试子节点
BVH节点定义:
struct BVHBuildNode { AABB bounds; BVHBuildNode* left; BVHBuildNode* right; Object* object; // 叶子节点存储对象 int split_axis; // 分割轴 int first_prim_offset; // 第一个图元的偏移 int n_primitives; // 图元数量
void init_leaf(int first, int n, const AABB& b) { first_prim_offset = first; n_primitives = n; bounds = b; left = right = nullptr; }
void init_interior(int axis, BVHBuildNode* c0, BVHBuildNode* c1) { left = c0; right = c1; bounds = AABB(); bounds.expand(c0->bounds); bounds.expand(c1->bounds); split_axis = axis; n_primitives = 0; }};18.1.2 BVH构建算法的数学理论
分割策略的数学分析
1. 中点分割(Midpoint Split): 选择最长轴的中点进行分割:
优点:简单快速,保证树的平衡性 缺点:可能产生空的子树或不均匀的分布
2. 表面积启发式(SAH - Surface Area Heuristic): 基于期望相交测试次数的最优化分割策略。
SAH的数学推导
期望相交测试次数: 对于一个包围盒 ,其子节点 和 ,光线与该节点相交的期望测试次数为:
其中:
- 是遍历节点的固定开销
- 是光线与 相交时也与 相交的条件概率
- 是左子树的期望测试次数
几何概率假设: 假设光线方向均匀分布,则:
其中 是包围盒 的表面积。
SAH代价函数:
其中 和 分别是左右子树的图元数量。
基于GAMES101项目的实现
BVHBuildNode* BVHAccel::recursiveBuild(std::vector<Object*> objects) { BVHBuildNode* node = new BVHBuildNode();
// 计算所有对象的包围盒 AABB bounds; for (int i = 0; i < objects.size(); ++i) { bounds.expand(objects[i]->getBounds()); }
if (objects.size() == 1) { // 创建叶子节点 node->bounds = objects[0]->getBounds(); node->object = objects[0]; node->left = nullptr; node->right = nullptr; return node; } else if (objects.size() == 2) { node->left = recursiveBuild(std::vector{objects[0]}); node->right = recursiveBuild(std::vector{objects[1]}); node->bounds = AABB(); node->bounds.expand(node->left->bounds); node->bounds.expand(node->right->bounds); return node; } else { // 选择分割轴(最长轴) AABB centroid_bounds; for (int i = 0; i < objects.size(); ++i) { centroid_bounds.expand(objects[i]->getBounds().centroid()); }
int dim = centroid_bounds.max_extent();
switch (splitMethod) { case SplitMethod::NAIVE: { // 中点分割 std::sort(objects.begin(), objects.end(), [dim](const auto& a, const auto& b) { return a->getBounds().centroid()[dim] < b->getBounds().centroid()[dim]; });
auto beginning = objects.begin(); auto middling = objects.begin() + (objects.size() / 2); auto ending = objects.end();
auto leftshapes = std::vector<Object*>(beginning, middling); auto rightshapes = std::vector<Object*>(middling, ending);
assert(objects.size() == (leftshapes.size() + rightshapes.size()));
node->left = recursiveBuild(leftshapes); node->right = recursiveBuild(rightshapes); break; } case SplitMethod::SAH: { // SAH分割(表面积启发式) // 实现SAH算法... break; } }
node->bounds = AABB(); node->bounds.expand(node->left->bounds); node->bounds.expand(node->right->bounds); }
return node;}18.1.3 BVH遍历算法
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const { Intersection isect; if (!node) return isect;
// 检查光线是否与节点包围盒相交 Vector3f inv_dir = Vector3f(1.0f / ray.direction.x(), 1.0f / ray.direction.y(), 1.0f / ray.direction.z()); std::array<int, 3> dir_is_neg = { ray.direction.x() > 0 ? 0 : 1, ray.direction.y() > 0 ? 0 : 1, ray.direction.z() > 0 ? 0 : 1 };
if (!node->bounds.intersect_p(ray, inv_dir, dir_is_neg)) { return isect; }
// 叶子节点:与对象求交 if (node->left == nullptr && node->right == nullptr) { return node->object->getIntersection(ray); }
// 内部节点:递归遍历子节点 Intersection hit1 = getIntersection(node->left, ray); Intersection hit2 = getIntersection(node->right, ray);
return hit1.distance < hit2.distance ? hit1 : hit2;}18.2 SAH(表面积启发式)
18.2.1 SAH理论基础
成本函数:
Cost = C_trav + P_left × N_left × C_isect + P_right × N_right × C_isect其中:
- C_trav:遍历成本
- P_left/P_right:光线击中左/右子树的概率
- N_left/N_right:左/右子树的图元数量
- C_isect:相交测试成本
概率计算:
P_left = SA_left / SA_parentP_right = SA_right / SA_parent18.2.2 SAH实现
struct SAHBucket { int count = 0; AABB bounds;};
float evaluate_sah(const std::vector<Object*>& objects, int split_axis, float split_pos) { AABB left_bounds, right_bounds; int left_count = 0, right_count = 0;
for (const auto& obj : objects) { Vector3f centroid = obj->getBounds().centroid(); if (centroid[split_axis] < split_pos) { left_bounds.expand(obj->getBounds()); left_count++; } else { right_bounds.expand(obj->getBounds()); right_count++; } }
float cost = 1.0f + // 遍历成本 (left_count * left_bounds.surface_area() + right_count * right_bounds.surface_area()) / parent_bounds.surface_area();
return cost;}
int find_best_split_sah(const std::vector<Object*>& objects) { const int n_buckets = 12; float min_cost = INFINITY; int best_split = -1;
for (int dim = 0; dim < 3; ++dim) { // 创建桶 std::vector<SAHBucket> buckets(n_buckets);
// 将对象分配到桶中 for (const auto& obj : objects) { int bucket = n_buckets * centroid_bounds.offset(obj->getBounds().centroid())[dim]; bucket = std::min(bucket, n_buckets - 1); buckets[bucket].count++; buckets[bucket].bounds.expand(obj->getBounds()); }
// 计算每个分割位置的成本 for (int i = 0; i < n_buckets - 1; ++i) { AABB b0, b1; int count0 = 0, count1 = 0;
for (int j = 0; j <= i; ++j) { b0.expand(buckets[j].bounds); count0 += buckets[j].count; }
for (int j = i + 1; j < n_buckets; ++j) { b1.expand(buckets[j].bounds); count1 += buckets[j].count; }
float cost = 1.0f + (count0 * b0.surface_area() + count1 * b1.surface_area()) / bounds.surface_area();
if (cost < min_cost) { min_cost = cost; best_split = i; } } }
return best_split;}18.3 其他加速结构
18.3.1 八叉树(Octree)
基本原理:递归地将3D空间分割为8个子立方体
数据结构定义:
class Octree {private: struct OctreeNode { AABB bounds; std::vector<Object*> objects; std::array<std::unique_ptr<OctreeNode>, 8> children; bool is_leaf;
OctreeNode(const AABB& b) : bounds(b), is_leaf(true) {} };
std::unique_ptr<OctreeNode> root; int max_depth; int max_objects_per_node;
public: void build(const std::vector<Object*>& objects, const AABB& scene_bounds) { root = std::make_unique<OctreeNode>(scene_bounds); build_recursive(root.get(), objects, 0); }
private: void build_recursive(OctreeNode* node, const std::vector<Object*>& objects, int depth) { if (depth >= max_depth || objects.size() <= max_objects_per_node) { node->objects = objects; return; }
// 创建8个子节点 Vector3f center = node->bounds.center(); Vector3f min_pt = node->bounds.min_point; Vector3f max_pt = node->bounds.max_point;
// 8个子立方体的包围盒 std::array<AABB, 8> child_bounds = { AABB(Vector3f(min_pt.x(), min_pt.y(), min_pt.z()), Vector3f(center.x(), center.y(), center.z())), // 000 AABB(Vector3f(center.x(), min_pt.y(), min_pt.z()), Vector3f(max_pt.x(), center.y(), center.z())), // 100 AABB(Vector3f(min_pt.x(), center.y(), min_pt.z()), Vector3f(center.x(), max_pt.y(), center.z())), // 010 AABB(Vector3f(center.x(), center.y(), min_pt.z()), Vector3f(max_pt.x(), max_pt.y(), center.z())), // 110 AABB(Vector3f(min_pt.x(), min_pt.y(), center.z()), Vector3f(center.x(), center.y(), max_pt.z())), // 001 AABB(Vector3f(center.x(), min_pt.y(), center.z()), Vector3f(max_pt.x(), center.y(), max_pt.z())), // 101 AABB(Vector3f(min_pt.x(), center.y(), center.z()), Vector3f(center.x(), max_pt.y(), max_pt.z())), // 011 AABB(Vector3f(center.x(), center.y(), center.z()), Vector3f(max_pt.x(), max_pt.y(), max_pt.z())) // 111 };
// 将对象分配到子节点 std::array<std::vector<Object*>, 8> child_objects; for (const auto& obj : objects) { for (int i = 0; i < 8; ++i) { if (child_bounds[i].intersects(obj->getBounds())) { child_objects[i].push_back(obj); } } }
// 递归构建非空子节点 node->is_leaf = false; for (int i = 0; i < 8; ++i) { if (!child_objects[i].empty()) { node->children[i] = std::make_unique<OctreeNode>(child_bounds[i]); build_recursive(node->children[i].get(), child_objects[i], depth + 1); } } }
public: Intersection intersect(const Ray& ray) const { return intersect_recursive(root.get(), ray); }
private: Intersection intersect_recursive(OctreeNode* node, const Ray& ray) const { if (!node || !node->bounds.intersect(ray)) { return Intersection(); }
if (node->is_leaf) { Intersection closest_hit; float closest_t = INFINITY;
for (const auto& obj : node->objects) { Intersection hit = obj->getIntersection(ray); if (hit.happened && hit.distance < closest_t) { closest_hit = hit; closest_t = hit.distance; } } return closest_hit; }
// 遍历子节点 Intersection closest_hit; float closest_t = INFINITY;
for (const auto& child : node->children) { if (child) { Intersection hit = intersect_recursive(child.get(), ray); if (hit.happened && hit.distance < closest_t) { closest_hit = hit; closest_t = hit.distance; } } }
return closest_hit; }};优缺点分析:
- ✅ 优点:空间分割均匀,易于理解和实现
- ❌ 缺点:对象可能跨越多个节点,存储冗余
18.3.2 kD树(k-Dimensional Tree)
基本原理:沿坐标轴交替分割空间
数据结构:
struct KDNode { AABB bounds; int split_axis; // 分割轴:0=x, 1=y, 2=z float split_value; // 分割位置
std::unique_ptr<KDNode> left; // 小于split_value的子树 std::unique_ptr<KDNode> right; // 大于等于split_value的子树
std::vector<Object*> objects; // 叶子节点存储的对象 bool is_leaf;};
class KDTree {private: std::unique_ptr<KDNode> root; int max_depth; int max_objects_per_leaf;
public: void build(const std::vector<Object*>& objects, const AABB& bounds) { root = build_recursive(objects, bounds, 0); }
private: std::unique_ptr<KDNode> build_recursive(const std::vector<Object*>& objects, const AABB& bounds, int depth) { auto node = std::make_unique<KDNode>(); node->bounds = bounds;
// 终止条件 if (depth >= max_depth || objects.size() <= max_objects_per_leaf) { node->is_leaf = true; node->objects = objects; return node; }
// 选择分割轴(循环选择或基于最长轴) int axis = depth % 3; // 或者选择bounds的最长轴
// 计算分割位置(中位数或中点) std::vector<float> centroids; for (const auto& obj : objects) { centroids.push_back(obj->getBounds().center()[axis]); }
std::sort(centroids.begin(), centroids.end()); float split_pos = centroids[centroids.size() / 2]; // 中位数
node->split_axis = axis; node->split_value = split_pos; node->is_leaf = false;
// 分割对象 std::vector<Object*> left_objects, right_objects; for (const auto& obj : objects) { float centroid = obj->getBounds().center()[axis]; if (centroid < split_pos) { left_objects.push_back(obj); } else { right_objects.push_back(obj); } }
// 计算子节点包围盒 AABB left_bounds = bounds, right_bounds = bounds; left_bounds.max_point[axis] = split_pos; right_bounds.min_point[axis] = split_pos;
// 递归构建子树 if (!left_objects.empty()) { node->left = build_recursive(left_objects, left_bounds, depth + 1); } if (!right_objects.empty()) { node->right = build_recursive(right_objects, right_bounds, depth + 1); }
return node; }};遍历算法:
Intersection traverse_kd_tree(KDNode* node, const Ray& ray) { if (!node || !node->bounds.intersect(ray)) { return Intersection(); }
if (node->is_leaf) { // 与叶子节点中的所有对象求交 Intersection closest_hit; float closest_t = INFINITY;
for (const auto& obj : node->objects) { Intersection hit = obj->getIntersection(ray); if (hit.happened && hit.distance < closest_t) { closest_hit = hit; closest_t = hit.distance; } } return closest_hit; }
// 确定光线与分割平面的关系 float t_split = (node->split_value - ray.origin[node->split_axis]) / ray.direction[node->split_axis];
KDNode* first_child, *second_child; if (ray.origin[node->split_axis] < node->split_value) { first_child = node->left.get(); second_child = node->right.get(); } else { first_child = node->right.get(); second_child = node->left.get(); }
// 遍历第一个子节点 Intersection hit = traverse_kd_tree(first_child, ray);
// 如果找到交点且在分割平面之前,直接返回 if (hit.happened && hit.distance < t_split) { return hit; }
// 否则遍历第二个子节点 Intersection hit2 = traverse_kd_tree(second_child, ray);
// 返回最近的交点 if (hit2.happened && (!hit.happened || hit2.distance < hit.distance)) { return hit2; } return hit;}18.3.3 加速结构性能对比
理论复杂度:
| 结构 | 构建时间 | 内存消耗 | 查询时间 | 适用场景 |
|---|---|---|---|---|
| BVH | O(n log n) | O(n) | O(log n) | 通用,动态场景 |
| Octree | O(n log n) | O(n) | O(log n) | 均匀分布的场景 |
| kD-Tree | O(n log n) | O(n) | O(log n) | 静态场景,点查询 |
| Grid | O(n) | O(n + cells) | O(1) | 均匀密度场景 |
实际性能测试:
struct PerformanceTest { std::string structure_name; double build_time; double query_time; size_t memory_usage; int ray_count;
void print_results() const { std::cout << structure_name << ":\n"; std::cout << " 构建时间: " << build_time << "ms\n"; std::cout << " 平均查询时间: " << query_time / ray_count << "ms\n"; std::cout << " 内存使用: " << memory_usage / 1024.0 / 1024.0 << "MB\n"; }};
void benchmark_acceleration_structures(const std::vector<Object*>& objects, const std::vector<Ray>& test_rays) { std::vector<PerformanceTest> results;
// 测试BVH { auto start = std::chrono::high_resolution_clock::now(); BVHAccel bvh(objects); auto build_end = std::chrono::high_resolution_clock::now();
auto query_start = std::chrono::high_resolution_clock::now(); for (const auto& ray : test_rays) { bvh.Intersect(ray); } auto query_end = std::chrono::high_resolution_clock::now();
PerformanceTest test; test.structure_name = "BVH"; test.build_time = std::chrono::duration<double, std::milli>(build_end - start).count(); test.query_time = std::chrono::duration<double, std::milli>(query_end - query_start).count(); test.ray_count = test_rays.size(); results.push_back(test); }
// 类似地测试其他结构...
// 输出结果 for (const auto& result : results) { result.print_results(); }}蒙特卡洛方法与积分
19.1 蒙特卡洛积分理论
19.1.1 蒙特卡洛积分的数学基础
基本蒙特卡洛估计
积分估计公式: 对于一维积分 ,蒙特卡洛估计为:
其中 是在区间 上均匀分布的随机样本。
多维积分推广: 对于 维积分:
其中 是积分域的体积。
理论保证
强大数定律:
中心极限定理:
其中 。
收敛速度: 标准误差为 ,与维度无关,这是蒙特卡洛方法的重要优势。
19.1.2 重要性采样理论
重要性采样的数学推导
基本变换:
其中 是概率密度函数,。
蒙特卡洛估计:
方差分析与最优采样
估计量的方差:
最优概率密度函数: 使方差最小的最优采样密度为:
此时方差为:
实用采样策略: 在实际应用中,选择 可以显著减少方差。
代码实现:
class ImportanceSampler {private: std::function<float(float)> pdf; // 概率密度函数 std::function<float(float)> inverse_cdf; // 累积分布函数的逆
public: float sample() { float u = random_float(); // [0,1)均匀随机数 return inverse_cdf(u); }
float evaluate_pdf(float x) { return pdf(x); }};
// 余弦加权半球采样(用于漫反射)Vector3f cosine_weighted_hemisphere_sample() { float u1 = random_float(); float u2 = random_float();
float cos_theta = std::sqrt(u1); float sin_theta = std::sqrt(1.0f - u1); float phi = 2.0f * M_PI * u2;
return Vector3f(sin_theta * std::cos(phi), sin_theta * std::sin(phi), cos_theta);}
float cosine_hemisphere_pdf(float cos_theta) { return cos_theta / M_PI;}19.1.3 方差减少技术的数学理论
分层采样(Stratified Sampling)
基本原理: 将积分域分割成 个不相交的子域 ,在每个子域内独立采样:
方差减少效果: 分层采样的方差为:
其中 是第 层内的方差,通常 。
多重重要性采样(MIS)
问题背景: 当有多个采样策略时,如何组合它们以获得最优结果?
平衡启发式(Balance Heuristic): 对于 个采样策略,权重函数为:
其中 是策略 的样本数, 是对应的概率密度函数。
幂启发式(Power Heuristic):
通常取 。
MIS估计量:
工程实现
// 分层采样实现class StratifiedSampler {public: std::vector<Vector2f> generate_2d_samples(int sqrt_samples) { std::vector<Vector2f> samples; float inv_sqrt = 1.0f / sqrt_samples;
for (int i = 0; i < sqrt_samples; ++i) { for (int j = 0; j < sqrt_samples; ++j) { float jitter_x = random_float(); float jitter_y = random_float();
float x = (i + jitter_x) * inv_sqrt; float y = (j + jitter_y) * inv_sqrt;
samples.emplace_back(x, y); } }
// 随机打乱以避免相关性 std::shuffle(samples.begin(), samples.end(), rng); return samples; }};
// 多重重要性采样实现class MISIntegrator {public: static float power_heuristic(float pdf_a, float pdf_b, int beta = 2) { float weight_a = std::pow(pdf_a, beta); float weight_b = std::pow(pdf_b, beta); return weight_a / (weight_a + weight_b); }
Vector3f estimate_direct_lighting(const Intersection& hit) { Vector3f L(0, 0, 0);
// 策略1:BRDF采样 Vector3f brdf_dir = sample_brdf(hit); float brdf_pdf = evaluate_brdf_pdf(hit, brdf_dir); float light_pdf = evaluate_light_pdf(hit, brdf_dir);
if (brdf_pdf > 0) { float weight = power_heuristic(brdf_pdf, light_pdf); Vector3f brdf_value = evaluate_brdf(hit, brdf_dir); Vector3f incoming = trace_ray(hit.position, brdf_dir); L += weight * brdf_value * incoming / brdf_pdf; }
// 策略2:光源采样 Vector3f light_dir = sample_light(hit); light_pdf = evaluate_light_pdf(hit, light_dir); brdf_pdf = evaluate_brdf_pdf(hit, light_dir);
if (light_pdf > 0) { float weight = power_heuristic(light_pdf, brdf_pdf); Vector3f brdf_value = evaluate_brdf(hit, light_dir); Vector3f incoming = trace_ray(hit.position, light_dir); L += weight * brdf_value * incoming / light_pdf; }
return L; }}; trace_ray(hit.position, brdf_dir) * weight_brdf / brdf_pdf;
// 光源采样 Vector3f light_dir = sample_light(hit); light_pdf = evaluate_light_pdf(hit, light_dir); brdf_pdf = evaluate_brdf_pdf(hit, light_dir); float weight_light = mis_weight(light_pdf, brdf_pdf);
Vector3f light_contribution = evaluate_brdf(hit, light_dir) * trace_ray(hit.position, light_dir) * weight_light / light_pdf;
return brdf_contribution + light_contribution;}19.2 渲染中的蒙特卡洛应用
19.2.1 直接光照的蒙特卡洛估计
渲染方程的直接光照部分:
$$L_{direct} = \int_\Omega f_r(\omega_i, \omega_o) L_i(\omega_i) \cos \theta_i d\omega_i$$蒙特卡洛估计:
Vector3f estimate_direct_lighting(const Intersection& hit, const Vector3f& wo) { Vector3f direct_lighting(0, 0, 0); int samples = 64;
for (int i = 0; i < samples; ++i) { // 采样光源 Intersection light_sample; float light_pdf; scene.sample_light(light_sample, light_pdf);
Vector3f light_dir = (light_sample.coords - hit.coords).normalized(); float distance = (light_sample.coords - hit.coords).norm();
// 阴影测试 Ray shadow_ray(hit.coords + EPSILON * hit.normal, light_dir); shadow_ray.t_max = distance - EPSILON;
if (!scene.intersect(shadow_ray).happened) { // 计算BRDF Vector3f brdf = hit.m->eval(light_dir, wo, hit.normal);
// 计算贡献 float cos_theta = std::max(0.0f, hit.normal.dot(light_dir)); Vector3f contribution = brdf * light_sample.emit * cos_theta / light_pdf;
direct_lighting += contribution; } }
return direct_lighting / samples;}19.2.2 全局光照的路径追踪
路径追踪算法:
Vector3f path_tracing(const Ray& ray, int depth) { if (depth <= 0) return Vector3f(0, 0, 0);
Intersection hit = scene.intersect(ray); if (!hit.happened) return scene.background_color;
Vector3f color(0, 0, 0);
// 自发光 color += hit.m->getEmission();
// 直接光照 color += estimate_direct_lighting(hit, -ray.direction);
// 间接光照(俄罗斯轮盘赌) float russian_roulette = 0.8f; if (random_float() < russian_roulette) { // 采样BRDF Vector3f wi = sample_hemisphere(hit.normal); float pdf = 1.0f / (2.0f * M_PI); // 均匀半球采样
Vector3f brdf = hit.m->eval(wi, -ray.direction, hit.normal); float cos_theta = std::max(0.0f, hit.normal.dot(wi));
Ray indirect_ray(hit.coords + EPSILON * hit.normal, wi); Vector3f indirect = path_tracing(indirect_ray, depth - 1);
color += brdf * indirect * cos_theta / (pdf * russian_roulette); }
return color;}19.2.3 双向路径追踪
基本思想:同时从光源和摄像机追踪路径,在中间连接
算法框架:
Vector3f bidirectional_path_tracing(const Ray& camera_ray) { // 从摄像机追踪路径 std::vector<PathVertex> camera_path; trace_path_from_camera(camera_ray, camera_path);
// 从光源追踪路径 std::vector<PathVertex> light_path; trace_path_from_light(light_path);
Vector3f color(0, 0, 0);
// 尝试所有可能的连接 for (int i = 0; i < camera_path.size(); ++i) { for (int j = 0; j < light_path.size(); ++j) { Vector3f contribution = connect_paths(camera_path, i, light_path, j); color += contribution; } }
return color;}
struct PathVertex { Vector3f position; Vector3f normal; Vector3f wi, wo; // 入射和出射方向 Material* material; float pdf_forward, pdf_backward; Vector3f throughput; // 路径权重};
Vector3f connect_paths(const std::vector<PathVertex>& camera_path, int cam_idx, const std::vector<PathVertex>& light_path, int light_idx) { if (cam_idx >= camera_path.size() || light_idx >= light_path.size()) { return Vector3f(0, 0, 0); }
const PathVertex& cam_vertex = camera_path[cam_idx]; const PathVertex& light_vertex = light_path[light_idx];
// 检查连接的可见性 Vector3f connection_dir = (light_vertex.position - cam_vertex.position).normalized(); float distance = (light_vertex.position - cam_vertex.position).norm();
Ray visibility_ray(cam_vertex.position + EPSILON * cam_vertex.normal, connection_dir); visibility_ray.t_max = distance - EPSILON;
if (scene.intersect(visibility_ray).happened) { return Vector3f(0, 0, 0); // 被遮挡 }
// 计算连接的贡献 Vector3f brdf_cam = cam_vertex.material->eval(connection_dir, cam_vertex.wo, cam_vertex.normal); Vector3f brdf_light = light_vertex.material->eval(-connection_dir, light_vertex.wi, light_vertex.normal);
float cos_cam = std::max(0.0f, cam_vertex.normal.dot(connection_dir)); float cos_light = std::max(0.0f, light_vertex.normal.dot(-connection_dir));
Vector3f geometry_term = Vector3f(cos_cam * cos_light / (distance * distance));
return cam_vertex.throughput * brdf_cam * geometry_term * brdf_light * light_vertex.throughput;}全局光照与路径追踪
20.1 渲染方程深度解析
20.1.1 渲染方程的数学推导与物理意义
渲染方程的完整数学形式
积分形式的渲染方程:
各项的物理意义与数学定义
1. 出射辐射度 : 从表面点 沿方向 的出射辐射度,单位为
2. 自发光项 : 表面点 自身发出的辐射度(对于光源)
3. BRDF : 双向反射分布函数,定义为:
4. 入射辐射度 : 从方向 入射到点 的辐射度
5. 余弦项 : Lambert余弦定律,其中 是入射方向与表面法向量的夹角
6. 立体角积分域 : 以点 为中心的上半球面, 立体角
递归形式与光传输
递归渲染方程:
其中 是沿方向 的光线与场景的下一个交点:
这个递归形式体现了光在场景中的多次弹射传播。
20.1.2 光传输算子的数学理论
算子形式的渲染方程
线性算子表示:
其中:
- :辐射度函数(在所有表面点和方向上的分布)
- :自发光项
- :光传输算子
光传输算子的定义:
Neumann级数展开
级数解: 从算子方程 可得:
各项的物理意义:
光线弹射次数与光照贡献:
直接光照(0次弹射):
一次间接光照(1次弹射):
二次间接光照(2次弹射):
n次间接光照(n次弹射):
收敛性分析
收敛条件: 级数收敛当且仅当 ,即光传输算子的谱半径小于1。
物理意义: 这对应于能量守恒定律——每次反射都会损失一部分能量,因此无限次弹射后总能量收敛。
实际应用: 在路径追踪中,通过俄罗斯轮盘赌(Russian Roulette)来无偏地截断无限级数。
20.2 路径追踪算法详解
20.2.1 基础路径追踪
算法原理:从摄像机发射光线,在每个交点随机选择一个方向继续追踪
基于GAMES101 Assignment7的实现:
Vector3f Scene::castRay(const Ray &ray, int depth) const { if (depth > this->maxDepth) { return Vector3f(0.0, 0.0, 0.0); }
Intersection intersection = Scene::intersect(ray); if (!intersection.happened) { return this->backgroundColor; }
Material *m = intersection.m; Object *hitObject = intersection.obj; Vector3f hitColor = this->backgroundColor; Vector3f hitPoint = intersection.coords; Vector3f N = intersection.normal; Vector2f uv = intersection.uv;
switch (m->getType()) { case DIFFUSE: { // 直接光照采样 Vector3f L_dir(0, 0, 0);
// 对光源进行采样 Intersection light_pos; float light_pdf = 0.0f; sampleLight(light_pos, light_pdf);
Vector3f obj2light = light_pos.coords - hitPoint; Vector3f obj2lightdir = obj2light.normalized(); float distance = obj2light.norm();
// 发射阴影光线 Ray shadowRay(hitPoint, obj2lightdir); Intersection shadowIntersection = intersect(shadowRay);
// 检查是否被遮挡 if (shadowIntersection.distance - distance > -EPSILON) { Vector3f f_r = m->eval(obj2lightdir, -ray.direction, N); L_dir = light_pos.emit * f_r * dotProduct(obj2lightdir, N) / light_pdf / distance / distance; }
// 间接光照采样(俄罗斯轮盘赌) Vector3f L_indir(0, 0, 0); float ksi = get_random_float(); if (ksi < RussianRoulette) { Vector3f wi = toWorld(sampleHemisphere(uv), N); Ray indirectRay(hitPoint, wi); Intersection indirectIntersection = intersect(indirectRay);
if (indirectIntersection.happened && !indirectIntersection.obj->hasEmit()) { Vector3f f_r = m->eval(wi, -ray.direction, N); float pdf = 1.0f / (2.0f * M_PI); // 均匀半球采样 L_indir = castRay(indirectRay, depth + 1) * f_r * dotProduct(wi, N) / pdf / RussianRoulette; } }
hitColor = L_dir + L_indir; break; } }
return hitColor;}20.2.2 重要性采样优化
BRDF重要性采样:
// 余弦加权采样(适用于Lambert材质)Vector3f cosine_sample_hemisphere(const Vector2f& u) { float cos_theta = std::sqrt(u[0]); float sin_theta = std::sqrt(1.0f - u[0]); float phi = 2.0f * M_PI * u[1];
return Vector3f(sin_theta * std::cos(phi), sin_theta * std::sin(phi), cos_theta);}
float cosine_hemisphere_pdf(float cos_theta) { return cos_theta / M_PI;}
// 镜面反射采样Vector3f sample_specular_reflection(const Vector3f& wi, const Vector3f& normal) { return wi - 2.0f * wi.dot(normal) * normal;}
// 微表面模型采样(GGX分布)Vector3f sample_ggx_distribution(const Vector2f& u, float alpha) { float cos_theta = std::sqrt((1.0f - u[0]) / (1.0f + (alpha * alpha - 1.0f) * u[0])); float sin_theta = std::sqrt(1.0f - cos_theta * cos_theta); float phi = 2.0f * M_PI * u[1];
return Vector3f(sin_theta * std::cos(phi), sin_theta * std::sin(phi), cos_theta);}20.2.3 多重重要性采样
结合BRDF采样和光源采样:
Vector3f estimate_direct_lighting_mis(const Intersection& hit, const Vector3f& wo) { Vector3f L(0, 0, 0);
// 光源采样 Intersection light_sample; float light_pdf; scene.sample_light(light_sample, light_pdf);
if (light_pdf > 0) { Vector3f wi = (light_sample.coords - hit.coords).normalized();
// 检查可见性 if (!scene.occluded(hit.coords, light_sample.coords)) { Vector3f f = hit.material->eval(wi, wo, hit.normal); float brdf_pdf = hit.material->pdf(wi, wo, hit.normal);
if (brdf_pdf > 0) { float weight = power_heuristic(light_pdf, brdf_pdf); float cos_theta = std::max(0.0f, hit.normal.dot(wi)); L += f * light_sample.emit * cos_theta * weight / light_pdf; } } }
// BRDF采样 Vector3f wi; float brdf_pdf; Vector3f f = hit.material->sample_f(wo, wi, hit.normal, brdf_pdf);
if (brdf_pdf > 0) { Ray ray(hit.coords + EPSILON * hit.normal, wi); Intersection light_hit = scene.intersect(ray);
if (light_hit.happened && light_hit.obj->hasEmit()) { float light_pdf_value = scene.pdf_light(hit.coords, light_hit.coords); float weight = power_heuristic(brdf_pdf, light_pdf_value); float cos_theta = std::max(0.0f, hit.normal.dot(wi)); L += f * light_hit.obj->getEmission() * cos_theta * weight / brdf_pdf; } }
return L;}
// Power启发式权重函数float power_heuristic(float pdf_a, float pdf_b, int beta = 2) { float a = std::pow(pdf_a, beta); float b = std::pow(pdf_b, beta); return a / (a + b);}20.3 高级全局光照技术
20.3.1 光子映射
基本思想:
- 光子发射:从光源发射光子,记录其在场景中的交互
- 光子存储:将光子存储在空间数据结构中
- 密度估计:在渲染时查询附近光子估计辐射度
光子结构:
struct Photon { Vector3f position; // 光子位置 Vector3f direction; // 入射方向 Vector3f power; // 光子能量 short plane; // kD树分割平面};
class PhotonMap {private: std::vector<Photon> photons; int stored_photons; int max_photons;
public: void store(const Photon& photon) { if (stored_photons < max_photons) { photons[stored_photons] = photon; stored_photons++; } }
Vector3f irradiance_estimate(const Vector3f& position, const Vector3f& normal, float max_distance, int max_photons) { // 查找最近的光子 std::vector<std::pair<float, int>> nearest_photons; find_nearest_photons(position, max_distance, max_photons, nearest_photons);
if (nearest_photons.empty()) return Vector3f(0, 0, 0);
Vector3f irradiance(0, 0, 0); float max_dist_sqr = nearest_photons.back().first;
for (const auto& [dist_sqr, idx] : nearest_photons) { const Photon& photon = photons[idx];
// 检查光子方向与表面法向量的关系 if (photon.direction.dot(normal) < 0) { irradiance += photon.power; } }
// 密度估计:$irradiance = \frac{power}{area}$ return irradiance / (M_PI * max_dist_sqr); }};光子追踪过程:
void trace_photon(const Ray& ray, const Vector3f& power, int depth) { if (depth <= 0 || power.norm() < EPSILON) return;
Intersection hit = scene.intersect(ray); if (!hit.happened) return;
// 存储光子(除了第一次交互) if (depth < max_depth) { Photon photon; photon.position = hit.coords; photon.direction = ray.direction; photon.power = power; photon_map.store(photon); }
// 俄罗斯轮盘赌决定是否继续 float survival_prob = std::min(0.9f, power.maxCoeff()); if (random_float() > survival_prob) return;
// 采样新方向 Vector3f new_direction; Vector3f brdf = hit.material->sample_f(-ray.direction, new_direction, hit.normal);
Vector3f new_power = power * brdf * hit.normal.dot(new_direction) / survival_prob; Ray new_ray(hit.coords + EPSILON * hit.normal, new_direction);
trace_photon(new_ray, new_power, depth - 1);}20.3.2 实时全局光照近似
屏幕空间环境光遮蔽(SSAO):
float calculate_ssao(const Vector2f& screen_pos, const Vector3f& position, const Vector3f& normal, const Texture& depth_buffer) { float occlusion = 0.0f; int samples = 64; float radius = 0.5f;
for (int i = 0; i < samples; ++i) { // 在法向量半球内采样 Vector3f sample_dir = generate_hemisphere_sample(normal); Vector3f sample_pos = position + radius * sample_dir;
// 投影到屏幕空间 Vector2f sample_screen = world_to_screen(sample_pos);
// 采样深度缓冲 float sample_depth = depth_buffer.sample(sample_screen); float actual_depth = world_to_depth(sample_pos);
// 比较深度 if (actual_depth > sample_depth + bias) { occlusion += 1.0f; } }
return 1.0f - (occlusion / samples);}光传播体积(Light Propagation Volumes):
class LightPropagationVolume {private: struct VoxelGrid { std::vector<Vector3f> red_coeffs; // 红色通道球谐系数 std::vector<Vector3f> green_coeffs; // 绿色通道球谐系数 std::vector<Vector3f> blue_coeffs; // 蓝色通道球谐系数 int resolution; };
VoxelGrid grid;
public: void inject_light(const std::vector<VirtualPointLight>& vpls) { // 将虚拟点光源注入体素网格 for (const auto& vpl : vpls) { Vector3i voxel_coord = world_to_voxel(vpl.position);
// 计算球谐系数 Vector3f sh_coeffs = compute_spherical_harmonics(vpl.direction, vpl.intensity);
grid.red_coeffs[voxel_index(voxel_coord)] += sh_coeffs * vpl.color.r; grid.green_coeffs[voxel_index(voxel_coord)] += sh_coeffs * vpl.color.g; grid.blue_coeffs[voxel_index(voxel_coord)] += sh_coeffs * vpl.color.b; } }
void propagate_light() { // 迭代传播光照 for (int iteration = 0; iteration < 4; ++iteration) { VoxelGrid new_grid = grid;
for (int z = 0; z < grid.resolution; ++z) { for (int y = 0; y < grid.resolution; ++y) { for (int x = 0; x < grid.resolution; ++x) { Vector3i coord(x, y, z);
// 从6个邻居传播光照 propagate_from_neighbors(coord, new_grid); } } }
grid = new_grid; } }
Vector3f sample_indirect_lighting(const Vector3f& position, const Vector3f& normal) { Vector3i voxel_coord = world_to_voxel(position);
// 三线性插值采样球谐系数 Vector3f red_sh = trilinear_sample(grid.red_coeffs, voxel_coord); Vector3f green_sh = trilinear_sample(grid.green_coeffs, voxel_coord); Vector3f blue_sh = trilinear_sample(grid.blue_coeffs, voxel_coord);
// 计算法向量方向的辐射度 float sh_basis = evaluate_spherical_harmonics(normal);
return Vector3f(red_sh.dot(Vector3f(sh_basis)), green_sh.dot(Vector3f(sh_basis)), blue_sh.dot(Vector3f(sh_basis))); }};