Dorapocket
moveonmoveonmoveonmoveon
Dorapocket

光线追踪初探

光线追踪初探

最近看到本Github上的电子书,讲光线追踪的,就跟着做了一下,收益匪浅!原书奉上:https://raytracing.github.io/books/RayTracingInOneWeekend.html


# 图形学大作业-光线追踪算法 说明文档
18061615 李国宇

## PPM图像表示
首先介绍图像的表示方式,在这里我们直接通过PPM图像表示生成最后的图像
PPM图像的定义如下(可用记事本打开)

![img](https://raytracing.github.io/images/img.ppm-example.jpg)

## 基础类实现
### vec3类
几乎所有的图形程序都有一些用于存储几何矢量和颜色的类。在许多系统中,这些向量是4D(3D加上几何的齐次坐标,而RGB加上颜色的alpha透明通道)。就我们的目的而言,三个坐标就足够了。我们将对vec3颜色,位置,方向,偏移量等使用相同的类。
同时类内对向量运算进行了重载
类内代码和注释见vec3.h

### color模块
创建转换函数write_color将像素颜色写入标准输出

### ray光线类
在齐次坐标中,一条光线可以用齐次坐标形式 $ \mathbf{P}(t) = \mathbf{A} + t \mathbf{b}$进行表示,其中,$ \mathbf{A}$代表光线起始点,$ \mathbf{b}$代表光线的方向,参数t代表光线延伸的长度,正数代表沿着方向前进,负数代表背向方向。这样你就能确定一条空间中的三维直线。
类内代码和注释见ray.h

### hittable 可显示物体类
hittble类设立的初衷是为了给各种形状的几何体创造一种标准,方便未来几何体形状的扩充。定义了统一的接口函数。

此外,该文件内还设置了hit_record结构体,用于记录光线与物体相交位置和其他信息。比如:击中点坐标、光线方向向量、物体材质信息、法线方向。定义法线方向的目的在于,对于两面都会产生反射或折射的物体,这样的设置有利于知道光是从物体外射入还是物体内射出。

hittable的内部定义了一个hit函数,用于计算是否击中,如果击中,将会把击中点的信息存储到hit_record记录中,方便后续运算。为了防止在很细微的空间频繁反射,定义了一个$t_{min}$,忽略了t极小情况下的反射光线计算,防止暗疮。

### hittable_list 数据结构定义
考虑到光线追踪算法中的光线计算点可能会很多,专门设置了一个hittable_list数据结构来进行存储,其操作方法参考了vector向量方式。

其内部有一个hit函数,用于遍历整个列表内的记录点并调用其hit方法来计算相应参数。

## 光线算法、材质、形体

### 向场景投射光线
光线追踪器的核心是使光线穿过像素并计算沿这些光线方向看到的颜色。
涉及的步骤有:
* 计算从眼睛到像素的射线
* 确定射线与哪些对象相交
* 计算交点的颜色
先设置图像的基本信息,在此使用16:9的宽高比。

除了设置渲染图像的像素尺寸外,我们还需要设置一个虚拟视口,以使场景射线通过。视口的宽高比应与我们渲染的图像相同。我们在此选择一个高度为两个单位的视口,并将投影平面和投影点之间的距离设置为一个单位。

在我的坐标系中,将相机放在$(0,0,0)$。世界坐标y轴向上,而x轴向右。为了遵守惯用的右手坐标系,在屏幕中是负Z轴。在代码中,从左下角遍历屏幕,并沿屏幕两侧使用两个偏移矢量在屏幕上移动射线端点。

![img](https://raytracing.github.io/images/fig.cam-geom.jpg)

### 形体相关的计算
#### 光线与球的交点计算
在三维坐标系中,球体的表示如下:
$$
x^2 + y^2 + z^2 = R^2
$$
已知点$(x,y,z)$我们可以通过$x^2 + y^2 + z^2$与$R$的大小关系判断点在球体内部、外部还是表面上。
若球体球心不在原点,则可以球体表示为
$$
(x – C_x)^2 + (y – C_y)^2 + (z – C_z)^2 = r^2
$$

其中$(C_x,C_y,C_z)$为球心,现在令$\mathbf{C}=(C_x,C_y,C_z) , \mathbf{P}=(x,y,z) $ 根据矢量运算规则,有
$
(\mathbf{P} – \mathbf{C}) \cdot (\mathbf{P} – \mathbf{C})
= (x – C_x)^2 + (y – C_y)^2 + (z – C_z)^2=r^2
$
在光线追踪中,我们的$P$是光线上的点,可以表示为
$$
(\mathbf{P}(t) – \mathbf{C}) \cdot (\mathbf{P}(t) – \mathbf{C}) = r^2
$$
带入表达式,有
$$
(\mathbf{A} + t \mathbf{b} – \mathbf{C})
\cdot (\mathbf{A} + t \mathbf{b} – \mathbf{C}) = r^2
$$
其中$t$为未知量,化成以$t$为表示的二次方程
$$
t^2 \mathbf{b} \cdot \mathbf{b}
+ 2t \mathbf{b} \cdot (\mathbf{A}-\mathbf{C})
+ (\mathbf{A}-\mathbf{C}) \cdot (\mathbf{A}-\mathbf{C}) – r^2 = 0
$$
用二次方程求根公式可得其无解、单根和二重根,从而确定t,进而确定像素点。

![img](https://raytracing.github.io/images/fig.ray-sphere.jpg)

### 折射、反射
#### 斯涅耳定律
斯涅尔定律描述如下:
$$
\eta \cdot \sin\theta = \eta’ \cdot \sin\theta’
$$
其中θ和θ’是与法线的夹角,而η和η’是折射率(通常为空气= 1.0,玻璃= 1.3-1.7,金刚石= 2.4)
求解sinθ’来确定折射射线的方向,:
$$
\sin\theta’ = \frac{\eta}{\eta’} \cdot \sin\theta
$$
将折射光R‘分成垂直和平行于面法向的两部分:
$$
\mathbf{R’} = \mathbf{R’}_{\parallel} + \mathbf{R’}_{\bot}
$$
解得:
$$
\mathbf{R’}_{\parallel} = \frac{\eta}{\eta’} (\mathbf{R} + \cos\theta \mathbf{n})
$$
$$
\mathbf{R’}_{\bot} = -\sqrt{1 – |\mathbf{R’}_{\parallel}|^2} \mathbf{n}
$$
而$ \mathbf{R’}_{\parallel} $中出现的$\cos\theta$,当$a,b$均为单位向量时有
$$
\mathbf{a} \cdot \mathbf{b} = \cos\theta
$$
带入$ \mathbf{R’}_{\parallel} $的表达式,有
$$
\mathbf{R’}_{\parallel} =
\frac{\eta}{\eta’} (\mathbf{R} + (\mathbf{-R} \cdot \mathbf{n}) \mathbf{n})
$$
根据此公式可以计算$\mathbf{R’}_{\parallel}$和$\mathbf{R’}_{\bot}$,二者相加即可求出折射光

#### Schlick逼近(Christophe Schlick’s Approximation)
鉴于运算的复杂性,Schlick提出了一种逼近菲涅尔因数的方法(Christophe Schlick’s Approximation),方便我们对反射率的计算。公式如下:
$$
R(\theta)=R_{0}+\left(1-R_{0}\right)(1-\cos \theta)^{5}
R_{0}=\left(\frac{n_{1}-n_{2}}{n_{1}+n_{2}}\right)^{2}
$$
代码见vec3.h refract函数

### 材质的计算
对于任意一种材质,需要解决以下问题:
* 是否产生散射射线?
* 产生的散射射线的信息?

为此在material材质类中,首先定义父类虚函数$scatter$,他负责计算光线照道材质上之后产生的反射线信息(颜色,方向,$etc..$)

对于以后任意一种材质,只需要重载父类$scatter$函数
* 对于金属材质,计算其全反射光,并加入一些扰动($fuzz$模糊度参数,会对反射线方向进行相应比例的扰动,具体实现方式是原反射方向+$fuzz*random_unit$)
* 对于透射材质,如玻璃球等物体,需要考虑光的折射(用折射定理和折射率)、光的反射(反射率,详见上文schlick逼近),进行随机化后计算光的最后方向。

具体各材料代码实现详见源程序和内部注释。

## 主函数运行逻辑
主函数通过随机生成场景来进行图片的生成,代码详见$random\_scene()$函数,此函数生成球物体并添加到$hittable\_list$数据结构中。
$ray\_color()$函数是光线追踪的主函数,她负责生成每一个像素点的颜色值,其具体做法是:先给每一个光初值,接着调用$hittable\_list()$的$hit()$方法求出击中的$hit\_record$信息。若击中,将该信息传入材质$scatter()$函数进行光线颜色、折射反射的判断,获取反射光和颜色,作为新的光线递归调用$ray\_color()$进行光线追踪。若没有击中,则按插值公式返回背景。
$main()$函数中为一些摄像机设置,接着对于每一个图片像素发射一道光进行扫描,为防止产生锯齿状纹理,对于其附近的像素点进行随机采样并平均,调用$ray\_color()$函数进行光线追踪主循环,获取到颜色后限幅$(0,255)$输出到PPM图像中,并在错误输出中刷新显示程序执行进度。

## 程序运行
将程序主函数中场景改为自己的场景,并调整摄像机位置、图像大小等参数。运用g++或VS进行生成。
在程序目录打开PowerShell窗口或者CMD命令行,输入:
“`
./RayTracing.exe > result.ppm
“`
其中,RayTracing.exe是生成的可执行文件, > 的作用在于重定向标准输出到文件。
**注意!在VS环境中,生成的格式编码可能UTF-16,而PPM格式为UTF-8编码,若出现ppm图像打不开的状况,请检查文本编码!(可用VScode右下角编码选项进行调整) **
将在同一目录下生成 result.ppm 图像。
用IrfanView 64可以预览PPM文件。观察实验效果。

## 实验结果

![final1920](C:\Users\dorap\Desktop\cg\RayTracing_LGY\final1920.jpg)

发表回复

textsms
account_circle
email

Dorapocket

光线追踪初探
最近看到本Github上的电子书,讲光线追踪的,就跟着做了一下,收益匪浅!原书奉上:https://raytracing.github.io/books/RayTracingInOneWeekend.html # 图形学大作业-光线追踪…
扫描二维码继续阅读
2020-06-09