Shading
着色概述
shading: 着色,给物体引入亮暗,颜色,不考虑阴影
在图形学中,就是给物体应用不同的材质
Blinn-Phong反射模型
一个物体的光照反射中存在高光、漫反射和环境光
高光是指镜面反射得到的高亮区域,环境光是指通过其他地方反射间接得到的光
Blinn-Phong反射模型中,需要将三个项分开模拟,最后再合在一起
漫反射
我们把要着色的一个点看作一个极小的平面,我们定义出三个单位向量,法线,光照方向,和观察方向,
对于漫反射,入射的光线会被均匀地反射到各个方向,从任何一个位置观测着色的点都是一样的效果,所以我们可以忽略观察方向
光的入射角度
我们先考虑,在法线和光照方向变化的各种情况下,单位面积的shading point会得到多少光
我们用lambert's consine law这个模型来模拟
f(θ) = max(cosθ,0) = max(L•n,0),f(θ)就代表了该单位面积的shading point得到光的系数,当为1,光照方向和法线方向相同,此时得到所有光;当为0,得不到光
光的强度
光从点光源放出来后,是成球壳状的向外辐射,根据能量守恒,每一个球壳上的能量都相同,从而得到越外的球壳上的光的强度越小,且强度与半径的平方成一个反比关系
光的吸收
shading point可能会吸收一部分光,反射一部分光,我们用, 来表示这个shading point的反射系数,代表他会反射多少光,如果是1则全部反射,如果是0则全部吸收
我们假设屏幕左上角有一个光源,可以得到这样的漫反射图
越大,光线越亮;越大,光线越暗
高光
我们采用的是Blinn-Phong model这个经验性模型来模拟高光
当观测方向和反射方向接近时,可以看到高光,Phong model就是用反射向量和观察向量点乘结果来判断是否能看到高光
但Blinn-Phong model 是求光线方向和观察方向的半程向量,也就是平分两个向量角度的向量,半程向量比反射向量更好求。如果半程向量和法线接近,则可以看到高光
和漫反射模型不同的是,我们给加上一个指数,这个指数p往往取到100到200,来使得Blinn-Phong模型更好的模拟高光,只要偏移了一点角度,高光就会迅速减弱
这是高光随着和变化的情况(图中漫反射也被加上了)
如果越大,渲染表现出来的结果是高光越大,显得表面没有那么shiny;金属一般有小的高光
环境光
某些位置不能直接接受到光源发出的光,而需要其他地方反射的光,我们称之为环境光
环境光是非常复杂的,Blinn-Phong简化了这个模型
我们用来作为一个常数,代表每个shading point都接受同样的强度的环境光
将漫反射、镜面反射和环境光加起来,就得到Blinn-Phong反射模型
多光源
对于多光源,环境光只需要计算一次,但高光和漫反射需要对每个光源计算一次,然后求和
着色频率
着色频率分为三种
flat shading: 对每个三角形进行一次着色
Gouraud shading: 对每个三角形顶点进行一次着色
Phong shading: 对每个像素进行一次着色
当三角形足够多,可以使用比较简单的flat shading,来减小计算量
在Gouraud Shading中怎么求顶点的法线
当我们知道三角形要表示的是一个简单图形,比如是球时,而顶点在球面上,我们就可以直接取球在该点的法线
如果是不规则的图形,我们取与这个顶点相连的所有三角形的法向量求平均,既可以是简单平均,也可以是加权平均
图形管线
图形管线就是将三维模型渲染到二维平面的一系列操作的集合
实时渲染管线的步骤为
- 取三维空间中图形的顶点,将顶点进行MVP变换
- 将原图形中顶点的连线,在投影结果上重新连上
- 光栅化三角形
- 着色
shader:用OpenGL等API,在GPU上规定顶点或者像素/fragment如何着色。
GPU: 实现了一套图形管线,其中shader部分可编程
插值
我们知道了三角形三个顶点的属性后,想要三角形内部属性平滑的过渡
插值就是通过顶点得到内部的属性
重心坐标Barycentric coordinates
用于做三角形内部的插值
重心坐标是定义在一个特定的三角形上的,换了一个三角形就是另一个坐标系
三角形所在平面的任意一点,都可以用三个顶点的坐标线性组合得到,规定系数之和为1,使得组合出来的点一定在三角形所在平面内
就是重心坐标
点在三角形内的充要条件:重心坐标三个分量均大于0
重心坐标还可以用面积来定义
重心:与三个顶点的连线,将三角形三等分
重心坐标的一般计算公式如下
我们用重心坐标的三个分量,就可以用来做对任何属性的插值,也就是任何一个点的属性都是三个顶点属性的特定线性组合
但是投影变换后,会使得重心坐标发生变化,所以插值一定要在三维空间中进行,而不是在投影后的二维平面上进行插值
比如在Z-Buffer中对三角形内部点进行插值,不能用二维平面上的重心坐标,要用三维空间中的重心坐标
纹理映射
不同的shading point会有不同的属性,比如漫反射系数
当我们渲染一个真实物体时,不同的点往往有不同的属性,我们将物体的shading point的属性的集合叫做纹理
我们将纹理看作一个二维平面
我们要给物体着色时,就将一张二维纹理套在一个三维物体的表面,也就是将纹理映射到物体表面
映射的过程为:
将纹理图放在一个二维u-v坐标系中
三维模型中的每个三角形或者是每个点都对应一个uv坐标,也就是纹理映射
屏幕上的任意一个点,如果是三角形顶点,则直接找对应的纹理u-v,否则,则用插值来找u-v,则完成了纹理映射。纹理往往对应的就是漫反射系数
不合适的纹理
纹理过小
如果纹理太小,而三维物体太大,则需要进行纹理放大
如图,红点是pixel,黑点是texel
texel指的是纹理上的单位元素
当pixel比texel数量多,就会出现图中,红点会出现在黑点不存在的地方的情况,也就是texel数量不足以支持每个pixel取一个独特的texel
最朴素的解决方法就是,整个小方格内的红点pixel都会取小方格内中心黑点代表的texel,这样相邻的多个pixel都有着同样的texel,这样完成效果并不好
双线性插值Bilinear是更好的解决方案
取红点附近的四个黑点,根据离四个黑点的水平和垂直距离,按照比例进行插值
我们假设texel之间的距离为1,则和的范围都是[0,1]
Bicubic就是取红点周围16个黑点,计算量更大,但带来更好的效果
纹理过大
纹理过大概述
如果纹理太大,也会出现问题
在投影后,像素是一样大的,但较远处的texel在透视投影后会变小
可能会出现一个pixel覆盖多个texel的情况
当一个pixel覆盖多个texel,这个pixel的高频信息比较多,需要更多的采样点,才能避免混叠
所以supersampling可以解决一个pixel覆盖多个texel的情况,但计算量比较大
我们想要用范围查询算法,直接得到一个范围内的texels的平均值,而不需要多个采样点
mipmap
速度快,但是近似的,并且只能用于正方形的范围查询
mipmap预处理:我们将原图进行处理,得到分辨率缩小到一半的图,图中每一块分割都有着自己的一个值,是原先对应区域的平均值,并不断重复缩小,直到变为1x1的图,总共得到张图
通过等比数列可以得到,我们用了原来的4/3的空间来存储原图和处理后的图
如何计算一个pixel占据texture space多大范围
将相邻的几个像素中心点映射到texture space上
以一个红点为基准,找到相邻的点的距离的较大值,作为L,以L为边长以红点为中心构造一个正方形,我们用这个正方形来近似这个pixel覆盖的范围
这样每个pixel占据了范围的texture space,我们利用预处理得到的mipmap,level 的mipmap中分割成的每一块都是的大小,那对于占据范围的pixel,我们就取 level的mipmap中的一块分割,而且我们已知这块分割的值,所以我们可以直接得到这个pixel占据的范围的平均值
如果将四舍五入来取mipmap层数,就会得到如下的分层效果图
这样会出现断层的现象,我们希望查询可以是连续的,也就是可以查到非整数层
我们就进行一个三线性插值
对于我们同时在level D和level D+1上查询,将两个的结果进行一次线性插值,得到最终结果,这种方法就使得查询连续
这就是三线性插值的结果
其他范围查询
但是投影后,pixel覆盖的纹理区域很多时候不是方形,而且有可能是斜的,mipmap只能处理方形
anisotropic filtering各向异性过滤
比起mipmap,还得到了只压缩宽、不压缩高,和只压缩高,不压缩宽的处理图,集合叫做ripmap
只压缩了一个属性的处理图中的一个方形,对应的是原图中的矩形
这样可以查询不是斜的矩形的区域,但显存开销是原来的3倍
EWA filtering
将不规则的多边形分为多个圆形,进行多次查询
纹理的应用
渲染环境光
environment map环境光
用纹理来描述来自整个环境的光
左图为环境的平面展开,右图为茶壶的环境光渲染,叫做Utah Teapot
环境光中,我们假设光都是来自无限远处,只有方向这一个属性,来自同一个方向的光都是同一强度,没有深度信息
spherical map
我们可以用一个镜面球来记录环境光,然后再将球面平面展开,得到环境光纹理
但直接展开会在图的上下部发生扭曲
cube map
通过cube map可以解决扭曲,将球放在一个方块中,球上的环境光信息,都按照径向投影到方块上,这样得到六个面的信息
凹凸贴图
纹理可以定义任意一点的相对高度
某一点的相对高度发生变化,该点的法线就会发生变化,着色就会发生改变,形成凹凸不平的效果
凹凸贴图本质上就是改变任意一点的法线,而不会改变模型的几何信息,没有改变顶点、像素位置,只是改变纹理贴图,欺骗了人的眼睛
下图中黑线是原图形,黄线是凹凸贴图
如何找出凹凸贴图后的法线
- 在二维情况下
我们用局部坐标系来计算,假设shading point是平面,法线表示为(0,1)
用来近似切线,计算过程中是用于衡量凹凸程度的系数
得到切线后再转成法线
- 在三维情况下
与二维一致,过程如下
位移贴图
和凹凸贴图一样,也是改变相对高度,但凹凸贴图只是假的改变,在模型边缘无法得到凹凸效果,而且凹凸造成的阴影不对
而位移贴图是真正地改变顶点的高度,解决了凹凸贴图的问题
但会需要更细致的模型,更多的三角形
directx曲面细分思想,我先提供一定数量的三角形,如果不够,就将三角形拆分成多个三角形,再进行进一步贴图
三维贴图
不存在一张二维纹理图,有的是一个三维空间中的噪声函数
三维模型中每一个三维点(包括内部)都有纹理
ambient occlusion
一些纹理在贴图后,会因为模型的形状等造成阴影,ambient occlusion就是提前计算好纹理上的阴影等信息,再进行贴图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!