Lecture 09 Shading 3 (Texture Mapping cont.)
Shading 3
Barycentric coordinates 重心坐标
为了在三角形内部任何一点内插值,我们引入重心坐标
- 为什么需要插值?
- 指定顶点属性
- 在三角形内部保持平滑变化
- 插值什么内容?
- 纹理坐标、颜色、法向量,...
- 怎么做插值?
- 重心坐标
重心坐标
重心坐标是定义在三角形内的
在三角形\(ABC\)内任何一点都可以表示为三个顶点的线性组合
\((x,y)=\alpha A+\beta B+\gamma C\\\)
\(\alpha+\beta+\gamma=1\\\)
为了描述一个点的位置,再也不需要知道直角坐标系之类的坐标系,只要有一个点在三角形内部的平面内,都可以得到这个点的位置
重心坐标虽然是三个数(\(\alpha、\beta、\gamma\)),但由于三角形所在平面是二维的,只用两个数就能表示,因此只需知道任意两个系数即可求得第三个系数(\(\alpha+\beta+\gamma=1\))
如果该点在三角形内,还需满足一点,\(\alpha、\beta、\gamma\)均非负,否则该点虽然在三角形所在平面内,但在三角形外
\[由定义可得,A点坐标(\alpha,\beta,\gamma)=(1,0,0)\\ (x,y)=\alpha A+\beta B+\gamma C\\ =A\\ 同理可知B、C两点坐标\\ \\ \alpha、\beta、\gamma可通过面积算出\\ \alpha=\frac{A_A}{A_A+A_B+A_C}\\ \beta=\frac{A_B}{A_A+A_B+A_C}\\ \gamma=\frac{A_C}{A_A+A_B+A_C}\\ 由上述定义可得出一个特殊点\\ 三角形的重心 (\alpha,\beta,\gamma)=(\frac{1}{3},\frac{1}{3},\frac{1}{3})\\ (x,y)=\frac{1}{3}A+\frac{1}{3}B+\frac{1}{3}C\\ \\ 要计算任意点的重心坐标\\ \alpha=\frac{-(x-x_B)(y_C-y_B)+(y-y_B)(x_C-x_B)}{-(x_A-x_B)(y_C-y_B)+(y_A-y_B)(x_C-x_B)}\\ \beta=\frac{-(x-x_C)(y_A-y_C)+(y-y_C)(x_A-x_C)}{-(x_B-x_C)(y_A-y_C)+(y_B-y_C)(x_A-x_C)}\\ \gamma=1-\alpha-\beta \]应用重心坐标
\(V=\alpha V_A+\beta V_B+\gamma V_C\)
要用插值,要插值的属性用重心坐标线性组合起来,得到任意一个点的属性,
比如位置、纹理坐标\(uv\)、颜色、法线、深度、材质属性...
但是重心坐标经过投影会改变
因此若我们想插值一个三维空间的属性,则应该在三维空间做插值,而不是在二维空间,比如要对三角形深度进行插值,应先找到像素对应三维空间的点,在三维空间进行插值(要从二维空间坐标找到三维空间坐标,进行逆变换即可)
Texture queries
屏幕上任何一个采样点(无论是像素还是如MSAA的采样点),通过插值可以算出其在纹理上的\(uv\)坐标,然后在纹理上查询这个坐标
Texture Magnification 纹理放大
Nearest
当我们要渲染的图形比纹理分辨率大时:
我们插值出的\(uv\)坐标可能不是整数,若将其四舍五入成整数,则在一定范围内我们要查找的是同一个纹理上的像素(texel,纹理元素、纹素)(图Nearest)
Bilinear 双线性插值
当我们的\(uv\)坐标为红点时,会采样红点所在黑点的纹素,就会形成一块一块(图Nearest)
于是我们取相邻四个点,进行线性插值
\[Linear\ interpolation\ (1D)\\ lerp(x,v_0,v_1)=v_0+x(v_1-v_0)\\ \]\[在水平方向上,对上面两个纹素和下面两个纹素分别进行一次线性插值\\ u_0=lerp(s, u_{00},u_{10})\\ u_1=lerp(s,u_{01},u_{11})\\ 在竖直方向上,对刚刚插值得到的两个值进行插值 f(x,y)=lerp(t,u_0,u_1)\\ (先水平再竖直或先竖直再水平不影响最终结果) \]于是,我们经过双线性插值综合考虑了四个纹素,得到了平滑过渡的结果(图Bilinear)
Bicubic (双向三次的插值)
Bicubic取的不是周围临近4个纹素,而是取周围临近16个纹素,这16个纹素进行竖直和水平的插值,每次用4个纹素做一个三次的插值(不是线性)
- 比Bilinear结果更好
- 运算量更大
Texture Magnification (hard caes) 纹理过大
纹理过大反而会引起更严重的问题
如果纹理过大,我们直接采样,会发生走样(图2摩尔纹)
在近处时,一个像素覆盖少量纹素,可以通过线性插值采样,而远处的像素覆盖了很多纹素,我们就不能用其像素中心取采样了
超采样?
效果很好,但是消耗太大了
一个解决方法 Mipmap
(应用广泛,硬件支持)
- 如果我们不采样呢?
- 只需要获取一定范围内的平均值
这其实是一个算法问题(Point Query点查询问题和Range Query范围查询问题)
点查询问题->双线性插值
范围查询问题->Mipmap
这里的范围查询问题是求平均值,叫平均查询,也有些范围查询要查最大值、最小值,与平均查询完全不一样
Mipmap允许快速、近似、正方形的范围查询
Mipmap就是从一张图生成一系列图。假设原始纹理为第0层纹理,我们可以生成更多更高层的纹理,使得每一层都是上一层的一半分辨率,做到最后只剩一个像素(对于一个\(N\times N\)的纹理,一共需要生成\(Log_2N\)层Mipmap,引入了额外\(\frac{1}{3}\)倍原始纹理大小的存储量)
计算Mipmap层级
任何一个像素,都能映射到纹理上的一个区域。对任意像素点,取其临近两个像素,映射到纹理空间,计算该点到两个临近点分别要移动多大距离,取最大值,就能近似得到Mipmap Level。若这个距离是1个纹素,则一定对应level0的纹理,若这个距离是两个纹素,则一定对应level1的纹理,以此类推(均是指在level0的原始纹理上对应的纹素)
三线性插值 如果要在第非整数层mipmap呢?
如果只是简单地取整数层mipmap(四舍五入),在采样两层mipmap连接处,会产生一道缝
我们希望能真的查询比如1.8层,于是先查询第一层(Bilinear result),再查询第二层(Bilinear result),两层结果再做个线性插值(Trilinear Interpolation 三线性插值)
于是在纹理内部,不论是整数坐标还是浮点数坐标都可以双线性插值出一个平滑过渡的值,在层与层之间也可以线性插值出一个平滑过渡的值
并且三线性插值只是多了一次插值,性能开销小
Mipmap的限制
远处会模糊
有一个方法可以部分解决三线性插值的问题
Anisotropic Filtering 各向异性过滤
相比Mipmap,各向异性过滤多了单独水平或单独竖直方向上的压缩(总共的开销为原始纹理大小的三倍),通过这样一个预计算,就可以解决非正方形的区域进行快速的范围查询
各向异性过滤生成的叫Ripmap
多少X各向异性过滤代表计算多少层,但随着X增大,总存储量会逐渐收敛到三倍,所以显存足够,尽量开高就好
在图Texture space中,有一个斜着的矩形,各向异性过滤仍然无法解决
EWA filtering
将任意不规则形状拆成很多不同的圆形,去覆盖这个不规则形状,每次查询一个圆形,多次查询就能覆盖这个不规则的形状,代价是多次查询带来的性能开销
Applications of textures 纹理应用
在现代GPU中,可以将纹理理解成一块内存,并且能对这块内存上的区域进行点查询、范围查询(滤波)
-
Store microgeometry 微几何(小于像素级别)
-
Procedural textures 程序化生成纹理(过程纹理)
-
Solid modeling实体建模
-
Volume rendering 体渲染
-
Environment lighting 环境光
Envirnmental Lighting 环境光
-
纹理可以用来表示环境光(类似Unity烘焙?)
-
认为光照来自于无限远,只记录方向
Spherical Map
将环境光记录在球面上,然后展开球面得到贴图
展开球面会有扭曲问题,因为展开不是均匀的,在极点会出现扭曲现象
Cube Map
将环境光记录在六张贴图上而不是一个球上,可以解决扭曲问题
但是带来的问题是要计算光照在那一个方向的贴图上,不过非常快
Textures can affect shading 影响着色的贴图
Bump/Texture mapping
通过凹凸贴图/法线贴图改变模型表面的高度/法线,从而改变shading结果,而没有引入更多的三角形
Bump Mapping 凹凸贴图
- 定义不同位置的高度和临近位置的高度差,重新计算法线
如何计算法线?
以二维为例
-
原法线向上,为\((0,1)\)
-
站在某一个地方,向右移动一个单位的距离,向上会移动多少距离(梯度)
从而得到导数(近似)\(dp=c*[h(p+1)-h(p)]\),引入一个常数\(c\)来定义凹凸贴图影响大不大
-
法线其实就是垂直于切线的一个方向,切线用一个向量表示,为\((1,dp)\),则法线为切线逆时针旋转\(90°\)(\(xy\)交换,并且把\(y\)加上一个负号),得到\(n(p)=(-dp,1).normalized()\)
在三维情况下(这里假设了\(uvh\)是正交笛卡尔坐标系)
- 原法线\(n(p)=(0,0,1)\)
-
- \(dp/du=c1*[h(u+1)-h(u)]\)
- \(dp/dv=c2*[h(v+1)-h(v)]\)
- \(n=(-dp/du,-dp/dv,1).normalized()\),方向为\(uv\)叉乘方向
这里都假设了原法线向上,为\((0,0,1)\),但是实际情况下原法线有可能有各个不同的方向
所以定义了一个局部坐标系,认为局部法线为\((0,0,1)\),并且有两个垂直分量\(s、t\),\(s、t、n\)形成了这个局部坐标系(切线空间),将算出来的法线方向重新计算回世界坐标中
Displacement mapping 位移贴图
相较于Bump mapping更加现代化的做法
二者起点是一模一样的,都是定义任何一个点的相对高度差别
凹凸贴图没有改变顶点位置
- 在边缘处
- 在自身几何复杂处应产生自投影时,凹凸贴图不会有自投影
而位移贴图会真的移动顶点,解决的凹凸贴图的缺陷,但带来的代价是需要模型三角形要足够细,使得模型三角形变化的频率比位移贴图变化的频率还要高
(贴图修改方便,模型后期修改难度大)
DirectX提供了动态曲面细分(Tessellation),可以不需要一开始有一个足够细致的模型,根据需要来做细分
3D Procedural Noise + Solid Modeling
对于一个2D纹理,如果从中间砍掉一半,内部什么也看不见
上图定义了空间中任意一个点的纹理,而且这种纹理实际上没有真的生成纹理图,而是用了一个噪声函数(如Perlin Noise柏林噪声),在空间中任意一点都有一个解析式,能够算出噪声值是多少,噪声值经过一系列处理,可以做二值化,做某些运算,变成我们需要的样子(如大理石裂缝)
Provide Precomputed Shading 提供预计算着色
如环境光遮蔽纹理
3D Textures and Volume Rendering
在光照模型中我们只考虑一个表面,但在医学中如核磁共振成像、CT成像扫描,返回的信息是一个三维的信息,通过这些信息来渲染出一个结果
Shadow mapping
涉及到全局的光线传输的东西,包括阴影,都是光栅化不太好做的东西
着色是一种局部的现象,只考虑着色点、光源、摄像机,完全不考虑其他物体,甚至是着色物体的其他部分,因此着色解决不了阴影
因此有了Shadow mapping (阴影映射、阴影图),对应的结构叫Shadow map
Shadow Mapping是一种图像空间(屏幕空间)的算法
- 在计算阴影时无需指定场景的几何信息
- 一定要处理走样现象
关键思想:如果有点不在阴影里,说明摄像机和光源都能“看”到这个点
经典的Shadow mapping只能处理点光源和方向光,这种阴影都会有明显的阴影边界,要么在阴影里,要么在阴影外,这种阴影叫做硬阴影
而软阴影是由于光源有大小,阴影边界会被部分光源照到,有过渡(物理上本影、半影、影子之外的过渡),因此只有有大小的光源才会产生软阴影
Pass 1: Render from Light 先从光源渲染
这张图无需做着色,只需记录深度
Pass 2: Render from Eye
将实际相机看到的像素投影回光源看到的深度图(逆变换)记录的像素上,如果该点深度与光源记录的深度图中对应像素深度一致,则说明是同一个点,可以被看到,否则说明这个点是光源看不到的点,说明在阴影中
阴影会有“脏”的现象,这是由于精度问题,两次渲染得到的像素覆盖很多点,这些点的位置可能存在微小差异,并且浮点数和浮点数很难判断相等
所以有一些办法
- 不判断距离相等,只判断是否大于深度图,就认为在阴影里,但仍存在数值精度问题
- 引入Bias(偏差),不仅要大于记录的深度,还要大于深度加上偏差,也仍有问题(peter panning 阴影悬浮)
- Shadow Map本身有分辨率,如果分辨率过小,而渲染场景分辨率过高,则记录的阴影信息实际上是走样的
总结:
Shadow mapping是一个两趟的做法,先从光源看向场景,记录深度,再从摄像机看向场景,测试对应像素是否与光源看向场景中为同一个像素
因为渲染了两趟,所以会引起更大开销,但仍不影响Shadow mapping成为几乎3D游戏和早期电影的主流技术
标签:贴图,cont,插值,纹素,09,线性插值,Mapping,纹理,像素 From: https://www.cnblogs.com/Tellulu/p/18092220