最后一个实验要求做一个RayTracing的小东西,本实验采用了Cornell Box作为模型,实现了简单的tracer。 主要从以下几个方面简单的介绍一下:
算法框架

算法伪代码如上,即对每个像素点随机选取光线方向,如果击中点的话对其进行render,并根据反射、折射等递归计算,没有击中点则返回背景颜色。
数学基础
在实验的过程中,调用glm后发现实际的速度并没有快很多,反而各种方法比较凌乱,因此自己实现了一个Vector的struct:
1 |
|
这里面有个叫做Qsqrt的东西,其实是鼎鼎有名的卡马克常数的那个平方根快速算法,具体解释我可能有时间再写一篇博客探讨下:
1 |
|
光线
光线类,o和d分别表示起始位置和方向
1 |
|
球
之后写了一个叫做Sphere的类,材质包括漫反射、反射与折射,并实现一下与光线相交的函数,返回光线的参数方程(o+td)中第一个交点的t:

1 |
|
场景构建
由于当半径很大的时候,球的部分表面可以被看做是平面,因此不必单独写平面的类,直接调用sphere就好。 下面的代码设置了上下左右前后六个表面、灯光和材质分别是反射和折射的两个球:
1 | Sphere spheres[] = {//Scene: radius, position, emission, color, material |
图像、采样率、摄像机的设置
设置一些基本的图像参数,用于进一步的构建图像:
1 | int w = 1024, h = 768, samps = 1; |
循环处理像素点
对于每个像素点,选取它的四邻域进行随机取样,取样的个数就是采样率的samps:
1 | for (int y = 0; y<h; y++) { // Rows |
其中,radiance就是进行raycast的递归过程,计算之后对各个采样点计算得来的颜色取平均值。
RayCast过程
即radiance过程,是一个递归的过程,首先intersect方法得到这个光线击中的物体:
1 | inline bool intersect(const Ray &r, double &t, int &id) { |
设置了递归的深度是5,得到击中的物体后计算法向量(和背面剔除后的法向量),如果递归超过深度则按照概率衰减或直接返回强度(灯光)
1 | double t; |
之后是漫反射、镜面反射的着色过程,在之前的博客中有提及,这里不再赘述计算过程。需要注意的是,漫反射之后的来源光线是随机选取的,随机的过程可以用下图表示。
1 | if (obj.refl == DIFF) { |
之后是折射与反射的判断关系……首先要复习一下全反射(Total internal reflection )的相关知识,判断一下是需要进行全反射就好还是需要进一步计算折射:

如果有折射,用下面这张图中的公式计算:
1 | Ray reflRay(x, r.d - n * 2 * n.dot(r.d)); // Ideal dielectric REFRACTION |
最后结果
输出ppm图像如下:
1 | FILE *f; |
采样率低的时候噪点很多,随着采样率升高效果逐渐平滑和稳定: