-
Notifications
You must be signed in to change notification settings - Fork 28
/
Chapter 8 Lighting.html
459 lines (326 loc) · 34.6 KB
/
Chapter 8 Lighting.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
<head>
<link rel="stylesheet" href="github-markdown.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.11.0/styles/default.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/katex.min.css">
<script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML'></script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ['\\(','\\)'] ]
}
});
</script>
</head>
<body class="markdown-body"></body>
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<h1>Chapter 8 LIGHTING</h1>
<p>观察图片<a href="#Image8.1">8.1</a>,可以发现左边的球没有被光照,右边的球有光照。
就如你看到的那样,左边的球显得非常的平坦,感觉和圆基本上没啥区别。
而另外一边的球,就很有3D的感觉了——光照和阴影使得我们看到这个物体更具有体积感。
实际上,我们的视觉感知就是基于光和材质的相互作用,即光照射到物体然后射入人眼。
因此要渲染出更真实的场景的话,就必须了解和解决很多关于光照模型的问题。</p>
<p><img src="Images/8.1.png" alt="Image8.1" /></p>
<p>当然,如果我们的光照模型越精确,那么我们的计算光照的开销就会越大。
因此我们必须要平衡好真实性和速度。
例如,电影中的3D特效场景我们就可以使用比游戏使用的更加逼真和复杂的光照模型,毕竟电影中的每一帧都是提前渲染好的,我们可以花费不止一天的时间去渲染一帧。
但是对于游戏来说,它的渲染是实时的,我们至少需要每一秒渲染30帧才能保证游戏的流畅性。</p>
<blockquote>
<p>目标:
- 对光照和材质的相互作用有基本了解。
- 了解全局光照和局部光照的区别。
- 学会使用代数描述面上的一个点的方向(即法线),从而我们能够确定光照到这个面的角度。
- 学会如何正确的转换法向量。
- 能够区分环境光,漫反射光以及镜面光。
- 学会如何实现平行光,点光源以及聚灯光。
- 能够通过控制衰减参数来使得光的强度能够随着深度而变化。</p>
</blockquote>
<h2>8.1 LIGHT AND MATERIAL INTERACTION</h2>
<p>如果我们使用光照的话,我们就不需要直接指定顶点的颜色。
但是我们需要指定材质和光照,然后通过一个关于光照的方程来计算出我们顶点的颜色。
并且这样能够使得我们的物体更加的真实(对比<a href="#Image8.1">图片8.1</a>中的两个球体)。</p>
<p>材质我们可以认为就是一种属性,来决定光照到物体的一个面的时候的作用。
例如材质就决定了光照到一个面后,哪些颜色的光会被反射,哪些颜色的光会被吸收,这个面的折射率,这个面的光滑程度以及这个面的透明程度。
通过使用材质和光照,我们可以实现各种各样的真实世界的物体的表面,例如木头,石头,玻璃,金属,水等。</p>
<p>在我们的光照模型中,我们的光源可以发出不同强度的红光,蓝光以及绿光。
因此,我们可以模拟出各种颜色的光。
当光从光源出发,然后射到一个物体的时候,会有一些光会被吸收,有一些会被反射(对于透明的物体来说,例如玻璃,光会穿过它。但是这里我们不讨论)。
被反射的光会改变它的方向,然后射到新的物体上,然后继续被吸收一部分,被反射一部分。
在一条光线被完全吸收之前(即没有能力继续反射),他可能会射到过多个物体。
也有很大可能会射到人眼中去(参见<a href="Image8.2">图片8.2</a>),射到视网膜上的视觉细胞上去。</p>
<p><img src="Images/8.2.png" alt="Image8.2" /></p>
<p>通过三色理论(<strong>trichromatic theory</strong>),视网膜中包含着三种不同的光线受体,分别对红光,蓝光,绿光敏感。
然后照射过来的光线会刺激对应的光线受体,然后光线受体会根据光线的强度来产生不同强度的刺激。
当光线受体接收到光后会产生神经冲动然后传向大脑,然后你的大脑就会产生对应的视觉(如果你闭上眼睛,那么光线受体就没有接收到任何光线,那么你就会看到一片黑色)。</p>
<p>假设在<a href="#Image8.2">图片8.2</a>中,圆柱体的材质能够反射$75\%$的红光,$75\%$的绿光,然后吸收剩下的光。
球体能够反射$25\%$的红光,然后吸收所有的光。
并且我们的光源能够发出白色的光。
当我们的光源发出的光射到圆柱体的时候,所有的蓝光都会被吸收,然后只有$75\%$的红光和绿光会被反射(但是看起来的话会是黄色)。
反射的光会分散开来,一部分可能射到人眼中,一部分可能射向球体。
射向人眼的光会刺激细胞产生视觉,然后你就会看到一个黄色的圆柱体。
射向球体的光则会继续被反射,反射$25\%$的红光然后吸收其余的光。
由于经过球体的光在圆柱体反射的时候就已经被削弱了(它只反射了$75\%$),然后球体又削弱一次,因此它看起来会比较暗淡,没有那么强烈。</p>
<p>我们上面介绍的光照模型我们称之为局部光照。
在局部光照中每个物体的照明都是独立的,也就是说我们在计算一个物体的光照的过程中只讨论光源直接照射到这个物体的情况。</p>
<p><a href="#Image8.3">图片8.3</a>介绍了局部光照,光源发出的光线本应该墙挡住,但是在图中,被墙挡住的球体仍然被视为被光照到了。</p>
<p><img src="Images/8.3.png" alt="Image8.3" /></p>
<p>另一方面,全局光照就不仅仅只是考虑光线从光源出发直接照射到物体了,还要考虑被其他物体反射的光照射到物体的情况。
因此我们称之为全局光照,当在考虑一个物体的光照的时候,我们需要考虑整个场景的所有的物体对它的影响。
并且它虽然能够渲染出更加真实的场景,但是他的开销却非常大。</p>
<h2>8.2 NORMAL VECTORS</h2>
<p>面法线是一个描述多边形面向的方向的单位向量(<strong>它垂直于这个多边形</strong>)。
<a href="#Image8.4">图片8.4a</a>,面法线就是一个垂直点和这个点所在的表面相切的平面的单位向量。
<a href="#Image8.4">图片8.4b</a>,面法线能够决定表面上的一个点的朝向。</p>
<p><img src="Images/8.4.png" alt="Image8.4" /></p>
<p>为了进行光照计算,我们需要知道所有的三角形网格上的每一个点的面法线,从而确定光线照射到这个点时和这个点所在的表面成的角的角度。
因此,我们会在每个顶点数据中加入一个称之为面法线的分量(<strong>vertex normals</strong>)。
然后我们会在光栅化阶段对顶点数据中的面法线分量进行插值,从而得到网格中所有的面上的点的法线的近似值(<strong>显然点是无限多的,但是我们不可能去插值所有的</strong>)。关于插值,<a href="#Image8.5">图片8.5</a>介绍了插值。</p>
<p><img src="Images/8.5.png" alt="Image8.5" /></p>
<p>简单来说我们知道起始和终止状态($p_0$和$p_1$),然后我们对于任意的一个$p=p_0 + t(p_1 - p_0)$,那么$p$这个点的面法线就是$n = n_0 + t(n_1 - n_0)$。</p>
<p>如果我们在像素着色器中,通过插值得到面法线,然后进行光照计算,我们称之为逐像素光照。
我们还有一种开销相对小的,但是质量不高的方法,叫做逐顶点光照,简单来说就是对每个顶点进行光照计算,然后每个像素的值就通过插值来获得。有时候,在使用两种光照视觉效果差别不是很大的时候,我们就会经常将原本的逐像素光照换成逐顶点光照,以便于能够节约开销提高性能。</p>
<h3>8.2.1 Computing Normal Vectors</h3>
<p>计算一个三角形(<strong>$p_0,p_1,p_2$</strong>)的面法线,我们可以先计算出三角形的任意两条边的向量,然后得到三角形的面法线。</p>
<p>两条边的向量:</p>
<p>$$u = p_1 - p_0$$</p>
<p>$$v = p_2 - p_0$$</p>
<p>那么面法线就是:</p>
<p>$$n = \frac{n \times v}{|u \times v|}$$</p>
<p>在一个平面上,我们可以通过计算来得到一个面的法线。
但是在网格中我们要需要计算出顶点的法向量,因此我们需要一个方法。
我们假设有一个点$v$,他的法向量为$n$,那么这个点的法向量$n$就是网格中所有的包含这个顶点的多边形的面法线的平均值。例如在<a href="Image8.6">图片8.6</a>,一个点被4个多边形共用,那么这个点的法向量的计算方法就是:</p>
<p>$$n_{avg} = \frac{n_0 + n_1 + n_2 + n_3}{|n_0 + n_1 + n_2 + n_3|}$$</p>
<p><img src="Images/8.6.png" alt="Image8.6" /></p>
<p>在上面的式子中,我们不会对其除以4,而是直接将其标准化(<strong>Normalize</strong>)。
那么在更复杂的图形上,我们的计算也会变得相对简单,我们只需要将所有的面法线相加然后标准化后就可以得到一个顶点的法向量了。</p>
<pre><code> for (int i = 0; i < numTriangles; i++)
{
uint index0 = indices[i * 3 + 0];
uint index1 = indices[i * 3 + 1];
uint index2 = indices[i * 3 + 2];
Vertex vertex0 = vertices[index0];
Vertex vertex1 = vertices[index1];
Vertex vertex2 = vertices[index2];
Vector3 e0 = vertex1.pos - vertex0.pos;
Vector3 e1 = vertex2.pos - vertex0.pos;s
Vector3 faceNormal = Cross(e0, e1);
vertices[index0].normal += faceNormal;
vertices[index1].normal += faceNormal;
vertices[index2].normal += faceNormal;
}
for (int i = 0; i < numVertices; i++)
vertices[i].normal = Normalize(vertices[i].normal);
</code></pre>
<h3>8.2.2 Transforming Normal Vectors</h3>
<p>在<a href="Image8.7a">图片8.7a</a>中,向量$u = v_1 - v_0$垂直法向量$n$。
如果我们对其进行一个非均匀的变换$A$,变换成<a href="Image8.7b">图片8.7b</a>中的样子,我们会发现变换后的向量$uA = v_1A - v_0A$和变换后的$nA$并不垂直。</p>
<p><img src="Images/8.7.a.png" alt="Image8.7.a" />
<img src="Images/8.7.b.png" alt="Image8.7.b" />
<img src="Images/8.7.c.png" alt="Image8.7.c" /></p>
<p>其中a部分是变换前,b部分是将其X轴部分缩放两个单位后,c部分是将法向量进行一次逆转变换。</p>
<p>也就是说,我们的问题是,一个变换矩阵$A$(<strong>Transformation Matrix</strong>)对一些点和对应的向量(不要求标准化)进行变换,我们需要找到另外一个矩阵$B$,使得变化后的法向量和变化后的向量垂直($uA \cdot nB = 0$)。</p>
<p>现在我们来推导这个矩阵:</p>
<table>
<thead>
<tr>
<th>式子</th>
<th>解释</th>
</tr>
</thead>
<tbody>
<tr>
<td>$u \cdot n = 0$</td>
<td>法向量$n$垂直于这个向量$u$。</td>
</tr>
<tr>
<td>$un^T = 0$</td>
<td>将点乘写成矩阵乘法的形式。</td>
</tr>
<tr>
<td>$u(AA^{-1})n^T = 0$</td>
<td>插入一个单位矩阵$I = AA^{-1}$。</td>
</tr>
<tr>
<td>$(uA)(A^{-1}n^T) = 0$</td>
<td>矩阵乘法的结合律。</td>
</tr>
<tr>
<td>$(uA)((A^{-1}n^T)^T)^T = 0$</td>
<td>转置矩阵的性质$(M^T)^T = M$。</td>
</tr>
<tr>
<td>$(uA)(n(A^{-1})^T)^T = 0$</td>
<td>转置矩阵的性质$(AB^T)^T = B^TA^T$。</td>
</tr>
<tr>
<td>$uA \cdot n(A^{-1})^T = 0$</td>
<td>重新将矩阵乘法写成点乘法的形式。</td>
</tr>
<tr>
<td>$uA \cdot nB = 0$</td>
<td>因此这样的转换就能够使得向量垂直。</td>
</tr>
</tbody>
</table>
<p>因此我们只需要使用矩阵$B = (A^{-1})^T$将法向量进行变换就可以使得法向量重新垂直了。</p>
<p>注意如果矩阵是正交矩阵(即$A^T = A^{-1}$)那么矩阵$B$就是:</p>
<p>$$B = (A^{-1})^T = (A^T)^T = A$$ 。</p>
<p>也就是说我们并不需要对矩阵$A$进行变换,但总的来说这样的情况只是特例,我们还是需要对矩阵$A$求逆并且转置从而得到矩阵$B$。</p>
<pre><code>Matrix InverseTranspose(Matrix matrix)
{
Matrix A = matrix;
A[3] = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
//Get the Determinant
Matrix determinant = Matrix::Determinant(A);
Matrix inverse = Matrix::Inverse(&determinant, A);
return Matrix::Transpose(inverse);
}
</code></pre>
<p>由于这个变换是对向量进行变换而不是对一个点进行变换,因此我们可以将所有的平移变换清除。不过我们在3.2.1中提到过如果向量的$w$分量为$0$的话,那么平移变换对这个向量是没有任何作用的。也就是说我们并不需要将平移变换清楚也是可以的。但是如果我们要将逆矩阵的转置矩阵和一个不包含非均匀变换的矩阵$V$组合在一起的话,即变成$(A^{-1})^TV$,矩阵$(A^{-1})^T$中被转置后的关于平移的部分就会导致他们的积出现问题。因此,我们就将矩阵$A$中关于平移部分的变换清零,从而避免这样的问题。并且,如果我们要将矩阵$A$的变换和矩阵$V$的变换组合在一起的话,最好的方法还是将其先进行变换然后求逆然后转置,即$((AV)^{-1})^T$形式。</p>
<p>下面就是一个缩放平移矩阵以及逆转置矩阵的例子(<strong>没有将平移部分的清零</strong>)。</p>
<p>原矩阵:</p>
<p>$$
A = \left[
\begin{matrix}
1 & 0 & 0 & 0 \\
0 & 0.5 & 0 & 0 \\
0 & 0 & 0.5 & 0 \\
1 & 1 & 1 & 1
\end{matrix}
\right]
$$</p>
<p>逆转置矩阵:</p>
<p>$$
(A^{-1})^T = \left[
\begin{matrix}
1 & 0 & 0 & -1 \\
0 & 2 & 0 & -2 \\
0 & 0 & 2 & -2 \\
0 & 0 & 0 & 1
\end{matrix}
\right]
$$</p>
<ul>
<li>注意,法向量经过逆转置矩阵的变换后他的模长可能不是单位长度,因此需要将其重新标准化。</li>
</ul>
<h2>8.3 IMPORTANT VECTORS IN LIGHTING</h2>
<p>在本部分,我们将会介绍一些参与光照的重要的向量。参考<a href="Image8.8">图片8.8</a>,$E$表示我们眼睛的位置,并且我们正在注视着点$p$,从点$p$指向我们的眼睛$E$的向量我们设为$v$。点$p$在的平面的法线我们设为$n$,并且点$p$被一条入射方向为向量$I$的光线照过。向量$L$则是从点$p$开始的一个和光线方向相反的单位向量。虽然我们使用向量$I$来表示光线可能较为直观,但是在这里,我们将使用向量$L$来表示。尤其是在我们计算朗伯余弦定理(<strong>Lambert's Cosine Law</strong>)的时候,向量$L$将会被用来计算$L \cdot n = \cos \theta_i$,其中$\theta_i$是$L$和$n$之间的角度。向量$r$则是入射光线照射到点$p$后关于这个面的法线$n$的反射向量。</p>
<p><img src="Images/8.8.png" alt="Image8.8" /></p>
<p>反射向量$r$则为(可以参考<a href="Image8.9">图片8.9</a>,我们假设$n$是单位向量):</p>
<p>$$r= I - 2(n \cdot I)n$$</p>
<p>实际上的计算我们可以使用<code>HLSL</code>内置函数<code>reflect</code>来在着色器程序中帮我们计算反射向量。</p>
<h2>8.4 LAMBERT&RSQUO'S COSINE LAW</h2>
<p>光可以被认为是一些光子在空间中朝某一个方向传播。每个光子都是含有能量。我们将光子每秒散发的能量称之为辐射量(<strong>radiant flux</strong>)。辐射量的密度,即每单位面积内的辐射量我们又称之为辐射度(<strong>irradiance</strong>)。辐射度将会决定被光照射到的面的单位面积内能够接收的光,或者说决定面的亮度。简单来说,我们可以认为辐射度就是单位面积内照射到面上的光的数量,或者穿过空间中某一区域的光的数量。</p>
<p>垂直照射到面上的光线会比以某个角度照射到面上的光线强烈很多。思考有一条辐射量为$P$的光线穿过一个横截面$A_1$。如果光线垂直照射到面上(<a href="#Image8.10">图片8.10</a>a),那么光线照射到的范围就是$A_1$,$A_1$处光线的辐射度$E_1 = P / A$。如果光线是以某一个角度照射到面上(<a href="#Image8.10">图片8.10</a>b),那么光线照射到的范围就是$A_2$,光线的辐射度$E_2 = P / A_2$。</p>
<p>$A_1$和$A_2$的关系如下:</p>
<p>$$\cos \theta = \frac{A_1}{A_2} \Rightarrow \frac{1}{A_2} = \frac{\cos \theta}{A_1}$$</p>
<p>也就是说:</p>
<p>$$E_2 = \frac{P}{A_2} = \frac{P}{A_1} \cos \theta = E_1 \cos \theta = E_1(n \cdot L)$$</p>
<p><img src="Images/8.10.png" alt="Image8.10" /></p>
<p>换句话说,光线照射到$A_2$的辐射度就是光线照射到垂直它的面$A_1$的辐射度再乘以$n \cdot L = \cos \theta$。这就是朗伯余弦定理。为了处理光线照射到面的背面的情况(即式子中的点积会为负数),我们就需要到使用<code>Max</code>函数。</p>
<p>$$f(\theta) = max(\cos \theta, 0) = max(L \cdot n, 0)$$</p>
<p><a href="#Image8.11">图片8.11</a>就是一张$f(\theta)$的图表,描述了$\theta$在$[0, 2]$范围内的值域。</p>
<p><img src="Images/8.11.png" alt="Image8.11" /></p>
<h2>8.5 DIFFUSE LIGHTING</h2>
<p>考虑一个不透明的物体的表面,例如<a href="#Image8.12">图片8.12</a>。当光线照射到表面上的一个点时,一些光线会射入到物体的内部,一些光线会作用到物体表面。射入到物体内部的光线会在物体内部不断的反射,其中一些光线会被吸收掉,剩余的光线则会从表面的各个方向散发出去。我们称这种现象为<strong>漫反射(diffuse reflection)</strong>。简化的话,我们可以认为散发出的光线都是从光线照射进来的那个点散发出去的。并且物体的材质将会决定有多少光线会被吸收,又有多少光线会重新发散出去。例如,木头,泥土,砖块,瓷砖,泥灰等物体它们吸收和散发的光各自不同(这也是它们看起来材质不一样的原因)。在我们的模型中,我们规定经过发散后的光线就是从它照射到面上的点出发射向各个方向的光线(但是不穿过这个面,因此射向面的反面的光线是不包括的)。因此,无论眼睛的位置在哪里,发散后的光线都能够到达我们的观察点(眼睛,当然我们的眼睛位置必须在面的上方)。因此,我们并不需要关心观察点(眼睛)的位置(换言之,关于漫反射的光照计算是独立于观察点的),并且面上的点的颜色无论观察点在哪里,它们看起来都会是一样的。</p>
<p><img src="Images/8.12.png" alt="Image8.12" /></p>
<p>我们将漫反射光照的计算分为两个部分。第一部分,确定光的颜色和材质漫反射的反射率。反射率决定了照射到这个面的光会有多少被反射(用能量守恒的观点来说,那些没有被反射的光就会被材质所吸收)。我们会使用颜色乘法来计算被反射的光的强度。例如,面上的一个点能够反射$50\%$的红光,$100\%$的绿光以及$75\%$的蓝光,而照射到这个点的光线则是强度为$80\%$的白光。也就是说光为$B_L = (0.8, 0.8, 0.8)$以及材质的反射率为$m_d = (0.5, 1.0, 0.75)$,那么这个点反射后的光就为:</p>
<p>$$c_d = max(L \cdot n, 0) \cdot B_L \otimes m_d$$</p>
<h2>8.6 AMBIENT LIGHTING</h2>
<p>早先我们讲过,我们的光照模型不去讨论那些被其他物体反射后的光,但是在真实场景中我们看到的很多光线都是经过了其他的物体反射后才到我们的眼中。例如,我们有一个走廊和一个房间,房间里面一盏灯,虽然走廊并没有光直接照射到,但是房间的灯发出的光仍然可以通过照射到墙上然后反射然后照射到走廊上。再举一个例子,假设我们坐在一个有一个桌子和茶壶的房间里,并且有一盏灯,茶壶只有正面会被照射到,然而茶壶的背面却并不是完全黑的,这是因为有一些光照射到其他物体或者墙上的时候被反射到了茶壶的背面,因此茶壶的背面也是实际有光线照射的。</p>
<p>为了模拟这样的情况,我们将会介绍坏境光,以及关于它的方程:</p>
<p>$$c_d = A_L \otimes m_d$$</p>
<p>$A_L$表示这个面会有接收到多少的坏境光。虽然这样做会和真实情况有一些不同。漫反射率$m_d$则是表示有多少照射过来的光会被反射。并且这里的$m_d$和我们在漫反射光照中使用的$m_d$是同一个量。也就是说,环境光照就是处理非直接照射到物体的光的漫反射。显然这样的计算并不是真实的物理模拟,但是由于通常光线会经过很多次的散射或者反射,所以可以近似的认为光线照射到物体是从全方位的。</p>
<h2>8.7 SPECULAR LIGHTING</h2>
<p>我们使用漫反射光照来处理物体的漫反射,也就是处理光线照射到介质后,部分被吸收然后剩下的从介质中散发到各个方向。由于菲涅耳效应(<strong>Fresnel Effect</strong>),第二种反射就出现了。当光线照射到两种折射指数不同的介质的交界处时,会有一部分光会被反射,剩下的一部分光则会折射(参见<a href="#Image8.13">图片8.13</a>)。折射指数是介质的物理性质,它是光在介质中传播的速度和光在真空中的传播速度的比值。我们将这种光反射的过程称为镜面反射(<strong>specular reflection</strong>),由镜面反射反射的光我们称之为镜面光(<strong>specular light</strong>)。</p>
<p><img src="Images/8.13.png" alt="Image8.13" /></p>
<p><a href="#Image8.13">图片8.13</a>: 法线为$n$的平面镜上的菲涅尔效应。入射光$I$分成了两部分,一部分被反射且方向为$r$,另外一部分折射到介质中方向为$t$。同时这些方向向量都共面。并且反射角(即反射向量和法线的夹角)和入射角始终都为$\theta_i$,即向量$r$和法线$n$的夹角等于向量$L = -I$和法线$n$的夹角。折射方向$t$的向量$-n$的夹角$\theta_t$的大小则取决于两个介质间的折射系数以及斯涅尔定理。由于大多数物体都不是完全光滑的,所以真实的光线会从反射方向和折射方向散开。</p>
<p>如果折射光线从介质中出来(从另外一边)并且射入眼中,那么呈现出的物体是透明的。这也是为什么会有透明物体。实时图形(<strong>Real-time Graphics</strong>)往往通常会使用混合或者处理后的效果去实现透明物体的折射,我们将会在后面一部分讲到这些。现在我们只考虑不透明的物体。</p>
<p>对于不透明的物体来说,折射光线进入介质中去然后会经历漫反射。因此对于不透明的物体,我们可以参见图片<a href="#Image8.14">8.14</a>b,物体表面反射的光由主要的反射(漫反射)以及镜面反射混合而成。相比漫反射,由于镜面反射的反射方向是某一特定的方向,因此它最后未必会射向眼睛。也就是说,镜面反射的计算是需要知道视点的。这也意味着当我们的视点在场景中移动的时候,它接收到的镜面反射发出的光的数量也会改变。</p>
<p><img src="Images/8.14.png" alt="Image8.14" /></p>
<p><strong>a</strong>部分中镜面反射的光线方向为向量$r$,<strong>b</strong>部分中反射到眼中的光线由镜面反射的光线和漫反射的光线混合而成。</p>
<h3>8.7.1 Fresnel Effect</h3>
<p>我们考虑一个在两个折射系数不同的介质之间的光滑的平面。由于折射系数的原因,当入射光线照射到平面上的时候,部分光线会被反射出去,另外一部分则会折射,射入平面内(参见<a href="#Image8.13">图片8.13</a>)。菲涅尔方程则给出了有百分之多少的入射光线会被反射,$0 \le R_F \le 1$。从能量的角度来看,如果$R_F$是反射的光的量,那么$(1 - R_F)$就是折射光线的量。由于我们的光是以<strong>RGB</strong>定义的,因此$R_F$会是一个<strong>RGB</strong>向量。</p>
<p>介质(有一些材质反射的光会多于其他的材质)以及法线$n$和光线方向向量$L$的夹角$\theta_i$将会决定反射光线的量。由于原本的菲涅尔方程过于复杂,因此我们在实时渲染中并不使用它,我们是使用的是近似值(<strong>Schlick's approximation</strong>):</p>
<p>$$R_F(\theta_i) = R_F(0^{\omicron}) + (1 - R_F(0^{\omicron}))(1 - \cos \theta_i)^5$$</p>
<p>$R_F(0^{\omicron})$是介质的属性,下面是一些常见材质的值。</p>
<table>
<thead>
<tr>
<th>介质</th>
<th>$R_F(0^{\omicron})$</th>
</tr>
</thead>
<tbody>
<tr>
<td>水</td>
<td>$(0.02, 0.02, 0.02)$</td>
</tr>
<tr>
<td>玻璃</td>
<td>$(0.08, 0.08, 0.08)$</td>
</tr>
<tr>
<td>塑料</td>
<td>$(0.05, 0.05, 0.05)$</td>
</tr>
<tr>
<td>黄金</td>
<td>$(1.00, 0.71, 0.29)$</td>
</tr>
<tr>
<td>银</td>
<td>$(0.95, 0.93, 0.88)$</td>
</tr>
<tr>
<td>铜</td>
<td>$(0.95, 0.64, 0.54)$</td>
</tr>
</tbody>
</table>
<p><a href="#Image8.15">图片8.15</a>则显示了不同的介质的$R_F(0^{\omicron})$的近似值的图像。我们可以发现,反射光线的数量随着$\theta_i$增长到$90^{\omicron}$而增长。我们以现实中的情况为例。参见<a href="#Image8.16">图片8.16</a>。假设我们站在一个只有几英尺深的池塘里,池塘的水清澈见底。如果我们向下看,我们基本上可以看到池塘底的沙子和石头。这是由于光线照射到水面后以非常小的角度$\theta_i$(接近于$0.0^\omicron$)射入我们的眼中。也就是说,射入我们的眼中的光线中反射后的光线会较少,而折射后的光线会较多。另一方面,如果我们水平的看向水面的话,我们将会看到水面反射出非常强烈的光。这是由于光线照射水面后以接近$90.0^\omicron$的角度射入我们的眼中,因此反射的光线会较多。我们将这样的现象称为菲涅尔效应。总的来说,反射的光线的量由入射角和材质($R_F(0^\omicron)$)决定。</p>
<p><img src="Images/8.15.png" alt="Image8.15" /></p>
<p>不同的材质的近似值,依次为水,红宝石和铁。</p>
<p>金属会吸收射入的光线,这意味着他没有漫反射。但是金属不会只显示为黑色,因为只要金属的$R_F(0^\omicron)$够高的话,即使是接近于$0^\omicron$的入射角,它仍然会反射出大量的镜面光。</p>
<h3>8.7.2 Roughness</h3>
<p>在现实中,物体并不是完美平滑的。即使一个物体看起来非常的平滑,但是在微观层面中它还是粗糙不平的。参考<a href="#Image8.17">图片8.17</a>,我们可以认为完美的镜面是光滑的,即没有任何粗糙的地方,这也意味着在微观上,它的所有法线(<strong>micro-normals</strong>)和在宏观上这个面的法线(<strong>macro-normal</strong>)的方向是一致的。随着表面的粗糙度的增强,微观上的法线的方向将会逐渐散开,导致反射的光线展开成镜瓣(如果你把镜子稍微靠近地面或者其他地方你可以看见地面有一层光,这个就指的这个东西)。</p>
<p><img src="Images/8.16.png" alt="Image8.16" /></p>
<p>a部分,就是入射角非常小的时候,折射的光线比较多,而反射的比较少。b部分则是入射角比较大的时候,反射的光线比较多,而折射的光线比较少。</p>
<p><img src="Images/8.17.png" alt="Image8.17" /></p>
<p>a部分中,黑色的部分是一块平面的一部分,我们将其放大。在微观层次上,由于表面粗糙的原因,这个表面就会有很多朝向不同的法线。而如果表面越光滑,那么这些法线的方向则会越接近宏观上这个面的法线的方向。
b部分,这是由于表面粗糙的原因,光线照射到一块表面的时候,反射的光线会朝各个方向散发出去。</p>
<p>为了给粗糙度数学建模,我们需要使用到微平面这个模型,即认为一块微小的平面由多块光滑的平面构成。同时,这些光滑的平面的法线,就是我们之前所说的微观上的法线。假设有一个视点$E$以及一条光线$L$。我们想知道有多少光滑的平面能够将光线$L$反射到视点$E$,换句话说就是有多少的光滑平面它的法线$h = normalize(L + v)$。观察<a href="#Image8.18">图片8.18</a>,就是一个能够将光线$L$反射到视点$v$的光滑的平面。</p>
<p><img src="Images/8.18.png" alt="Image8.18" /></p>
<p>光滑平面的法线$h$我们则称之为中间法线(<strong>halfway</strong>),即它位于光线向量$L$和视点向量$v$之间。同时我们设中间法线$h$和整个面的法线(即宏观上的法线)的夹角为$\theta_h$。</p>
<p>我们定义一个函数$\rho(\theta_h) \in [0, 1]$来表示微面的法向量$h$和平面的法向量$n$的夹角$\theta_h$,也就是说表示微面的法向量偏向于平面的法向量的程度。且当$\theta_h = 0^\omicron$时,$\rho(\theta_h)$取得最大值。也就是说,当$\theta_h$递增的时候($h$偏离$n$的时候),我们的函数值会随着递减。通常我们则使用下面的函数来对其建模:</p>
<p>$$\rho(\theta_h) = cos^m(\theta_h)$$</p>
<p>$$\rho(\theta_h) = cos^m(n \cdot h)$$</p>
<p>因为$h$和$n$都是单位向量,因此$cos(\theta_h) = (n \cdot h)$是成立的。<a href="#Image8.19">图片8.19</a>则显示了$m$取不同的值的时候,函数的图像。随着$m$的减少,平面就越来越粗糙,微面的法线逐渐和平面的法线分离。而随着$m$的增加,平面变的越来越光滑,微面的法线逐渐偏向于平面的法线。</p>
<p><img src="Images/8.19.png" alt="图片8.19" /></p>
<p>我们可以将$\rho(\theta_h)$以及标准化系数(normalization factor)组合成一个新的函数,使镜面反射的量与粗糙度相关。即下面这个函数:</p>
<p>$$S(\theta_h) = \frac{m + 8}{m}cos^m(\theta_h)$$
$$S(\theta_h) = \frac{m + 8}{m}(n \cdot h)^m$$</p>
<p><a href="#Image8.20">图片8.20</a>则呈现了当$m$取不同值的时候函数的图像。就和刚刚的图像一样,$m$将控制着粗糙的程度,只是我们增加了一个系数$\frac{m + 8}{m}$。当$m$越小的时候,平面就越粗糙,反射产生的镜瓣的范围就越广,能量也越分散。因此产生的镜面高光就会因为能量的分散而减弱。而当$m$越大的时候,平面就越光滑,产生的镜瓣的范围就越小,能量也就越集中。因此产生的镜面高光就非常的强烈。从几何角度来看,$m$控制着镜瓣的大小范围。</p>
<p>如果我们需要的是光滑的平面,那么$m$的值大一点好,如果需要粗糙的平面,那么小一点好.</p>
<p><img src="Images/8.20.png" alt="Image8.20" /></p>
<p>最后我们来总结,我们将菲涅尔效应以及平面的粗糙度组合在一起,首先我们要计算有多少的光反射后朝向方向$v$。将光线反射到$v$的微面的法线为$h$(即中间法线)),设$\alpha_h$为光线向量和中间法线两者之间的夹角,那么$R_F(\alpha_h)$就是反射到$v$向量方向的光的数量。由于粗糙度的存在,可能不止只有一条光线会反射到$v$的方向,因此我们还要将我们的$R_F(\alpha_h)$乘以一个$S(\theta_h)$(它将告诉我们大概有多少光线会反射到$v$的方向)。然后,我们设$(max(L \cdot h, 0) \cdot B_L)$为光线射到我们正在处理光照的点上的光的量,那么我们最后的式子则是:</p>
<p>$$c_s = max(L \cdot n, 0) \cdot B_L \otimes R_F(\alpha_h) \frac{m + 8}{m}(n \cdot h)^m$$</p>
<p>如果$L \cdot h \leq 0$那么就表示光线照射到的是点所在的面的背面。</p>
<h2>8.8 LIGHTING MODEL RECAP</h2>
<p>总的来说,平面反射光线就是将环境光,漫反射光,镜面反射光组合在一起。</p>
<ul>
<li>环境光$c_a$: 一些并不直接射向物体而是经过多次反射后再设想物体的光照。</li>
<li>漫反射光$c_d$:射入介质内部,然后在内部经过多次反射折射,剩余的光重新从表面扩散出去。</li>
</ul>