基础术语
表面反射可以分为4大类:
几何坐标系以及工具函数
pbrt中的反射是在反射坐标系中进行计算的。坐标系由着色点处法向量与两个切向量组成,也就是
正交基向量(s,t,n),分别于x,y,z相对齐。以球体坐标系来表达方向。
从图8.3中可以了解到以下内容,代码在reflection.h中
inline Float CosTheta(const Vector3f &w) { return w.z; }
inline Float Cos2Theta(const Vector3f &w) { return w.z * w.z; }
inline Float AbsCosTheta(const Vector3f &w) { return std::abs(w.z); }
inline Float Sin2Theta(const Vector3f &w) {
return std::max((Float)0, (Float)1 - Cos2Theta(w));
}
inline Float SinTheta(const Vector3f &w) { return std::sqrt(Sin2Theta(w)); }
inline Float TanTheta(const Vector3f &w) { return SinTheta(w) / CosTheta(w); }
inline Float Tan2Theta(const Vector3f &w) {return Sin2Theta(w) / Cos2Theta(w);}
inline Float CosDPhi(const Vector3f &wa, const Vector3f &wb) {
return Clamp((wa.x * wb.x + wa.y * wb.y) /std::sqrt((wa.x * wa.x + wa.y * wa.y) *(wb.x * wb.x + wb.y * wb.y)),
-1, 1);
}
基本接口
BRDF与BTDF共享公共基类BxDF。BxDFType为反射属性枚举(按照不同的子类修改里面部分枚举的值)
MatchesFlags()
用于确定当前BxDF的标签是否与用户提供的一样。
f()
返回分布函数值。
完美反射物体例如镜子、玻璃、水之类只能从一个入射方向散射到另一个出射方向,对于这些光线散射情况,就需要特殊处理。这样的BxDF最好是使用delta分布来表示,在此PBRT提供了Sample_f()函数,可以用来处理散射的随机方向采样问题。(使用这个函数氛围使用delta分布与不适用)
virtual Spectrum Sample_f(const Vector3f &wo, Vector3f *wi,const Point2f &sample, Float *pdf,BxDFType *sampledType = nullptr) const;
反射率
半球方向反射率(半球方向上的全反射):
virtual Spectrum rho(const Vector3f &wo, int nSamples,const Point2f *samples) const;
半球-半球反射率(当前半球方向上所有光线都是一样的情况下,类似兰伯特材质):
如果没有提供,计算ρhh,则使用:
virtual Spectrum rho(int nSamples, const Point2f *samples1,const Point2f *samples2) const;
PS.之前看《ray tracing from the ground up》都不知道rho这个函数是啥意思
BxDF缩放适配器
用于取得BxDF的值,并把它缩放至光谱的贡献值。类名为ScaledBxDF
反射高光与透射
在光滑表面(理想状态下)中,反射的出射角与入射角大小相同,遵守菲尼尔定理。一般情况下,计算计算机图形学中的常见操作都会忽略色散现象,以此极大地简化光线传输计算(色散:折射率随着光的波长而变化)。Snell定律:
菲涅尔方程描述了光线接触到表面后反射与透射的比,它实际上Maxwell方程在光滑表面上的求解。
因为在现实环境中光的偏振现象较少,所以在PBRT假设光不偏振。在这个假设下,菲尼尔反射率表示为平行和垂直偏振项的平方的平均值。我们使用表示折射率
对于非偏振光的菲尼尔反射率:
存在几个重要类别需要加以区分:
- 第一类是绝缘体(非金属),即不导电的材料。它们有真实的折射率(通常在1-3范围内),并能透射一部分入射光。例如玻璃、矿物油、水和空气。
- 第二种是导体(金属),比如金属。导体的导电性会对电磁波中的电子产生一定影响,所以与介质不同,导体的折射率较为复杂:·,k为吸收系数。
- 第三种是半导体,本书暂时不考虑。
因为能量守恒的关系,所以绝缘体中透射的能量为1-,FrDielectric函数计算了非偏振光对于绝缘体的菲尼尔反射:
Float FrDielectric(Float cosThetaI, Float etaI, Float etaT) {
cosThetaI = Clamp(cosThetaI, -1, 1);
//为了计算透射角的cos值,必须确定是在介质内还是外
bool entering = cosThetaI > 0.f;
if (!entering) {
std::swap(etaI, etaT);
cosThetaI = std::abs(cosThetaI);
}
// Snell定律计算cosThetaT
Float sinThetaI = std::sqrt(std::max((Float)0, 1 - cosThetaI * cosThetaI));
Float sinThetaT = etaI / etaT * sinThetaI;
if (sinThetaT >= 1) return 1;
Float cosThetaT = std::sqrt(std::max((Float)0, 1 - sinThetaT * sinThetaT));
Float Rparl = ((etaT * cosThetaI) - (etaI * cosThetaT)) /
((etaT * cosThetaI) + (etaI * cosThetaT));
Float Rperp = ((etaI * cosThetaI) - (etaT * cosThetaT)) /
((etaI * cosThetaI) + (etaT * cosThetaT));
return (Rparl * Rparl + Rperp * Rperp) / 2;
}
导体与绝缘体分界面处的菲尼尔反射率为:
其中:
$\eta + ik = \overline {\eta_t}/\overline{\eta_i}$为这个复杂计算后的折射率。但是因为$\overline{\eta_i}$是绝缘体的关系,这个部分的计算可以被代替。
Spectrum FrConductor(Float cosThetaI, const Spectrum &etai,
const Spectrum &etat, const Spectrum &k) {
cosThetaI = Clamp(cosThetaI, -1, 1);
Spectrum eta = etat / etai;
Spectrum etak = k / etai;
Float cosThetaI2 = cosThetaI * cosThetaI;
Float sinThetaI2 = 1. - cosThetaI2;
Spectrum eta2 = eta * eta;
Spectrum etak2 = etak * etak;
Spectrum t0 = eta2 - etak2 - sinThetaI2;
Spectrum a2plusb2 = Sqrt(t0 * t0 + 4 * eta2 * etak2);
Spectrum t1 = a2plusb2 + cosThetaI2;
Spectrum a = Sqrt(0.5f * (a2plusb2 + t0));
Spectrum t2 = (Float)2 * cosThetaI * a;
Spectrum Rs = (t1 - t2) / (t1 + t2);
Spectrum t3 = cosThetaI2 * a2plusb2 + sinThetaI2 * sinThetaI2;
Spectrum t4 = t2 * sinThetaI2;
Spectrum Rp = Rs * (t3 - t4) / (t3 + t4);
return 0.5 * (Rp + Rs);
}
为了方便起见,PBRT抽象了一个Fresnel类,并且提供一个Evaluate()方法,用于估算并且返回菲尼尔反射率。在此基础上创建了FresnelConductor、FresnelDielectric、FresnelNoOp类。
FresnelConductor类存储了入射折射率、透射折射率以及吸收系数k。
FresnelDielectric类存储了入射折射率与透射折射。
FresnelNoOp类为菲尼尔反射率为1的情况。在现实生活中不太可能,但为一些操作提供便捷。
反射
SpecularReflection类继承自BxDF类。
其反射公式:
如果使用狄拉克分布来构造这个BRDF,则有:
如果希望BRDF在除了完美反射的地方,别的地方皆为0,就建议使用狄拉克分布,则有:
将其带入方程后得:
然而它并不正确,因为有了个额外参数,将因子分离出来,得到正确结果:
因为对于任意方向,delta函数不会返回散射值的原因,所以f()返回0.
然而PBRT确实实现了Sample_f()方法,它根据delta分布选择适当的方向。它将输出变量wi设置为提供的方向wo在表面法线附近的反射。*pdf值设置为1;
Spectrum SpecularReflection::Sample_f(const Vector3f &wo, Vector3f *wi,const Point2f &sample, Float *pdf,BxDFType *sampledType) const {
*wi = Vector3f(-wo.x, -wo.y, wo.z);
*pdf = 1;
return fresnel->Evaluate(CosTheta(*wi)) * R / AbsCosTheta(*wi);
}
如何求反射向量?参看图8.8就明白了。代码:
inline Vector3f Reflect(const Vector3f &wo, const Vector3f &n) {
return -wo + 2 * Dot(wo, n) * n;
}
因为由于在BRDF坐标系系统中,法向量为(0,0,1)所以:*wi = Vector3f(-wo.x, -wo.y, wo.z);
透射
我们使用τ来表示光线透射过介质的能量(别的都被反射了):
则该介质透射的辐射通量的微分为:
使用辐射学的定义代替后:
将平面角度坐标系转换到球面角坐标后:
将Snell定律进行变换,对于θ有:
将其代入公式8.7中则有:
因为所以,从而得到:
对于BRDF,我们需要约去一个去取得一个正确BTDF用于透射计算。
其中为函数通过法向量与出射角计算出来的透射向量。
因为BTDF是一个缩放后的狄克拉函数,所以f()返回0。
inline bool Refract(const Vector3f &wi, const Normal3f &n, Float eta,
Vector3f *wt) {
Float cosThetaI = Dot(n, wi);
Float sin2ThetaI = std::max(Float(0), Float(1 - cosThetaI * cosThetaI));
Float sin2ThetaT = eta * eta * sin2ThetaI;
if (sin2ThetaT >= 1) return false;
Float cosThetaT = std::sqrt(1 - sin2ThetaT);
*wt = eta * -wi + (eta * cosThetaI - cosThetaT) * Vector3f(n);
return true;
}
Spectrum SpecularTransmission::Sample_f(const Vector3f &wo, Vector3f *wi,const Point2f &sample, Float *pdf,BxDFType *sampledType) const {
//确定哪个折射率为透射哪个为入射
bool entering = CosTheta(wo) > 0;
Float etaI = entering ? etaA : etaB;
Float etaT = entering ? etaB : etaA;
//计算透射向量
if (!Refract(wo, Faceforward(Normal3f(0, 0, 1), wo), etaI / etaT, wi))
return 0;
*pdf = 1;
//透射量计算
Spectrum ft = T * (Spectrum(1.) - fresnel.Evaluate(CosTheta(*wi)));
//如果是辐射学模式的话再乘以两个介质的折射率平方商
if (mode == TransportMode::Radiance) ft *= (etaI * etaI) / (etaT * etaT);
return ft / AbsCosTheta(*wi);
}
菲尼尔模式下的反射与透射
为了在蒙特卡洛光线传递算法中(14章~16章会介绍)有更好的效率,我们将会使用一种同时带有折射与反射的BxDF。类名为:FresnelSpecular。具体内容详见代码(会在13~14章介绍具体内容)。
LAMBERTIAN反射
类名为:LambertianReflection。
f()返回 辐射度/π
基于微表面的反射模型
OREN–NAYAR漫反射模型
Oren与Nayar在1994年发现现实世界的物体并没有表现出完美的Lambertian反射具体来说,粗糙的表面通常会随着光照方向接近观察方向而变得更亮。
于是他们开发了一个描述粗糙表面反射模型,基于v形状的微表面,使用球形高斯分布参数与一个变量σ描述。σ为光线与视线的偏差角。
因为假设在V形状微表面的情况下,所以仅需考虑相邻的微表面可以互相反射。这是一个经验近似模型:
其中σ为弧度制:
代码参见:OrenNayar::f(const Vector3f &wo, const Vector3f &wi)
微表面分布函数
MicrofacetDistribution为微表面分布的基类。其中一个重要特征就是微表面分布函数D(ωh)。该函数定义于与BxDF相同的BSDF坐标系中。
因此,当ωh等于面法线,且狄克拉函数结果不为0的情况下,完美的光滑表面就可以用狄克拉函数来描述。D(ωh)=δ(ωh−(0,0,1))。
微面分布函数必须normalized,以确保它们在物理上是正确的。直观地说,如果我们考虑沿法线方向n的入射光线,那么每条光线必须与微平面精确相交一次。更正式地说,给定微表面的微分面积dA,那么该面积以上的微面投影面积必须等于dA(参见图8.15)。
MicrofacetDistribution::D()对应D(ωh)。返回
微表面朝向与法向量对于该区域面积的微分。
Beckmann和Spizzichino(1963)提出了一种基于微面坡度高斯分布的微面分布函数,该函数得到了广泛的应用;实现在BeckmannDistribution类中。Beckmann–Spizzichino模型定义为:
如果σ代表了微表面斜度的平方根,那么α=√2σ
这对定义各项异性非常有用,同时正态分布也取决于ωh的方位取向各不相同。那相对的微表面的各向异性分布函数为:
注意,当时,该公式将会变成各项同性。
另一种有用的微表面分布函数来自Trowbridge and Reitz (1975).具有各项异性变化:
与Beckmann–Spizzichino相比,Trowbridge–Reitz精度更高,但是速度更慢。
遮蔽与阴影
但是微表面的法线分布还是不够的,还需要考虑到可见性问题。这个效果可以用Smith’s masking-shadowing函数G1(ω, ωh)来表现(注意他的定义域为[0,1])。通常情况下可见性与微表面法线分布式是相互独立的。
换句话说,可见的微表面对于方向ω,必有cosθdA,其中cosθ=(ω · n))
微表面可以看成是一个高度场,每个背光的微表面会遮蔽ω方向上投射面积的向光的微表面。
定义A+为向光面的微表面,A-为背光面的微表面,于是。所以masking-shadowing函数为:
在每个微表面中不显示区域的比例为:
我们使用lambda这个函数来计算,同时:
这个lambda函数可以使用不同的模型来模拟,对于点与点之间没有相关性的模型可以使用Beckmann–Spizzichino:
其中,
PBRT使用了合理的基于物理的多项方程式计算 Beckmann–Spizzichino,这样避免计算了std::erf()与std::exp(),所以计算效率更高。
Masking-shadowing函数的各项异形版本可以通过各向同性版本修改得到。
<Compute alpha for direction w> ≡ 543
Float alpha = std::sqrt(Cos2Phi(w) * alphax * alphax +
Sin2Phi(w) * alphay * alphay);
在高度不相关的前提下,对于Trowbridge–Reitz分布可以简化为:
最后一个有用的是几何属性的微表面分布,假设从这两个方向观察这个微表面,其可见度的概率是相互独立的,则有
实际上不可能的,因为漏算了当ωo=ωi这个情况。在这个情况下G(ωo, ωi) = G1(ωo) = G1(ωi).这会导致下G(ωo, ωi)过小。同时,当ωo与ωi越接近,其相关性也越大。因此可以推断一个更加精确的微表面模型,假设在微表面上的点越高,其可见度越高。
这个模型相当精确,PBRT也在使用这个,更为精确的模型请看扩展阅读,当然那个地方我就懒得看了。
Float G(const Vector3f &wo, const Vector3f &wi) const {
return 1 / (1 + Lambda(wo) + Lambda(wi));
}
TORRANCE–SPARROW模型
Torrance and Sparrow 于1967年发现了这个微表面模型用于模拟金属表面。这段还是去看知乎上那些介绍实时渲染PBR模型的文章比较好。
代码在MicrofacetReflection中的f()中。MicrofacetTransmission则实现了透射效果。入射介质折射率、透射介质折射率以及的关系如下:
代入之前的公式:
其中
多层材质模型
很多BRDF模型都没有考虑到在多层材质中,最底层的材质对于光线的影响。Ashikhmin和Shirley(2000, 2002)开发了模拟这种表面是粗糙的漫反射,底面为光滑的镜面反射的多层材质的BRDF模型。图8.23显示了这个想法:当入射方向接近垂直时,大部分光被传输到漫反射表层,漫反射占主导地位。当入射方向接近掠射时,底层的镜面反射占主导地位。车漆就适合用这种模型来表现。类名为FresnelBlend。
FresnelBlend类设置了两个辐射度变量分别来控制漫反射与镜面反射,以及一个微表面分布变量来控制高光层。
这个模型基于镜面反射层与漫反射层的权重和。再考虑了能量守恒的情况下,其镜面高光部分为:
下面使用了Schlick菲尼尔近似模型。其漫反射部分为:
最终返回镜面高光+漫反射的值。