本次光线追踪系列从基础重新开始,主要参照 Ray Tracing in One Weekend ,具体实现代码框架见 https://github.com/RayTracing/raytracing.github.io/。本文只是主要精炼光追相关理论,具体实现可参照原文。
一、PPM输出
1.1 PPM格式
ppm是一种直接存储RGB颜色值的文件格式,第一行是p3,表示颜色值用ASCII存。第二行是图像的宽和高。接下来是每一行按顺序存放的颜色值。
,具体实现可参照原文,本人实现效果如下:
二、自定义vec3类
几乎所有图形程序都有一些用于存储几何矢量和颜色的类。在许多系统中,这些向量是4D(3D加上几何的齐次坐标,而RGB加上颜色的alpha透明通道)。就我们的目的而言,三个坐标就足够了。我们将对vec3颜色,位置,方向,偏移量等使用相同的类。
原文中,除了实现一些基本的操作外,还在该类中重载了许多vec3的拓展,用户可自己前去查看。
三、光线、简单相机及天空采样
本部分主要实现:
以相机所处的原点(0,0,0)为起点,对屏幕的每个像素都发射一条射线:
①. 如果命中物体,就处理命中物体的情况,这种情况放到下节处理
②. 如果没有命中,获取射线方向的单位向量的y值,从0到1对浅蓝色和白色进行插值,以模拟天空的颜色,最终将颜色值输出到屏幕。
3.1 光线类
Ray Tracing是一个光学成像的逆过程。所有光线跟踪器都具有的一件事是光线类别和对沿光线看到的颜色进行计算。让我们将射线视为函数P(t)= A +tb。这里 P 是沿3D线的3D位置。 一个 是射线的起源, b是射线方向。射线参数t是一个实数。插入其他t和 P(t)沿射线移动点。加上负数t值,您可以在3D线上的任何位置。对于正确的t,您只会得到在正向的t,这就是通常所说的半线或射线。
射线类代码如下:
class ray
{
public:
vec3 A; //起点
vec3 B; //方向
ray() {}
ray(const vec3 &a, const vec3 &b)
{
A = a;
B = b;
}
vec3 origin() const { return A; }
vec3 direction() const { return B; }
vec3 point_at_parameter(float t) const { return A + t * B; } //终点的坐标
};
3.2 简单相机和背景输出
首先用一种非常简单的方式定义相机和屏幕:相机就是位于原点的一个点,屏幕就是一个4x2的矩形。
如图所示,假定Y轴指向上方,Z轴反方向面向屏幕,X轴指向右边。
u为像素在水平方向上的比值,v为像素在竖直方向上的比值,范围为[0,1]。
将相机的位置放置在原点(0,0,0)处,屏幕的左下角为(-2,-1,-1),右上角为(2,1,-1),宽为4,高为2。
接下来实现颜色输出函数,该函数的功能:发射一条射线,并采样该射线最终输出到屏幕的颜色值。
//发射一条射线,并采样该射线最终输出到屏幕的颜色值
vec3 color(const ray &r)
{
vec3 blue = vec3(0.5, 0.7, 1.0);
vec3 white= vec3(1.0, 1.0, 1.0);
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5 * (unit_direction.y() + 1.0); //确保t的范围为[0,1]
return (1.0 - t) * white + t * blue ; //对白色和浅蓝色插值
}
- 函数解释:由于目前我们还没有定义任何会被射线命中的物体,在没有命中物体的情况下,所以上面我们直接用线性插值公式,根据射线方向的单位向量的y,对白色和浅蓝色进行插值,以模拟天空的颜色。
该现行插值公式如下:
其中C为输出的颜色,A为起始颜色,B为目标颜色。
说明:使用这种对射线方向的单位向量的y分量进行插值的方式,可以实现任意方向平行光一致的效果。也就是说,无论射线的起点在哪里,射线采样到的颜色只和仰角(即射线和XOZ平面的夹角)有关。举个例子,在这种情况下,在屋顶的东面45°仰望天空,和在南面45°仰望天空,观察到的颜色值是一样的。因为后面会涉及射线命中物体后的反射行为,反射后,光线的起点和方向会被修改,但这不影响射线对最终天空颜色值的正常采样,因此要事先特别说明一下。后面我们将会看到绝对光滑的金属材质,可以像镜子一样反射得到天空的颜色。
接下来,我们会在入口函数中,从原点出发,朝着屏幕上的每一个像素发射一条射线,然后用 color( r ) 函数返回该射线对应的颜色值,输出到屏幕上。
vec3 lower_left_corner(-2.0, -1.0, -1.0); //左下角
vec3 horizontal(4.0, 0.0, 0.0);
vec3 vertical(0.0, 2.0, 0.0);
vec3 origin(0.0, 0.0, 0.0);// 相机原点
void RayTracing()
{
for (int j = ny - 1; j >= 0; j--)
{
for (int i = 0; i < nx; i++)
{
float u = float(i) / float(nx); //像素在水平方向上的比值,范围[0,1]
float v = float(j) / float(ny); //像素在竖直方向上的比值,范围[0,1]
ray r(origin, lower_left_corner + u*horizontal + v*vertical);
vec3 col = color(r);
int ir = int(255.99 * col[0]);
int ig = int(255.99 * col[1]);
int ib = int(255.99 * col[2]);
DrawPixel(i, j, ir, ig, ib);//根据像素点i.j位置绘制rgb颜色
}
}
}
最终可得到这张类似于天空的图像: