图形学笔记(七)着色 —— Blinn-Phone 反射模型、着色频率、渲染管线、GPU
图形学笔记(九)几何 ——几何表示方法(CSG、距离函数、水平集 、点云、网格(obj格式))、贝塞尔曲线(面)
文章目录
1 纹理映射定义
纹理映射(Texture Mapping),又称纹理贴图,是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程。简单来说,就是把一幅图像贴到三维物体的表面上来增强真实感。
任意三角形的顶点都能找到顶点在纹理上哪个位置。
2 纹理的坐标系 —— UV坐标
U和V的范围都在0到1内。三角形的三个顶点,每个顶点都对应一个UV。
纹理能被多次使用。如果纹理重复上下左右可以无缝衔接,则称这个纹理是tiled textures。(有一种算法叫Wang tiling)
3 三角形的插值:重心坐标(Barycentric Coordinates)
3.1 插值的用处
如果对三角形的三个顶点都有特定的值,那么通过重心坐标的方法可以得到三角形内任何一点的这个值,并且这些值从一个顶点到另一个顶点是平滑过渡的。
对什么进行插值?
纹理坐标,颜色,法线向量…
3.2 重心坐标系
-
重心坐标是定义在三角形上的。
-
在重心坐标系中,三角形平面的任何一个点(x,y)都可以表示成三角形三个顶点的线性组合(系数分别是 α β γ \alpha \beta \gamma αβγ,且满足这三个系数相加和为1)。
-
如果这个点在三角形内,则需要 α β γ \alpha \beta \gamma αβγ都是非负的。
eg:
A点的重心坐标 ( α , β , γ ) = ( 1 , 0 , 0 ) (\alpha,\beta,\gamma)=(1,0,0) (α,β,γ)=(1,0,0)且 ( x , y ) = α A + β B + γ C = A (x,y)=\alpha A+\beta B+\gamma C=A (x,y)=αA+βB+γC=A
3.3 获得三角形任意一点的重心坐标
可以通过面积比求重心坐标。 α , β , γ \alpha,\beta,\gamma α,β,γ的计算如下所示,其中A点对应 A A A_A AA(表示A点的面积),B点对应 B B B_B BB,C点对应 C C C_C CC。
然后通过叉积求面积,得到重心坐标的一般的表达式。
3.4 获得三角形的重心
重心的性质:将重心与三个顶点相连,可以把三角形分割成等面积的三个三角形。下面是重心坐标系和普通坐标系下的三角形重心坐标。
3.5 使用重心坐标
使用重心坐标系通常有如下步骤:
- 先算出重心坐标的位置
- 再利用重心坐标做插值
如下图所示,V可以是任何属性,只要根据位置把 α β γ \alpha \beta \gamma αβγ确定下来,就可以确定该位置对应某个属性的插值。
Tips:投影变换下,重心坐标是不能保持不变的。所以想要插值三维空间的属性,就要找三维的坐标,不能在投影后的三角形做(比如深度)。
4 将重心坐标的插值应用到纹理上
对于每一个光栅化的样本(x,y)的应用流程如下:
- 根据三角形顶点的纹理坐标使用重心坐标进行插值,获得该位置的(u,v)。
- 在纹理上查询这个(u,v)对应的颜色值
- 在该样本设置这个颜色
5 纹理的放大 Texture Magnification 相关问题
5.1 纹理太小了
5.1.1 问题引出
如果有4k分辨率,纹理只有515X512怎么办?
5.1.2 纹理太小的问题
对于任何一个点找到纹理上的位置,如果纹理上的这个位置不是整数,采用四舍五入的办法变成整数。这样的话在一定范围内查找的是相同的纹理上的像素(texel 称为纹理元素、纹素 )。
很多pixel由于纹理太小了可能会映射到同一个texel上去(就像最左边的图,不是很连续,有马赛克格子)。
5.1.3 解决纹理太小问题的目标
通过改进,使得图片看上去像下面右边的两张图一样,虽然有点糊,但是没那么有割裂感。
5.1.4 方法一 —— 双线性插值 Bilinear interpolation
-
找到映射的中心的临近的四个点
-
找到中心点距离四个点距离左下的那个点的宽和高(s和t)。
-
定义一个线性插值的操作。
-
先获得两个辅助点的插值。
-
最后获得竖直的插值
这样的话,这个点就可以获得四个点的颜色信息,实现了平滑过渡。
因为做了两次插值,所以称为双线性插值。
5.1.5 方法二 —— Bicubic 双向三次插值
效果好于Bilinear,取的不是四个点,而是十六个点。做的是三次的插值,而不是线性的插值。
5.2 纹理太大了
5.2.1 纹理太大出现的问题 —— 摩尔纹和锯齿
可以发现如果纹理过大会出现如右图的情况,远处摩尔纹,近处锯齿。
5.2.2 原因分析
1> 从pixel与texel上的对应来看
屏幕上的像素从近到远,一个像素覆盖纹理上的区域是由少到多的。
但是对于很远的点,一个像素的颜色值以一个中心点来采样是相当不准确的。
解决办法:超采样(消耗太大,需改进)
2> 从采样来看纹理过大的影响
纹理太大时,一个像素包含很大一块纹理,采样的速度跟不上信号变化的速度。(所以可以通过增加采样频率来解决)
解决方法:避免采样,不采样就不会消耗那么大了,所以有办法能快速获得一片区域的平均值吗?
5.2.3 方法一 —— Mipmap
1 引出
- 超采样可以解决纹理过大的问题,但是它消耗过大,如果有一种算法可以立刻获得平均而不用进行太多计算就好了。
- 从近到远一个像素对应的采样区域大小是不同的,所以这个算法的范围查询应该可以查出任意不同范围的大小。
2 作用
允许进行不同大小的范围查询。(快速的正方形的近似范围查询fast、approx.、square)
3 定义
Mip意思是很多不同的小东西。
对于一张贴图,可以不断将分辨率缩小一半。
然后只需要找出每个像素在对应层对应位置的映射就可以了。
Tips:经过Mipmap,只多了三分之一的存储量。
4 Mipmap的流程
-
获得自己和邻居的中心位置
-
将这四个位置在纹理空间上进行映射
-
根据以下公示进行计算
其中,D是层数,只需要知道L就可以计算,代表这个像素应该在第D( l o g 2 L log_2L log2L)层Mipmap去寻找颜色。
- 根据D和映射后的UV坐标得到结果
5 问题
如下图是Mipmap可视化,可以发现有渐变,但是渐变不连续。
6 深度不连续的解决方法 —— 插值
进行插值(比如在1.8层,则可以对第1层和第二层进行插值)
6 几种插值方法
6.1 三线性插值 Trilinear Interpolation
6.1.1 介绍
三线性插值即两次查询,一次插值,找到D和D+1层的结果然后进行插值。
经过三线性插值后的Mipmap可视化
6.1.2 三线性插值的的局限性
出现了Overblur(远处模糊的过分了)
原因:只能对正方形进行近似,插值不是精确而是近似值。
6.2 (部分)解决三线性插值问题:各向异性过滤(Anisotropic Filtering)
6.2.1 介绍
Anisotropic Filtering相比于Mipmap多了不均匀的水平和竖直的压缩,如下图所示(下图又称Mipmap)
Tips:
- 各向异性的意思是不同方向上的表现各不相同
- 倍率不管开多大,存储量最多是3倍
使用各项异性,就可以查询到一个矩形的区域,而不用限制在一个正方形上。
6.2.2 局限性
虽然Anisotropic Filtering解决了矩形查询的问题,但是对于斜着的效果不好,仍然具有局限性。
6.3 覆盖不规则的形状 —— EWA filtering
对于不规则的形状可以拆成几个圆形去覆盖这个形状。每次查询一个圆形,然后多次查询来覆盖不规则的形状。
7 纹理的应用
7.1 纹理的理解
在现代的GPU中, texture = memory + range query
7.2 环境光照(贴图) Environment Map
如下茶壶会被环境光照亮,即它可以反射出任何方向来的光。如下图就反射出了窗户和门。
假设,环境光只记录方向,深度没有实际意义。
可以把环境光记录到球上,并且展开。
展开后出现了扭曲问题(向中间扭曲)。
解决办法 —— Cube Map
即不把光照信息存在球的表面上,而是立方体的表面。
因为立方体各个面是均匀的,所以不会出现扭曲。
存在的问题:相对于球来说,需要多记录一个方向。
本质:记录不同方向的光照信息。
7.3 纹理可以影响着色
纹理可以定义任何不同位置任意不同的属性,比如定义任何一个点的相对高度。
如下图就是一种凹凸贴图(定义法线的不同),这样就可以不用定义复杂的几何形体。
7.3.1 Bump Mapping 凹凸贴图
Normal Mapping法线贴图不等于Bump Mapping凹凸贴图,法线贴图存储的是法线,凹凸贴图存储的是高度差。
1> 凹凸贴图的作用
凹凸贴图可以定义复杂的纹理,但是不改变任何几何信息(三角形数量不变)。
- 把任何一个像素的法线做扰动
- 纹理定义任何一个点相对高度的移动
- 然后可以通过高度的变化改变法线
2> 如何扰动法线(in flatland)
- 假设原本的表面是平面(原本 n ( p ) = ( 0 , 1 ) n(p)=(0,1) n(p)=(0,1))
- p点处的导数 d p = c ∗ [ h ( p + 1 ) − h ( p ) ] dp = c * [h(p+1) - h(p)] dp=c∗[h(p+1)−h(p)] (c是一个常数,用来定义凹凸贴图的影响程度)
- 扰动后的法线是 n ( p ) = ( − d p , 1 ) . n o r m a l i z e d ( ) n(p) = (-dp, 1).normalized() n(p)=(−dp,1).normalized()(就是把切线旋转90°)
3> 如何扰动法线(in 3D)
- 假设原本的表面是平面(原本n(p)=(0,0,1))
- p点处的导数
dp/du = c1 * [h(u+1) - h(u)]
dp/dv = c2 * [h(v+1) - h(v)] - 扰动后的法线是n(p) = (-dp/du,-dp/dv, 1).normalized() (就是把切线旋转90°)
- 注意以上都是处于局部坐标系内
4> TBN矩阵(参考)
TBN矩阵用于将存储在纹理空间的法向量转换到模型空间中。(但是实际使用时,由于光线条数远远小于法线数目,为了减少计算量,要将光线从模型空间转换到纹理空间,然后计算反射光线。)
法线纹理的映射方式和漫反射纹理相似。麻烦之处在于如何将法线从各三角形局部空间(切线空间tangent space,亦称图像空间image space)变换到模型空间(着色计算所采用的空间)。
如何定义切线空间?
众所周知,定义一个空间需要三个向量。现在向上的向量已经有了(就是法线)。
然后需要切线 T ⃗ \vec T T,这个 T ⃗ \vec T T需要和曲面平行,理论上满足这个条件的切线很多。
但是为了方便起见,也为了保持连续性,避免衔接出现瑕疵,标准的做法是将切线方向和纹理空间对齐:
那么这组基还差一个向量(副切线 B ⃗ \vec B B),为了好算,选一条垂直于刚才算出的两个轴的切线。
最后来计算TBN矩阵,记三角形的两条边为deltaPos1和deltaPos2,deltaUV1和deltaUV2是对应的UV坐标下的差值;则问题可用如下方程表示:
deltaPos1 = deltaUV1.x * T + deltaUV1.y * B
deltaPos2 = deltaUV2.x * T + deltaUV2.y * B
求解T和B就得到了切线和副切线!
已知T、B、N向量之后,即可得TBN矩阵,完成从切线空间到模型空间的变换:
有了TBN矩阵,我们就能把(从法线纹理中获取的)法线变换到模型空间。
可我们需要的却是从切线空间到模型空间的变换,法线则保持不变。所有计算均在切线空间中进行,不会对其他计算产生影响。
只需对上述矩阵求逆即可得逆变换。这个矩阵(正交阵,即各向量相互正交的矩阵,参见下文”延伸阅读”小节)的逆矩阵恰好也就是其转置矩阵,计算十分简单:
7.3.2 位移贴图
得到结果要比凹凸贴图结果更好一些,但是要求三角形比较细致。(要求三角形顶点间的间隔高于纹理定义的频率)
- 与凹凸贴图使用相同的纹理
- 实际上移动了顶点的位置
7.4 三维纹理
没有真实生成纹理的图,而是定义了三维空间中的噪声函数,通过三维噪声函数来计算空间中每一点噪声的值,来动态得到纹理。
3D Procedural Noise + Solid Modeling
7.5 存储计算好的着色信息
计算好了之后得到一张纹理的图,后面再把着色的结果乘以环境光遮蔽得到最终结果。(把计算拿到之前去做,也说明了纹理不止可以存储颜色)