1)Model transformation(placing objects) = 找好一个场景,让人物摆好姿势
2)View transformation(placing camera) = 放置好照相机
利用camera和物体的相对运动关系,始终让camera从任一位置变换到原点看向-z方向且向上为y,其余物体也跟着camera做相同的变换,从而保证model transformation应用到每一个model上,并且view transformation也应用到了每一个model上。
3)Projection transformation = 按下快门,将三维物体表现在二维平面上
在view transformation之后我们知道场景中的所有物体都是由一个标准位置的camera看过去的,接着将所看到的东西从3D投影成2D的一张照片。又分为正交投影(忽略深度信息)和透视投影(拥有近打远小的效果)。
经过mvp变换矩阵之后的所有东西都会在一个 [ − 1 , 1 ] 3 [-1,1]^3 [−1,1]3的立方体里,那么下一步该干什么呢?答案是“光栅化”!
但在讲光栅化之前先看如果根据已知的Near、Far、Fov、Aspect来定义frustum:
aspect是宽高比,说的是Near和Far。
而Fov则是从camera,点A处,往上下两边的中点各连一条线,而两条线的夹角叫垂直可视角度。同理也可以求出水平可视角度,往左右两边的中点各连一条线,而两条线的夹角叫水平可视角度。
Near和Far是两个面的位置。
有了这些已知条件就可以计算推导出Near和Far的面积有多大,以及知道一个垂直可视角度就可以推导出水平可视角度。
接下来我们来了解如何将 [ − 1 , 1 ] 3 [-1,1]^3 [−1,1]3的立方体画在屏幕上
对于图形学来说,我们抽象的将屏幕定义为一个二维的数组,数组中的每一个元素是一个像素(an array of pixels)。
(tips:例如1920 * 1080分辨率,就是说长为1920个像素点,宽为1080个像素点,总像素为1920 * 1080个像素,也叫做1080P。)
屏幕是一个典型的光栅成像设备。Raster的由来是德语中屏幕的意思,而光栅化(Rasterize)就是把东西画在屏幕上的意思。所以把东西画在屏幕上的过程就是光栅化的过程。
像素:
像素名字的由来是picture element,我们将其缩写为pixel
在图形学中我们将像素看为:
屏幕空间:
屏幕空间就是以屏幕左下角为原点建立一个二维坐标系,如下图:
现在我们需要把这个中心在坐标原点 [ − 1 , 1 ] 3 [-1,1]^3 [−1,1]3的立方体显示在长为height、宽为width的屏幕中,先不考虑Z值,如下图所示:
第二步的具体操作是进行一次视口变换(viewport),准确地说是通过缩放与平移变换将原本的 [ − 1 , 1 ] 2 [-1,1]^2 [−1,1]2拉伸到屏幕空间中并将中心移动到屏幕空间中心,视口变换矩阵(viewport transform matrix)如下:
M v i e w p o r t = [ w i d t h 2 0 0 w i d t h 2 0 h e i g h t 2 0 h e i g h t 2 0 0 1 0 0 0 0 1 ] M_{viewport}= \left[ \begin{matrix} width \over 2 & 0 & 0 & width \over2 \\ 0 & height \over2 & 0 & height \over2 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] Mviewport= 2width00002height0000102width2height01
至此我们得到了一张映射在屏幕空间的2D场景图,接下来我们要将场景中的多边形其离散成一系列的像素画在屏幕空间上。
光栅化的过程实际上就是将多边形拆分成一系列的像素,从而将3D空间中的多边形或多边形的顶点变换到屏幕空间上去。
一般将图像拆分成许多不同的三角形 进行显示,下图是由三角形表示的二维空间的不同的图形。
我们可以看到三角形的表达能力很强,不论是在3D还是2D中,之所以选择三角形是因为:
三角形具有不错的性质:
Ⅰ. 三角形的三个点连接在一起,其三点一定在一个平面上。
Ⅱ. 三角形的内外部定义的很清楚,可以通过向量的叉积判断点是否在三角形内部。
我们知道一个像素内部是不会再发生颜色变化的,那三角形三条边上只包含了像素一部分的这些区域该如何设置颜色呢?是有颜色还是无颜色呢?
因此我们需要判断一个像素和三角形之间的位置关系,更确切的来说是判断像素的中心点与三角形之间的位置关系。
而这个关系的判断我们可以通过采样实现。
采样:采样就是把一个函数给离散化的过程,可以理解为给你一个连续的函数,在不同地方问此处的函数值是多少,这就是采样.
例如f(x) = sinx,让你求x=1,x=3,x=5或者x=-1时候的对应的f(x)的值是多少,如:
采样就是把一个函数离散化的过程。
我们这里的采样是通过利用像素中心对屏幕空间进行采样,也就是算出定义在屏幕空间上的某一函数在不同像素中心的值是多少。
采样是图形学的核心理念,可以采样的:时间(1D)、面积(2D)、方向(2D)、体积(3D)等。
给你一个三角形,我们需要在像素中心来判断其是否在三角形内。
我们可以定义一个inside函数,来判断像素是否在三角形内:
for (int x = 0; x < xmax; ++x)
for (int y = 0; y < ymax; ++y)
image[x][y] = inside(tri, x + 0.5, y + 0.5);
x,y不必为整数,是实数。
屏幕空间是[0,width] x [0,height]这么一个区域,我们利用像素中心来进行采样就是指算出需要某一个定义在屏幕空间内的函数在不同的像素中心的值是多少
比如我们在这里采样的就是在屏幕空间中定义的inside函数在不同像素中心的值,在不同像素中心采样到的结果是1则在三角形内,是0则在三角形外,通过这个值我们来判断此像素是否在三角形内.
这个函数具体是如何判断像素点在不在三角形内部则是通过向量叉乘运算得到的。如果正巧点在三角形边线上,那可以自己定义一个规则。
接下来就可以遍历屏幕像素将三角形显示在屏幕中了
有两种较为高效的扫描遍历策略Bounding Box和Incremental Triangle Traversal。
(Bounding Box) 检测像素的步骤:
首先通过三角形三个顶点的 x,y 的坐标从最小值到最大值选出包围盒(Bounding Box)。即蓝色包围部分,去掉第一列白色部分。
严格来说这个又叫做轴向包围盒=AABB包围盒(Axis-aligned bounding box)
在蓝色区域内进行采样,使用inside函数确定像素中心点的值。
(Incremental Triangle Traversal)增量三角形遍历:
对三角形所覆盖的区域每一行找覆盖的最左和最右,相当于在每一行确定一个包围盒。
这种方法适用于实际上覆盖了很少的像素但AABB包围盒范围却很大的情况,也就是一个窄长三角形经历了旋转的情况下。
最终我们采样信号显示为:
我们找到了那些像素是在三角形内部的,接着我们对其进行颜色填充。
在像素内进行颜色填充之后会变为下图,我们称这种情况为锯齿(Jaggies)现象。出现的原因是 因为像素本身具有大小,采样率不高,从而信号走样(Aliasing)。
与我们想要的效果不一样:
锯齿一直是图形学在不断改进的问题,下节课将介绍如何解决“锯齿”问题,引入抗锯齿和反走样的概念。
光栅化的过程其实就是我们将3D空间的多边形离散成屏幕空间一系列的点(像素的中心点),并通过采样某一定义在屏幕空间的函数的结果判断那些像素需要着色从而将多边形显示在屏幕空间上。
反走样的主要思想是,在采样之前进行模糊处理(Antialiasing idea:bluring before sampling).
如果不先进行模糊处理而是直接对三角形采样,由于我们是通过像素中心点来采样,得到的结果这个像素点要么在三角形内像素点纯红,要么在三角形外像素点纯白,最终会导致锯齿:
如果我们先做一遍模糊处理在进行采样:
我们会发现采样点增加了,并且在边缘处有一些颜色比较淡的点,也就是采样到了模糊三角形的边界。
三角形的原锯齿边缘的像素取了一个介于红色和白色之间的像素值,离边界近就接近红色,离边界远就接近白色,再进行染色时,边缘的像素点就会染色为中间值,边缘就能够获得一些更加平滑的效果,起到抗锯齿的作用。
这也就是对原始的函数或者信号进行了一个模糊或者滤波的处理,然后再去做采样,从而解决了锯齿的问题。
先采样再模糊并不能实现抗锯齿的效果,其仍然得到的是一个走样的结果,只不过在走样之后又进行了模糊操作,称其为Blurred Aliasing,并不是Anti-Aliasing。
硬件支持:提高显示器分辨率。如果用一个640*480的显示器,光栅化一个三角形,就能够明显发现锯齿很严重,但是如果使用1920 * 1080的显示器就能够明显改善这种情况,采样的密集了,走样现象也就会减少。但是这种情况需要依赖于高分辨率的显示器,若是显示器分辨率不能改变时,这种方法就不行了。
MSAA是一个对反走样的近似,之前采样的最小单位是像素的中心点,在MSAA中,我们将以划分的每个像素再进行划分,将原有的一个像素分为更多个小像素.如下图,我们将原来的一个像素的区域又重新划分为了4*4 = 16 个像素的新区域.
而重新划分的每一个小像素也都有一个像素中心,然后可以对这个小像素进行采样,判断这些小像素在不在三角形内,然后将最后的结果平均起来,就能够求的三角形对一个像素的区域的近似的覆盖。比如:
可以看到一个三角形覆盖了若干个很大的原像素.
现在假设说我们将原有的一个大像素分为四个小像素,也就是在像素内部多加一些采样点,这就导致了时域上我们采样的间隔变小,频域上的频谱搬移空间变大,从而进行了反走样.
然后每个点都可以判断是否在三角形内,如果四个点都在三角形内就是100%覆盖,也就是100%的红色,如果只有三个点在三角形内,则三角形对这个像素就是75%覆盖,其颜色也就是75%的红色+25%的白色。于是就可以得出每个像素的覆盖结果:
MSAA通过更多的样本,来近似的进行反走样的第一步,模糊这个过程。模糊完了之后就相当于每一个像素内部都已经知道了三角形的覆盖率,求了平均之后是什么,就会得到上图的结果。
到了这一步,就相当于模糊做完了,就相当于用卷积核对图像进行了一次卷积得到了模糊的效果。
然后再采样,这时采样就会十分简单,因为我们规定像素是最小的单位格子,每个格子的颜色都是平均的(像素内的颜色都是一样的),采样完也就会得到上图的结果。
注意!!!
Painter’s Algorithm:画家算法,由远及近画画,近处画面覆盖远处画面。无法处理复杂的深度判断,例如三个三角形互相重叠。如果事先把深度排序的话时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn).
Z-buffer:额外开辟一块屏幕大小的空间存放像素深度。渲染之前先于存放的值进行比较,如果深度更近则进行绘制,并更新值。空间换时间,时间复杂度 O ( n ) O(n) O(n)。