第六节
上节课复习

MVP(model模型变换- view跟随摄影机变换到相对(0,0,0)位置 - project 投影变换(-1,1的3次方的空间))变换之后在进行视口变换(viewport,-1,1的3次方的空间变换到分辨率空间)
Antialiasing 抗锯齿
根据上节课的判断,我们可以得到某些像素带你的中心在三角形内部,某些不在,如图:
我们想得到的是这样的三角形:
结果把对应的像素填充完得到这样的三角形:
Aliasing 走样/锯齿
我们要想抗锯齿首先要明白锯齿是怎么来的。
采样是图形学中广泛存在的做法,光栅化的过程,其实就是在屏幕空间用一系列离散的点(也就是像素的中心)进行是否在三角形内这么一个函数的采样。
照片也是采样:一副照片,就是说所有到达这个感光元件的所在平面的光学信息,我们把它们离散成一系列图片上的像素的过程。
采样不光可以发生在不同的未知,也可以发生在不同的时间。
视频就是在时间上的采样。
采样是广泛存在的,采样的问题也是广泛存在的。
Artifacts(瑕疵)
采样的问题:
- 锯齿
- 摩尔纹(把采样的过程的奇数行和奇数列去掉在对起来就会产生这样的问题)
- 顺时针变成逆时针(采样速度太慢)
信号变化太快,导致采样速度跟不上。





为什么?
为什么说采样的速度的跟不上信号变换的速度就会产生走样,
为什么先去做采样,后做模糊操作达不到反走样效果
为了解释为什么需要引入频率
Frequency 频率:







左边的图经过傅里叶变换可以变成右边这幅图。
图像空间变成频率空间。
频率空间怎么理解呢,中心我们把它理解为最低频的区域,边缘是最高频的区域。在不同频率上有多少信息我们用亮度表示,可以看出这幅图大多数信息集中在低频上。对于自然的图片一般都是这样的。为什么会有水平和竖直的道呢?简单说原因是我们在分析一个信号时会认为它是一个周期性重复信号,对于不周期重复的信号,我们认为它是Tiling的,那我们会发现,正常的图片的左右和上下是会发生剧烈变换的,就会产生一个极高的高频。
图形的频率信息可以理解为图像相邻像素间色彩的变化。
傅里叶变换能够可以让我看到任何图像(任何信号)在各个不同的频率上长什么样,也叫做频谱图。





Convolution 卷积:










Antialiasing 反走样:





一个三角形对它覆盖的每个像素求个平均,怎么才能把一个三角形在某一个像素里面覆盖的区域算出来?
一个近似的方法。
一个像素划分成4*4个点,算出近似覆盖率,
MSAA解决了对信号的模糊这一步操作,采样是隐含在里面的,MSAA 并没有提高分辨率,而是为了得到一个近似合理的覆盖率。
结果还是挺明显的。
没有免费的午餐,增大了计算量,工业界会使用不同排列的采样点,而且每个采样点会得到复用,所以实际上的计算量没有那么高。
抗锯齿有很多种方法,最有代表的就是FXAA(图像后期处理)和 TAA(复用上一帧得到感应到结果)
超分辨率:
从低分辨率到高分辨率
本质上还是 采样不足的问题,用深度学习猜出来。
第七节
上节复习
- Rasterization 光栅化
- 采样
- 反走样
先采样后做模糊为什么是错的?先采样就是在频谱上进行搬移,产生混叠。再模糊等于对画面施加了一个低通滤波。
Visibility/Occlusion 可见和遮蔽
Z-buffering



重要的说明,我们在变换中假设摄影机放在原点朝-Z看,所以Z越小离摄影机越远,越大离摄影机越近。
为了方便ZBuff计算,现在我们重新定义Z越小离摄影机越近,Z越大越远。
越近值越小,越黑,越大值越大,越白。
假如有一个像素,我先画了地板,后面又被物体覆盖了,那么在这个像素上,我们查看一下两个东西在这个点的深度,小的覆盖大的。并且更新右边深度图。
每一个像素内记录最浅深度。

下面是伪代码。
1 | for t in [所有三角形列表]:#在所有三角形列表内循环 |


假设不会有两个三角形在同一个像素有完全一致的深度,那么如果先画紫色或者先画红色三角形,结果都是一致的。
对于反走样的MSAA的方法,对于一个像素中间采样多个点,对于Zbuffer也要对每一个采样点求一个值。
注:Zbruffer处理不了透明物体。
Shading 着色
为什么要学着色?
引入明暗和颜色。
这门课里,对不同物体应用不同材质的过程叫做shading。
简单的反射(着色)模型




Diffuse Item 漫反射的表示:





- 点光源距离shading point 为2,我们知道点光源强度I知道r就可以算出来在shading point shang 到达了多少能量。
- 根据Lambert’s, n点乘i可以得到余弦(为了避免得到没有物理意义的负数,光线从下面射到上面,这里用了一个max函数),知道了会接收多少能量。
- shading point为什么会有颜色,是因为它本身吸收了一定能量,然后反射出来是它不吸收的能量,也就是被看到的颜色,不同的点有不同的吸收率也就得到不同的颜色,这里我们定义了一个系数kd(表示diffuse),如果kd是1(灰度)就表示所有能量都被吸收了,没有能量被反射出去,如果是一个3维向量(rgb)就表示了颜色。
- 光线打到shading point 反射光是均匀的半球,所以从哪看都一样,所以跟v完全没关系。

注:这是一个经验模型,并不是完全符合物理的模型。
第八节
上节课复习

Specular Term 高光项的表示:


- ks 表示镜面反射系数。
- (I/r的平方)多少能量到达。
- 这里为啥不考虑多少能量接收,因为blinn phone是一个经验模型
- n和h点乘判断我的视角和高光有多接近,(为了避免得到没有物理意义的负数,光线从下面射到上面,这里用了一个max函数)
- 指数P是因为点乘的容忍度太高,向量偏离很大点乘数还是很高,如图,如果直接拿来用的话高光范围太大到45度还能看到,给定一个指数可以控制曲线也就是控制高光范围比如系数64到15度的时候就没有高光了,blinn pong模型经常用到100-200.
列是镜面反射系数变换,行是系数变换。 ##### Ambient Term 环境光照的表示:

所有的项都加起来

Shading Frequencies 着色频率

把以上三种方法做一个正规的定义
- Flat Shading(把三角形面的面法线求出来,算出来一个shading的结果应用到这个三角形上)
- Gouraud shading(每个顶点(vertexes)的法线求出来,对三个点着色之后再插值到整个面上)
- Phong Shading (根据每一个三角形顶点法线,进行插值,求出每一个像素的法线,然后着色)注意不要和Blinn Phong 反射模型混淆
不同模型不同着色频率的对比图 ##### 不同的法线怎么算 逐顶点的顶点法线怎么算?
结论:任何一个顶点都会和很多个不同的三角形有所关联,那么顶点的法线就是它相邻面的面法线求个平均(加一下三角形面积的权) 逐像素的像素法线怎么算?
结论:知道两个顶点的法线,怎么插值出像素的法线呢?这里要用到重心坐标
Graphics Pipeline
已知:
- 着色模型
- 着色模型怎么用
- 三维模型
- 不同的光照条件
求:
渲染结果。
这种从三维场景到一张图片中间的过程叫做渲染的图形管线(老说法)现在叫做实时渲染管线。
- Application 应用阶段: 输入一堆空间中的点(3D 空间)
- Vertex Processing 阶段: 把顶点变换到屏幕空间
- Triangle Processing 阶段: 在屏幕空间中形成三角形
- Rasterization 阶段: 通过光栅化离散成屏幕上的像素(fragment类比与像素)
- Fragment Processing 阶段: 判断像素或者fragment是否可见
- Framebuffer Operations 阶段: 对fragment进行着色求出所有的像素
- Display 阶段: 得到一张二维的图片
这些操作都是在显卡上进行的。
之前都是在说把三维空间中的三角形投影到屏幕上,这里为啥说把三维空间中的点投影到屏幕上在连成三角形呢?
这个问题是因为我们如何定义空间中的模型?
我们可以定义所有三角形的顶点,然后定义哪三个顶点会形成三角形。
这样点投影完之后形成三角形,就跟直接三角形投影就没有区别了。
例子:
- MVP变换,对每一个顶点
- 对应的三个顶点连接成三角形
- 光栅化
- 判断像素或者fragment是否可见
- 这里根据 着色频率不同会产生差异
- 如果着色频率是Gouraud shading(每个顶点进行着色),着色会发生在vertex processing顶点阶段,Fragment Processing 阶段插值
- 如果着色频率是Phong shading(每个像素进行着色), 就是在Fragment processing 像素或者Fragment阶段
- 纹理映射稍后说
shader概念完善

对于一个像素或者顶点shader,它需要完成这个像素/顶点最后的颜色是什么,要写清楚怎么样算,然后把它输出出去,这就是一个着色的过程。
1 | uniform sampler2D myTexture; //uniform指的是 全局变量 纹理 |

纹理映射






下一个问题是,我如果知道三角形三个顶点对应的纹理坐标,那我如何知道三角形内部任意一个点对应的纹理坐标?
这里还是使用插值算法。
我如果已经知道三角形三个顶点有各自不同的属性,那么我如何把这个属性在三角形内部做一个平滑的过度,给我任何一个三角形内部的点,我都可以知道对应的属性插值之后的值。
这里用到了重心算法,下节讲。
纹理和着色的关系:
纹理是定义着色的时候需要的各个不同点的属性。也就是不希望着色的时候每个点都以相同的方式来着色,然后我就用纹理方式改变一些逐点的属性。
着色和材质有什么区别:
不同的材质就是不同的着色方法。
第九节
前情提要

学习了着色可以应用到不同的地方,应用在面上就是flat shading、应用在顶点上就是Gouraud shading、应用在像素上就是phong shading。这中间都涉及到大量的插值,如果是Gouraud shading,我们可以算出三角形三个顶点的着色结果,并且要在三角形内做插值,如果是phong shading,我们需要三角形三个顶点的法线方向然后对三角形内部的顶点做插值算出来任意像素的法线方向,然后shading。
学习了在硬件中是如何实现的,整个一套渲染管线,最重要的顶点的处理和 像素的处理。这两个都是可以编程的,如果是对顶点进行编程就是vertex shader,如果是对像素就是 fragment shader 或者 pixel shader。
学习了简单的纹理映射的知识,把三维空间中实际是二维的物理表面贴上一张图,提到了物体表面上各个不同的位置如何去映射到不同的二维空间(实际上是提前得到的)。
插值之前要明确的
为了学会如何在三角形内部进行不同属性的插值,我们需要学习重心坐标。
在学习之前这些问题需要搞明白:
- 我们为什么要在三角形内部进行插值?
我们为了完成着色,很多内容是在三角形顶点上进行定义和计算的,在三角形内部任何一个点也想获得一个值,如果每个点的值都知道也就是得到从一个点过度到另外一个点的平滑的过度。 - 我们插值什么内容?
定义在顶点上有各种各样不同的属性,比如纹理(之前定义了三个顶点分别映射到纹理上面哪三个坐标,那三角形内部映射到哪里呢?)颜色(三个顶点分别是红绿蓝,那么在三角形内部是怎么样的过度?),法线(三个顶点的法线插值出三角形内部像素的法线)
基本上来说可以对三角形顶点上任意的值进行插值。 - 怎么做插值?
用重心坐标做插值。Barycentric Coordinate 重心坐标
重心坐标:
- 定义在一个三角形上的,换了三角形重心坐标就变了
- 在三角形ABC所形成的平面内,任何一个点(x,y)都可以表示成三个顶点abc的线性组合(x,y) = αA+βB+γC (ABC指的都是坐标),线性组合的系数要满足a+b+r =1
- 给我任意三个点ABC,不管ABC是在什么坐标系里面表示三角形,只要有一个点在ABC所在的平面上,我都可以通过α+β+γ=1这三个数做一个线性的平均,然后得到这个任意点在重心坐标下的表示。
- 实际上由于α+β+γ=1这个关系,其实知道两个数就知道了第三个数。
- 如果这个点在三角形内,这三个系数不为负,如果有某一个以上的数为负但同时满足α+β+γ=1,就证明这个点在三角形所在平面但是不在三角形内。


为什么α+β+γ=1呢?
这个是重心坐标规定的事情,如果加起来不等于就证明这个这个点不在三角形所在的平面内,这个相对复杂,不多解释,拿来用即可。
结论: α+β+γ=1限制了你所要的点在三角形所在的平面内。
从重心坐标的定义我们已经知道了ABC三个点的坐标是什么。
那如果要求任意一个点的重心坐标是什么呢?这里重心坐标给出了另外一个定义:
任意点的重心坐标其实是可以通过面积比求出来。
面积比指的是 对面的三角形面积比整个三角形面积。
例如,我要求α的值,就是求点A对面的AA三角形面积比上三角形总面积的值。
从这个定义我们就知道了如果已知三角形三个点,那么可以非常轻松的得到三角形的重心坐标(就是把三角形均匀分成三个等面积的三角形的点)。
对于任意的一个点我们怎么计算呢?
- 利用面积算
- 简化版本
使用重心坐标

注意:经过投影,三角形三个点坐标发生变换,重心坐标会改变。
这个事情告诉我们要插值三维空间中的属性,就要用三维空间内的坐标,不能在投影之后的三角形坐标上做.
深度的问题:光栅化之后三角形已经投影到屏幕上了,会覆盖很多像素,像素都有中心,像素这些中心的这些点,我可以知道它投影了的三角形的哪里,在投影了的三角形做深度的插值是不对的。
应该找到像素中心点对应的三角形在三维空间的位置,在三维空间中做插值以三维空间的坐标做深度插值,然后再换回来。
怎么样把投影到屏幕上的在投影回去,应用逆变换即可。
Texture Magnification 如果纹理很小怎么办









为什么会出现这个问题?
首先近处的一个像素覆盖的纹理范围是相对较小,如上图左边方块,在远处,一个像素覆盖了很大的纹理。 这个情况告诉我们,屏幕上的像素覆盖纹理的大小是各不相同的。 那么对于一个像素,如果它覆盖的纹理范围很小,那通过像素中心的纹理坐标查询一下它纹理空间的纹理对应值,没有问题,但是如果一个像素覆盖了很大的纹理范围,那我通过这个纹理的中心(蓝点)拿到对应的纹理坐标一个纹理像素的值,就会产生错误,因为一个纹理像素代表不了一个范围。
如何解决?
MSAA或者超采样,都差不多一个概念,
一个像素用更多的样本去采样纹理空间,如果每个像素用512个采样点,把这点对应的纹理空间值平均一下。就会得到下面的结果。


- Mipmap
Mipmap 允许快速的、大约的、正方形的范围查询。又叫图像金字塔。
什么叫Mipmap:从一张图生成一系列图。这里为了显示,所以拉大了图片.
可以产生log2(图片像素)个mipmap,生成的mipmap占用了1/3原始图片的大小。
怎么知道要查询的区域有多大?
近似的办法:任何一个像素都可以映射到纹理,如图:红蓝点分布对应
在Pixel 上距离为1的像素,对应到Texel上的距离是可以求出来的。如图:
du和dv是纹理空间的两个像素采样点的距离,dx和dy表示pixel空间距离。
max(横向两个像素对应纹理实际空间长度,竖向两个像素对应纹理实际空间长度)。
然后近似营造一个正方形空间。
当我们可以把一个像素覆盖的区域,近似成正方形算出来之后,就是如何根据预计算好的Mip查询这个边长是L的正方形平均值是多少。
举个例子,如果这个L是4,那么我们就知道它肯定在第二层mipmap的时候变成一个像素。
也就是说根据这个区域,可以求出它在第几层会变成一个像素的大小,然后直接查询那个mipmap的像素的值就好了。
总结,通过计算一个pixel映射的texel近似范围,可以求出这个范围在第几层mip map是一个像素,然后拿到这个像素的值。拿空间换时间的技术。
近大:使用底层mipmap纹理,远小:使用高层纹理mipmap对应。
变换不连续,只算了整数层的mipmap效果。
三线性插值:为了避免出现这样的结果,可以利用插值求出非整数层。
比如1.8层,我们可以先找第一层做对应范围双线性插值,再找第二层做对应范围双线性插值,然后把这个结果当做层与层直接在做一次线性插值。这样我们利用三线性插值可以在任意的浮点数位置求出对应的平滑的像素数值,而且在层与层也可以求出平滑的像素数值。
MipMap能完全解决问题吗?
如图,mipmap跟超采样对比。
mipmap远处会出现过度的模糊(overblur)。主要因为采样的毕竟是近似的正方形。
各向异性过滤比mimmap效果要好。
什么叫各向异性:在不同的方向上它的表现各不相同,考虑各个方向,之前认为在水平和竖直上完全相同(各向同性)。
为什么会这样呢?
屏幕上的像素映射到纹理上,很有可能会出现超级狭长的斜着的范围,把它近似的算成一个框,那就会求很大的一个范围的平均值,必然就overblur,
各向异性允许我们在一个矩形范围内快速查询,自然得到的结果就会好很多,但注意还是近似。各向异性开销是MipMap的三倍。
各向异性在游戏引擎中一般还有选项X,2X就是横向竖向压缩两倍,以此类推。
存储量随着X的上升会逐渐达到3倍。也就是跟计算力没有关系,主要跟显存的大小有关系,如果显存足够,可以把X开到最高。
EWA过滤器,利用圆形多次求取值。
Applications of texture 纹理的其他应用
以后再说。