04 Shading
04 Shading
Blinn-Phong光照模型
着色:对不同的物体应用不同材质的过程
对于某一点,只考虑光线方向和观测方向,不考虑光源是否被其他物体遮挡,也就是说,不考虑这一点在其他物体的阴影内的情况。
某一点的光照 = 漫反射 + 高光 + 环境光
漫反射
首先,同样的光,照射到物体表面上,依据入射角度的不同,获得的能量有区别,因而导致了明暗的不同。
其次,对于一个点光源,我们认为,某个时刻传播出的能量是不变的。如下图,每次传播形成的球壳的总能量不变,但是随着时间推移,球壳的大小越来越大。于是,单位面积接收到的光源的能量与到光源的距离成平方反比。
于是,我们可以得到单个点光源照射下,某个点的总漫反射的强度
$L_d = K_d \left( \frac{I}{r^2} \right) \max(0, \mathbf{n} \cdot \mathbf{l})$
- I:表示单位距离的光源能量
- $K_d$表示该着色点的光吸收率
- n:表示法线方向的单位向量
- l:表示光源方向的单位向量
高光
比较光滑的表面,其入射光的反射方向接近镜面反射方向。
观测方向越接近反射光的方向,等价于观测向量和入射向量相加得到的半程向量和法线向量夹角越小。
- $k_s$表示表示镜面反射系数。
- 这里没有考虑入射光和表面因为夹角,导致部分能量未被吸收的问题,漫反射项中考虑了。
- 指数p是为了强化夹角余弦的差异,凸显夹角的影响。所以p可以控制高光的大小,p越大,高光越小。
环境光
假设任何一点接收到环境光的大小都相同,是常数。
着色频率
Shading Frequencies
着色方式
Flat Shading(平面着色)
以三角面为单位进行着色,借助于平面的法线进行运算。对于光滑的几何体效果很差
Gouraud Shading(高洛德着色)
以三角形的顶点为单位进行着色,在三角形内部,通过顶点的颜色进行插值计算,实现点与点之间颜色的平滑过渡
Phone Shading(冯氏着色)
以像素为单位进行着色,点的法向量是通过顶点法向量插值得到的,冯氏着色最接近现实,
可以在减少三角面数的情况下达到相同的效果(插值后法向量会光滑变化),当然,性能开销也非常大
法线计算
顶点的法线计算:
一般是该顶点相邻面上的法线求平均,也可以是按照相邻面的面积求加权平均
像素法线的计算:
利用顶点的属性(法线,颜色)来对三角形内部的像素点进行插值,都使用同样的方法。
三角形的重心坐标
对于三角形所在平面上的任意一点坐标,都可以用三角形的三个顶点坐标的线性组合来表示
$(x,y) = aA + bB + cC$
当满足$a + b + c = 1$时,称$(a,b,c)为该点的重心坐标$
- 如果a,b,c均在0到1之间,则该点是三角形内部的点
- 顶点的属性插值,就是借助于重心坐标来对三个顶点的属性进行加权求和。
- 注意:在三维空间投影到二维空间时,投影前后的重心坐标会改变,所以要应用逆变换在三维空间中进行插值,再投影回去。
图形管线
Graphics Pipline
纹理映射
Texture Mapping
纹理映射是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程
三维图形的每个三角面顶点都可以对应一个uv坐标系下的坐标,uv坐标范围约定在[0,1]之间,至于三角形中的点的uv坐标,可以通过插值计算出来。
可复用纹理: 纹理本身可以被设计为无缝衔接(tilable)
纹理过小
屏幕的pixel太高,纹理的texel太小,多个pixel映射到了同一个texel
- Nearest: 直接四舍五入
- Bilinear:考虑这个pixel周围的4个texel,应用双线性插值
- BiCubic: 考虑这个pixel周围的16个texel,应用双三次插值
如上图,红点是pixel,黑点是texel,pixel没有直接对应的texel。Nearest就是找最近的texel,但是这样会导致不够平滑,锯齿严重。BiCubic效果要比Bilinear更好,但更耗费性能。
纹理过大
屏幕的pixel太小,纹理的texel太大,特别是看向远处的时候,一个pixel映射到了多个texel,这时候因采样频率不足而导致摩尔纹+锯齿(走样)
我们可以用上面提到的超采样方法进行范围查询,比如一个pixel对应的是512个texel的区域,我们可以对这些texel进行加权,但是这样性能要求高,我们可以采用Mipmap算法
Mipmap算法
事先准备多张不同级别(D)的纹理贴图,每升一个级别,横纵纹素各减小一半,最后显存消耗仅为原来的4/3
如此分级之后,设屏幕空间下采样像素与相邻像素中心点之间的距离为L,在u-v坐标系找到这些像素的中心点对应的坐标
如上图,左侧为屏幕空间,右侧为纹理空间。红色的点即是屏幕空间中的点在纹理空间中的对应。我们通过对这个点和相邻点连线的垂直平分线,可以获得一个粉色的多边形区域。接着用一个正方形框来近似这个多边形区域,这个正方形区域就是pixel对应的覆盖区域,根据这个区域的大小,选择相应的层数进行查询,如果没有合适的层数,进行三线性插值即可。
但是,Mipmap算法只能在u-v坐标系下做正方形块的查询,有时候会造成过度模糊的情况,为了避免这种情况,引入各向异性过滤
各向异性过滤
在准备不同级别的纹理贴图时,不再是简简单单横纵纹素各减小一半进行分级,而是长减半宽不变 or 宽减半长不变 or 长和宽各减半三种情况各进行一次分级,显存消耗为原来的三倍,但性能方面并没有多少影响,这种方法就可以实现在u-v坐标系下进行矩形查询。
在显存足够的情况下,各向异性过滤级别开越高越好
贴图
环境光贴图
- 假设光源无限远,只记录光照的方向信息,这种贴图被称作环境光贴图
球面环境映射 Spherical Environment Map
- 球心为世界中心。类比地球仪展开铺平,存在纹理的拉升扭曲问题,解决d方法:Cube Map
立方体贴图 Cube Map
- 将环境光照信息记录在一个立方体表面上,但会需要额外判断某一方向上的光照应该记录在立方体的哪个面上,计算量更大
凹凸贴图(法线贴图)(Bump mapping)
- 记录了纹理的高度移动,并不改变原来模型的几何信息,通过法线扰动,得到模拟出来的着色效果,以假乱真
位移贴图(Displacement mapping)
- 与凹凸贴图类似,但位移贴图是真的改变了几何信息,对模型的顶点做位移,比凹凸贴图更加逼真,但是对模型的精度(三角面数量)要求更高,并且运算量也会随之上升
程序纹理
- 三维的纹理,并非真正生成了纹理的图,而是定义空间中任意点的颜色,具体来说,就是定义三维空间中的噪声函数,再通过映射,得到预想的效果
预计算着色
- 将环境光进行预计算处理,附在原先纹理上,这样使用了纹理的模型看起来就有阴影。
三维渲染
- Solid Modeling &. Volume Rendering
- 广泛应用于物体渲染,如核磁共振等扫描后得到的体积信息,通过这些信息进行渲染,得到结果。
- 我们把这些体积信息当作三维的纹理。
阴影
这一节是在Geometry 3那节课最后部分讲的
阴影贴图
Shadow Mapping
参考:图形学基础 - 阴影 - ShadowMap及其延伸 - 知乎
核心思想:如果一个点不在阴影里,那么这个点可以被摄像机和光源都看到
具体实现:
黄色区域为点光源光照示意范围,绿色区域为相机视锥体范围,很明显,红线处是阴影部分,实际操作时怎样确定这个红线位置呢?有以下步骤:
1) 第一次 pass,生成阴影贴图。
将相机放在光源位置,用 z-buffer 的方式存一张深度缓冲,称之为阴影贴图 (Shadow Map),并记录此时的投影变换矩阵 M,点光源对应透视投影,定向光对应正交投影
2) 第二次 pass,正式渲染场景。
将相机放到“人眼”的位置,考察每个片元处是否处于阴影。方法为:用第一次 pass 里面的矩阵 M 将三维点 $(P_x,P_y,P_z)$ 变换为二维坐标 $(p_x,p_y)$ 和深度 $p_z$ ,将 $p_z$ 与第一次 pass 存下来的阴影贴图对应点的深度 $c(p_x,p_y)$ 进行对比,若 $p_z> c(p_x,p_y)$ ,则认为此片元处于阴影中
此过程如下图所示:
但是存在一些问题:
1) 点光源应该是各个方向都有光,而上图中表现得更像是聚光灯,如果要实现点光源的效果,一个常用的方法是:分别朝六个方向生成阴影贴图,然后构成一个立方体贴图
2) 由于深度的数值精度和阴影贴图分辨率都有限,所以在进行深度比较的时候,有可能会出现 Z-fighting 的现象,所以需要在比较时添加偏差,称之为 Depth Bias
3) 由于阴影贴图分辨率是有限的,每个像素占据一定大小,并且离光源越远,每个像素覆盖的片元就越多,并且涉及到采样和重采样,那么就可能导致阴影产生锯齿 Aliasing
硬阴影和软阴影
区别在于阴影的轮廓是否清晰可见,硬阴影轮廓清晰可见,软阴影轮廓模糊
如果只有点光源,那么一定是硬阴影