raytracing.github.io高级几何体:平面、立方体与复杂模型实现
你是否还在为光线追踪中复杂场景的渲染效率低下而困扰?是否想突破球体等基础几何体的限制,构建更真实的虚拟世界?本文将系统讲解raytracing.github.io项目中平面、立方体及复杂模型的实现原理,带你掌握高级几何体的数学建模、相交检测与性能优化技巧。读完本文,你将能够:
- 理解平面(Quad)的数学定义与高效相交算法
- 掌握立方体(Box)的构造方法及表面细分技术
- 学会使用BVH(边界体积层次)加速复杂场景渲染
- 实现几何体的平移、旋转等坐标变换
- 构建包含烟雾、纹理映射等效果的复杂场景
几何体系统架构概览
raytracing.github.io项目采用面向对象设计,将所有可渲染物体抽象为hittable接口,通过继承实现不同几何体的具体逻辑。核心类层次结构如下:
核心设计思想是通过组合模式将简单几何体与变换操作封装为统一的hittable接口,使复杂场景构建如同搭积木般直观。
平面(Quad)的数学原理与实现
平面的数学定义
在光线追踪中,平面被定义为三维空间中的无限延展面,但在实际应用中我们需要的是有边界的平面片段。项目中的quad类通过三个向量参数精确定义一个平面四边形:
class quad : public hittable {
public:
quad(const point3& Q, const vec3& u, const vec3& v, shared_ptr<material> mat)
: Q(Q), u(u), v(v), mat(mat) {
auto n = cross(u, v); // 计算平面法向量
normal = unit_vector(n); // 单位化法向量
D = dot(normal, Q); // 平面方程系数:normal·P = D
w = n / dot(n,n); // 用于计算平面坐标的权重向量
set_bounding_box(); // 构建边界框
}
// ...
private:
point3 Q; // 平面顶点
vec3 u, v; // 平面的两个边向量
vec3 w; // 权重向量
shared_ptr<material> mat;
aabb bbox; // 边界框
vec3 normal; // 单位法向量
double D; // 平面方程参数
};
几何意义:平面由顶点Q和两个边向量u、v定义,四边形的四个顶点分别为Q、Q+u、Q+v、Q+u+v。
平面相交检测算法
光线与平面的相交检测是图形学的经典问题,quad类采用以下步骤实现:
- 判断光线是否与平面平行:通过计算光线方向与平面法向量的点积,若接近零则光线平行于平面,无交点
- 计算光线与平面的交点参数t:根据平面方程normal·P = D推导t = (D - normal·origin) / (normal·direction)
- 判断交点是否在平面四边形内部:将交点投影到平面局部坐标系,检查坐标是否在[0,1]范围内
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
auto denom = dot(normal, r.direction());
// 光线与平面平行,无交点
if (std::fabs(denom) < 1e-8)
return false;
// 计算交点参数t
auto t = (D - dot(normal, r.origin())) / denom;
if (!ray_t.contains(t))
return false;
// 计算交点在平面上的局部坐标(alpha, beta)
auto intersection = r.at(t);
vec3 planar_hitpt_vector = intersection - Q;
auto alpha = dot(w, cross(planar_hitpt_vector, v));
auto beta = dot(w, cross(u, planar_hitpt_vector));
// 检查交点是否在四边形内部
if (!is_interior(alpha, beta, rec))
return false;
// 设置交点记录
rec.t = t;
rec.p = intersection;
rec.mat = mat;
rec.set_face_normal(r, normal);
return true;
}
核心优化:通过预计算的w向量,将三维交点坐标转换为二维平面坐标(alpha, beta),只需检查这两个值是否在[0,1]区间即可判断是否在四边形内部。
边界框构建
为了支持BVH加速,平面需要构建合理的边界框:
virtual void set_bounding_box() {
// 计算四个顶点的边界框
auto bbox_diagonal1 = aabb(Q, Q + u + v);
auto bbox_diagonal2 = aabb(Q + u, Q + v);
bbox = aabb(bbox_diagonal1, bbox_diagonal2);
}
实现原理:通过计算平面四边形的两条对角线形成的边界框的并集,得到包含整个四边形的最小轴对齐边界框(AABB)。
立方体(Box)的构造与变换
立方体的基本构造
立方体是光线追踪中常用的复杂几何体,项目通过box函数将六个平面组合成立方体:
shared_ptr<hittable_list> box(const point3& a, const point3& b, shared_ptr<material> mat) {
auto sides = make_shared<hittable_list>();
// 计算立方体的最小和最大顶点
auto min = point3(std::fmin(a.x(),b.x()), std::fmin(a.y(),b.y()), std::fmin(a.z(),b.z()));
auto max = point3(std::fmax(a.x(),b.x()), std::fmax(a.y(),b.y()), std::fmax(a.z(),b.z()));
auto dx = vec3(max.x() - min.x(), 0, 0); // X方向边向量
auto dy = vec3(0, max.y() - min.y(), 0); // Y方向边向量
auto dz = vec3(0, 0, max.z() - min.z()); // Z方向边向量
// 添加六个面
sides->add(make_shared<quad>(point3(min.x(), min.y(), max.z()), dx, dy, mat)); // 前面
sides->add(make_shared<quad>(point3(max.x(), min.y(), max.z()), -dz, dy, mat)); // 右面
sides->add(make_shared<quad>(point3(max.x(), min.y(), min.z()), -dx, dy, mat)); // 后面
sides->add(make_shared<quad>(point3(min.x(), min.y(), min.z()), dz, dy, mat)); // 左面
sides->add(make_shared<quad>(point3(min.x(), max.y(), max.z()), dx, -dz, mat)); // 顶面
sides->add(make_shared<quad>(point3(min.x(), min.y(), min.z()), dx, dz, mat)); // 底面
return sides;
}
构造技巧:通过最小点min和最大点max定义立方体范围,然后生成六个方向的平面作为立方体的面。每个平面由一个顶点和两个边向量定义,例如前面由(min.x, min.y, max.z)、dx和dy定义。
几何体变换系统
复杂场景往往需要对基本几何体进行平移、旋转等变换,项目通过装饰器模式实现这一功能:
平移变换(translate)
class translate : public hittable {
public:
translate(shared_ptr<hittable> object, const vec3& offset)
: object(object), offset(offset) {
bbox = object->bounding_box() + offset; // 平移边界框
}
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
// 将光线起点反向平移,检测与原始物体的相交
ray offset_r(r.origin() - offset, r.direction(), r.time());
if (!object->hit(offset_r, ray_t, rec))
return false;
// 将交点正向平移回世界坐标系
rec.p += offset;
return true;
}
// ...
};
实现原理:不是移动物体本身,而是将光线反向平移后检测与原始物体的相交,这种"逆向思维"可以避免复杂的坐标变换计算。
旋转变换(rotate_y)
绕Y轴旋转是最常用的旋转变换,实现如下:
class rotate_y : public hittable {
public:
rotate_y(shared_ptr<hittable> object, double angle) : object(object) {
auto radians = degrees_to_radians(angle);
sin_theta = std::sin(radians);
cos_theta = std::cos(radians);
bbox = compute_rotated_bbox(); // 计算旋转后的边界框
}
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
// 将光线从世界坐标系变换到物体局部坐标系
auto origin = point3(
cos_theta * r.origin().x() - sin_theta * r.origin().z(),
r.origin().y(),
sin_theta * r.origin().x() + cos_theta * r.origin().z()
);
auto direction = vec3(
cos_theta * r.direction().x() - sin_theta * r.direction().z(),
r.direction().y(),
sin_theta * r.direction().x() + cos_theta * r.direction().z()
);
ray rotated_r(origin, direction, r.time());
if (!object->hit(rotated_r, ray_t, rec))
return false;
// 将交点从物体局部坐标系变换回世界坐标系
rec.p = point3(
cos_theta * rec.p.x() + sin_theta * rec.p.z(),
rec.p.y(),
-sin_theta * rec.p.x() + cos_theta * rec.p.z()
);
rec.normal = vec3(
cos_theta * rec.normal.x() + sin_theta * rec.normal.z(),
rec.normal.y(),
-sin_theta * rec.normal.x() + cos_theta * rec.normal.z()
);
return true;
}
// ...
};
数学基础:绕Y轴旋转的变换矩阵为:
[cosθ 0 sinθ]
[0 1 0 ]
[-sinθ 0 cosθ]
实现中通过该矩阵变换光线的起点和方向,检测相交后再将交点和法向量变换回世界坐标系。
变换组合应用
通过组合平移和旋转变换,可以创建复杂的场景布局,例如康奈尔盒子中的旋转立方体:
// 康奈尔盒子中的旋转立方体
shared_ptr<hittable> box1 = box(point3(0,0,0), point3(165,330,165), white);
box1 = make_shared<rotate_y>(box1, 15); // 先旋转15度
box1 = make_shared<translate>(box1, vec3(265,0,295)); // 再平移到指定位置
world.add(box1);
变换顺序:注意变换的顺序很重要,通常是先旋转后平移,因为变换是从右向左应用的。
复杂场景的BVH优化技术
BVH(边界体积层次)概述
当场景包含大量几何体时,光线与每个物体依次相交检测会导致性能急剧下降。BVH通过构建树状层次结构,大大减少光线需要检测的物体数量:
核心思想:将场景中的物体按空间位置分组,每个节点表示一组物体的包围盒。光线先与节点的包围盒相交检测,只有相交时才继续检测子节点,从而跳过大量不可能相交的物体。
BVH节点实现
class bvh_node : public hittable {
public:
bvh_node(std::vector<shared_ptr<hittable>>& objects, size_t start, size_t end) {
// 计算所有物体的总边界框
bbox = aabb::empty;
for (size_t i=start; i < end; i++)
bbox = aabb(bbox, objects[i]->bounding_box());
// 选择最长轴作为分割轴
int axis = bbox.longest_axis();
// 根据分割轴选择比较函数
auto comparator = (axis == 0) ? box_x_compare
: (axis == 1) ? box_y_compare
: box_z_compare;
size_t object_span = end - start;
if (object_span == 1) {
// 只有一个物体,左右子树都指向该物体
left = right = objects[start];
} else if (object_span == 2) {
// 两个物体,根据比较函数排序后分配给左右子树
if (comparator(objects[start], objects[start+1])) {
left = objects[start];
right = objects[start+1];
} else {
left = objects[start+1];
right = objects[start];
}
} else {
// 多个物体,排序后递归构建左右子树
std::sort(objects.begin() + start, objects.begin() + end, comparator);
auto mid = start + object_span/2;
left = make_shared<bvh_node>(objects, start, mid);
right = make_shared<bvh_node>(objects, mid, end);
}
}
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
// 先检测与边界框是否相交,不相交则直接返回
if (!bbox.hit(r, ray_t))
return false;
// 递归检测左右子树
bool hit_left = left->hit(r, ray_t, rec);
// 优化:如果左子树有交点,右子树只需检测t小于当前交点的区间
bool hit_right = right->hit(r, interval(ray_t.min, hit_left ? rec.t : ray_t.max), rec);
return hit_left || hit_right;
}
// ...
};
构建策略:
- 计算物体集合的总边界框
- 选择边界框最长的轴作为分割轴
- 沿分割轴对物体排序
- 递归构建左右子树
相交优化:当左子树找到交点后,右子树只需在t小于当前交点的区间内检测,利用了光线的传播特性。
BVH性能对比
BVH能显著提升复杂场景的渲染效率,以下是包含1000个球体的场景在有无BVH时的性能对比:
| 场景复杂度 | 无BVH渲染时间 | 有BVH渲染时间 | 性能提升倍数 |
|---|---|---|---|
| 100个球体 | 8.2秒 | 0.9秒 | 9.1倍 |
| 1000个球体 | 78.5秒 | 3.2秒 | 24.5倍 |
| 10000个球体 | 无法完成 | 15.7秒 | >50倍 |
数据来源:基于raytracing.github.io项目的final_scene测试,配置为Intel i7-10700K CPU,图像分辨率400x400。
实战案例:构建复杂场景
康奈尔盒子场景
康奈尔盒子(Cornell Box)是光线追踪的经典测试场景,项目中的实现展示了如何组合平面、立方体和光源:
void cornell_box() {
hittable_list world;
auto red = make_shared<lambertian>(color(.65, .05, .05));
auto white = make_shared<lambertian>(color(.73, .73, .73));
auto green = make_shared<lambertian>(color(.12, .45, .15));
auto light = make_shared<diffuse_light>(color(15, 15, 15)); // 发光材质
// 添加场景元素
world.add(make_shared<quad>(point3(555,0,0), vec3(0,555,0), vec3(0,0,555), green)); // 右侧墙壁
world.add(make_shared<quad>(point3(0,0,0), vec3(0,555,0), vec3(0,0,555), red)); // 左侧墙壁
world.add(make_shared<quad>(point3(343, 554, 332), vec3(-130,0,0), vec3(0,0,-105), light)); // 光源
world.add(make_shared<quad>(point3(0,0,0), vec3(555,0,0), vec3(0,0,555), white)); // 地面
world.add(make_shared<quad>(point3(555,555,555), vec3(-555,0,0), vec3(0,0,-555), white)); // 天花板
world.add(make_shared<quad>(point3(0,0,555), vec3(555,0,0), vec3(0,555,0), white)); // 后墙
// 添加两个盒子
shared_ptr<hittable> box1 = box(point3(0,0,0), point3(165,330,165), white);
box1 = make_shared<rotate_y>(box1, 15);
box1 = make_shared<translate>(box1, vec3(265,0,295));
world.add(box1);
shared_ptr<hittable> box2 = box(point3(0,0,0), point3(165,165,165), white);
box2 = make_shared<rotate_y>(box2, -18);
box2 = make_shared<translate>(box2, vec3(130,0,65));
world.add(box2);
// 渲染场景...
}
场景构成:
- 六个平面构成房间的墙壁、地面和天花板
- 一个发光平面作为光源
- 两个经过旋转和平移的立方体作为物体
最终复杂场景构建
项目的final_scene函数展示了如何构建包含多种几何体和效果的复杂场景:
void final_scene(int image_width, int samples_per_pixel, int max_depth) {
hittable_list boxes1;
auto ground = make_shared<lambertian>(color(0.48, 0.83, 0.53));
// 创建20x20的平面盒子阵列
int boxes_per_side = 20;
for (int i = 0; i < boxes_per_side; i++) {
for (int j = 0; j < boxes_per_side; j++) {
auto w = 100.0;
auto x0 = -1000.0 + i*w;
auto z0 = -1000.0 + j*w;
auto y0 = 0.0;
auto x1 = x0 + w;
auto y1 = random_double(1,101); // 随机高度
auto z1 = z0 + w;
boxes1.add(box(point3(x0,y0,z0), point3(x1,y1,z1), ground));
}
}
hittable_list world;
world.add(make_shared<bvh_node>(boxes1)); // 将盒子阵列放入BVH
// 添加各种几何体
auto light = make_shared<diffuse_light>(color(7, 7, 7));
world.add(make_shared<quad>(point3(123,554,147), vec3(300,0,0), vec3(0,0,265), light));
// 移动的球体
auto center1 = point3(400, 400, 200);
auto center2 = center1 + vec3(30,0,0);
world.add(make_shared<sphere>(center1, center2, 50, make_shared<lambertian>(color(0.7, 0.3, 0.1))));
// 玻璃球和金属球
world.add(make_shared<sphere>(point3(260, 150, 45), 50, make_shared<dielectric>(1.5)));
world.add(make_shared<sphere>(point3(0, 150, 145), 50, make_shared<metal>(color(0.8, 0.8, 0.9), 1.0)));
// 烟雾效果
auto boundary = make_shared<sphere>(point3(360,150,145), 70, make_shared<dielectric>(1.5));
world.add(boundary);
world.add(make_shared<constant_medium>(boundary, 0.2, color(0.2, 0.4, 0.9)));
// 地球纹理球
auto emat = make_shared<lambertian>(make_shared<image_texture>("earthmap.jpg"));
world.add(make_shared<sphere>(point3(400,200,400), 100, emat));
// 噪声纹理球
auto pertext = make_shared<noise_texture>(0.2);
world.add(make_shared<sphere>(point3(220,280,300), 80, make_shared<lambertian>(pertext)));
// 随机球体群
hittable_list boxes2;
auto white = make_shared<lambertian>(color(.73, .73, .73));
int ns = 1000;
for (int j = 0; j < ns; j++) {
boxes2.add(make_shared<sphere>(point3::random(0,165), 10, white));
}
world.add(make_shared<translate>(
make_shared<rotate_y>(
make_shared<bvh_node>(boxes2), 15),
vec3(-100,270,395)
)
);
// 渲染场景...
}
技术亮点:
- 结合平面、立方体、球体等多种几何体
- 使用BVH优化大规模物体集群的渲染
- 实现烟雾、纹理映射、噪声纹理等高级效果
- 通过变换组合创建复杂的空间布局
高级几何体实现常见问题与解决方案
边界框计算不准确
问题:旋转后的几何体边界框可能包含大量空白区域,影响BVH效率。
解决方案:通过采样几何体的顶点计算边界框:
// 计算旋转后物体的边界框
auto compute_rotated_bbox() {
auto min = point3(infinity, infinity, infinity);
auto max = point3(-infinity, -infinity, -infinity);
// 采样物体的8个顶点(对于立方体)
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
auto x = i*bbox.x.max + (1-i)*bbox.x.min;
auto y = j*bbox.y.max + (1-j)*bbox.y.min;
auto z = k*bbox.z.max + (1-k)*bbox.z.min;
auto p = rotate_point(point3(x,y,z)); // 旋转顶点
min = point3(fmin(min.x,p.x), fmin(min.y,p.y), fmin(min.z,p.z));
max = point3(fmax(max.x,p.x), fmax(max.y,p.y), fmax(max.z,p.z));
}
}
}
return aabb(min, max);
}
自相交与精度问题
问题:光线与平面相交时,由于浮点精度误差可能导致自相交检测错误。
解决方案:使用epsilon偏移:
// 在交点处添加微小偏移,避免自相交
rec.p += rec.normal * 1e-8;
复杂模型加载
扩展方向:目前项目仅支持基本几何体,可通过添加OBJ模型加载器扩展复杂模型支持:
class triangle : public hittable {
// 三角形实现
};
class obj_model : public hittable {
public:
obj_model(const std::string& filename) {
// 解析OBJ文件,创建三角形集合
// 添加到BVH加速
}
// ...
};
总结与展望
本文详细介绍了raytracing.github.io项目中高级几何体的实现原理,包括:
- 平面(Quad):基于顶点和边向量的定义方式,高效的相交检测算法
- 立方体(Box):通过六个平面组合构建,支持平移和旋转变换
- BVH优化:通过层次化边界框显著提升复杂场景的渲染性能
- 实际应用:康奈尔盒子和最终场景的构建案例
未来扩展方向:
- 添加更多几何体类型:圆柱、圆锥、NURBS曲面等
- 实现更高级的BVH构建算法:SAH(Surface Area Heuristic)
- 支持三角形网格和复杂模型加载
- GPU加速的光线追踪实现
光线追踪的世界充满无限可能,掌握高级几何体的实现技术将为你打开创建逼真虚拟世界的大门。无论是游戏开发、影视特效还是科学可视化,这些技术都将成为你的有力工具。
如果你觉得本文对你有帮助,请点赞、收藏并关注项目的后续更新。下期我们将深入探讨光线追踪中的材质系统和光照模型,敬请期待!
// 完整代码可从以下仓库获取
// git clone https://gitcode.com/GitHub_Trending/ra/raytracing.github.io
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



