基于物理的着色真的不是特别好理解···在这里记录一下自己比较粗浅的认识···
立体角
要了解BRDF的相关内容,首先要对立体角有一定的理解。立体角是对平面角在三维空间的一个扩展。平面角就是一个顶点两条射线围成的那个角度,它的大小等于单位圆上的弧长,单位是弧度或者度,那么类比到三维空间,立体角就是从一个顶点发出的一个锥体所围成的一个三维角度,它的大小等于单位球上的面积,单位是球面度,一个单位球的面积是4π,因此一个球面的立体角就是4π。
,其中A是视锥与球相交的面的面积,r^2是球的半径,由此可知,和平面角与圆的大小无关一样,立体角是与球的大小无关的。
那么为什么要引入立体角这一概念呢?设想现在要做光线追踪,从视点朝像素的方向发出一条射线,这条射线最终落在几何物体上的颜色就是像素的颜色,可是这样做是不准确的,事实上像素并不是一个点,它是有面积的,也就是说原则上不能实现“从视点朝像素的方向发出一条射线”这样的操作,因为这样的射线是有无数条的。那么正确的做法应该是:像素的颜色是视点朝像素的平面发出的射线构成的视锥与几何体相交的那个曲面的颜色,然而这种做法几乎不可能实现,因为求相交面这样的操作很难做到。而又想更为精确的绘制一个像素的颜色,这就需要用到立体角了。
不妨将像素就看做一个点,从视点朝像素方向发出一条射线,与几何体相交,此时不直接将几何体落点的颜色作为像素的颜色,而是在落点面向其法线方向做一个单位半球,如图所示,
将照过来的这一条光线看做是照在这个棕色视锥里的一束光线,也就是说,现在采用的方式是将投影区域作为一个点,然后将这个椎体与半球的相交的这个面来作为投影面积捕获光照信息,这一过程就是在对立体角作积分,很显然,这种方式也是不准确的,是一种近似的手段,但它相较于之前的模型已经有了很大的改进。
关于立体角还有一个问题就是它的微分式的代换:
推导过程:由立体角的定义可知,
那么:
由球面微分的代换式(将x、y换元然后雅克比行列式):
将2式带入1式即可。
辐射学理论
基于物理的着色都是遵循了一定的物理规律,其中最根本的就是辐射学理论,以下是辐射学的基本量:
辐射通量(Radiant Flux)
也叫辐射通率,光子都是有能量的,辐射通量就是指光在单位时间内通过表面的能量的总量,用符号
表示,
。
辐照度(Irradiance)和辐出度(Radiant Existance)
辐照度指的是单位时间内通过单位面积的能量的总量,也可以看做是辐射通量对面积的密度,用符号E表示,
。而辐出度也表示辐射通量对面积的密度,不同的是辐照度衡量的是到达表面的光,而辐出度表示的是离开表面的光。
现在需要考虑这个面积指的是哪个面积,如果光是垂直设向这个表面的,很显然,面积就是表面的面积,但是如果光是倾斜地射向表面的呢?如图所示,倾斜照射的情况下,相同的辐射通量的光到达的表面变大了,也就是说这个时候辐照度是变小了的,因此在光线倾斜的时候,应该只考虑表面垂直与光线的那一部分面积,即将表面投影到与光线垂直的地方,用式子来表示这一关系:
其中 表示光线完全垂直照射到表面时的辐照度, 表示光线与法线的夹角,因此倾斜光线的辐照度为 。
现在假设一个点光源放在一个球的球心位置,那么它朝着各个方向的辐射通量都是相同的,那么通过整个球面的辐照度就是 ,当也就是说辐照度实际上是与球的半径的平方成反比,也就是说光的衰减与距离的平方成正比。
辐射强度(Radiant Intensity)
如果要衡量一个点的辐射通量密度该怎么做?显然不能用辐照度来衡量,因为一个点是没有面积的,点的辐照度肯定为零。前文所描述立体角时所说的实际在做光线追踪的时候,对于一个像素,是将其看成一个点,然后求光线与几何体的交点,然后再在这个交点的位置做一个半球,将光线视作一个锥体射向了交点,这样做是因为光线不仅有方向也有粗细,它不只是一条简单的射线。那么也就是说,可以对一个点衡量它的单位立体角的辐射通量,那么这就是辐射强度的定义。辐射强度用 表示, ,立体角是不随距离的变化而变化的,因此辐射强度也不随着距离的变化而变化。
辐射率(Radiance)
首先有一点一定要记住就是,在基于物理的着色中辐射率就是最终人眼看到的颜色。现在考虑人眼看物体的一个事实:在正常情况下,对于一个物体来说,它离人眼的距离并不影响人眼看到的这个物体的颜色,一个物体离人眼越远,它确实会变小,但它的颜色还是不变的,该是蓝色的汗是蓝色,而不会因为离的远了,就变成了红色或者其他颜色,因此辐射率首先一定是与距离无关的。再进一步想一下,当物理离人眼远了,那么物体的辐照度实际上是变小了的,也就是人眼收到的辐射通量肯定是变化了的,那么为什么颜色还能不变?原因就是在辐照度变小的同时,立体角也变小了,如图所示。既然辐射率表示颜色,那么在辐照度一定的时候,辐射率与立体角就成反比;在立体角一定的时候,辐射率就与辐照度成正比,那么就可以大胆地写出辐射率的定义式: ,其中L是辐射率, 是投影面积。
这就是辐射学的一些基本量的定义,最困扰我的地方是为什么这么多量要考虑单位面积、单位立体角下的辐射通量?我个人的理解是,这要考虑人眼看物体的规律,假设一个面发出了一定能量的光,那么换个更大的平面发出等量的光,人眼看到的实际效果还会一样吗?肯定不会,因为分散到平面上的每个部分的光实际上是变化了的,因此这个平面整体的颜色是会变化的。再考虑有一个光源,它发出的光线粗细发生变化,但光的能量通量还是不变,肯定是更细的显得更加刺眼,因为它分散到每个方向上的光的通量更大,这也就是为什么要考虑到单位立体角的原因。还有一些光源,它在各个方向上发出的光的通量还是不同的,那么这就更需要考虑到立体角了。
还有一个问题就是,立体角与面积的大小是有关系的,可能在理解单位立体角和单位面积的时候总觉得它们是有关系的,容易混淆。可以这样想,立体角实际上是方向的集合,面积是位置的集合,考虑单位面积时一般是对一个有面积的区域作分析,而考虑单位立体角时一般是对一个点作分析,这两者在辐射学基本量的定义中其实是没什么联系的。
BRDF的定义与性质
BRDF的全名是bidirectional reflectance distribution function,它的定义式为
其中 是出射光的辐射率, 是入射光的辐照度。
这样一个式子有两个问题:
1.它为什么要定义两个微分的比值? 2.为什么是辐射率和辐照度的比值?
第一个问题在《Real Time Rendering》关于BRDF的这一章中有这样的描述:
The discussion in this chapter is restricted to shading with non-area
light sources such as point or directional lights. In this case, the BRDF
definition can be expressed in a non-differential form.
由此可见,微分的形式是为了表示面光源的情形,我的理解是微分是用来表示面光源的微小点光源
对于第二个问题,查阅了一些资料,较权威的解释是跟测度有关,详情可见brdf为什么要定义为一个单位是sr-1的量?
个人感觉其实不太必要在这个地方纠结,BRDF只是一个反映入射光和反射光关系的函数,理解这一点就好了。
BRDF的非微分形式为
这个式子应该很好理解,分子就是出射光在v方向上的辐射率,分母是入射光l在垂直于它的平面上的辐照度。
而我们着色要做的是求出射光的辐射率,因此我们的目标是利用已知的 来计算 即
其中k是光源的编号 。 符号表示各个分量相乘,因为BRDF和辐射率、辐照度都是三维向量,分别表示RGB的数据。
BRDF的性质:
有两个物理约束,第一
就是说互换入射光和反射光的方向,BRDF的结果应该是不变的,这一点是基于光路的可逆性,这个性质可以用来判断给定的BRDF的物理理论方面的可靠程度。
第二就是能量守恒,出射的能量不能高于入射的能量,在离线渲染中为了保证最终渲染的效果,这一点是要被严格遵守的,而在实时渲染中,只需要近似地接近能量守恒就好了。
半球定向反射率(directional-hemispherical reflectance)
半球定向反射率
是一个和BRDF有关的函数,它被用来衡量一个BRDF的能量守恒的程度,尽管它的名字看起来好像很屌,实际上也不过是一个很普通的概念。它测定从一个给定方向的入射光一共反射了多少出去,而不考虑它反射到哪些方向上去了,从根本上来说,它其实描述了一个给定方向的入射光的能量损失。它的定义式为:
同样地,由于我们目前只考虑非面光源,它的非微分形式为:
其中 是入射光在垂直于它的平面部分上的辐照度,而M是出射光的辐出度,辐出度和辐照度都是辐射通量对面积的密度。
由于能量守恒, 的值应该位于0到1之间,当它为0时表示所有的入射光都被吸收了,当它为1时表示所有的入射光都被反射出去了,注意, BRDF和 都与波长有关,所以 是一个三维向量,分别代表R、G、B的值,并且它的每一个分量都在0-1之间,但是BRDF并没有这样的限制,作为一个分布函数,BRDF在某些方向上的值可能会非常高,前提是BRDF描述的分布状况是高度不均匀的(non-uniform)。
与BRDF的关系式为:
其中区域 表示在整个半球面上积分, 表示的是出射光线与法线的夹角,那么这个式子的意思就是说, 等于BRDF在所有可能的出射方向上的值乘上它与法线夹角的cos的积分。
菲涅尔反射率
光不仅有反射现象,还有折射现象,在光经过物体表面是,并不是所有的都被表面反射了,还有一部分透过表面进入了表面内部发生折射现象,发生反射的比例可由菲涅尔反射率(Fresnel Reflectance)
来表示,那么进入物体内部的部分就是
,光线在物体内部发生了偏折,它的投影面积和立体角都会发生变化,入射光的辐射率和折射光的辐射率的关系为:
如图所示
由高中物理选修课本上的讲述, 与 满足
因此
菲涅尔反射率的性质:
当光线从空气进入物体中时:
当 时,即光线与表面垂直时, 有一个初始值,这个初始值与表面材质的性质有关, 可被看做是材质的高光颜色(Specular Color)
当 增加, 是呈逐渐增加的趋势,最终当 会到达1。
一些材质的菲涅尔反射率的变化曲线如图所示,可以观察到,这个曲线是高度非线性的,当 时,曲线变化非常猛烈,并且快速到达1。而在60°之前,变化都不明显。在实际编码实现中不可能完全按照曲线的值来做,要做曲线的近似,常用的一种近似方式是:
这种近似曲线唯一需要确定的就是 的值,通常光线都是从空气射入物体的,因此可以直接用物体表面的材质的折射率来近似计算 的值:
在《Real Time Rendering》中给出了一些典型的菲涅尔反射率的值:
光线从物体内部射向空气时
之所以要区分这两种情况,是因为物体内部的折射率相对于空气的折射率大部分都是大于1的,因此在有出射的情况时,出射角都是大于入射角的,如图所示
并且这时入射角只有在满足一定的范围时才会发生折射,即存在一个临界值
,只有当
小于等于
时才有折射现象。基于这一点,从内部射向空气时
的变化曲线肯定和之前有所不同
如图所示,两种情况的曲线对比。
对于内部射向空气的曲线,最关键的就是确定
,
当处理光线从内部射向空气时,有两种方式:
1.修改近似曲线,使之与内部射向空气的曲线拟合
2.计算折射角,转换到从空气射向内部的曲线中计算
之后会看到,菲涅尔反射率会作为BRDF中的一个系数。
局部次表面散射
像雪、地面、皮肤这样的绝缘体,他们的结构都是很复杂的,包含了很多的不连续性,当光线进入这些物体的内部后,可能会被部分或完全地吸收,那些没有被吸收掉的光线,会从这些物体的表面再一次射出,为了给这样的一种反射也建立一个BRDF模型,必须假定再次射出的光线是从它原本入射的点射出的,如果不作这样的假设是没办法建立一个BRDF模型的,这种情况需要全局光照技术来解决,这里不深究。像假设这样的情况又被称作局部次表面散射(local subsurface scattering),而非假设的那种情况叫做全局次表面散射(global subsurface scattering)。
规定进入物体内部的光线中又射出的那部分光线的能量与进入物体内部的光线的总能量的比值为ρ,ρ的值显然介于0-1之间,0表示进入物体内部的光线被完全吸收,1表示进入物体内部的光线完全重新射出,这依旧是与波长有关的量,并且也是一个三维向量。像雪粒和空气这种,在内部吸收的情况非常少,ρ应该差不多为0.8左右,然而在大多数情况,像石头、泥土那种实心介质,再反射出来的就比较少,ρ应该差不多为0.15左右。
局部次表面散射模型大多都是基于兰伯特模型做的。
微平面理论
现实世界中完全平整的面是不存在的,基本上任何面都是凹凸不平的。微平面理论认为任何平面都是由法线不同的微小的完全平整的面构成的,在人眼观察的时候只有将光线恰好反射入人眼的那部分平面才起了作用,
如图所示,只有那些法线为光线和视线的半向量的平面才起到了反射的效果。那么为了建立这样的模型,首先需要知道法线为给定方向的微平面有多少,或者说占多少比例。即需要一个法线分布函数NDF,用
表示,h表示法线的方向,NDF函数会返回法线方向为h的微表面占所有微表面的比例。
当然,对于一个凹凸不平的平面,并不是所有的微表面都能被光线照射到,即存在遮挡现象。
因此我们需要一个几何衰减因子(Geometrical Attenuation Factor)
表示入射光线为l,反射光线为v的光线中被遮挡而看不到的比例。
还需要考虑到射入平面的光线并不都是被反射了的,也就是需要我们前面提到的菲涅尔反射率
。一个具体的实例是,基于微平面的理论中,Torrance-Sparrow模型的BRDF为:
好了与BRDF相关的一些基础理论就说完了,接下来看一些具体的BRDF模型。
经验模型——Lambertian的BRDF版本
兰伯特光照模型是一个非常简单的经验模型,它只考虑漫反射光,并且认为入射光在各个方向上的反射分布是均匀的,联想到之前所说的那个半球定向反射率,所谓的反射率在这里其实就是物体材质的颜色,因为物体的颜色取决于它反射的光的颜色,在Lambertian模型中就可以认为是它的DiffuseColor,那么由
与BRDF的关系:
其中,在Lambertian模型中, , 是一个常数,因为漫反射光是均匀分布的,那么就可以推出:
那么就得到了Lambertian模型中的BRDF:
在Unity中的实现代码为:
Shader "Custom/LambertBRDF"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
}
SubShader
{
PASS{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
uniform float4 _DiffuseColor;
struct v2f
{
float4 vertPos : SV_POSITION;
float4 vertColor : COLOR;
};
float3 Lambert_BRDF()
{
return _DiffuseColor.xyz; //没有除以π!?
}
v2f vert(appdata_full IN)
{
v2f o;
float3 normalDir = normalize(mul(float4(IN.normal,0.0),_World2Object).xyz);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 brdf = Lambert_BRDF();
o.vertColor = float4(_LightColor0.rgb * brdf * max(0,dot(normalDir,lightDir)),1.0);
o.vertPos = mul(UNITY_MATRIX_MVP, IN.vertex);
return o;
}
float4 frag(v2f v) : COLOR
{
return v.vertColor;
}
ENDCG
}
}
}
注意,在这里BRDF项并没有除以π,为什么呢?看BRDF的公式:
这里左边是辐射率,是颜色,而右边呢,右边是入射光的辐照度,那可不是颜色,而在上述程序中,是直接用的入射光的颜色来算的,那么在《Real Timer Rendering》中有如下描述:
The version of this equation typically used in real-time rendering applications
lacks the 1/π term. This is because real-time applications typically
factor this term into the light’s irradiance (effectively using ELk/π as the
light property indicating color and brightness, instead of ELk ).
也就是说在实时渲染的应用程序中,一般都是将辐照度\π作为颜色,因此,在_LightColor0.rgb中实际上已经暗含除以了一个π,于是就不需要再除π了,不然最终的颜色就会变得非常暗。
下面两张图是分别将材质的颜色设为纯红色时,算BRDF时带上除以π和不带的情况,可以看见,除以π的结果非常不好。
这也提醒我们,如果在看一些光照模型时,如果遇到了一些论文中定义了BRDF除以了π什么的,需要仔细考虑在实际应用中是否需要除。
经验模型——Phong的BRDF版本
首先来看一下Phong模型的计算式:
其中, 为入射光和法线的夹角, 为反射光和视线的夹角, 和 都是材质本身的属性, 是入射光的 颜色,注意不是 辐照度。即: 那么由
可得:
首先要认识到它的漫反射项的反射率是和兰伯特模型一样的, ,但是它的高光项的反射率显然不是 ,因此现在考虑如何让
在《Real Time Rendering》中所介绍的对Phong模型修改的第一步是将高光项分母项 直接去掉,因为当 趋近于90°的时候,BRDF趋近无穷了,这样在最终的光照中很有可能在90°的时候出现颜色的突变
去掉 对于原Phong经验模型来说,其实就是在其高光项上又乘了一个 ,即在高光项上也考虑到了光线倾斜照射时辐照度的一个减弱,这其实是更符合物理规律的。
好的,现在BRDF变成了:
现在要保证
这个不等式在左侧取最大值的时候要取等,其中k是要对其作修正的一个系数。
注意到这个积分项目前的值是不固定的,它不好积或者说不能积,它的大小与视线和出射光线的夹角有关,但是,可以求出它的最大值。想一下什么时候出射光的能量是最大的?很显然,当入射光垂直照射到表面的时候,出射光的能量一定最大!于是这个时候光线和法线就满足:入射光、反射光、法线平行,这个时候还有一个重要的事实是, 与 相等!因为这个时候视线的方向就代表着半球积分中所有可能的出射光方向!于是这个积分就可以求得:
因此如果取
修正后的BRDF被称作 BRDF Normalization。
Normalized BRDF的好处是参数m既控制了光线的强度,也控制了光线的分布,在这种实现中,当高光的变窄时,亮度也会变大,但在原始的Phong BRDF中,这是达不到的一个效果。
Phong BRDF在Unity中的实现如下:
Shader "Custom/PhongLighting"
{
Properties
{
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
_Mfactor("m",Float) = 10
}
SubShader
{
PASS
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
uniform float4 _DiffuseColor;
uniform float4 _SpecularColor;
float _Mfactor;
struct v2f
{
float4 vertPos : SV_POSITION;
float3 normalDir : TEXCOORD0;
float3 viewDir : TEXCOORD1;
float3 lightDir : TEXCOORD2;
};
float3 Phong_BRDF(float3 view,float3 lightDir,float3 normalDir)
{
float3 reflectDir = normalize(reflect(-lightDir, normalDir));
float3 specular = (_Mfactor + 2.0 ) / 2.0 * _SpecularColor * pow(max(0,dot(view,reflectDir)),_Mfactor);
return _DiffuseColor.xyz + specular;
}
v2f vert(appdata_full IN)
{
v2f o;
float3 normalDir = normalize(mul(float4(IN.normal,0.0),unity_WorldToObject).xyz);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 view = normalize(UnityWorldSpaceViewDir(IN.vertex));
o.normalDir = normalDir;
o.viewDir = view;
o.lightDir = lightDir;
o.vertPos = UnityObjectToClipPos(IN.vertex);
return o;
}
float4 frag(v2f v) : COLOR
{
float3 brdf = Phong_BRDF(v.viewDir,v.lightDir,v.normalDir);
float4 color = float4(_LightColor0.rgb * brdf * max(0,dot(v.normalDir,v.lightDir)),1.0);
return color;
}
ENDCG
}
}
}
上下两图分别是Normalized和非Normalized的Phong BRDF模型在m取不同值下的效果,显然上边的高光效果要好。
经验模型——Blinn-Phong的BRDF版本
Blinn-Phong与Phong模型唯一的区别就是高光项上,Phong模型用的是视线与出射光的夹角,而Blinn-Phong将其改为入射光与视线的半向量和法线的夹角。
其中 为法线和半向量的夹角。
和Phong模型基本相似,它也要作Normalize处理,这里推导一下它的Normalize系数:
依旧假设入射光、反射光、法线平行,这样半向量与法线的夹角就是视线与法线夹角的1/2。因此可以求高光项的R(l):
对积分号下的做计算:
设
则原式可化为:
因此系数应该为
而实际多使用:
Cook-Torrance模型
关于这个模型下面这篇文章讲的非常好了,限于时间就不详述了……
基于物理着色:BRDF
欢迎交流讨论、指出错误