Games101 基于蒙特卡洛积分的光线路径追踪 作业7 框架解读
1 前言
这次的光线追踪,主要是基于 Radiometry 的一种实现,也就是基于物理的一种实现。本文对辐射度量学做了解释,同时给出了程序中的关键代码以及参考资料,实现了微表面模型,模型的代码正确无误,但是运用到路径追踪上会出现很强的噪声,以目前的实力无法解决。


2 辐射度量学
关于辐射度量学,这里给出一种理解方式,
- Radiant energy (barely used in CG) \(Q [J = Joule]\) :The energy of electromagnetic radiation
Raiant energy说白了就是能量。可以类比于位置,假设初始的位置是 0 m, 最终的位置是 100 m。我们可以说,兔子一开始在0,最后在100, 我们也可以说乌龟一开始在0,最终在 100。很显然,位置没有什么用,我们必须要把位置和时间结合起来,才可以获得更加直观和直觉的信息。
我们不会说自行车和汽车都能走100公里,而是会说走这100公里的速度。
一个10w的灯泡可以放出1000J的能量,可这个时间就可能是开10个小时。一个100w的灯泡也可以放出1000J的能量,这个时间可能是1个小时。我们直觉的感到,10w的灯泡使用的时候会比100w的暗淡,如何描述这种性质呢?就引出了
- Radiant flux(power) $ \Phi \equiv \frac {dQ}{dt} $ [W=Watt][lm=lumen] : Energy per unit time

我们可以采用量子化的方法,把一份能量看作一个小球。而 Radiant flux(power) 就是描述 一秒内 能射出出多少个小球。
这可以使用向量来表示,小球的射出方向就是向量的方向,小球的大小就是向量的大小。需要注意的一点是,在物理中,向量只有大小和方向,没有所谓的固定的起始点。
Radiant flux(power)就是包蓝色的小球。一包 power 里有许多 能量(黄色的小球),我们用蓝色的时间包裹,就成了power。这是辐射度量学里常用的东西,也是基本的东西。而 power 比 energy更好描述这个世界,我们可以将它比喻为力量感或者什么东西,它代表了某种强度。
Radiant intensity $I(\omega) \equiv \frac {d\Phi }{d\omega } $ :power per unit solid angle。
Solid Angle $ \Omega $ = $ \frac {A}{r^ {2}} $ :ratio of subtended area on sphere to radius squared

一个物体某个点发出的黄色的小球会朝着空间中四散而去, 在一个锥形的固体角中,一秒中的黄色小球数量。
需要注意的是,上述公式中用到了\(d\), 我们知道这是一个极限的过程,立体角的定义是球面上某处面积与整个球面面积之比,当处于极限过程时,一块面积趋近于一个非常非常小的点,而这时立体角就可以看作是一个箭头。有了这个思想,就接下来就好做多了。
光线可以理解为一个糖葫芦。穿着power。
- Irradiance
Definition: The irradiance is the power per unit area incident on a surface point.
\]
\]
可以理解为,四面八方来的光线这个面积上,可以引申为这个点上,由于极限的关系。
- Radiance
Definition: The radiance is the power emitted, reflected,transmitted or received by a surface, per unit solid angle, per projected unit area.
\]
Radiance是光线Ray传播时(即不与物体交互时)的一个性质。他在物体发出光线,或者光线打到物体上时会有折损,这种折损与物体表面的法线方向与传播光线的方向有关** Lambert’s Cosine Law**。所以我们用Radiance表示光线的传播。
一个光线带有100power,打到物体上由于角度关系我们只得到了50power, 但是我们不能说光线是50power的。
\]
\]
接收到的 Irradiance 由于在接收时,会因为 Lambert’s Cosine Law而进行缩小, 所以在还原时,需要除以 \(\cos \theta\)进行放大,还原。
3 Coding
Pseudo Code
shade(p, wo)
Uniformly sample the light at xx (pdf_light = 1 / A)
Shoot a ray from p to x
If the ray is not blocked in the middle
L_dir = L_i * f_r * cos_theta * cos_theta_x / |x-p|^2 / pdf_light
L_indir = 0.0
Test Russian Roulette with probability P_RR
Uniformly sample the hemisphere toward wi (pdf_hemi = 1 / 2pi)
Trace a ray r(p, wi)
If ray r hit a non -emitting object at q
L_indir = shade(q, wi) * f_r * cos_theta / pdf_hemi / P_RR
Return L_dir + L_indir
3.1 uniform random point in triangle in 3D
// Triangle::Sample()
void Sample(Intersection &pos, float &pdf){
float x = std::sqrt(get_random_float()), y = get_random_float();
pos.coords = v0 * (1.0f - x) + v1 * (x * (1.0f - y)) + v2 * (x * y);
pos.normal = this->normal;
pdf = 1.0f / area;
}
在Triangle::Sample()里给出了三角形内随机采样某一点的算法,来历如下。
https://www.cs.princeton.edu/~funk/tog02.pdf
https://math.stackexchange.com/questions/18686/uniform-random-point-in-triangle-in-3d
3.2 Multithreading
int spp = 64;
std::vector<std::thread> threads;
std::vector<Vector3f> colors(spp);
/* Muti-threads implenmetation for each Ray
Ray ray(eye_pos, dir);
for(int i = 0; i < spp; i++){
threads.emplace_back( std::thread(&Renderer::RT_thread, this, std::ref(colors[i]), std::ref(scene), ray) );
}
for(int i = 0; i < threads.size(); i++){
threads[i].join();
}
for (int k = 0; k < spp; k++){
framebuffer[m] += colors[k] / spp;
}
threads.clear();
*/
https://www.educative.io/blog/modern-multithreading-and-concurrency-in-cpp
3.3 Eval()
使用了 Diffuse / Lambertian Material 的BRDF Lecture17 P8。总计两条假设
入射的radiance为常数。Suppose the incident lighting is uniform
各个方向的出射radiance为常数 Light is equally reflected in each output direction
关于 ρ=1 时,就是所谓的入射与反射的能量相同。设一条入射光带着 x 能量打到表面,这些能量会均匀的向四面散开,这时某一方向的出射光能量很少,而有无数条从任意方向来的入射光带着 x 能量打到表面,某一方向的出射光 能量就等于 x。这建立在上述两条假设成立的情况下。
Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){
switch(m_type){
case DIFFUSE:
{
// calculate the contribution of diffuse model
float cosalpha = dotProduct(N, wo);
if (cosalpha > 0.0f) {
/*
Vector3f diffuse = Kd / M_PI;
return diffuse;
*/
// wi is the cam_to_pos, wo is pos_to_light, so we need to reverse the wi let them all outward for the
// compute of the cos
return brdfMicrofacet(-wi, wo, N, 0.5f, 0.2, Kd, 0.5);
}
else
return Vector3f(0.0f);
break;
}
}
}
3.4 Sample()
https://www.zhihu.com/question/522815454
对于水平和垂直方向的极坐标角度θ和φ分别采样(利用三角函数将其控制在合适的区间内),然后再转化为直角坐标。
由于这个半球面是以 Normal 为中心的,所以需要进行一个变换,代码中为 toWorld()。由于代码中并没有给出这个变换的来源与依据, 经过测试, wi是一个单位向量。比较好的方法是,既然已经得到了一个球面的采样,与一个normal,要以这个 normal 定义的半球面采样,只需要判断这个球面的向量与normal的夹角,如果为正那很好,若为负数,直接取逆即可。
Vector3f Material::sample(const Vector3f &wi, const Vector3f &N){
switch(m_type){
case DIFFUSE:
{
// uniform sample on the hemisphere
float x_1 = get_random_float(), x_2 = get_random_float();
float z = std::fabs(1.0f - 2.0f * x_1);
float r = std::sqrt(1.0f - z * z), phi = 2 * M_PI * x_2;
Vector3f localRay(r*std::cos(phi), r*std::sin(phi), z);
return toWorld(localRay, N);
break;
}
}
}
4 Microfacet Model
GGX分布函数,可以理解为这个表面和 h 一样方向的有多少 [0-1]
G_Smith阴影遮蔽函数主要是由两部分的乘积组成,一个是 cam2pos 和 light2pos 这两个都会产生遮挡。
我们常会遇到这两个GGX分布函数的表达式,实际上他们两个是一样的, \(\theta_m\) 为 half-vector 与 normal 的夹角。
\]
\]
\]
\]
// For Microfacet Model 2007 Walter
// Author:GSN Composer from youtube
// lihgt = in, view = out, N = Macro Normal
// metallic use to define the dielectric(glass air and plastic with 0.0f) metal(Iron copper and gold with 1.0f)
// roughness using at GGX-D and G_Smith function
// relectance use to modifine the F0(Fresnel relectance), eg. if2 F0 = 0.04 the material is Glass
// baseColor use to the BRDF diffuse part and also F0(Fresnel relectance) whth metal
inline Vector3f brdfMicrofacet(Vector3f Light, Vector3f View, Vector3f N, float metallic, float roughness, Vector3f baseColor, float reflectance);
inline Vector3f fresnelSchlick(float cos, Vector3f F0);
inline float D_GGX(float NOH, float roughness);
inline float G_Smith(float NoV, float NoL, float roughness);
inline float G1_GGX_Schlick(float NoV, float roughness);
Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){
switch(m_type){
case DIFFUSE:
{
// calculate the contribution of diffuse model
float cosalpha = dotProduct(N, wo);
if (cosalpha > 0.0f) {
/*
Vector3f diffuse = Kd / M_PI;
return diffuse;
*/
// wi is the cam_to_pos, wo is pos_to_light, so we need to reverse the wi let them all outward for the
// compute of the cos
return brdfMicrofacet(-wi, wo, N, 0.5f, 0.2, Kd, 0.5);
}
else
return Vector3f(0.0f);
break;
}
}
}
Vector3f Material::brdfMicrofacet(Vector3f Light, Vector3f View, Vector3f N, float metallic, float roughness, Vector3f baseColor, float reflectance)
{
// half vector
Vector3f H = normalize(Light + View);
float NoL = clamp(0.001f, 1.f, dotProduct(N, Light));
float NoV = clamp(0.001f, 1.f, dotProduct(N, View));
float NoH = clamp(0.001f, 1.f, dotProduct(N, H));
float VoH = clamp(0.001f, 1.f, dotProduct(View, H));
Vector3f F0(0.16 * (reflectance * reflectance));
F0 = lerp(F0, baseColor, metallic);
Vector3f F = fresnelSchlick(VoH, F0);
float D = D_GGX(NoH, roughness);
float G = G_Smith(NoV, NoL, roughness);
Vector3f spec = (F * D * G) / (4.0f * NoV * NoL);
Vector3f rolD = baseColor;
// only the transmitted part contribute to diffuse for the energy conservation;
rolD = rolD *(Vector3f(1.0f) - F);
// the diffuse part for the metal matriels must be equal to 0.0f;
rolD = rolD * (1.0f - metallic);
Vector3f diff = rolD / M_PI;
return diff + spec;
}
inline Vector3f Material::fresnelSchlick(float cos, Vector3f F0)
{
return F0 + ( Vector3f(1.0f) - F0 ) * std::pow(1.0 - cos, 5.0);
}
inline float Material::D_GGX(float NoH, float roughness)
{
float alpha = roughness * roughness;
float alpha2 = alpha * alpha;
float NoH2 = NoH *NoH;
float b = NoH2 * (alpha2 -1.f) + 1.0f;
return alpha2 / (M_PI * b * b);
}
inline float Material::G_Smith(float NoV, float NoL, float roughness)
{
return G1_GGX_Schlick(NoV, roughness) * G1_GGX_Schlick(NoL, roughness);
}
inline float Material::G1_GGX_Schlick(float NoV, float roughness)
{
float alpha = roughness * roughness;
float k = alpha * 0.5f;
return NoV /(NoV * (1.0f - k ) + k);
}
https://zhuanlan.zhihu.com/p/20119162
https://zhuanlan.zhihu.com/p/25539396
https://mitsuba2.readthedocs.io/en/latest/generated/plugins.html#rough-conductor-material-roughconductor
Microfacet BRDF: Theory and Implementation of Basic PBR Materials [Shaders Monthly #9]: https://www.youtube.com/watch?v=gya7x9H3mV0
GGX 2007 :https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
Games101 基于蒙特卡洛积分的光线路径追踪 作业7 框架解读的更多相关文章
- 全局光照:光线追踪、路径追踪与GI技术进化编年史
全局光照(Global Illumination,简称 GI), 作为图形学中比较酷的概念之一,是指既考虑场景中来自光源的直接光照,又考虑经过场景中其他物体反射后的间接光照的一种渲染技术. 大家常听到 ...
- Knowledge Tracing -- 基于贝叶斯的学生知识点追踪(BKT)
目前,教育领域通过引入人工智能的技术,使得在线的教学系统成为了智能教学系统(ITS),ITS不同与以往的MOOC形式的课程.ITS能够个性化的为学生制定有效的 学习路径,通过根据学生的答题情况追踪学生 ...
- [python] A*算法基于栅格地图的全局路径规划
# 所有节点的g值并没有初始化为无穷大 # 当两个子节点的f值一样时,程序选择最先搜索到的一个作为父节点加入closed # 对相同数值的不同对待,导致不同版本的A*算法找到等长的不同路径 # 最后c ...
- 基于全注解的SpringMVC+Spring4.2+hibernate4.3框架搭建
概述 从0到1教你搭建spring+springMVC+hibernate整合框架,基于注解. 本教程框架为基于全注解的SpringMVC+Spring4.2+hibernate4.3,开发工具为my ...
- 基于netty轻量的高性能分布式RPC服务框架forest<下篇>
基于netty轻量的高性能分布式RPC服务框架forest<上篇> 文章已经简单介绍了forest的快速入门,本文旨在介绍forest用户指南. 基本介绍 Forest是一套基于java开 ...
- 基于netty轻量的高性能分布式RPC服务框架forest<上篇>
工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...
- 基于MVC4+EasyUI的Web开发框架形成之旅--框架总体界面介绍
在前面介绍了一些关于最新基于MVC4+EasyUI的Web开发框架文章,虽然Web开发框架的相关技术文章会随着技术的探讨一直写下去,不过这个系列的文章,到这里做一个总结,展示一下整体基于MVC4+Ea ...
- 接口自动化 基于python实现的http+json协议接口自动化测试框架源码(实用改进版)
基于python实现的http+json协议接口自动化测试框架(实用改进版) by:授客 QQ:1033553122 欢迎加入软件性能测试交流QQ群:7156436 目录 1. ...
- GPUImage ==> 一个基于GPU图像和视频处理的开源iOS框架
Logo 项目介绍: GPUImage是Brad Larson在github托管的开源项目. GPUImage是一个基于GPU图像和视频处理的开源iOS框架,提供各种各样的图像处理滤镜,并且支持照相机 ...
- 基于mui的H5套壳APP开发web框架分享
前言 创建一个main主页面,只有主页面有头部.尾部,中间内容嵌入iframe内容子页面,如果在当前页面进行跳转操作,也是在iframe中进行跳转,而如果点击尾部按钮切换模块.页面,那就切换ifram ...
随机推荐
- MPI在Deep Learning的主流时代背景下除了传统计算领域外对DL的应用前景如何,MPI与NCCL的区别在哪???
做分布式计算的基本上10年之前只听说过MPI,14年之前只听过hadoop的MapReduce,17年之前只听过TensorFlow. 那么这三个分布式计算软件或者说框架有什么区别呢???现在都是搞d ...
- Auto.js 入门教程(二)
来了来了 ~ 下面开始学习auto.js 了! 准备材料 : android7.0及以上版本的手机一部(需要开启 '无障碍服务') auto.js软件 vscode (安装配套插件Auto.js-VS ...
- 深度解读KubeEdge架构设计与边缘AI实践探索
摘要:解读业界首个云原生边缘计算框架KubeEdge的架构设计,如何实现边云协同AI,将AI能力无缝下沉至边缘,让AI赋能边侧各行各业,构建智能.高效.自治的边缘计算新时代,共同探索智能边缘的新篇章. ...
- 微信支付退款和退款结果查询接口简单实现(.Net 7.0)
〇.前言 相较于支付宝,微信支付对 .Net 的支持就没那么充分,官方没有提供 SDK. 但值得庆幸的是,在社区有大佬封装了 v3 版 .Net SDK. 原文链接:https://developer ...
- BossPlayersCTF靶机笔记
BossPlayersCTF靶机 靶机概述 这是vulnhub上的一个简单的linux靶机,适合初级渗透测试人员,同时也告诉我们在渗透测试过程中要有耐心,要允许有兔子洞. 靶机整体思路: 主机端口探测 ...
- 免费word简历 简历制作平台
分享一个简历制作平台. 免费的word模版 链接地址 https://www.xyjianli.com/ https://www.xyjianli.com/list https://www.xyjia ...
- Linux查看硬件信息超强命令sar,以及可视化工具ksar
一.概述 sar(System Activity Reporter,系统活动情况报告)是Linux下系统运行状态统计工具,可从多方面对系统的活动进行报告,包括:文件的读写情况.系统调用的使用情况.磁盘 ...
- JavaScript设计模式样例八 —— 适配器模式
适配器模式(Adapter Pattern) 定义:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作.目的:主要解决在软件系统中,常常要将一些&quo ...
- Python开发工具:VSCode+插件
本篇是 Python 系列教程第 3 篇,更多内容敬请访问我的 Python 合集 Visual Studio Code的安装非常简单,就不放这里增加文章篇幅了. 相比PyCharm,VSCode更加 ...
- 【测试平台开发】——01后端web开发框架Flask
官方中文地址:https://flask.net.cn/ 官方英文地址:https://flask.palletsprojects.com/en/2.1.x/ github地址:https://git ...