我保证这是我目前写过的所有科普教程中最不科普、***层、最不想被读、最难理解、最晦涩的一篇。
但我也保证这是我目前写过的所有科普教程中最专业、最珍贵、最耗时、最尽力,最精益求精的一篇。
以下正文。
做3D开发(尤其是建模相关)的话,“材质”一定是个会经常遇到的概念,对于这个总在用的东西,你究竟了解多少呢?——至少我在不久前还是个只用参数不知原理的人。因此,今日特来与大家分享下我的学习心得和研究理解。
厌倦或者害怕晦涩枯燥的图形学教科书吗?不用担心,在白蓝紫的文章中不会出现那些过于严谨的绕口词语——我只提供科普级别的读物而已。So,Let's go!
首先,让我们理清思路,看看这三者间的关系。
材质是反映物体与光线互相作用,最终影响着色的属性;因此要讲什么是材质,就先要说说在实时渲染中是如何进行着色的;而着色又是根据光照来计算的,因此要先梳理下光照。
【光照】
光照就是根据光学原理计算光强变化。
光学知识在初高中物理课上都讲过,什么直线传播啊,反射折射吸收啊,这些都是排在课本最前面几章的入门知识,比力学电学容易得多,如果你不幸忘记了,可以敲开邻居的门,请教一下人家孩子——或者问谷歌吧。
对于计算机渲染而言,我们有一些额外的说明,比如什么光线都是有颜色的,不考虑不可见光,光源分为点光源,体积光,平行光,聚光灯等等……有兴趣的人可以——不用我说了吧?找一本以专业严谨和枯燥催眠而著称的清华大学出版社的图形学书籍啃吧!
【着色】
着色就是根据光线和材质确定物体颜色的步骤啦。
首先要说明的一点是,光照模型——也就是模拟光效的计算方法——有很多种,而我只是针对实时渲染而言——你不想游戏跟3dsMax的渲染一样,一帧等一小 时吧?——因此,这里以常用的像素级别的PHONG模型为准(另一种常用的是顶点级别的GOURAUD模型,这两种是目前可行而常用的模型,详见最下面的 链接)
材质对光线的影响有这些:
1、漫反射
众所周知,这是由于表面粗糙不平所引起的,它的结果只与光源位置有关而与视点位置无关,即:
物体表面相对于光源的角度决定它对来自光源的光的反射。表面越接近垂直于光线,被反射的漫射光线就越多。
根据lambert余弦定律可以这样求出结果:
漫反射强度=材质的漫反射系数×光源强度×(法线方向向量·入射光方向向量)
例如:0.5(反射一半)×1(***,白色)×cos0(入射光垂直于法线,夹角0)=0.5(半减,灰色)
*所有的“强度”“系数”都是和“颜色”是等价的,因为将RGB三个分量计算过后,合并的结果就是颜色而不再是强度了。即“漫反射颜色=材质的漫反射颜色×光源颜色×(法线方向向量·入射光方向向量)”(下同)
*注意,“×”是数乘,而“·”则是点乘,也就是说***两向量的点积表示的就是它们夹角的cos值。(下同)
*注意,所有向量都是单位向量,并且入射光向量由反射点指向平面外。(下同)
*图示,L是入射光,N是发现,R是反射光,eye是视点
2、镜面反射
理想镜面是不存在的,对一般的光滑表面而言,反射光会集中在一个范围内,它的结果与视点有关,即:
镜面反射强度=材质镜面反射系数×光源强度×(法线方向向量·入射光与视点夹角一半的方向向量)^光泽度
例如:0.5(反射一半)×1(***,白色)×cos0(视点位于反射光方向上,夹角0)^1(加权1)=0.5(半减,灰色)
*“^光泽度”是“光泽度次方”,光泽度越大,高光的亮斑越小
*标准算法应该是指向视点的光线和反射线的单位向量的点乘(即上图中的α角),但为了节省计算,我们按blin模型用此代替。
*那个半向量=视点方向向量+入射光方向向量,如果按下图的箭头,那么就是视点方向向量-入射光方向向量
*图示,H为半向量,它与eye的夹角等于它与L的夹角,这个角只是近似(你可以很容易的发现,其实它少了一半),但对于实时渲染而言,这已经足够了。
3、环境反射
环境光是一种经过多次反射平衡的光,它的强度是均匀的,并且分布是一样的,即:
环境光没有位置或方向上的特征,只有一个颜色亮度值,而且不会衰减,所以在所有方向和所有物体表面上投射的环境光的数量是恒定不变的。
因此,在局部光照明模型中(全局光照明包含对象间的作用,需要光线追踪等算法。为了效率,实时渲染中可以忽略),我们用一个常量进行计算:
环境反射强度=材质环境反射系数×环境光强度
例如:0.5(反射一半)×1(***,白色)=0.5(半减,灰色)
4、自发光
自发光是指对象自己发出的光,即:
自发光只增加自身颜色,而不影响场景内其他对象,因为它不是光源,自发光不参与光运算。
通常来说,我们用一个常量代表自发光强度,但事实上为了节约计算,我们通常会忽略它,而改用漫反射系数代替,或者说,将它的结果融入漫反射系数中,最终合成我们的老朋友——贴图。
5、各向异性的反射、菲涅尔效应、焦散与色散、下表面散射
略,PHONG模型忽略它们,同时为了实时渲染效率我也忽略它们。想了解更多,请——这次换寂寞的百度吧。
***,让我们来说重头戏——PHONG模型。
PHONG模型就是漫反射、镜面反射与环境反射的和,即:
像素颜色=环境反射色+漫反射色+镜面反射色
很简单吧?但也许你会问,既然是和,那么超过上限的怎么办?最简单的当然是截断不要,当作白色处理!但不管怎么说,毕竟是三个颜色相加,超过上限的几率也太大了,这时候,你就需要分配权值再累加,比如说:
像素颜色=环境反射色×0.3+漫反射色×0.4+镜面反射色×0.3
不过呢,一般来说这个权值已经包含在材质的反射系数里了,或者说,反射系数其实就是权值。
如果你理解了以上内容,那么恭喜你,你的光学物理和向量数学过关了,所以——准备好把高考不屑一顾的光学知识上升到一个新的高度,让我们见识下真正用于程序中的计算方法吧!
不过在此之前,为了便于理解和自学,还是让我们先来了解一下这些之前被我隐藏掉的英文吧:
light=光
ambient=环境反射(光/色/强度/系数)=阴影色
diffuse=漫反射(光/色/强度/系数)=固有色
specular=镜面反射(光/色/强度/系数)=高光色
emissive=自发光(光/色/强度/系数)=辐射色
shininess=rough=光泽度=镜面反射加权系数n
ka=环境反射系数
kd=漫反射系数
ks=镜面反射系数
N=法线方向单位向量
L=入射光方向单位向量
R=反射光方向单位向量
V=视点方向单位向量
H=半方向单位向量
通常而言,漫反射系数加上镜面反射系数等于1,因为材质通常除了漫反射就是镜面反射。另外,环境反射和漫反射的默认值常为:
ambient=0x333333(颜色)=0.2(系数)
diffuse=0xCCCCCC(颜色)=0.8(系数)
如果你已经晕了,那么请从头对照着图示和范例再看一遍,如果还没有晕,那么做好晕的准备——来迎接挑战吧!
这就是经典的openGL光照模型,很吓人么?
——不?那你一定学过计算机图形学,或者是3D编程老鸟,或者是看了之前的内容256遍,再或者是在满嘴跑火车。
——是?那你需要的是学好英语,或者对应之前讲的理解,或者开始像德国boy那样狂气地毁键盘,再或者是……听我讲解?那么好吧,让我们对照着这个吓人的上古魔咒一句一句地翻译,以找出破解2012末日诅咒的法术。
首先要补充一个概念,如你所见,这里计算的是顶点颜色(vertexcolor),原因是……嗯,这个一两句话不好说明,但你应该知道,像素的颜色是通过 插值得到的,因此我们需要先计算出顶点的颜色。同时,为了光滑着色(避免一块一块边界分明)又不失效率,我们可以使用GOURAUD明暗处理,即双线性光 强插值算法。具体算法大家可以自己谷歌——但我觉得,Stage3D已经为我们选好了插值的方法,所以我们需要的只是计算出每个顶点的法线——注意,是顶 点法线而不是面法线!如果需要人工计算的话,顶点法向量可以用与其相邻的所有多边形法向量相加得出的均值来近似表示。
好了,让我们开始翻译这鬼画符。
emission(material)指的是材质的自发光颜色,如你所见,这个值是作为常量直接被加的——但为了速度省略它更好。
ambient(lightmodel)指的是全局环境光颜色,而ambient(material)则是材质的环境反射系数,与上面讲的一样,它们相乘 得出环境色的最终值。但请注意倒数第三行,这里也有个ambient(light)和ambient(material),这一对儿表示的是针对各个光源 的环境色,主要用于计算物体间的影响(比如说,把手靠近国旗就会被它染红),但通常为了速度也会省略(毕竟很玄的效果与很炫的帧频相比,还是后者更有吸引 力)。因此,我们只计算***行的全局环境光就好了——当然你也可以省略计算,直接用一个常量代替它,视觉上并不会相差多少,但效率上却会有所体现。
之后这个肥大的汉堡包就是各个光源的结果总和。如你所见,左边的西格玛(∑)代表求和,这相当于是一个循环(for i=0;i<n-1;i++),按照右边的公式计算出各个光源的结果,并将其累加——毫无疑问,只有一个光源是最快的,这就是当年pv3d为什么只 能为一个对象赋予一个光源的原因!而事实也表明,我们通常不会需要那么多的动态光源——比起耗时的计算,我们更愿意将结果烘焙进模型的顶点颜色中!
来看这个汉堡的***层:一个分式乘以点光源效果(spotlight_effect),再将其结果乘到后面的三层——PHONG模型——上面。这是做什么用的呢?是光线衰减。
显而易见,对同一个光源而言,离得越近的物体越亮。这个点光源效果就是点光源强度的加强系数——忽略(视为1)也未尝不可——而前面的分式则是距离比率。
这个分数表示的是“光的衰减比例为传播距离平方的倒数”这一光学定律,即1/(d^2)。但如你所见,这里的分母却分明是(d+k)^2的展开式,为什么 除了距离d还多了一个系数k呢?很简单,因为你不会希望d<1(特别是无限趋近于0)时,点光源的亮度变成无限大。因此,我们补上一个系数来确保分 式的结果不会大于1——也就是光源原本的亮度。
需要注意的是,这个衰减仅对点光源有效,因为平行光是无视距离的,它的分式永远是1,即不衰减,而环境光在***行就算过了,至于其它种类的光(比如聚光灯),在实时渲染中则很少见(速度***,影响速度的统统忽略!)。
***,让我们来看PHONG模型。
左边的那个中括号“[”表示后面这三行的和,也就是三行累加的结果。
***行之前说过了,究竟是去是留请根据需求自便,略过不再提。
第二行是漫反射色,第三行则是镜面反射色,与之前的公式完全一致,也不必多说,不过:
1、其中diffuse(light)漫反射光和specular(light)镜面反射光可以用一个光源颜色表示以节约内存——也更合理。
2、diffuse(material)材质漫反射系数和specular(material)材质镜漫反射系数通常相加得1(0xFFFFFF),因此 你可以只记录一个参数,或者直接用通用的0.8(0xCCCCCC)与0.2(0x333333)常量来代替以加速计算。
3、向量乘法与之前所说的一样,其中n是法向量,L是光线入射向量,s是半向量,shinine是光泽度。需要注意的是,这里对其结果做了一个与0取较大值的计算,这是为了避免处于背面还被光线照亮(即便颜色是负的)的缘故。
【材质】
***,来看材质。
材质是记录物体表面对光线影响的参数集合,如果你已经理解了上面的一堆公式,那么,材质所包含的参数也就显而易见了。
虽然由于渲染引擎的不同(实时与非实时)和光照模型的不同(非实时光照模型会更细腻——也更耗时),材质的参数会有多有少——这一点打开3dsMax的材质编辑器就可以发现——但对于实时渲染而言,总有那么几个参数是常见的,那就是:
ambientColor——环境色,材质对环境光颜色RGB各分量的反射系数
diffuseColor——漫反射色,材质对光源颜色RGB各分量的漫反射系数
specularColor——镜面反射色,材质对光源颜色RGB各分量的镜面反射系数
emissiveColor——自发光色,材质自身的颜色
shininess——光泽度,高光光斑的大小范围
transparency——透明度,材质的透明状态,与真透明不同,这里的透明与厚度没有关系
*对于其他叫法,请参考之前的中英文对照表
That's all for today~
希望对大家(特别是渴望写shader或引擎的人)有所帮助。
***,附赠另一种着色贴图的公式:
最终颜色=漫反射贴图(正常纹理)×光线贴图×混合因子+反射贴图×(1-混合因子)