第九章反射模型本章定义了一组类来描述光源是如何在表面上散射的。
回忆一下,在第5.4节中我们介绍了双向反射分布函数(BRDF),来描述光在某个表面上的反射;又介绍了BTDF来描述在表面上的透射,而BSDF把这两种效果综合起来。
描述真实表面上的散射的最好方法常常是多个BRDF和BTDF的综合;在第10章里,我们将介绍一个BSDF对象,将多个BRDF和BTDF组合在一起来表示光在表面上的总体上的散射效果。
本章不考虑在表面上反射性质和透射性质发生变化的情况,在第11章中,描述纹理的类会处理这个问题。
表面反射模型有几个来源:1. 实验数据:许多真实世界中的表面的反射分布性质是在实验室里测量出来的。
这些数据可以直接放在表格里使用,或者用来计算一组基函数的系数。
2.现象学模型:建立描述真实世界里表面的性质的方程,并且这些方程可以很有效地模拟出这些性质。
像BSDF这类模型就非常好用,因为它们可以用直观的参数来改变模型的行为(例如“粗糙度”)。
计算机图形学中的许多反射函数都属于这个范畴。
3.仿真:有时我们知道一个表面的组成部分的底层信息。
例如,我们可能知道一种颜料是由悬浮在介质中的大小大致相同的彩色颗粒组成的,或者知道一种编织物是有两种类型的线构成,每种线都有已知的反射特性。
在这样的情况下,我们可以通过模拟微观几何的散射来得到反射数据。
这个模拟过程可以是在渲染中进行的,也可以是一种预处理过程,并由此拟合出一组基函数以备后用。
4. 物理(波动)光学: 有些反射模型是由一种非常精细的光模型推衍出来的,其中光被视为一种波,并通过解麦克斯韦方程来研究光在表面上的散射过程。
这些模型通常需要耗时的计算,却并不一定会得到比几何光学模型更精确的结果。
5,几何光学:跟仿真方法相似,如果知道了表面的底层的散射和几何性质,就可以从这些性质中推导出有解析表达式的反射模型。
几何光学模型使得光对表面的交互作用更容易处理,因为诸如偏振现象的复杂效应被忽略了。
基本术语为了比较不同反射模型的视觉效果,我们介绍一些关于表面反射的基本术语。
表面反射可以分为四大类:漫反射(diffuse),光泽镜面反射(glossy specular),理想镜面反射(perfect specular),逆反射(retro-reflective)。
大多数表面的反射是这四类反射的混合。
漫发射将光线均等地向所有方向上散射。
虽然很难见到理想的漫反射表面,但接近漫反射的例子有无光泽的黑板,无光涂料等等。
象塑料、高光泽涂料这样的光泽镜面表面向一组特定的方向进行散射--它们可以映照出其它物体的模糊的映像。
理想镜面表面只向一个单一的方向散射。
镜子和玻璃是理想镜面表面的例子。
最后,象天鹅绒或在地球上看到的月亮这样的逆反射表面主要向入射光方向对光线进行散射。
(如图:a -漫反射, b -光泽镜面反射, c -理想镜面反射, d -逆反射)。
给定了一个反射类型,反射分布函数可以是各向同性(isotropic)的或者是各向异性(anisotropic)的。
大多数物体是各向同性的:如果选定表面上的一个点绕着该点的法向量旋转它,所反射的光的总量不变。
相反地,对各向异性材料做这样的旋转,就会反射出不同量的光。
各向异性表面的例子有被擦亮的金属、唱机唱片和CD盘片。
几何设置在pbrt中的反射计算是在一个反射坐标系中进行的,在该坐标系中,被着色的点上的两个切向量和法向量分别跟x,y,z轴对齐,所有BRDF和BTDF函数的传入向量和返回向量都是在这个坐标系下定义的。
理解好这个坐标系对后面理解BRDF和BTDF的实现非常重要。
着色坐标系还给出了一个用球面坐标(θ,Φ)来表示方向的标架;角度θ是给定方向跟z轴的夹角,Φ是给定方向在xy平面上的投影跟x轴的夹角。
(如图)给定了在该坐标系中的一个方向,就可以很容易地计算出它与法向量夹角的余弦,等等:cos θ = (n . ω) = ( (0, 0, 0) . ω) = ωz下面就是求这个余弦值的工具函数:<BSDF Inline Function> =inline float CosTheta (const Vector &w) { return w.z;}利用sin2θ + cos2θ = 1, 可得到相应的正弦值:<BSDF Inline Functions> +=inline float SinTheta(const Vector &w) {return sqrtf(max(0.f, 1.f - w.z * w.z));}下面是求sin2θ的工具函数:<BSDF Inline Functions> +=inline float SinTheta2(const Vector &w) {return 1.f - CosTheta(w) * CosTheta(w);}类似地我们可以用着色坐标系统来简化关于Φ的正弦和余弦计算。
在着色点所在的平面上方向ω有坐标(x, y),分别为r cosΦ 和r sinΦ。
而半径r即是sin θ,因此:cos Φ = x / r = x / sin θsin Φ = y / r = y / sin θ<BSDF Inline Functions> +=inline float CosPhi(const Vector &w) {return w.x / SinTheta(w);}inline float SinPhi(const Vector &w) {return w.y / SinTheta(w);}我们要遵守的一个约定是,入射光ωi和向外的观察方向ωo 在被变换到表面上的局部坐标系以后,都是被正规化的,且方向都朝外。
根据约定,表面法向量总是指向物体之外的,这就很容易确定光线是进入还离开透光物体:如果入射光方向ωi跟n在同一个半球,则光线是在进入,否则就是在离开。
因此,需要记住的这一点:表面法向量可能跟ωi或者ωo(或者两者都有)不在物体的同一侧。
跟许多其它渲染器不同的是,pbrt并不为了保持ωo和法向量在表面同一侧而翻转法向量。
所以,在实现BRDF和BTDF时就不能做这样的假定。
还有,应该注意用于着色的局部坐标系可能并不等同于Shape::Intersect()所返回的坐标系,它们在求交过程和着色过程之间可以做些改动以达到象凸凹纹理映射(bump mapping)这类的效果。
在阅读本章时还要注意的一点是,BRDF和BTDF的实现不应该关心ωi和ωo是否位于同一个半球。
例如,虽然一个反射BRDF应该探测是否入射光方向在表面的上面并且出射方向在表面的下面,从而可判定在这种情况下不存在反射,但是我们希望反射函数能够利用反射模型中的相应公式来计算光的反射量,而忽略它们是否在同一个半球这个细节。
更高层的pbrt代码会保证反射例程或透射的散射例程能在恰当的时机被调用。
9.1 基本接口首先我们定义BRDF和BTDF函数的接口。
BRDF和BTDF共享一个基类,BxDF,它定义了要实现的基本接口。
两类函数共用一个接口,共享一个基类,可以减少重复性代码,也可以是系统的某些部分可以使用BxDF而不必区分BRDF和BTDF。
<BxDF Declarations> =class COREDLL BxDF {public:<BxDF Interface><BxDF Public Data>};将要在第10.1节介绍BSDF类存放一组BxDF对象来共同地描述表面上一个点的散射情况。
虽然我们通过使用一个共同接口而隐藏了关于反射材质和透射材质的实现细节,但是第16章所介绍的一些光传输算法需要区分这两种类型。
因此,所有的BxDF要有一个BxDF::type成员还存放BxDFType标志。
对于每个BxDF而言,标志必须在BSDF_REFLECTION或BSDF_TRANSMISSION集合中取一个值,而且必须是漫反射、光泽反射和镜面反射标志之一。
注意这里没有逆反射标志,这里的分类将逆反射视为光泽反射。
<BSDF Declarations> =enum BxDFType {BSDF_REFLECTION = 1 << 0,BSDF_TRASNMISSION = 1 << 1,BSDF_DIFFUSE = 1 << 2,BSDF_GLOSSY = 1 << 3,BSDF_SPECULAR = 1 << 4,BSDF_ALL_TYPES = BSDF_DIFFUSE | BSDF_GLOSSY | BSDF_SPECULAR,BSDF_ALL_REFLECTION = BSDF_REFLECTION | BSDF_ALL_TYPES,BSDF_ALL_TRASNMISSION = BSDF_TRASNMISSION | BSDF_ALL_TYPES,BSDF_ALL = BSDF_ALL_REFLECTION | BSDF_ALL_TRASNMISSION};<BxDF Public Data> =const BxDFType type;<BxDF Interface> =BxDF(BxDFType t) : type (t) { }MatchesFlags()工具函数用来确定BxDF是否具备用户提供的标志:<BxDF Interface> +=bool MatchesFlag(BxDFType flags) const {return (type & flags) == type;}BxDF的关键函数是BxDF::f()。
它返回给定的一对方向所对应的分布函数值。
这个接口隐性地假定不同波长上的光是不相干(decoupled) 的,即一个波长上的能量不会以不同的波长被反射出去。
这样一来,就不会支持萤光材料了。
有了这个假定,反射函数的结果就可以直接用一个Spectrum来表示:<BxDF Interface> +=virtual Spectrum f(const Vector &wo, const Vector &wi) const = 0;并不是所有的BxDF都用该函数求值。
例如,象镜子、玻璃或水这样的全镜面反射物体只在单一出射方向上对单一方向的入射光进行散射。
描述这样的BxDF的最好方法是delta分布函数:除了出射方向之外,其它方向的值都为0.在pbrt中,这些BxDF需要特殊的处理,所以我们提供BxDF::Sample_f()函数。
我们用这个函数处理由delta分布来描述的散射,也用它来对BxDF所散射出的多个光线的方向进行随机采样(见第15章)。