7天从零实现光线追踪:第一周完整代码与核心原理剖析

7天从零实现光线追踪:第一周完整代码与核心原理剖析

【免费下载链接】raytracing.github.io Main Web Site (Online Books) 【免费下载链接】raytracing.github.io 项目地址: https://gitcode.com/GitHub_Trending/ra/raytracing.github.io

引言:为什么选择光线追踪?

你是否曾好奇3D电影中的逼真光影是如何实现的?传统光栅化渲染虽然高效,但在处理全局光照、反射折射等效果时往往力不从心。光线追踪(Ray Tracing)作为一种基于物理的渲染技术,通过模拟光线传播路径,能够产生极其真实的视觉效果。本文将带你从零开始,通过raytracing.github.io项目的第一周教程,用不到500行代码实现一个基础但功能完整的光线追踪程序。

读完本文你将掌握:

  • 向量数学在3D空间中的核心应用
  • 光线与物体相交检测的数学原理
  • 三种基本材质(漫反射/金属/玻璃)的实现
  • 抗锯齿与景深效果的编程技巧
  • 完整可运行的C++光线追踪代码框架

项目架构概览

raytracing.github.io项目采用模块化设计,第一周教程包含10个核心文件,构成了光线追踪程序的基础架构:

src/InOneWeekend/
├── main.cc          # 主程序入口,场景定义与渲染控制
├── rtweekend.h      # 全局常量与工具函数
├── vec3.h           # 向量/点/颜色基础运算
├── ray.h            # 光线类定义
├── color.h          # 颜色处理与伽马校正
├── hittable.h       # 可命中物体接口
├── sphere.h         # 球体几何体实现
├── hittable_list.h  # 物体集合管理
├── material.h       # 材质系统(漫反射/金属/玻璃)
└── camera.h         # 虚拟相机与光线生成

核心模块依赖关系

mermaid

基础数学工具:向量与光线

向量类(vec3.h)详解

向量是光线追踪的数学基础,负责表示3D空间中的点、方向和颜色。该项目中的vec3类实现了完整的3D向量运算:

class vec3 {
public:
    double e[3];  // 存储x,y,z分量

    // 构造函数
    vec3() : e{0,0,0} {}
    vec3(double e0, double e1, double e2) : e{e0, e1, e2} {}

    // 分量访问
    double x() const { return e[0]; }
    double y() const { return e[1]; }
    double z() const { return e[2]; }

    // 基础运算重载
    vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
    double operator[](int i) const { return e[i]; }
    double& operator[](int i) { return e[i]; }

    vec3& operator+=(const vec3& v) {
        e[0] += v.e[0]; e[1] += v.e[1]; e[2] += v.e[2];
        return *this;
    }

    // 向量运算函数
    double length() const { return std::sqrt(length_squared()); }
    double length_squared() const { return e[0]*e[0] + e[1]*e[1] + e[2]*e[2]; }
    
    // 随机向量生成
    static vec3 random() {
        return vec3(random_double(), random_double(), random_double());
    }
};

// 类型别名,增强代码可读性
using point3 = vec3;  // 3D点
using color = vec3;   // RGB颜色
关键向量运算
运算类型函数实现应用场景
点积dot(u, v) = u.x()*v.x() + u.y()*v.y() + u.z()*v.z()计算角度关系,判断方向
叉积cross(u, v)构建坐标系,计算法线
单位化unit_vector(v) = v / v.length()方向向量标准化
反射reflect(v, n) = v - 2*dot(v,n)*n金属材质高光计算
折射refract(uv, n, etai_over_etat)玻璃材质光线弯曲

光线类(ray.h)实现

光线被定义为P(t) = origin + t*direction,其中t是一个标量参数:

class ray {
public:
    ray() {}
    ray(const point3& origin, const vec3& direction) : orig(origin), dir(direction) {}

    const point3& origin() const  { return orig; }
    const vec3& direction() const { return dir; }

    // 光线行进函数:计算t参数对应的3D点
    point3 at(double t) const {
        return orig + t*dir;
    }

private:
    point3 orig;  // 光线起点
    vec3 dir;     // 光线方向(非单位向量)
};

核心渲染逻辑:从光线到像素

相机系统(camera.h)

相机类负责将2D像素坐标转换为3D光线,并处理景深效果:

class camera {
public:
    double aspect_ratio      = 16.0 / 9.0;  // 宽高比
    int    image_width       = 1200;        // 图像宽度(像素)
    int    samples_per_pixel = 10;          // 每像素采样数(抗锯齿)
    int    max_depth         = 20;          // 光线最大反弹次数

    // 相机位置与朝向
    point3 lookfrom = point3(13,2,3);  // 相机位置
    point3 lookat   = point3(0,0,0);   // 观察点
    vec3   vup      = vec3(0,1,0);     // 上方向向量

    // 景深参数
    double defocus_angle = 0.6;  // 散焦角度
    double focus_dist    = 10.0; // 对焦距离

    void render(const hittable& world) {
        initialize();  // 初始化相机参数

        // 输出PPM图像头
        std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";

        // 逐行扫描像素
        for (int j = 0; j < image_height; j++) {
            std::clog << "\rScanlines remaining: " << (image_height - j) << ' ' << std::flush;
            for (int i = 0; i < image_width; i++) {
                color pixel_color(0,0,0);
                // 多采样抗锯齿
                for (int sample = 0; sample < samples_per_pixel; sample++) {
                    ray r = get_ray(i, j);
                    pixel_color += ray_color(r, max_depth, world);
                }
                // 写入颜色(已进行伽马校正)
                write_color(std::cout, pixel_samples_scale * pixel_color);
            }
        }
    }

private:
    // 相机初始化函数:计算视口和像素位置
    void initialize() {
        image_height = int(image_width / aspect_ratio);
        pixel_samples_scale = 1.0 / samples_per_pixel;

        // 计算相机坐标系
        w = unit_vector(lookfrom - lookat);
        u = unit_vector(cross(vup, w));
        v = cross(w, u);

        // 计算视口参数
        auto theta = degrees_to_radians(vfov);
        auto h = std::tan(theta/2);
        auto viewport_height = 2 * h * focus_dist;
        auto viewport_width = viewport_height * (double(image_width)/image_height);

        // 计算像素增量向量
        vec3 viewport_u = viewport_width * u;
        vec3 viewport_v = viewport_height * -v;
        pixel_delta_u = viewport_u / image_width;
        pixel_delta_v = viewport_v / image_height;

        // 计算左上角像素位置
        auto viewport_upper_left = center - (focus_dist * w) - viewport_u/2 - viewport_v/2;
        pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);

        // 计算散焦圆盘参数(景深)
        auto defocus_radius = focus_dist * std::tan(degrees_to_radians(defocus_angle / 2));
        defocus_disk_u = u * defocus_radius;
        defocus_disk_v = v * defocus_radius;
    }

    // 生成通过像素(i,j)的光线
    ray get_ray(int i, int j) const {
        auto offset = sample_square();  // 采样正方形内的随机偏移
        auto pixel_sample = pixel00_loc
                          + ((i + offset.x()) * pixel_delta_u)
                          + ((j + offset.y()) * pixel_delta_v);

        auto ray_origin = (defocus_angle <= 0) ? center : defocus_disk_sample();
        auto ray_direction = pixel_sample - ray_origin;

        return ray(ray_origin, ray_direction);
    }
};

光线颜色计算(ray_color)

光线颜色计算是光线追踪的核心,通过递归追踪光线与物体的相交,并计算材质的反射/折射效果:

color ray_color(const ray& r, int depth, const hittable& world) const {
    // 递归深度达到上限,返回黑色
    if (depth <= 0)
        return color(0,0,0);

    hit_record rec;

    // 判断光线是否命中物体(t_min=0.001避免自相交)
    if (world.hit(r, interval(0.001, infinity), rec)) {
        ray scattered;
        color attenuation;
        // 调用材质的散射函数计算新光线
        if (rec.mat->scatter(r, rec, attenuation, scattered))
            return attenuation * ray_color(scattered, depth-1, world);
        return color(0,0,0);  // 无散射,返回黑色
    }

    // 未命中物体,返回天空背景色(线性插值)
    vec3 unit_direction = unit_vector(r.direction());
    auto a = 0.5*(unit_direction.y() + 1.0);
    return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}

材质系统:模拟真实世界的表面特性

材质系统是光线追踪中实现真实感渲染的关键,项目实现了三种基础材质:

材质基类(material.h)

class material {
public:
    virtual ~material() = default;

    // 散射函数:输入入射光线,输出散射光线和衰减系数
    virtual bool scatter(
        const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
    ) const {
        return false;
    }
};

1. 漫反射材质(lambertian)

漫反射材质会随机散射光线,产生柔和的无高光表面:

class lambertian : public material {
public:
    lambertian(const color& albedo) : albedo(albedo) {}

    bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
    const override {
        // 计算散射方向:法线方向+随机单位向量
        auto scatter_direction = rec.normal + random_unit_vector();

        // 处理退化方向(当随机向量与法线恰好反向)
        if (scatter_direction.near_zero())
            scatter_direction = rec.normal;

        scattered = ray(rec.p, scatter_direction);
        attenuation = albedo;  // 材质颜色决定光线衰减
        return true;
    }

private:
    color albedo;  // 反照率:表面颜色
};
漫反射光线方向生成

mermaid

2. 金属材质(metal)

金属材质会反射光线,可通过fuzz参数控制表面粗糙度:

class metal : public material {
public:
    metal(const color& albedo, double fuzz) : albedo(albedo), fuzz(fuzz < 1 ? fuzz : 1) {}

    bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
    const override {
        // 计算理想反射方向
        vec3 reflected = reflect(r_in.direction(), rec.normal);
        // 添加模糊效果:反射方向+随机向量*模糊系数
        reflected = unit_vector(reflected) + (fuzz * random_unit_vector());
        scattered = ray(rec.p, reflected);
        attenuation = albedo;
        // 确保反射光线在表面正面
        return (dot(scattered.direction(), rec.normal) > 0);
    }

private:
    color albedo;  // 金属颜色
    double fuzz;   // 模糊系数(0=完美镜面,1=最大模糊)
};

3. 电介质(玻璃)材质(dielectric)

玻璃材质同时表现反射和折射,并可能产生全内反射:

class dielectric : public material {
public:
    dielectric(double refraction_index) : refraction_index(refraction_index) {}

    bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
    const override {
        attenuation = color(1.0, 1.0, 1.0);  // 玻璃几乎不吸收光线
        // 确定折射率比值(入射介质/折射介质)
        double ri = rec.front_face ? (1.0/refraction_index) : refraction_index;

        vec3 unit_direction = unit_vector(r_in.direction());
        double cos_theta = std::fmin(dot(-unit_direction, rec.normal), 1.0);
        double sin_theta = std::sqrt(1.0 - cos_theta*cos_theta);

        // 判断是否发生全内反射
        bool cannot_refract = ri * sin_theta > 1.0;
        vec3 direction;

        // 使用Schlick近似计算反射概率
        if (cannot_refract || reflectance(cos_theta, ri) > random_double())
            direction = reflect(unit_direction, rec.normal);
        else
            direction = refract(unit_direction, rec.normal, ri);

        scattered = ray(rec.p, direction);
        return true;
    }

private:
    double refraction_index;  // 折射率(真空=1.0,玻璃≈1.5)

    // Schlick近似:计算反射系数
    static double reflectance(double cosine, double refraction_index) {
        auto r0 = (1 - refraction_index) / (1 + refraction_index);
        r0 = r0*r0;
        return r0 + (1-r0)*std::pow((1 - cosine),5);
    }
};
三种材质效果对比
材质类型核心特性关键参数视觉效果
漫反射随机散射albedo(颜色)柔和无高光
金属镜面反射albedo(颜色), fuzz(粗糙度)高光+边缘模糊
电介质反射+折射refraction_index(折射率)透明+内部反射+色散

场景构建:main.cc详解

主程序负责创建场景、设置相机参数并启动渲染:

场景定义

int main() {
    hittable_list world;  // 物体列表

    // 创建地面(大球体)
    auto ground_material = make_shared<lambertian>(color(0.5, 0.5, 0.5));
    world.add(make_shared<sphere>(point3(0,-1000,0), 1000, ground_material));

    // 随机生成小球体
    for (int a = -11; a < 11; a++) {
        for (int b = -11; b < 11; b++) {
            auto choose_mat = random_double();
            point3 center(a + 0.9*random_double(), 0.2, b + 0.9*random_double());

            // 避免与中心大球重叠
            if ((center - point3(4, 0.2, 0)).length() > 0.9) {
                shared_ptr<material> sphere_material;

                if (choose_mat < 0.8) {  // 80%概率漫反射材质
                    auto albedo = color::random() * color::random();
                    sphere_material = make_shared<lambertian>(albedo);
                    world.add(make_shared<sphere>(center, 0.2, sphere_material));
                } else if (choose_mat < 0.95) {  // 15%概率金属材质
                    auto albedo = color::random(0.5, 1);
                    auto fuzz = random_double(0, 0.5);
                    sphere_material = make_shared<metal>(albedo, fuzz);
                    world.add(make_shared<sphere>(center, 0.2, sphere_material));
                } else {  // 5%概率玻璃材质
                    sphere_material = make_shared<dielectric>(1.5);
                    world.add(make_shared<sphere>(center, 0.2, sphere_material));
                }
            }
        }
    }

    // 添加三个大球作为场景主体
    auto material1 = make_shared<dielectric>(1.5

【免费下载链接】raytracing.github.io Main Web Site (Online Books) 【免费下载链接】raytracing.github.io 项目地址: https://gitcode.com/GitHub_Trending/ra/raytracing.github.io

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值