Games101图形学笔记——着色
Shading
- Z-buffering(深度缓冲) Shading(着色)
- 画家算法
- Z-Buffer
- Shading(着色)
- Blinn-Phong Reflectance Model(布林·冯反射模型)
- 漫反射
- 能量守恒
- 着色高光
- Blinn-Phong Reflection Model
- ShadingFrequencies 着色频率
- FlatShading(面着色)
- GouraudShading(顶点着色)
- PhongShading(像素着色)
- 如何求逐顶点的法线
- 如何求逐像素的法线
- 图形管线/实时渲染管线
- Shader
- Shading -- 纹理处理
- 重心坐标
- 应用纹理
- 纹理放大
- 双线性插值
- 纹理缩小
- Mipmap
- 各向异性过滤
- EnvironmentMap -- 环境光贴图
- BumpMapping -- 法线贴图
- 位移贴图
Z-buffering(深度缓冲) Shading(着色)
画家算法
先画最远的物体,逐渐画近的物体,让近的物体覆盖远的物体
看起来是没问题的
但不是总生效的
这张图三个三角形互相遮挡,没办法定义深度关系,就不能采用画家算法
Z-Buffer
既然没办法判断三角形整体的深度,那么就判断每个像素的深度
像素内记录像素深度最浅的几何
对于深度来说,越小越近,越大越远
在渲染时不光要存渲染的图,也要存一张深度的图
算法如何进行?
对单个像素来说,逐步记录深度
如先画地板,先记录地板深度
物品来了后比对物品的深度和记录的深度
发现物品深度小于记录的地板深度,说明物品要遮挡住地板
下图一目了然
暂时假设不存在深度相同的像素
在浮点数的表示中,两个浮点数完全相同的概率很小
(实际上会有相同深度的,但本课中暂不考虑)
(透明物体Z-Buffer也处理不了,暂不考虑)
Shading(着色)
物体产生的颜色和光照、材质有关
Blinn-Phong Reflectance Model(布林·冯反射模型)
局部着色
对着色的描述是一个点
v、l、n都是单位向量
shininess表面有多亮(对比石膏和陶瓷)
不考虑阴影
漫反射
同样的光,以不同角度照上去,明暗不一样
- 物体表面法向量n,和光源方向l,的夹角θ,决定了明暗强度
可以把光当成能量,吸收的越多越亮
能量守恒
光的能量都集中在一个球壳上,一开始球壳的表面积很小,考虑到能量守恒的话,那么单位面积上光的能量就很多,光越向外扩散,单位面积的能量就越小
2. 通过球面公式可以计算出,距离光源为r的球壳上,单位面积上能量为I/r²
根据2.就知道有多少光从光源传播到shadingPoint处
再根据1.就知道有多少光被shadingPoint吸收
这样就能知道diffuse的公式
-
I/r² 表示有多少光到达了ShadingPoint(因为光会随着传播距离而衰减)
-
Kd表示了该点颜色的反射率
如果Kd=0,那么该点完全没有反射光出去,该点吸收了所有光,那么该点表现为黑色
如果Kd=1,那么该点反射了所有光,那么该点表现为白色
如果用RGB三个通道表示Kd,那么Kd就是Color
-
Max(0,n·l)表示反射角度,nl都是单位向量,n·l = cosθ,当入射光从表面下面照入,θ>90°,cos<0,这种情况没有意义,因为我们只考虑反射光,不考虑折射等光线,所以需要和0比,取最大值
着色高光
高光的方向 -> 越光滑 越趋于镜面反射的方向
观察的方向越接近镜面反射的方向 越能看到高光
当观察方向接近镜面反射方向的时候 <=> 法线方向n 接近于 半程向量h
知道两个单位向量l和v,求他俩的角平分线向量很简单,将两个向量相加,并求归一化
所以为了知道能否看到高光,布林冯模型只需要知道n和h是否接近
如果用视线方向v和高光方向R来判断能否看到高光,就是冯模型
(点乘接近1即向量接近)(冯模型计算量大)
向量之间的夹角余弦确实可以衡量两向量是否接近,但容忍度太大了
在一次幂的cos曲线下可看到,当夹角很大时,仍然有很大的值,这样生成的高光就会很大
正常情况下,我们认为高光都是很小很亮的
随着指数增加,能看到在大约0~30°之内才可以看到高光,这样就算一个合理的模型
在布林冯模型下,一般来说,指数选在100-200之间,高光角度大约在3~5°之间,算是比较真实的
纵向来看,反射系数Ks越大,高光越亮
横向来看,指数p越大,高光越小
- 环境光照
一个茶杯,在光源并没有直接照射的方向上也有一定的亮度,因为一个光线可以弹射很多次,从四面八方打到任何一个点,这些光照就算是环境光照
由于环境光照非常复杂,这里我们假设一个点受到的环境光照永远都是相同的,强度称为Ⅰa
任何一个点都有自己的颜色,Ka相当于环境光的系数
可以近似的得到一个环境光La = KaⅠa
Blinn-Phong Reflection Model
环境光(无论方向)(常数颜色) + 漫反射(无论观测方向)(光照/法线) + 高光
= 布林冯反射模型
ShadingFrequencies 着色频率
三个球具有完全相同的空间信息,着色频率不同后表现不一样
FlatShading(面着色)
GouraudShading(顶点着色)
PhongShading(像素着色)
三种着色频率产生的效果也取决于模型本身
每一行的模型本身顶点数是一样的,越往下顶点数越多
当几何足够复杂时用FlatShading得到的效果也很好
反过来说,当几何的面数大于像素数量时FlatShading的性能也不会好于PhongShading
如何求逐顶点的法线
将和顶点相邻的面的法向量做加权平均
如何求逐像素的法线
已知顶点法线,如何求中间某一点的法线---->插值、重心坐标
图形管线/实时渲染管线
-
顶点处理
将三维空间的点投影在平面上 -
三角形处理
将这些点连接形成三角形 -
光栅化
将三角形离散成为屏幕上的Fragment(未经处理的像素) -
着色
给像素上色 -
后处理
深度缓冲-处理遮挡关系,MSAA等抗锯齿
(以上操作都在硬件中处理好了,也就是gpu工作流程)
-
MVP变换 — 顶点处理
MVP变换本质上就是将不同的顶点进行变换
-
对像素采样 — 光栅化
-
判定fragment是否可见 — Fragment处理
Z-Buffer 深度测试
-
Shading — 顶点 或 像素处理
如果用的是GouraudShading,那么进行的就是顶点处理
如果用的是PhongShading,那么进行的就是像素处理
Shader
现代GPU中,这套渲染管线某些部分是可编程的,可以由开发者去定义顶点/像素如何着色
也就是用代码控制如何着色
这部分代码就叫Shader
Shader指定的是每一个像素/顶点如何着色,所以不能也不用去指定某一个像素如何着色
如果写的是顶点操作,这个shader就叫做VertexShader(顶点着色器)
如果写的是像素操作,这个shader就叫做FragmentShader(片段/片元着色器)/PixelShader(像素着色器)
TextureMapping — 纹理映射
我们希望得到一个三角形,三角形里面映射了一张图片,怎么得到?
Lecture 08 Shading 2 (Shading, Pipeline and Texture Mapping) P8 - 54:46
以球来说,我们发现不同位置有不同颜色,球整体其实公用同一个着色模型,唯一区别就是漫反射系数Kd不同
(Kd忘了的话看Lecture7末尾)
我们希望有一种方法,可以定义一个物体上任意一点的基本属性
3D物体的表面其实都是2D的,比如地球仪,将地球仪上的图撕下来,可以平铺成一张2D的图
物体的表面,通过这种方式可以和一张图有一一对应的关系,这张图就叫纹理
将这张图平铺/裁剪/拉伸到任何物体表面,就叫纹理映射
空间上模型的三角形怎么对应到纹理上的三角形?
由美术同学提供
纹理上的坐标系通常以UV来表示
通常约定U和V的范围[0,1]
当纹理不断重复贴到模型上,可以得到不错的效果,虽然看纹理效果可以看到两张纹理之间有很明显的变化,但是在场景中很自然的无缝衔接
说明这个纹理本身设计的好,这种纹理叫TilableTextures
Shading – 纹理处理
重心坐标
已知三角形顶点的属性,如何在三角形内部进行任何属性的插值?
- 在三角形ABC所在的平面中任意一点(x,y),都可以用三角形三个顶点的线性组合来表示
- ABC顶点前面的系数α + β + γ = 1,(α , β , γ)就是用来描述此三角形的重心坐标
- 如果点在三角形内α β γ都必须 ≥ 0
(α + β + γ = 1,是为了限制要求的点在平面内)
根据上述定理可得
A点的重心坐标就是(1,0,0)
设三角形内一点,点P,连接PA,PB,PC,会形成三个小三角形Aa,Ab,Ac,P的重心坐标就是小三角形面积占大三角形面积的比
α = Aa/(Aa + Ab + Ac)
所以可以求一个特殊的点,三角形重心,三角形重心将三角形划分为三个等面积的小三角形。
所以三角形重心的重心坐标为
对于三维空间中的点,不能保证其被投影后的重心坐标不变
如果想插值三维空间中的属性,就应该插值三维空间中的坐标。
因为在做光栅化时,需要知道像素中心在三角形的什么位置,此时不能直接求重心坐标进行插值
需要将该点重新投影回三维空间中,在三维空间中计算重心坐标插值
应用纹理
屏幕上的采样点(x,y)可以用重心坐标算出在纹理中采样的uv,得到对应纹理
纹理放大
当低分辨率纹理应用到高分辨率的屏幕上,纹理就会被拉大。
对于任意一点,可以找到对应纹理上的位置,位置可能不是整数,将位置坐标四舍五入,然后取纹理上的值
这样的话,一个texel就可能会被映射到多个pixel上,也就说可能在3*3的像素内用了同一个纹理的元素(texel)
这样就会产生马赛克效果如图Nearest
双线性插值
先用U01 U11插值出来U1
再用U00 U10插值出来U0
再把U0和U1进行插值,得到红点的最终值,即为双线性插值
得到图Bilinear
图Bicubic是将周围16个进行立方插值(双三次插值)
纹理缩小
如果直接简单的使用线性插值进行采样会得到右图,远处有摩尔纹近处有锯齿
远处的一个像素就会覆盖很大一片的纹理区域,单纯以像素的中心是标准取纹理的值是不对的
这其实就是转变为了采样率不足的问题,之前解决采样率不足的问题我们可以使用SSAA,每个像素内分为若干小像素进行采样。
以512个小像素为例,得到的结果如下图
正确,但花费了512倍的性能
既然采样有问题,那我们如果可以直接查询而不采样呢?
Mipmap
纹理缩小时,一个屏幕上的像素对应了纹理上的多个纹素,使图像看起来就变得模糊。
所以引出了**Mipmap(多级渐远纹理)**技术
将原纹理提前用滤波处理得到很多更小的图像,当物体远离相机时,直接查询较小的纹理,得到正确的结果像素
Mipmap就可以实现我们需要的查询,但仅仅是近似的、正方形的查询
因为生成了多个较小的图像,需要额外储存生成的小图像
- 所以Mipmap占用的额外空间是原来的1/3
这是一种典型的空间换时间的思想
要查询在屏幕空间内的某像素,映射在纹理空间内占多大区域
可以将自己中心和邻居的中心分别投影到纹理空间内,这样就能知道在纹理空间中,该点和邻居点之间的距离L,要求的区域可以近似为以L为边长的正方形区域
但是会出现不连续的纹理映射,因为查的纹理都是整数层,比如我们无法直接查询1.5层的Mipmap
所以需要在两层之间进行插值,称为三线性插值
各向异性过滤
运用上述Mipmap后,在远处产生的图像很模糊,因为Mipmap是近似的、正方形的查询
只能查询正方形区域,而且三线性插值也是近似
屏幕上的像素映射到纹理上不一定是正方形,对于不是正方形的Mipmap就无法处理。
如下图,对于右边不是正方形但是是比较规则的矩形,我们可以用其他方法提供查询
将原图宽度不变长度压缩,长度不变宽度压缩就可以提供矩形的查找,即为各向异性过滤
经常看到的各向异性过滤x2x4等,指的是要生成多少层的压缩图,x2就是一层,x4就是两层。占用的空间逐渐向三倍靠拢
但各向异性过滤也只是能解决映射在纹理空间是比较规则的矩形的情况,当出现不规则矩形的时候也无法处理
所以又引出了EWA过滤,EWA过滤可以将任意形状拆分为很多大小不同的椭圆,经过多次查询,就能查询出最终的结果。
但是代价也是需要多次查询的时间
EnvironmentMap – 环境光贴图
纹理本质上就是提供了一个快捷的查询,不只局限于图像,光照也能同理进行查询
光照贴图认为光照是无限远的,忽略了光的位置信息
怎样描述环境光?
如果在房间中有一光滑的金属球,我们观察他就会发现它反射出来的就是环境光。
那我们就可以把环境光储存在球上面,并且也能把它展开成平面
但展开后发现球形图的上下会扭曲。
虽然我们能描述球上不同的位置,但无法均匀的描述
所以我们可以将信息记录在这个球的外接立方体,这样信息就变得均匀了–CubeMap
BumpMapping – 法线贴图
法线贴图是为了在不增加三角形面数的情况下,在着色时显示更多细节
对某一点进行着色时,需要判断该点的法线方向,从而计算光照和颜色
需要从原本的模型表面映射到法线贴图中,查询新的法线位置
法线贴图如何知道法线的方向呢?
先看一维中的简单示例
- 先算该点切线
切线可以用该点与下一个点的位置差计算出来 - 将切线逆时针旋转90°,求归一化,得到法线
二维的贴图(3D空间)中如何求法线?
对u、v坐标分别求导,算出切线,旋转得到法线
位移贴图
环境光遮蔽也能预先计算好,存储到纹理中