入门图形学9——着色1
9.1 深度缓冲与可见性(Z-BUFFER)
9.1.1 画家算法
- 我们如何去描述一个场景,或者说场景式如何逐步的画出来的,这本质上涉及的算法就是我们所说的画家算法
- 画家算法是按照从远到近的顺序绘制图形的,实际上渲染管线本身的思路也是源自于这个过程。
9.1.1 画家算法实例
- 可以看到图中的绘制顺序分别是远处的天空,山脉,树林,最后是湖泊。但这样的算法依然存在问题,如果图像相互遮挡,就无法确定谁先谁后,这是就需要介绍一个重要的技术,深度缓冲。
9.1.2 深度缓冲
- 深度缓冲顾名思义,是对于深度的一次缓冲,为何要缓冲深度?这是因为我们现在要转换我们的思维方式。
- 先前我们提到的画家算法,实际上是在考虑物体直接映射到屏幕上,但根据先前我们学习的光栅化知识,我们应该发现其中漏掉了很重要的一点,那就是物体离散化到像素的过程。而如果从像素角度来思考,则可以很方便的理解深度缓冲的含义。
- 对于每个像素我们最后只需要将没有遮挡的对象填入其颜色值即可,所以说我们会一次又一次将不同的物体对象(也就是三角形片元)投射在像素上,就像我们之前做的那样,而此时我们同时记录每一个片元所对应像素的深度,如果深度比之前更小我们就覆盖这个像素,让这个物体的片元显示出来。
- 我们在渲染最后结果的frame时,同时也会渲染深度图,就像下面这样
9.1.2 Unity中的深度图
- 其中远处为无限深度,也就是黑色,而近处逐渐深度减小,所以为偏白色的灰色,并且越近越偏向白色。
- 我们可以通过一个简单的网格图进一步加深我们对这个过程的理解。
9.1.2.1 深度缓存步骤1
- 一开始像素深度都是无限远的,开始绘制第一个三角形,它的深度值都是5,通过原理比对,绘制在无限远(R)前。
- 然后我们开始绘制第二个三角形,它的每个像素深度都是不一样的,通过我们已经得出的算法,我们同样可以把它绘制到屏幕上,深度小的永远在前面,每个像素都会被绘制,并更新
9.1.2.2 深度缓存步骤2
- 以上提到的深度缓冲算法,我们可以来看一个伪代码实现,这种算法的时间复杂度是O(n),因为我们本身只关注n个像素的深度值,而并不是在给片元们排序,所以消耗的线性时间。
9.1.2.3 深度缓冲伪代码
9.2 着色初步
9.2.1 着色的定义
- 什么是着色?着色顾名思义,就是将颜色附着在物体表面的过程。也就是不同的材质,同一个模型拥有了不一样的材质,外表就会发生不一样的变换。
- 着色最基本的内容:添加明暗变化,添加颜色变化。比如下面这个材质球就是所谓的着色,它将原本的材质变化了属性,赋予了明暗和颜色变化。
9.2.1 不同的材质赋予不同的着色效果
9.2.2 Bullin-Phong(布林冯)模型
- 以上描述的着色模型有很多,其中一种最为基础而经典的就是布林冯模型,这个着色模型定义了三个最基本的着色组成部分,分别是
- 漫反射项(diffuse)
- 高光项(Specular)
- 环境光项(Ambient)
- 这三个部分共同组成了一个最基本的材质,我们可以用它来模拟一些简单的光照场景。
9.2.2 Unity布林冯着色模型
9.2.5 着色基础
- 实际上我们看到的着色模型,它们都是计算得来的(当然这是一句废话)。你肯定想问,计算什么呢?
- 着色模型计算的基本变量:
- 视角方向(ViewDir)
- 光线方向(LightDir)
- 法线方向(Normal)
- 并且它们都是单位向量(希望你还知道这代表什么意思),也就是说只考虑方向,因为我们只需要利用它们描述每一个点的接受光的多少,呈现出什么颜色而已。
9.2.5 着色计算的基本条件
- 相信细心的同学已经发现了,我们的向量是由点指向光源和摄像机的,这与我们着色的基本概念有关,我们只考虑每个点接受光的多少,而不考虑阴影(暂时的)因此我们定义LightDir作为点指向光源的向量,这个向量对于每个点都是不一样的:
- *L = Q - P
- 当然这是建立在光源是点光的基础上,我们才能这么表达这个向量,如果是方向光呢?最经典的例子就是太阳。太阳离我们很远,因此它发射的光线近乎可以看做是平行光,因此此时对于整个场景。它们的L都是一样的
- L’ = L
9.2.4 漫反射项
- 接下来我们来逐一了解这三个部分,首先先来看看漫反射项(diffuse)
- 漫反射在生活中随处可见,比如大家现在立刻看看自己的手臂,自己的衣服,是不是你能看到他们!(好奇怪的发言)但这本身说明他们在反射光线,而且是均匀地反射,这其实就是漫反射的定义。
- 这些物体也叫做哑光物体。
- 接下来我们考虑反射的多少,这取决于它接受的多少,能量守恒定律是普遍存在的客观规律。而如果我们要描述这种规律就需要聚焦于一个单位范围,因为我们要保持其他变量不会影响我们的理解。
- 单位区域内,所接受到的光,决定了漫反射的强弱,而如果我们想去描述它,就需要借助法线和光线了。
- 来想一下这个问题,什么时候光线最多?当然是全部射到上面的时候,这时候法线方向与入射光方向平行,我们借助三角函数描述,那就是
- cosa = N · L / |N| |L|
- (当然如果是单位向量也可以省略分母,希望你还记得我们用点积描述角度大小)
- 很明显,当N与L共线时,cosa = 1,此时为最大值。
9.2.4 漫反射强度与法线和入射光的夹角大小有关
- 除去接受部分,我们还需要关注传播部分,之前说过,光线在传递过程中也会衰减,这个频率我们描述为
- I’ = I / R(半径)^2
- 这个同样容易理解,我们可以想象点光源发射的光是球形的,这个球在传播的过程中r在变大,意味着其表面积在变大,球体表面积公式
- S = 4πR^2
- 因此单位面积上的I就变为 I / 4πr^2,且由于我们求得是比例关系,可以忽略常数,由此可得到变化规律。
- 最后,我们就可以描述漫反射了,它就是由发射部分和接受部分定义的,最后利用max操作取最大值的目的是规范一个取值范围,毕竟一个小于0的贡献值对于计算光线没用,它不可能从表面吸取光线吧?!所以我们限定其最小为0(黑色)
9.2.4.1 漫反射方程
- kd部分被称作漫反射系数,这个部分其实很好理解,毕竟不同材质漫反射也有强弱,这个值只是在控制宏观的反射关系的多少而已,越大反射整体越强烈
9.2.4.2 不同kd值(左<右)球体的不同亮度表现
- 这很直观,到此你已经可以让物体具有基本的亮暗面关系了。
9.2.4.3 Unity中的BlinnPhong漫反射项
9.2.5 高光项(Specular)
- 高光项也叫做镜面反射,这个词(Specular)来自于拉丁文,意思是“镜子”,那么我们来思考一下镜子有什么特点?
- 镜子的反射与漫反射相比是集中地,也就是说反射光线会在某个区间内集中。
9.2.5 镜面反射入射与反射关系
- 入射线L指向的是光源所在位置,如果镜面的光泽度(s)变化,会使得反射光线不只为一条,而是多条,越靠近R越为集中。因为表面不平整因此会有光线从其他角度散射,但大多数光线都是聚集在R附近的。越靠近R光线越多,因此光泽度决定了反射光线的衰减程度,接下来我们来探讨有多少光进入了我们的视野。
- 我们想知道有多少光线进入了我们的摄像机,本质上,就是视线向量V与反射射线R之间的夹角,当全部光线进入摄像机中时,a=0°,同样的,a=90°的时候,就没有光线进入了。
9.2.5.1 反射的强烈与反射光线和视线角度有关
- 我们对这个光照模型的值进行进一步的分析,a是V和R的夹角,而R则是通过V和N得到的。我们可以将L拆解为Lp和Ln这两个方向向量,他们分别垂直和平行于N,之后我们就可以对Ln进行表示,他就是N(N* L),括号里是他的长度,外面是他的方向与N平行。由于L = Lp+Ln,所以Lp = L - N(N* L)。而R是由-Lp和LN组成的,因此我们只需要略微变化,就可以得出我们所需要的,R的表达式:
- R = 2N(N* L) - L
9.2.5.1 计算反射光线
9.2.6 布林冯模型中的反射计算
- 经过上面一系列的推导,你是不是觉得头晕眼花了?(反正我是不想看),那么有没有一种可以简单计算反射光线多少的方法呢?答案当然是有的!
- 现在我们变换一下思路,回到问题的本源,我们所要知道的其实就是真正射到摄像机里的光线有多少,或者说我们其实是在模拟而不是仿真,因此我们只需要通过表达有多少光线射出的状态而不是真的去计算每一根光线(当然在后续的光线追踪系列中我们必须考虑)
- 所以布林冯为我们提出了一种取巧的方式,通过计算半程向量与法线夹角的关系,来近似的描述有多少光线反射了出去
- 什么是半程向量,其实就是入射光线和摄像机光线的角平分线上的向量,它的计算非常简单
- H = V + L / |V + L|
- 而通过它我们就能很方便的描述有多少光线反射了出去,因为它的变化趋势与光线和摄像机视线变化趋势相同。
- 至此,我们只需要依据之前推导漫反射部分的思路,推算出高光反射的表达式即可。
布林冯的高光反射模型
- 你发现了什么?好像有些不对劲?为什么会存在一个指数项呢?出错了吗?当然不是!
- 这个指数项被称为镜面反射指数,用来描述不同材质镜面反射高光的变化趋势,实际上,在材质制作与特效制作的过程中,我们经常会用到指数幂去控制变化范围,因为在数学上,它可以使得小于1的数值更小,大于1的数值更大,会增加函数的变化趋势
9.2.6 镜面反射指数
- 现在我们已经可以描述高光项了,让我们将它加到模型上,我们可以通过调节指数来控制高光的衰减范围。
`9.2.6.1 UnityUnity中的BlinnPhong漫反射项+ 高光项
9.2.7 环境光(Ambient)
- 恭喜你,到现在已经可以描述布林冯模型里的漫反射项和高光项了,我们还剩最后一项,那就是环境光。
- 环境光是来源于整个环境的所有光线的总和,计算其本身相对复杂,在Unity中会存在一个宏变量,供我们直接调用环境光照,同时在Lighting中你也可以设置环境光颜色等,Ue中的天空光照同样是模拟这个过程。全局光照系统是更进一步的高级环境光,这个之后会进行讨论,我们先假设已知了环境光
- La = KaIa
- 其中Ka 是环境光系数,Ia 是环境光强度。
9.2.8 布林冯模型总览
- 到现在,我们将这三项逐一加和,所得到的就是我们所想要的布林冯模型了!
9.2.8 布林冯模型全貌
- 现在,不管什么样的物体我们都可以对其进行基本的着色了,当然这任重而道远,不过我们已经掌握了第一步,后面的路也就铺开了,你还可以自行了解诸如兰伯特模型,半兰伯特模型等等经典的光栅化模型。下一节我们将继续深入着色系统并重谈渲染管线,相信你一定对之前所说的业界定制的管线有更深刻的理解!
- 让我们一起看看最后得到的图像
9.2.8.1 布林冯光照模型
参考资料
- UnityShader入门精要 冯乐乐著
- games101图形学入门——闫令琪 https://www.bilibili.com/video/BV1X7411F744
- 计算机图形学入门——3D渲染指南
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Pleasant233!