GAMES101(7-9):Shading
GAMES101-Shading
Visibility/occlusion(可见性/遮盖)
z-buffer算法
本质就是对一个物体的三角形面片的每个像素维护深度最浅处。
图示:
复杂度:假设每个三角形覆盖的像素数都是一个常数值,那么n个三角形复杂度就是$O(n)$
shading 基本理论
我们可以认为材质影响着物体的着色
我们看一个图:
我们认为决定材质的三种光为:高光,漫反射,间接光照(环境光),我们分别做出这三种光就可以完成一种材质。
我们可以认为每一个微分出的小表面都是平的:
着色和阴影没关系,着色是局部的。看下图可以发现,球应该会在桌子上有一个阴影,但实际上并没有。
漫反射(朗伯余弦定律lambert’s cosine law)
漫反射理论:lambert's cosine law
我们认为当光线垂直打到物体表面时可以是的物体亮度最大,那么当物体旋转60°时会导致只获得一半的亮度,因此我们可以认为亮度和角度有关系。
光在传播时,每一个圈的能量都是相同的,球面在越外面,球面越大,能量密度越小。可以认为光强$I$和半径平方($r^2$)成反比,
我们可以把慢反射公式化为,$发射的能量*接受的比率$,如下图:
其中我们对第二项的lambert cosine law为什么和0取了一个max?因为cos小于0说明是从后面来的光,我们认为从后面不能透过光,因此我们直接认为没光(即0)即可。
$k_d$是漫反射系数,不同的系数,决定了亮度:如下图
高光(Blinn-Phong模型)
怎么定义高光呢?我们经验性地认为:反射光和你眼睛观察方向接近时会出现高光。
Bling-pong中只不过换了种说法:
首先定义了一个叫做半程向量的概念(Half vector), 其实就是人眼睛观察方向和光照方向夹角的角平分线出伸出的一根单位向量。数学上表达为:
我们把半程向量和法线的接近程度作为衡量高光出现多少的标准。
bling-pong和漫反射的公式很像,但是他没有算吸收率多少,这是为了简化模型。
bling-phong模型还有一个指数$p$,,这是为什么?
当角度差距45度时还有一半高光会出现,我们认为这不是很合理,高光出现在一小片区域比较合理:因此添加指数,我们常常p取值100-200的情况较多。
我们看一个高光反射系数$k_s$和指数$p$对高光的影响图:
环境光
我们认为来自环境的光是一个常数,不受方位影响,相当于提升一个亮度的感觉。 虽然事实上并不可能是一个常数,这属于全局光照技术,这里我们大大简化了一下。
漫反射 + 高光 + 环境光 = Blinn-Phong 反射模型
Shading Frequencies着色频率
shading point采样频率不同的结果如下:
flat shading(shade each triangle)
我们考虑对每个三角形面做shading:叉积三角形的两个边获得法向量,然后做Blinn-phong反射模型,三角形面片里的每个像素的颜色都一样,为这个面的颜色,我们称为flat shading
Gouraud shading(shade each vertex)
着色是针对每个顶点计算,然后面内部像素的颜色用顶点的颜色进行插值的方法得出。这个方法有时也称为逐顶点着色(per-vertex shading)。
Phong shading(shade each pixel)
着色在每个像素上,同样是求面的顶点法线,然后面内部的每个像素的法线用插值的方法得出,再计算每个像素的颜色。
三者效果对比
顶点法线怎么算
考虑是一个球,我们可以这么算:找球的球心连接一下即可。
那对于其他的物体呢?我们提出 average surrounding face normals(周围面平均法线)的方法来计算:简单说就是把顶点周围的表面的法向量求个平均
给出顶点法向量,怎么对每个元素插值出法向量
我们有了橙色的顶点法向量后,还需要插值出中间的绿色法向量,后面的Barycentric interpolation(重心坐标插值)会解决这个问题。
除了发现,包括颜色都可以用重心坐标插值
Graphics(Real-time Rendering) Pipeline
上图其实再不同的方法中有着一些差别,比如在shading中,我们上面提到Grourad shading是对每一个顶点shading的,所以无需等光栅化完成后再做。而对于Phong shading我们贼需要光栅化出每一个pixel后再进行shading。
shader规定了会在每个顶点怎么执行,因此我们写shader是不用for循环对每一个点操作的。下面是一个phong模型的漫反射部分的GLSL实现:
Texture Mapping
我们有时需要定义一个物体上各个点的漫反射系数:
那么我们需要一个坐标来对应着这个点的信息,Texture mapping(纹理映射)可以帮助我们做到。
这一部分往往是美术工作者来做的,我们假设我们指导对应的关系。
我们认为u,v范围都在0-1
Barycentric Coodinates重心坐标
任何一个点都可以通过下图式子表示出来:
- 如果这三个数都大于0那么那个点一定在三角形内
我们可以换个视角,从面积来看这件事情,那么$\alpha,\beta,\gamma$相当于 对应部分的面积占比:
上面都在谈重心坐标,那么重心指什么呢?如下:
从$(x,y)$转化到$(\alpha,\beta,\gamma)$的过程来可用下式来做转换:
因此重心插值就可以这么做:
重心坐标有个缺点,就是投影后会改变中心坐标,因此我们在操作时一定要用三维坐标来插值,而不能用投影到viewport后的二位坐标来重心插值。
Texture Magnification纹理放大(纹理太小)
我们找到一个pixel对应的texture上的位置后,可能并不是整数,因此texture也是一块块的我们称之为texel(纹理元素)。常常的做法就是四舍五入。那么当纹理太小时,就会多个像素对应着一个texel,从而导致模糊。
除了导致模糊,更重要是导致了锯齿状,结果不连续 :因此我们要抛弃四舍五入的做法,转用插值来解决锯齿/块状的问题。
不妨先来看看线性插值: $x\in [0,1]$
当然我们这里是二维的,因此需要双线性插值:
小女孩那张图中第三个用了Bicubic
效果更好但是开销大了一些。
Texture Minification纹理缩小(纹理太大)
上面我们提到了纹理太小导致的问题,其实纹理太大也会造成纹理,这虽然是反直觉的,但却是事实:
仔细想想,这会导致相邻的像素在对应的texel上距离却很远。
从而会导致锯齿和摩尔纹:
解决方法: 反走样,MSAA/SSAA
但是反采样会使得效率变低,每个点采样多次。除了这种方法我们还可以考虑,对每个pixel覆盖了很多texel,不妨求这一片texel的平均值。
我们这里引入Mipmap,它可以做范围查询,而且很快,但是只可以对方形做查询。
Mipmap
Mipmap会提前生成logW层,W是图片的宽,这其实就是cv领域的图像金字塔…
这样操作会多占用多少空间呢?
$1+1/4+1/16+….+ …. = 4/3$,也就是只多占了1/3的空间而已。
那怎么寻找一个pixel在Texture上的样子呢?
我们可以得出两个长度$L1,L2$,以为只能是方形的,因此我们选择较长的一部分:
那么对应的区域就是:
这个区域在mipmap上的哪儿一层会变成一个texel的大小呢?$D=log_{2}L$层! 我们就可以去那一层查这个texel即可得到平均值,但实际上这一步我们得到的D可能并不是一个整数,比如:D = 2.8层。 因此我们需要层与层插值一下得到中间结果:三线性插值
你可能疑惑为什么是三线性:双线性对红色域内任何一点可以插值这点我们是知道的,我们还需要一个维度就是D和D+1层之间是如何插值的,因此这是第三个线性插值,因此是三线性插值。
现在层之间过渡的非常漂亮,下图不同颜色代表不同的层,越冷色层数越大。
各向异性过滤(Anisotropic Filtering)
三线性插值的Mipmap在远处直接糊掉了,变成一边蓝色了:
各向异性过滤效果比三线性插值会好一些:
我们先说为什么三次线性插值的Mipmap效果很差,主要是因为远处的一个像素转到texture上不一定还是一个比较正的形状了,可能是下图这样一长条。
我们还是把取最长边做正方形采样的话会使得采样过大造成overblurred(过于模糊)。我们有一些对应的解决方法,比如做mipmap的时候做一个二维的(如下图),即在长和宽应用不同的比例,这样对于texture space上矩形的形状就可以解决了。
但是对于斜着的矩形我们仍然是无法解决的,后面还有一种方法叫做EWA filter(EWA滤波):
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!