更详细的内容可以看知乎的这篇文章。
这里简要的说了一下几何光学的规则。
这里引出了光线追踪:正向(从光源开始)和反向(从眼睛开始)。
在介绍光线追踪前,先来看一些比较简单的。
W
h
i
t
t
e
d
Whitted
Whitted光线追踪算法,结合了反向光线追踪和
r
a
y
s
t
o
l
i
g
h
t
rays\ to\ light
rays to light(看图会更清晰一点),而且需要递归计算。具体的算法:
1.
1.
1.对于每个像素,沿
V
V
V方向跟踪主光线到第一个可见表面。
2.
2.
2.对于每个交点,跟踪次光线:
S
h
a
d
o
w
r
a
y
s
Shadow\ rays
Shadow rays,即从交点到光源的
L
i
L_i
Li、
R
e
f
l
e
c
t
e
d
r
a
y
Reflected\ ray
Reflected ray反射光线,如图中的
R
R
R所示、
R
e
f
r
a
c
t
e
d
r
a
y
Refracted\ ray
Refracted ray(折射光线)或者
t
r
a
n
s
m
i
t
t
e
d
r
a
y
transmitted\ ray
transmitted ray(透射光线),如图中的
T
T
T所示。
结合图示会更加清楚。
现在让我们逐步构建光剑追踪器,首先是光线投射和局部照明。
直接照明,首先给出了射线的定义,
I
d
i
r
e
c
t
I_{direct}
Idirect通过
B
l
i
n
n
−
P
h
o
n
g
Blinn-Phong
Blinn−Phong模型计算。
跟踪中的着色,看见这个公式了吗,是不是和
B
l
i
n
n
−
P
h
o
n
g
Blinn-Phong
Blinn−Phong模型中的有点像?
在这个基础上,我们做了两个修改:
1.
1.
1.距离衰减
A
j
d
i
s
t
A_j^{dist}
Ajdist的最大值为
1
1
1;
2.
2.
2.需要包含阴影衰减
A
j
s
h
a
d
o
w
A_j^{shadow}
Ajshadow。这个公式就是光线追踪中的着色公式。
光线追踪伪代码。我们通过向每个像素投射光线来构建光线跟踪图像。
C
O
P
COP
COP即
c
e
n
t
e
r
o
f
p
r
o
j
e
c
t
i
o
n
center\ of\ projection
center of projection(投影中心),
d
ˉ
\bar{d}
dˉ为投射光线的方向向量,
s
c
e
n
e
scene
scene为场景,那么可以计算得到
(
t
,
N
,
m
t
r
l
)
(t,N,mtrl)
(t,N,mtrl),
t
t
t为交点距
P
P
P的距离;
N
ˉ
\bar{N}
Nˉ为交点处的法向量;
m
t
r
l
mtrl
mtrl即
m
a
t
e
r
i
a
l
material
material,交点处的材质;那么也可以得到交点
Q
Q
Q的坐标;把
−
d
ˉ
-\bar{d}
−dˉ作为视线(反向光线追踪),即可通过
s
h
a
d
e
shade
shade(
P
h
o
n
g
/
B
l
i
n
n
−
P
h
o
n
g
Phong/Blinn-Phong
Phong/Blinn−Phong)计算出交点处的颜色。
着色伪代码。首先处理物体本身的发光(与材质有关)以及全局环境光,然后开始逐一处理光源(多光源),先计算距离衰减
a
t
t
e
n
atten
atten和
Q
Q
Q到
L
i
g
h
t
Light
Light的方向,然后计算漫反射
d
i
f
f
u
s
e
diffuse
diffuse和镜面反射
s
p
e
c
u
l
a
r
specular
specular,再把他们累加起来。
现在我们考虑阴影—通过投射阴影线来实现。
带阴影的着色。与之前的大体相同,多了求阴影衰减
s
h
a
d
o
w
A
t
t
e
n
shadowAtten
shadowAtten的过程。
这张图说明了如何计算阴影衰减。它可以简单到仅仅判断光线是否可以到达光源,这里以点光源为例,
g
e
t
D
i
r
e
c
t
i
o
n
(
P
)
getDirection(P)
getDirection(P)可以得到
P
P
P到光源的方向向量
d
ˉ
\bar{d}
dˉ,把它当作一条射线并与场景计算交点,从而得到交点到
P
P
P的距离
t
t
t,此时我们再计算光源到
P
P
P的距离
t
l
i
g
h
t
t_{light}
tlight,如果
t
<
t
l
i
g
h
t
t<t_{light}
t<tlight,说明被阻挡了,此时应返回
(
0
,
0
,
0
)
(0,0,0)
(0,0,0)(黑色),否则应返回
(
1
,
1
,
1
)
(1,1,1)
(1,1,1)。对于方向光源,我们可以认为
t
l
i
g
h
t
t_{light}
tlight为无穷大。
加上反射光。
设
I
(
P
,
d
ˉ
)
I(P,\bar{d})
I(P,dˉ)是沿射线看的强度,那么有
I
(
P
,
d
ˉ
)
=
I
d
i
r
e
c
t
+
I
r
e
f
l
e
c
t
e
d
I(P,\bar{d})=I_{direct}+I_{reflected}
I(P,dˉ)=Idirect+Ireflected,
I
d
i
r
e
c
t
I_{direct}
Idirect是由
B
l
i
n
n
−
P
h
o
n
g
Blinn-Phong
Blinn−Phong模型计算出来的(带阴影衰减),
I
r
e
f
l
e
c
t
e
d
I_{reflected}
Ireflected等于
K
r
∗
I
(
Q
,
R
ˉ
)
K_r*I(Q,\bar{R})
Kr∗I(Q,Rˉ),
K
r
K_r
Kr为反射系数,
Q
Q
Q为交点,
R
ˉ
\bar{R}
Rˉ为反射光,显然这是一个递归过程—递归计算反射光的强度。一般我们会令
K
r
=
K
s
K_r=K_s
Kr=Ks,即反射系数和镜面反射系数相等。
依据反射定律有
θ
i
=
θ
r
θ_i=θ_r
θi=θr,
R
ˉ
、
d
ˉ
、
N
ˉ
\bar{R}、\bar{d}、\bar{N}
Rˉ、dˉ、Nˉ三者共面。
更新一下光线追踪的伪代码。
终止递归的条件:
1.
1.
1.当递归深度大于最大限制时;
2.
∏
i
=
1
d
K
r
i
<
T
h
r
e
s
h
2.\prod_{i=1}^{d}K_{ri}<Thresh
2.∏i=1dKri<Thresh时,也就是说经过几次反射后,系数太小了,这点贡献可以忽略掉。
加上折射光。
T
T
T是折射光,
K
t
K_t
Kt是折射系数,一般我们令
K
s
=
K
r
K_s=K_r
Ks=Kr,且
K
t
=
1
−
K
s
K_t=1-K_s
Kt=1−Ks,如果物体不透明的话应该令
K
t
=
(
0
,
0
,
0
)
K_t=(0,0,0)
Kt=(0,0,0)。
折射定律。
n
i
、
n
t
n_i、n_t
ni、nt为折射率,由于
d
ˉ
、
N
ˉ
\bar{d}、\bar{N}
dˉ、Nˉ已知,所以
s
i
n
θ
i
sinθ_i
sinθi的值很容易得到,那么有
s
i
n
θ
t
=
n
i
∗
s
i
n
θ
i
/
n
t
sinθ_t=n_i*sinθ_i/n_t
sinθt=ni∗sinθi/nt。
上面是
T
ˉ
\bar{T}
Tˉ的推导过程,由于我们并不关心
T
ˉ
\bar{T}
Tˉ的长度,所以可以对结果进一步化简,使得
T
ˉ
=
(
n
c
o
s
θ
i
−
c
o
s
θ
t
)
N
ˉ
−
n
V
ˉ
\bar{T}=(ncosθ_i-cosθ_t)\bar{N}-n\bar{V}
Tˉ=(ncosθi−cosθt)Nˉ−nVˉ。
但是在计算折射光时,一定要考虑全内反射的情况。我们知道
s
i
n
θ
t
=
n
i
∗
s
i
n
θ
i
/
n
t
sinθ_t=n_i*sinθ_i/n_t
sinθt=ni∗sinθi/nt,当
θ
t
=
90
°
θ_t=90°
θt=90°时,
θ
i
θ_i
θi达到了临界角
θ
c
θ_c
θc,如果
θ
i
>
θ
c
θ_i>θ_c
θi>θc,就会发生全反射现象,此时没有折射光。
现在可以进一步完善我们的光线追踪伪代码了。由于计算折射光线时要考虑折射率
n
i
、
n
t
n_i、n_t
ni、nt,所以需要判断射线是正在进入物体还是传出物体,计算
N
ˉ
∗
V
ˉ
\bar{N}*\bar{V}
Nˉ∗Vˉ并与
0
0
0进行比较即可知道。
再次回顾一下阴影衰减。我们之前只是简单的判断了一下交点到光源之间是否有障碍物,有就返回
(
0
,
0
,
0
)
(0,0,0)
(0,0,0)(黑色),否则就返回
(
1
,
1
,
1
)
(1,1,1)
(1,1,1)。但是如果路径上有透明对象怎么办?正确的办法是把这些透明对象的
k
t
k_t
kt依次累成起来作为结果(由于一个物体可以被进入、穿出,所以可能会多次累乘一个物体的
k
t
k_t
kt)。这种模型简单的认为颜色只会在物体的表面,就像透明玻璃表面上的颜色涂层一样。
但是还有另外一种模型是认为玻璃的内部也有颜色的,在此不多赘述。
如图所示,当我们从物体内部穿出时,法向量其实是垂直于平面朝外的,所以在计算着色、反射、折射前需要先对法向量取反。而且在对物体内部的点进行着色时,环境光应该再乘上
k
t
k_t
kt,因为它必须穿过物体表面才能达到物体内部。
误差控制。因为计算往往涉及到浮点数,可能会有误差,所以一般会引入一个极小数进行误差控制。
球体、三角求交可以看我这篇博客。这里再讲一下坐标系的一些问题。
从上图可以看出,求交前先将光线转换成物体的局部坐标,如果有交点,再把交点处的法向量转换为全局坐标。