第12章体积散射.docx
《第12章体积散射.docx》由会员分享,可在线阅读,更多相关《第12章体积散射.docx(20页珍藏版)》请在冰豆网上搜索。
第12章体积散射
第12章体积散射到目前为止,我们都假定场景中的所有表面都在真空中,这意味者在表面之间的光线的辐射亮度是恒定的。
然而,在许多真实世界中的情景中,这个假设并不精
确:
雾和烟对光产生了衰减和散射作用,大气中的粒子的散射作用使得蓝天呈蓝色,使得夕阳晚景呈红色。
本章介绍相关的数学知识来描述参与介质(participating
media--分布于三维空间区域中的粒子)对光的影响。
有了对参与介质的效果的模拟,我们就可以渲染场景有雾霭的图像、穿透云层的光束、子表面的散射(即光
线的出射点跟入射点不同)。
12.1体积散射过程
在有参与介质的环境中,有三个主要过程可以影响辐射亮度的分布:
吸收(Absorption):
由于光能转变成其它形式的能量(如热)而减弱了辐射亮度。
放射(Emission):
发光的粒子增强了环境中的能量。
散射(Scattering):
由于光跟粒子的碰撞,在一个方向上传播的光被散射到其它方向上去。
所有这些特性既可以是均质的(homogeneous),也可以是非均质的(inhomogeneous)。
均质的特性在给定的一个空间范围之内是一个恒量,而非均质的特性在空间内
是变化的。
下图显示了一个体积散射的例子:
一个聚光灯照亮了参与介质,并投射出一个体积阴影(volumetricshadow)。
12.1.1吸收
考虑一下火产生的浓烟:
烟遮挡了后面的物体,因为烟的颗粒吸收了从物体到观察者的光线。
烟越浓,被吸收的光就越多。
下图显示了烟的这种特效,其体积密度是由一个模拟烟的精确物理模型生成的。
注意地面上的阴影:
参与介质已经吸收了光源和地面之间的光线,从而投射出一个阴影。
吸收是通过介质的吸收截面ca来表述的,它是光线在介质中单位距离之内被吸收的概率密度。
通常情况下,吸收截面随着位置p和方向3的变化而变化,虽然
严格地讲它是关于位置的函数,但是它通常也是随光谱变化的量。
ca的单位是距离的倒数(m-1)。
这意味者ca可以取任意正值,并不需要限定在0到1之内。
下图显示了沿着很短一段光线上的吸收效果。
厶(PT)
在点p上有一定的辐射亮度Li(p,-3)我们希望求出在微分体积之内被吸收后的出射辐射亮度Lo(p,3沿光线微分长度dt的辐射亮度变化值可以通过下面的微分
方程来表示:
Lo(p,3)他-3)=0血3)=a(p,c3i)pL-3)dt,
也就是说,沿着光线的辐射亮度的微分减少值跟其初始值呈线性关系。
这个微分方程的解实际上是要给出描述光线上被吸收的光的总比率的积分方程。
如果我们假定光线以p为起始点在介质中沿方向3上行进了距离d,则总的吸收
比率是:
12.1.2放射
在介质中,吸收过程对沿光线的辐射亮度有减弱作用,而放射过程却有增强作用,原因是由于化学、热力学或核作用将能量转换为可见光。
我们记在点p处的3
方向上单位距离上增加的出射辐射亮度为Lve(p,3那么有下列微分方程:
dLo(p,3)ve(p,3)dt.
注意这个方程基于这样的假定:
即出射光Lve是不依赖于入射光Li的。
在pbrt的线性光学的前提下这个假定总是成立的。
12.1.3外散射(out-scattering)和消光(extinction)
在参与介质中,第三个光的基本交互作用是散射。
当一束光穿过一种介质中,它可以跟介质中的粒子碰撞并被反射到不同的方向上。
这对光束的总辐射亮度有两
个效果:
它减弱了光束出离一个微分区域时的辐射亮度,因为部分光线被反射到其它方向上。
这个效果被称为外散射(out-scattering),即本节要介绍的主题。
然
而,其它光线的辐射亮度也会被加到当前的光线的路径中,这是下一节要介绍的内散射(in-scattering)。
单位距离内的外散射事件发生的概率由散射系数给c给出。
跟衰减系数类似,外散射在微分长度dt上所产生的辐射亮度减少值可以通过下列方程给出:
dLo(p,3)=s(p,c3i(p,-3)dt
由吸收和外散射所引起的总辐射亮度的减少是根据c+c来决定的。
这个综合效果被称为衰减或消光(extinction)。
为了方便起见,我们用衰减系数ct来代表两个
系数之和:
a(p,3)=p,(T3)+(p,T3)
有了衰减系数以后,我们可以得到描述总衰减效果的微分方程:
dLo(P,3)/dt=t(p,3(pL-3)
我们通过解这个方程可以得到光束透射率(beamtransmittanee),它给出了一条光线通过两个点所传输的辐射亮度的比率:
Tr{p十pO旺
其中d是点p到点p'的距离,3是两点之间的向量的正规化向量,Tr是两点之间的光束透射率。
如果表面上给定方向3上的出射辐射亮度是Lo(p,3)那么,通
过消光作用之后,在方向3上的入射辐射亮度是:
Tr(ptp')Lo(p,3)
光束透射率有两个很有用的性质:
一个点到它自身的透射率为1,即Tr(ptp)=1在真空中对于所有点p'都有Tr(ptp'=1。
另外,对于所有的介质,在光线上
各点上的透射率有相乘的关系(例如下面p,p',p”三个点):
这个性质对于体积散射的实现而言非常有用,因为我们可以靠计算相继点上透射率的乘积逐步地得到沿光线上的许多点上的透射率。
Tr(ptp')=-etd.
11.1.4内散射(In-Scattering)
外散射由于向不同方向进行散射的缘故减弱了光线的辐射亮度;与之相反的是,由于其它方向上被散射的光线加入到当前光线的方向,内散射加强了光线的辐射
亮度。
假定粒子之间的间距是它们半径的数倍,这样我们就可以忽略粒子之间的交互作用。
在这个假定之下,相函数p(3T3表示了在某点上的散射辐射(scattered
radiation)的角分布。
它相当于体积上的BSDF然而,这个比拟并不准确,因为相函数有一个归一化约束,即对于所有的3,满足下列条件:
这个约束条件意味着相函数实际上定义了在特定方向上散射的概率分布。
内散射所增加的总辐射亮度可以由源项S给出,即:
dLo(p,3)=S(p,3)dt
它包括了体积放射和内散射:
源项的内散射部分是单位距离内的散射概率T和在该点上所增加的辐射亮度的乘积,所增加的辐射亮度可以通过对入射辐射亮度和相函数的乘积进行球面积分
得到。
注意这个公式跟第5章的散射方程很相似,主要的不同是这里没有cosine项,因为相函数使用的是辐射亮度而不是微分辐射照度(differentialirradianee).
12.2相函数
人们为了描述表面上散射研究了许多BSDF模型,同样地,人们也开发了许多相函数来描述体积散射。
其中包括用少数参数来做拟合函数的参数化模型,还有基
于粒子形状和材质(如球形水滴)而推导出的散射辐射分布的解析模型,等等。
在大多数的自然产生的介质中,相函数是关于两个方向3和3的夹角B的一维函数;这样的介质被称为各向同性的(isotropic),它们的相函数常常被写成p(cosB。
)
在比较奇异的介质中,例如有水晶结构的介质,它的相函数是两个方向的4D函数。
除了前面提到的归一化性质以外,自然生成的介质还有一个重要的性质,即
互反性(reciprocal):
两个方向相互交换后,相函数值保持不变。
注意各向同性的介质的互反性很容易从cos(-0)=cos(得到I。
相函数本身可以是各向同性的(isotropic),也可以是各向异性的(isotropic)的。
因为各向同性的相函数描述的是在各个方向上的均匀散射,因此跟两个方向都无关。
1/4n
因为相函数是归一化的,因此只有唯一的各向同性函数,且为常量
=
floatPhaselsotropic(constVector&,constVector&){
return1.f/(4.f*M_PI);
}
本节下面所介绍的各向异性相函数描述的是各向同性介质,因此是用两个方向的夹角来定义的(如图)
跟表面上散射的约定(两个方向都背离散射点)有所不同的是,这里入射方向是指向散射点的,而出射方向是背离散射点的,这跟相函数的约定一致。
pbrt实现了描述Rayleigh散射和Mie散射的相函数。
Rayleigh模型表示了在地球大气中极小粒子(如分子)所产生的散射。
如果粒子半径比光波长入小(r/入《05),
Rayleigh模型就可以很精确地描述散射光的分布。
跟波长相关的Rayleigh散射也是蓝天之所以蓝、晚霞之所以红的原因。
Mie散射基于更一般性的理论:
它是从
Maxwell方程推导出来的,可以描述的粒子半径范围更为宽广。
例如,它可以很好地描述水雾中的天气。
=
floatPhaseRayleigh(constVector&w,constVector&wp);floatPhaseMieHazy(constVector&w,constVector&wp);
在计算机图形学中得到广泛应用的一个相函数是由对称参数),用来控制散射光的分布:
floatPhaseMieMurky(constVector&w,constVector&wp);
Henyey和Greenstein开发的。
这个相函数很容易和测量出的散射数据相拟合。
它有一个单一的参数g(称为非
223/2
Phg(cos0)=(1/4n))/((fl-+jg-2g(cos0))
下图显示了Henyey-Greenstein相函数,它具有可变的非对称参数g,变化范围是(-1,1)。
负的g值对应于向后散射(back-scattering),这时光大多数会在入射方向上
散射(图中的实线部分对应g=-0.35);正的g值对应于向前散射(forword-scattering)(图中的虚线部分对应g=0.67)。
g的值越大,散射方向就越靠近-3(向后散
射)或3(向前散射)方向。
+=
floatPhaseHG(constVector&w,constVector&wp,floatg){
floatcostheta=Dot(w,wp);
return1.f/(4.f*M_PI)*
(1.f-g*g)/powf(1.f+g*g-2.f*g*costheta,1.5f);
}
g:
非对称参数有很严格的意义。
它是相函数和3-3夹角余弦的乘积的平均值。
给定一个任意的相函数,可以用下式计算
这样,对于各向同性相函数来说,g=0。
满足这个方程的相函数有任意多个,单独使用g值不足以唯一地描述一个散射分布。
然而。
我们将复杂的散射分布化简为一个只有一个参数的模型,这样做所带
来的方便比可能的精度上的缺失更重要。
无法用单一的非对称参数描述的更复杂的相函数常常用相函数(如Henyey-Greenstein相函数)的加权和来表示,其中每个相函数带有不同的参数值:
其中所有的wi的和为1以保证正规性。
Blasi,LeSaec,和Schlick开发出了一个Henyey-Greenstein相函数的很高效的近似表示,由于它的高效性而被广泛地应用于计算机图形学领域中:
22
pschlick(cos0)=(1/4n/X(1-k其中参数k跟Henyey-Greenstein中的g项有类似的作用,-1对应于向后散射,0对应于各向同性散射,1对应于向前散射。
两者关系如下:
3k=1.55g-.55g3
+=
floatPhaseSchlick(constVector&w,constVector&wp,floatg){
floatk=1.55f*g-.55f*g*g*g;
floatkcostheta=k*Dot(w,wp);
return1.f/(4.f*M_PI)*
(1.f-k*k)/((1.f-kcostheta)*(1.f-kcostheta));
}
12.3体积区域接口和均匀介质
在pbrt中描述体积散射的核心抽象类是VolumeRegion,它是描述场景中一个区域中的体积散射的接口。
在场景中不同的部分,可以使用不同类型的VolumeRegion
来描述不同类型的散射。
+=
classCOREDLLVolumeRegion{
public:
};
所有的VolumeRegion必须可以计算和轴对齐的世界空间包围盒,它可以通过函数VolumeRegion:
:
WorldBound()来得到。
跟Shape和Primitive一样,这个包围盒可
以用来将VolumeRegion放入加速结构。
=
virtualBBoxWorldBound()const=0;
因为VolumeIntegrator需要知道光线在世界空间中穿过体积区域的参数范围,而且世界包围盒并不一定能够紧包住体积区域,所以我们提供了一个单独的函数
VolumeRegion:
:
lntersectP(),用来返回光线跟该区域重叠的那部分光线的参数t值的范围。
+=
virtualboollntersectP(constRay&ray,float*t0,float*t1)const=0;
这个接口有4个函数,分别对应于前面介绍的散射性质。
给定了世界空间的一个点和方向,VolumeRegion:
:
sigma_a(),VolumeRegion:
:
sigma_s(),VolumeRegion:
:
sigma_Lve()返回相应的吸收、散射和放射性质。
给定一对方向后,VolumeRegion:
:
p()函数返回给定点的相函数值。
+=
virtualSpectrumsigma_a(constPoint&,constVector&,
floattime)const=0;
virtualSpectrumsigma_s(constPoint&,constVector&,
floattime)const=0;
virtualSpectrumLve(constPoint&,constVector&,
floattime)const=0;
virtualfloatp(constPoint&,constVector&,
constVector&,floattime)const=0;
为了方便,还有一个函数VolumeRegion:
:
sigma_t()用来返回给定点上的衰减系数。
它的缺省实现是返回ca和us的和,但大多数的实现会重载该函数,直接计算
出(Tt
+=
SpectrumVolumeRegion:
:
sigma_t(constPoint&p,constVector&w,
floattime)const{
returnsigma_a(p,w,time)+sigma_s(p,w,time);
}
最后,VolumeRegion:
:
tau()函数根据点ray(ray.mint)和点ray(ray.maxt)来计算体积区域的光学厚度。
有些实现可以之间计算出这个值,例如下节要介绍的
HomogeneousVolume,而其它的实现需要MonteCarlo积分来计算。
为了方便使用MonteCarlo方法,该函数使用了两个可选的参数,step和offset,对于使用封闭形式解的实现而言,可以忽略这两个参数。
+=
virtualSpectrumtau(constRay&ray,floatstep=1.f,
floatoffset=0.5)const=0;
11.3.1均匀体积区域
最简单的体积表示是HomogeneousVolume,它描述了具有均匀散射性质的盒形区域。
它的构造器使用了ca和(Ts相函数的g值,还有放射量Lve。
再加上从世
界空间到体积区域的物体空间的变换还有一个轴对齐的物体空间的包围盒,就足够可以描述该区域的散射性质和空间范围。
=
classHomogeneousVolumeDensity:
publicVolumeRegion{
public:
private:
};
=
Spectrumsig_a,sig_s,le;
floatg;
BBoxextent;
TransformWorldToVolume;
由于包围盒是定义在物体空间的,所以在WorldBound()函数中使用到世界空间的变换。
=
BBoxWorldBound()const{
returnInverse(WorldToVolume)(extent);
}
如果区域的世界空间--物体空间变换包含了旋转操作,那么该区域在世界空间里就不是轴对齐的了,但我们可以将光线变换到区域的物体空间中,并求得它们的重叠部分,这样就可以得到更紧密的光线参数范围。
+=
boolIntersectP(constRay&r,float*t0,float*t1)const{
Rayray=WorldToVolume(r);
returnextent.IntersectP(ray,t0,t1);
}
剩下的接口函数都很简单。
它们都先要验证给定点是否是在区域中,如果是的话就返回相应的值。
这里只列出了sigma_a(),其它的函数就略去了。
+=
Spectrumsigma_a(constPoint&p,constVector&,float)const{
returnextent.Inside(WorldToVolume(p))?
sig_a:
0.;
}
由于ca和c在整个区域中是常量,我们可以通过Beer法则以封闭解的形式来计算光学厚度:
+=
Spectrumtau(constRay&ray,float,float)const{
floatt0,t1;
if(!
IntersectP(ray,&t0,&t1))return0.;
returnDistance(ray(t0),ray(t1))*(sig_a+sig_s);
}
12.4密度可变的体积区域
本章所介绍的其它的体积区域基于这样的一个假设:
介质中的粒子有相同的基本散射性质,但其密度是随空间位置变化的。
这个假设所产生的结论之一就是我们
可以用该点的密度乘以某个基准值来描述体积散射性质。
例如,我们可以为衰减系数c设置基准值为0.2。
对于粒子密度为1的区域,c返回0.2。
如果粒子密
度为3,则返回0.6。
为了减少重复性代码,并令不同的表示将重点放在定义粒子密度的方法上,我们定义了一个DensityRegion类,它定义了一个纯虚函数来获取一个点上的粒子密度。
体积区域的表示可以继承DensityRegion,就无需实现相同的逻辑了。
+=
classDensityRegion:
publicVolumeRegion{
public:
protected:
};
---世界空间的变换,但该类存放的是世界空间---体积
DensityRegion构造器使用了基本散射性质的值,并将它们放入相应的成员变量中。
注意接口声明了体积区域区域的变换。
=
TransformWorldToVolume;
Spectrumsig_a,sig_s,le;
floatg;
=
DensityRegion(constSpectrum&sa,constSpectrum&ss,floatgg,
constSpectrum&emit,constTransform&VolumeToWorld)
:
sig_a(sa),sig_s(ss),le(emit),g(gg),
WorldToVolume(Inverse(VolumeToWorld)){}
所有DensityRegion的继承类必须实现DensityRegion:
:
Density(),它返回了体积区域在物体空间中给定点的密度。
该密度用来乘以基本的散射参数,故在任何一点都应该是非负数。
+=
virtualfloatDensity(constPoint&Pobj)const=0;
这里只列出DensityRegion:
:
sigma_a()函数的实现,其它类似,从略:
+=
Spectrumsigma_a(constPoint&p,constVector&)const{
returnDensity(WorldToVolume(p))*sig_a;
}
其中一个例外是DensityRegion:
:
p()函数,它并不是用局部的密