基础术语

表面反射可以分为4大类:

  1. diffuse 漫反射
  2. glossy specular 镜面反射高光
  3. perfect specular 完美反射高光
  4. retro-reflective distributions 后反射分布

几何坐标系以及工具函数

pbrt中的反射是在反射坐标系中进行计算的。坐标系由着色点处法向量与两个切向量组成,也就是

正交基向量(s,t,n),分别于x,y,z相对齐。以\(( \theta ,\phi)\)球体坐标系来表达方向。

从图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共享公共基类BxDFBxDFType为反射属性枚举(按照不同的子类修改里面部分枚举的值)

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;
反射率

半球方向反射率(半球方向上的全反射):

$\rho_{hd}(\omega_o)=\int_{H^2(n)}f_r(p,\omega_o,\omega_i) \left|cos\theta_i \right| d\omega_i $

virtual Spectrum rho(const Vector3f &wo, int nSamples,const Point2f *samples) const;

半球-半球反射率(当前半球方向上所有光线都是一样的情况下,类似兰伯特材质):

$\rho_{hh}=\frac{1}{\pi}\int_{H2(n)}\int_{H2(n)}f_r(p,\omega_o,\omega_i) \left|cos\theta_i cos\theta_i \right|d\omega_o d\omega_i $

如果没有提供\(w_o\),计算ρ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定律:

\(\eta_i sin\theta_i =\eta_t sin \theta_t\)

菲涅尔方程描述了光线接触到表面后反射与透射的比,它实际上Maxwell方程在光滑表面上的求解。

因为在现实环境中光的偏振现象较少,所以在PBRT假设光不偏振。在这个假设下,菲尼尔反射率表示为平行和垂直偏振项的平方的平均值。我们使用\(\eta\)表示折射率

\(r_\parallel=\frac{\eta_t cos\theta_i-\eta_i cos\theta_t}{\eta_t cos\theta_i-\eta_i cos\theta_t}\)

\(r_\bot=\frac{\eta_i cos\theta_i-\eta_t cos\theta_t}{\eta_i cos\theta_i-\eta_t cos\theta_t}\)

对于非偏振光的菲尼尔反射率:

\(F_r=\frac{1}{2}(r_\parallel^2+r_\bot^2)\)

存在几个重要类别需要加以区分:

  1. 第一类是绝缘体(非金属),即不导电的材料。它们有真实的折射率(通常在1-3范围内),并能透射一部分入射光。例如玻璃、矿物油、水和空气。
  2. 第二种是导体(金属),比如金属。导体的导电性会对电磁波中的电子产生一定影响,所以与介质不同,导体的折射率较为复杂:·\(\overline{\eta}=\eta+ik\),k为吸收系数。
  3. 第三种是半导体,本书暂时不考虑。

因为能量守恒的关系,所以绝缘体中透射的能量为1-\(F_r\),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;
}

导体与绝缘体分界面处的菲尼尔反射率为:

\(r_\bot=\frac{a^2+b^2-2acos\theta+cos^2\theta}{a^2+b^2+2acos\theta+cos^2\theta}\)

\(r_\parallel=r_\bot\frac{cos^2\theta(a^2+b^2)-2acos\theta sin^2\theta +sin^4\theta}{cos^2\theta(a^2+b^2)+2acos\theta sin^2\theta +sin^4\theta}\)

其中:

\(a^2+b^2=\sqrt{(\eta^2-k^2-sin^2\theta)^2+4\eta^2k^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类。

其反射公式:

\(L_o(\omega_o)=\int f_r(\omega_o,\omega_i)L_i(\omega_i) \left| cos\theta_i \right| d\omega_i=F_r(\omega_r)L_i(\omega_r)\)

如果使用狄拉克分布来构造这个BRDF,则有:

\(\int f(x)\delta (x-x0)dx=f(x_0)\)

如果希望BRDF在除了完美反射的地方,别的地方皆为0,就建议使用狄拉克分布,则有:

\(f_r(\omega_o,\omega_i)=\delta(\omega_i-\omega_r)F_r(\omega_i)\)

将其带入方程后得:

\(L_o(\omega_o)=\int \delta(\omega_i - \omega_r)F_r(\omega_i)L_i(\omega_i) \left| cos \theta_i\right| d\omega_i=F_r(\omega_r)L_i(\omega_r) \left| cos \theta_r\right|\)

然而它并不正确,因为有了个额外参数\(\theta_r\),将因子分离出来,得到正确结果:

$f_r(p,\omega_o,\omega_i)=F_r(\omega_r)\frac{\delta(\omega_i-\omega_r)}{\left| cos\theta_r\right|} $

因为对于任意方向,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);

透射

我们使用τ来表示光线透射过介质的能量(别的都被反射了):

\(\tau = 1- Fr(\omega_i).\)

则该介质透射的辐射通量的微分为:

\(d\Phi_o=\tau d\Phi_i\)

使用辐射学的定义代替后:

\(L_ocos\theta_odAd\omega_o=\tau( L_i cos\theta_idAd \omega_i)\)

将平面角度坐标系转换到球面角坐标后:

\(L_ocos\theta_odAsin\theta_od\theta_od\phi_o=\tau( L_i cos\theta_idAsin\theta_id\theta_id\phi_i)\)

将Snell定律进行变换,对于θ有:

\(\eta_ocos\theta_od\theta_o=\eta_icos\theta_id\theta_i=>\frac{cos\theta_od\theta_o}{cos\theta_id\theta_i}=\frac{\eta_i}{\eta_o}\)

将其代入公式8.7中则有:

\(L_o\eta_i^2d\phi_o=\tau L_i\eta_o^2d\phi_i\)

因为\(\phi_i=\phi_o+\pi\)所以\(d\phi_i=d\phi_o\),从而得到:

\(L_o=\tau L_i\frac{\eta_o^2}{\eta_i^2}\)

对于BRDF,我们需要约去一个\(cos\theta_i\)去取得一个正确BTDF用于透射计算。

\(f_r(\omega_o,\omega_i)=\frac{\eta_o^2}{\eta_i^2}(1-F_r(\omega_i))\frac{\delta(\omega_i-T(\omega,n))}{\left| cos\theta_i\right|}\)

其中\(T(\omega_o,n)\)为函数通过法向量与出射角计算出来的透射向量。

因为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。具体内容详见代码(会在1314章介绍具体内容)。

LAMBERTIAN反射

类名为:LambertianReflection。

f()返回 辐射度/π

基于微表面的反射模型

OREN–NAYAR漫反射模型

Oren与Nayar在1994年发现现实世界的物体并没有表现出完美的Lambertian反射具体来说,粗糙的表面通常会随着光照方向接近观察方向而变得更亮。

于是他们开发了一个描述粗糙表面反射模型,基于v形状的微表面,使用球形高斯分布参数与一个变量σ描述。σ为光线与视线的偏差角。

因为假设在V形状微表面的情况下,所以仅需考虑相邻的微表面可以互相反射。这是一个经验近似模型:

\(f_r(\omega_i,\omega_o)=\frac{R}{\pi}(A+B max(0,cos(\phi_i-\phi_o))sin\alpha tan \beta)\)

其中σ为弧度制:

$ A=1-\frac{\sigma2}{2(\sigma2+0.33)}$

\(B=\frac{0.45\sigma^2}{\sigma^2+0.09}\)

\(\alpha=max(\theta_i,\theta_o)\)

\(\beta=min(\theta_i,\theta_o)\)

代码参见: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)。

\(\int_{H^2(n)}=D(\omega_h)cos\theta_h d\omega_h=1\)

MicrofacetDistribution:

PBRT笔记(7)——反射模型的更多相关文章

  1. PBRT笔记(5)——相机模型

    Camera class Camera { public: //实现相机在一定时间内进行特定的运动 AnimatedTransform CameraToWorld; //快门开/关数据,可以用于计算动 ...

  2. PBRT笔记(13)——光线传播1:表面反射

    采样反射函数 BxDF::Sample_f()方法根据与相应的散射函数相似的分布来选择方向.在8.2节中,该方法用于寻找来自完美镜面的反射和透射光线;在这里讲介绍实现其他类型的采样技术. BxDF:: ...

  3. PBRT笔记(10)——体积散射

    体散射处理过程 3个影响参与介质在环境中的辐射度分布的主要因素: 吸收:减少光能,并将其转化为别的能量,例如热量. 发光:由光子发射光能至环境中. 散射:由于粒子碰撞,使得一个方向的辐射度散射至其他方 ...

  4. 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化

    <深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...

  5. 操作系统学习笔记----进程/线程模型----Coursera课程笔记

    操作系统学习笔记----进程/线程模型----Coursera课程笔记 进程/线程模型 0. 概述 0.1 进程模型 多道程序设计 进程的概念.进程控制块 进程状态及转换.进程队列 进程控制----进 ...

  6. PBR Step by Step(四)Lambertian反射模型

    光照可分为局部光照和全局光照. 局部光照:直接照射到物体表面的光照 全局光照:物体表面受周围环境影响的光照 左图中点x接收到周围环境的光线照射,来自周围表面的反射光照称为全局光照:右图中点x接收来自太 ...

  7. V-rep学习笔记:机器人模型创建3—搭建动力学模型

    接着之前写的V-rep学习笔记:机器人模型创建2—添加关节继续机器人创建流程.如果已经添加好关节,那么就可以进入流程的最后一步:搭建层次结构模型和模型定义(build the model hierar ...

  8. V-rep学习笔记:机器人模型创建2—添加关节

    下面接着之前经过简化并调整好视觉效果的模型继续工作流,为了使模型能受控制运动起来必须在合适的位置上添加相应的运动副/关节.一般情况下我们可以查阅手册或根据设计图纸获得这些关节的准确位置和姿态,知道这些 ...

  9. ArcGIS模型构建器案例学习笔记-字段处理模型集

    ArcGIS模型构建器案例学习笔记-字段处理模型集 联系方式:谢老师,135-4855-4328,xiexiaokui@qq.com 由四个子模型组成 子模型1:判断字段是否存在 方法:python工 ...

随机推荐

  1. content+animation实现loading效果

    <dot></dot> dot { display: inline-block; height: 1em; line-height: 1; vertical-align: -. ...

  2. mac 配置jdk,maven环境变量

    Java和maven环境变量配置: 1.打开终端:输入命令:vi ~/.bash_profile 2.再输入 i 进入编辑模式 输入以下: export JAVA_HOME=/Library/Java ...

  3. C#中 将图片保存到Sql server 中

    private void Form1_Load(object sender, EventArgs e) { #region 保存数据库 string url = @"C:\Users\Adm ...

  4. LeetCode刷题-004两个排序数组的中位数

    给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 . 请找出这两个有序数组的中位数.要求算法的时间复杂度为 O(log (m+n)) . 示例 1:nums1 = [1, 3]num ...

  5. 2018-2019-2 网络对抗技术 20165337 Exp4 恶意代码分析

    1.实践目标 1.1是监控你自己系统的运行状态,看有没有可疑的程序在运行. 1.2是分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,sys ...

  6. [经验交流] kubeadm 安装 kubernetes 一年过期的解决办法

    kubeadm 是 kubernetes 提供的一个初始化集群的工具,使用起来非常方便.但是它创建的apiserver.controller-manager等证书默认只有一年的有效期,同时kubele ...

  7. Ubuntu更新Python3及pip3

    https://blog.csdn.net/good_tang/article/details/85001211 根据这篇文章的作者给出的方法进行的操作,但是其中出了两个问题: 我在操作之后重开bas ...

  8. java学习笔记03-基本语法

    编写java程序时,应注意以下点: 大小写敏感:Java是大小写敏感的,这就意味着标识符Hello与hello是不同的. 类名:对于所有的类来说,类名的首字母应该大写.如果类名由若干单词组成,那么每个 ...

  9. Vue + Element UI项目初始化

    1.安装相关组件 1.1安装Node 检查本地是否安装node node -v 如果没有安装,从Node官网下载 1.2安装npm npm -v 如果没有安装:使用该指令安装: npm install ...

  10. Java小程序练习

    1.选择排序法所谓的选择排序,就是把当前数据与它后面所有的数据做个比较,假如满足比较条件,则进行交换操作,直到最后二个数比较完毕,这样重新输出的数据就已经由大到小或者由小到大排好序了.for(int ...