今天继续计算机速成课Crash Course的系列讲解。
更多技术文章,全网首发公众号 “摸鱼IT” 锁定 -上午11点 - ,感谢大家关注、转发、点赞!
计算机速成课Crash Course - 27. 3D 图形 (qq.com)
27. 3D 图形
在过去五集,我们从基于电传打字机的命令行界面,讲到图形怎么显示到屏幕上,再到上集的 图形用户界面(GUI),以及图形界面的美味。
之前的例子都是2D, 但我们生活的世界是3D的,所以今天,我们讲3D图形的基础知识,以及如何渲染 3D 图形到 2D 屏幕上。
24集中说过可以写一个函数,从A到B画一条线,通过控制 A 和 B 的(X,Y)坐标,可以控制一条线。
在3D图像中, 点的坐标不再是两点, 而是三点 X,Y,Z,或读"Zee",但我之后会读成"Zed"
当然,2D的电脑屏幕上不可能有 XYZ 立体坐标轴,所以有图形算法负责把3D坐标"拍平"显示到2D屏幕上,这叫"3D投影"。
所有的点都从3D转成2D后,就可以用画2D线段的函数来连接这些点,这叫 "线框渲染"。
想象用筷子做一个立方体,然后用手电筒照它,墙上的影子就是投射,是平的。如果旋转立方体,投影看起来会像 3D 物体,尽管是投影面是平的。
电脑也是这样3D转2D,只不过用大量数学,而不是筷子,3D投影有好几种,你现在看到的,叫 正交投影。
立方体的各个边,在投影中互相平行,在真实3D世界中,平行线段会在远处收敛于一点,就像远处的马路汇聚到一点,这叫 透视投射。
过程是类似的,只是数学稍有不同,有时你想要透视投影,有时不想,具体取决于开发人员。
如果想画立方体这种简单图形,直线就够了,但更复杂的图形,三角形更好,在3D图形学中,我们叫三角形"多边形"(Polygons)。
看看这个多边形组成的 漂亮茶壶,一堆多边形的集合叫 网格,网格越密,表面越光滑,细节越多,但意味着更多计算量。
游戏设计者要平衡角色的真实度和多边形数量,如果数量太多,帧率会下降到肉眼可感知,用户会觉得卡,因此有算法用来简化网格。
之所以三角形更常用,而不是用正方形,或其它更复杂的图形,是因为三角形的简单性。
空间中三点定义一个平面,如果给3个3D点,我能画出一个平面,而且只有这一个答案。
4个或多于4个点就不一定了,而2个点不够定义平面,只能定义线段,所以3是最完美的数字,三角形万岁。
线框渲染虽然很酷,但3D图像需要填充,填充图形的经典算法叫 扫描线渲染 (Scanline Rendering) 于1967年诞生在犹他州大学。
为了例子简单,我们只看一个多边形,我们要思考,这个多边形如何转成一块填满像素的区域。
我们先铺一层像素网格,扫描线算法 先读多边形的3个点,找最大和最小的Y值,只在这两点间工作。
然后算法从上往下,一次处理一行,计算每一行和多边形相交的2个点,因为是三角形,如果相交一条边, 必然相交另一条。
扫描线算法 会填满2个相交点之间的像素,来看个具体例子。
第一行 相交于这里和这里,算法把两点间填满颜色,然后下一行,再下一行,所以叫 扫描..线..渲染。扫到底部就完成了。
填充的速度叫 fillrate(填充速率),当然,这样的三角形比较丑,边缘满是锯齿。
当像素较小时,就不那么明显,但尽管如此,你肯定在游戏里见过这种效果,特别是低配电脑。
一种减轻锯齿的方法叫抗锯齿(Antialiasing),与其每个像素都涂成一样的颜色,可以判断多边形切过像素的程度,来调整颜色。
如果像素在多边形内部,就直接涂颜色,如果多边形划过像素,颜色就浅一些,这种边缘羽化的效果,看着更舒服些。
抗锯齿 被广泛使用,比如字体和图标,如果你把脸贴近屏幕,近点..再近点,你能看到浏览器里字体是抗锯齿的,超平滑。
在3D场景中,多边形到处都是,但只有一部分能看见,因为其它的被挡住了,这叫 遮挡。
最直接的处理办法是用排序算法,从远到近排列,然后从远到近渲染,这叫 画家算法,因为画家也是先画背景,然后再画更近的东西。
看这个例子,有3个重叠的多边形,为了简单,我们画成不同颜色,同时,假设3个多边形都和屏幕平行,但在实际应用中,比如游戏里多边形可能是倾斜的。
3个多边形A,B,C,距离20,12,14,画家算法的第一件事,是从远到近排序。
现在有序了,我们可以用 扫描线算法 填充多边形,一次填一个,我们从最远的A开始。
然后重复这个过程,填充第二远的C,然后是 B。
现在完成了,可以看到顺序是对的,近的多边形在前面!
还有一种方法叫 深度缓冲,它和之前的算法做的事情一样,但方法不同,我们回到之前的例子,回到排序前的状态。
因为这个算法不用排序,所以速度更快,简而言之,Z-buffering 算法会记录,场景中每个像素和摄像机的距离,在内存里存一个数字矩阵。
首先,每个像素的距离被初始化为"无限大",然后 Z-buffering 从列表里第一个多边形开始处理,也就是A,它和扫描线算法逻辑相同,但不是给像素填充颜色。
而是把多边形的距离和 Z-Buffer 里的距离进行对比,它总是记录更低的值,A距离20,20小于"无限大",所以缓冲区记录20。
算完A之后算下一个,以此类推,因为没对多边形排序,所以后处理的多边形并不总会覆盖前面的。
对于多边形C,缓冲区里只有一部分值会被多边形C的距离值覆盖,缓冲区完成后,会和"扫描线"算法的改进高级版配合使用。
不仅可以勘测到线的交叉点,还可以知道某像素是否在最终场景中可见,如果不可见,扫描线算法会跳过那个部分。
当两个多边形距离相同时,会出现一个有趣问题,比如多边形 A 和 B 距离都是 20, 哪个画上面?
多边形会在内存中移来移去,访问顺序会不断变化,另外,计算浮点数有舍入误差,所以哪一个画在上面,往往是不可预测的。
导致出现 Z-fighting 效果,如果你玩过3D游戏,肯定见过。
说起 故障,3D游戏中有个优化叫 背面剔除。
你想想,三角形有两面,正面和背面,游戏角色的头部或地面,只能看到朝外的一面,所以为了节省处理时间,会忽略多边形背面。
减了一半多边形面数,这很好,但有个bug是如果进入模型内部往外看,头部和地面会消失。
继续,我们讲灯光,也叫 明暗处理。
因为3D场景中, 物体表面应该有明暗变化,我们回到之前的茶壶网格,用"扫描线"算法渲染所有多边形后,茶壶看起来像这样,没什么 3D 感。
我们来加点灯光,提高真实感。
为了举例,我们从茶壶上挑3个不同位置的多边形,和之前的例子不同,这次要考虑这些多边形面对的方向。
它们不平行于屏幕,而是面对不同方向,他们面对的方向叫 "表面法线",我们可以用一个垂直于表面的小箭头,来显示这个方向。
现在加个光源,每个多边形被照亮的程度不同,有的更亮,因为面对的角度,导致更多光线反射到观察者。
举个例子,底部的多边形向下倾斜,远离光源,所以更暗一些,类似的,最右的多边形更背对光源,所以只有部分照亮。
最后是左上角的多边形,因为它面对的角度,意味着会把光线反射到我们这里,所以会显得更亮。
如果对每个多边形执行同样的步骤,看上去会更真实!这叫 平面着色,是最基本的照明算法。
不幸的是,这使多边形的边界非常明显,看起来不光滑,因此开发了更多算法,比如 高洛德着色 和 冯氏着色。
不只用一种颜色给整个多边形上色,而是以巧妙的方式改变颜色,得到更好的效果。
我们还要说下"纹理",纹理在图形学中指外观,而不是手感,就像照明算法一样,纹理也有多种算法,来做各种花哨效果。
最简单的是 纹理映射,为了理解纹理映射,回到单个多边形,用"扫描线算法"填充时,可以看看内存内的纹理图像,决定像素用什么颜色。
为了做到这点,需要把多边形坐标和纹理坐标对应起来,我们来看看"扫描线算法"要填充的第一个像素,纹理算法会查询纹理。
从相应区域取平均颜色,并填充多边形,重复这个过程,就可以获得纹理,如果结合这集提到的所有技巧,会得到一个精美的小茶壶。
这个茶壶可以放进更大的场景里,场景由上百万个多边形组成,渲染这样的场景需要大量计算。
但重要的是,再大的场景,过程都是一样的,一遍又一遍,处理所有多边形,扫描线填充, 抗锯齿, 光照, 纹理化。
然而,有几种方法可以加速渲染。
首先,我们可以为这种特定运算,做专门的硬件来加快速度,让运算快如闪电。
其次,我们可以把3D场景分解成多个小部分,然后并行渲染,而不是按顺序渲染。
CPU不是为此设计的,因此图形运算不快,所以,计算机工程师为图形做了专门的处理器,叫 GPU "图形处理单元"。
GPU 在显卡上,周围有专用的 RAM,所有网格和纹理都在里面,让 GPU 的多个核心可以高速访问。
现代显卡,如 GeForce GTX 1080 TI,有3584个处理核心,提供大规模并行处理,每秒处理上亿个多边形!
好了,本集对3D图形的介绍到此结束,下节课我们聊全新的主题。
以上内容就是 27. 3D 图形 的内容,感兴趣的同学记得点赞、关注、转发、收藏哦!
我会不定期发布课程的讲解!
更多技术文章,全网首发公众号 “摸鱼IT” 锁定 -上午11点 - ,感谢大家关注、转发、点赞!