光线追踪8- Diffuse Materials 漫反射材料

2024-03-07 07:12

本文主要是介绍光线追踪8- Diffuse Materials 漫反射材料,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

现在我们有了物体和每个像素多条射线,我们可以创建一些逼真的材质。我们将从漫反射材质(也称为哑光材质)开始。一个问题是我们是否混合和匹配几何体和材质(这样我们可以给多个球体分配相同的材质,或者反过来),还是几何体和材质是紧密关联的(这对于几何对象和材质链接的过程化对象可能会有用)。我们将选择分开处理,这在大多数渲染器中是常见的做法,但请注意还有其他替代方法。

9.1 A Simple Diffuse Material

不发光的散射物体仅仅会呈现出其周围环境的颜色,但是它们会通过自身的固有颜色来调节。反射在散射表面上的光线方向是随机的,因此,如果我们将三束光线发送到两个散射表面之间的缝隙中,它们会表现出不同的随机行为:

Figure 9: 光线的反射

它们也可能被吸收而非反射。表面越暗,光线被吸收的可能性就越大(这就是为什么它是暗的!)。实际上,任何随机改变方向的算法都会产生看起来呈哑光效果的表面。让我们从最直观的开始:一个随机将光线平等地向所有方向反射的表面。对于这种材质,击中表面的光线具有同等的概率以任意方向远离表面反射。

Figure 10: 地平线以上的等反射


这种非常直观的材质是最简单的漫反射材质,事实上,许多最早的光线追踪论文使用了这种漫反射方法(在采用稍后将要实现的更精确方法之前)。我们目前还没有一种随机反射光线的方法,因此我们需要在向量工具头文件中添加一些函数。我们首先需要的是生成任意随机向量的能力:

class vec3 {public:
...double length_squared() const {return e[0]*e[0] + e[1]*e[1] + e[2]*e[2];}static vec3 random() {return vec3(random_double(), random_double(), random_double());}static vec3 random(double min, double max) {return vec3(random_double(min,max), random_double(min,max),
random_double(min,max));}
};

    然后我们需要找到一种方法来操作随机向量,以便只得到位于半球表面上的结果。有一些分析方法可以做到这一点,但实际上它们非常复杂,理解起来相当困难,并且实现起来也相当复杂。相反,我们将使用通常最简单的算法:拒绝采样法。拒绝采样法通过重复生成随机样本,直到满足所需的条件为止。换句话说,一直拒绝样本,直到找到一个好的样本。

使用拒绝采样法可以以许多等效有效的方式生成位于半球上的随机向量,但为了我们的目的,我们将选择最简单的方法:

1. 生成单位球内的随机向量。

2. 对该向量进行归一化。

3. 如果归一化后的向量位于错误的半球上,则反转该向量。

首先,我们将使用拒绝采样法生成单位球内的随机向量。从单位立方体中随机选择一个点,其中x、y和z的范围都是-1到+1,如果该点位于单位球外,则拒绝该点。

Figure 11: Two vectors were rejected before finding a good one

...
inline vec3 unit_vector(vec3 v) {
return v / v.length();
}inline vec3 random_in_unit_sphere() {
while (true) {
auto p = vec3::random(-1,1);
if (p.length_squared() < 1)return p;
}
}

Listing 43: [vec3.h] The random_in_unit_sphere() function

一旦我们在单位球中获得一个随机向量,我们需要对其进行归一化,以获得一个单位球上的向量。

Figure 12: The accepted random vector is normalized to produce a unit vector

...
inline vec3 random_in_unit_sphere() {
while (true) {auto p = vec3::random(-1,1);if (p.length_squared() < 1)return p;}
}inline vec3 random_unit_vector() {
return unit_vector(random_in_unit_sphere());
}

Listing 44: [vec3.h] Random vector on the unit sphere

既然我们在单位球的表面上有一个随机向量,我们可以通过将其与表面法线进行比较来确定它是否位于正确的半球上:

Figure 13: The normal vector tells us which hemisphere we need

我们可以对表面法线和随机向量进行内积,以确定它是否位于正确的半球上。如果内积为正数,则表示向量位于正确的半球上。如果内积为负数,则需要将向量反转。

...
inline vec3 random_unit_vector() {
return unit_vector(random_in_unit_sphere());
}inline vec3 random_on_hemisphere(const vec3& normal) {
vec3 on_unit_sphere = random_unit_vector();if (dot(on_unit_sphere, normal) > 0.0) // In the same hemisphere as the normalreturn on_unit_sphere;elsereturn -on_unit_sphere;
}

Listing 45: [vec3.h] The random_in_hemisphere() function

如果光线从材质上反射出来并完全保持其颜色,我们称该材质为白色。如果光线从材质上反射出来并完全失去其颜色,我们称该材质为黑色。作为对新的漫反射材质的第一个演示,我们将设置ray_color函数返回从反射中获得颜色的50%。我们应该期望得到一个不错的灰色。

class camera {...private:
...color ray_color(const ray& r, const hittable& world) const {hit_record rec;if (world.hit(r, interval(0, infinity), rec)) {vec3 direction = random_on_hemisphere(rec.normal);return 0.5 * ray_color(ray(rec.p, direction), world);}vec3 unit_direction = unit_vector(r.direction());auto a = 0.5*(unit_direction.y() + 1.0);return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);}
};

Listing 46: [camera.h] ray_color() using a random ray direction

...确实我们得到了非常漂亮的灰色球体:

Image 7: First render of a diffuse sphere


9.2 Limiting the Number of Child Rays(限制子光线的数量)
这里存在一个潜在的问题。请注意,ray_color函数是递归的。何时停止递归呢?当它未能击中任何物体时停止。然而,在某些情况下,这可能需要很长时间,足以导致堆栈溢出。为了防止这种情况发生,让我们限制最大递归深度,在达到最大深度时不返回任何光照贡献。

class camera {public:
double aspect_ratio      = 1.0;  // Ratio of image width over height
int    image_width        = 100;  // Rendered image width in pixel count
int    samples_per_pixel = 10;   // Count of random samples for each pixel
int    max_depth         = 10;   // Maximum number of ray bounces into scenevoid render(const hittable& world) {initialize();std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";for (int j = 0; j < image_height; ++j) {std::clog <<"\rScanlines remaining: "<<(image_height - j)<<' '<< std::flush;for (int i = 0; i < image_width; ++i) {color pixel_color(0,0,0);for (int sample = 0;sample<samples_per_pixel;++sample){ray r = get_ray(i, j);pixel_color += ray_color(r, max_depth, world);}write_color(std::cout,pixel_color,samples_per_pixel);}}std::clog << "\rDone.                 \n";}
...
private:...color ray_color(const ray& r, int depth, const hittable& world) const {hit_record rec;// If we've exceeded the ray bounce limit, no more light is gathered.if (depth <= 0)return color(0,0,0);if (world.hit(r, interval(0, infinity), rec)) {vec3 direction = random_on_hemisphere(rec.normal);return 0.5 * ray_color(ray(rec.p, direction), depth-1, world);}vec3 unit_direction = unit_vector(r.direction());auto a = 0.5*(unit_direction.y() + 1.0);return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}
};

Listing 47: [camera.h] camera::ray_color() with depth limiting
 

更新main()函数以使用这个新的深度限制:

int main() {
...
camera cam;
cam.aspect_ratio      = 16.0 / 9.0;
cam.image_width       = 400;
cam.samples_per_pixel = 100;
cam.max_depth         = 50;
cam.render(world);
}

Listing 48: [main.cc] Using the new ray depth limiting

对于这个非常简单的场景,我们应该得到基本相同的结果:

Image 8: Second render of a diffuse sphere with limited bounces


9.3 Fixing Shadow Acne(修复阴影粉刺)
还有一个需要解决的微小错误。当射线与表面相交时,射线会试图准确计算交点。不幸的是,这个计算容易受到浮点舍入误差的影响,导致交点略微偏离。这意味着下一条射线的起点,也就是从表面上随机散射出去的射线,可能与表面并不完全相切。它可能略高于表面,也可能略低于表面。如果射线的起点略低于表面,那么它可能再次与该表面相交。这意味着它会在 t=0.00000001 或其他浮点近似值处找到最近的表面,这是命中函数给出的结果。解决这个问题最简单的方法就是忽略非常接近计算交点的交点。

class camera {...private:
...
color ray_color(const ray& r, int depth, const hittable& world) const{hit_record rec;// If we've exceeded the ray bounce limit, no more light is gathered.if (depth <= 0)return color(0,0,0);if (world.hit(r, interval(0.001, infinity), rec)) {vec3 direction = random_on_hemisphere(rec.normal);return 0.5 * ray_color(ray(rec.p, direction), depth-1, world);}vec3 unit_direction = unit_vector(r.direction());auto a = 0.5*(unit_direction.y() + 1.0);return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}
};

Listing 49: [camera.h] Calculating reflected ray origins with tolerance


这样就解决了Shadow Acne。以下是结果:

 

9.4 True Lambertian Reflection(真实的兰伯特反射)

将反射光线均匀地散射在半球上,可以产生一个漂亮的柔和散射模型,但我们肯定可以做得更好。对真实的漫反射物体的更准确表示是兰伯特分布。这个分布以与表面法线之间的角度cos(ϕ)成比例的方式散射反射光线。这意味着反射光线最有可能在接近表面法线的方向散射,并且在远离法线的方向散射的可能性较小。相比于之前的均匀散射,这种非均匀的兰伯特分布更好地模拟了真实世界材料的反射性能。

我们可以通过将一个随机单位向量添加到法线向量上来创建这个分布。在表面的交点上,有一个击中点p和一个表面的法线n。在交点处,这个表面有两个完全不同的面,因此任何交点只能有两个唯一的与之相切的单位球(一个单位球对应于表面的每个面)。这两个单位球将被其半径的长度位移离开表面,对于单位球来说,其半径的长度正好为1。

一个球将被位移朝着表面的法线(n)的方向,另一个球将被位移朝着相反的方向(−n)。这样,我们得到了两个单位大小的球,它们只会在交点处与表面接触。其中一个球的中心位于(P+n),而另一个球的中心位于(P−n)。中心在(P−n)处的球被认为在表面内部,而中心在(P+n)处的球被认为在表面外部。

我们希望选择与光线起点处于同一侧的切线单位球。在这个单位半径球上选择一个随机点S,并从击中点P发出一条光线指向随机点S(即向量(S−P))。

Figure 14: Randomly generating a vector according to Lambertian distribution


    
实际上,这个改变是相当小的:

class camera {
...
color ray_color(const ray& r, int depth, const hittable& world) const{hit_record rec;// If we've exceeded the ray bounce limit, no more light is gathered.if (depth <= 0)return color(0,0,0);if (world.hit(r, interval(0.001, infinity), rec)) {vec3 direction = rec.normal + random_unit_vector();return 0.5 * ray_color(ray(rec.p, direction), depth-1, world);}vec3 unit_direction = unit_vector(r.direction());auto a = 0.5*(unit_direction.y() + 1.0);return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}};

Listing 50: [camera.h] ray_color() with replacement diffuse


渲染后,我们得到了一张相似的图像: 

Image 10: Correct rendering of Lambertian spheres
 

    在这两种漫反射方法中很难分辨出区别,因为我们的场景只包含两个简单的球体,但你应该能够注意到两个重要的视觉差异:

1. 改变后,阴影更加明显。

2. 改变后,两个球体都从天空中呈现出蓝色的色调。

这些变化都是由于光线的散射不均匀所造成的——更多的光线向法线方向散射。这意味着对于漫反射物体来说,它们会显得更暗,因为更少的光线反射到相机上。对于阴影来说,更多的光线会直接反射到上方,所以球体下面的区域会更暗。

日常生活中很少有完全漫反射的常见物体,因此我们对这些物体在光线下的行为的直观理解可能并不准确。随着本书中场景的复杂化,建议您在这里介绍的不同漫反射渲染器之间进行切换。大多数感兴趣的场景都会包含大量的漫反射材料。通过理解不同漫反射方法对场景照明的影响,您可以获得有价值的洞察。

9.5 使用伽马校正以获得准确的颜色强度

请注意球体下的阴影。图片非常暗,但是我们的球体只吸收每次反射的能量的一半,因此它们是50%的反射体。球体应该看起来相当亮(在现实生活中是浅灰色),但它们看起来相当暗淡。如果我们在漫反射材质上进行全亮度范围的遍历,我们可以更清楚地看到这一点。我们首先将ray_color函数的反射率从0.5(50%)设置为0.1(10%):

class camera {
...
color ray_color(const ray& r, int depth, const hittable& world) const {hit_record rec;// If we've exceeded the ray bounce limit, no more light is gathered.if (depth <= 0)return color(0,0,0);if (world.hit(r, interval(0.001, infinity), rec)) {vec3 direction = rec.normal + random_unit_vector();return 0.1 * ray_color(ray(rec.p, direction), depth-1, world);}vec3 unit_direction = unit_vector(r.direction());auto a = 0.5*(unit_direction.y() + 1.0);return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}
};

Listing 51: [camera.h] ray_color() with 10% reflectance

我们以这个新的10%反射率进行渲染。然后我们将反射率设置为30%,再次进行渲染。我们依次重复50%、70%和最后90%的设置。您可以在所选的图片编辑器中从左到右叠加这些图像,您应该能够得到一个非常好的视觉表现,显示您选择的亮度范围逐渐增加。这是我们迄今为止一直在使用的那个:

Image 11: The gamut of our renderer so far

    仔细观察或使用取色器工具,您会注意到50%的反射率渲染图(位于中间)比起处于白色和黑色(中灰)中间位置的亮度要暗得多。事实上,70%反射率更接近中灰色。这是因为几乎所有的计算机程序在将图像写入图像文件之前都会假设该图像经过了“伽马校正”。这意味着0到1之间的值在存储为字节之前会应用一些变换。没有经过转换而直接写入数据的图像被称为处于线性空间,而经过转换的图像则被称为处于伽马空间。您使用的图像查看器可能期望接收的是伽马空间中的图像,而我们提供的是线性空间中的图像,这就是为什么我们的图像看起来过于暗淡的原因。

图像存储在伽马空间中有很多好处,但对于我们的目的,我们只需要意识到这一点即可。我们将把数据转换为伽马空间,以便我们的图像查看器可以更准确地显示图像。作为一个简单的近似,我们可以使用“gamma 2”作为我们的转换,这是从伽马空间到线性空间的转换所使用的幂次。我们需要从线性空间转换到伽马空间,这意味着取“gamma 2”的倒数,也就是指数为1/gamma,即平方根。

inline double linear_to_gamma(double linear_component){
return sqrt(linear_component);
}void write_color(std::ostream &out, color pixel_color, int samples_per_pixel){auto r = pixel_color.x();auto g = pixel_color.y();auto b = pixel_color.z();// Divide the color by the number of samples.auto scale = 1.0 / samples_per_pixel;r *= scale;g *= scale;b *= scale;// Apply the linear to gamma transform.r = linear_to_gamma(r);g = linear_to_gamma(g);b = linear_to_gamma(b);// Write the translated [0,255] value of each color component.static const interval intensity(0.000, 0.999);out << static_cast<int>(256 * intensity.clamp(r))
<< ' '<< static_cast<int>(256 * intensity.clamp(g))<< ' '<< static_cast<int>(256 * intensity.clamp(b))<< '\n';
}

Listing 52: [color.h] write_color(), with gamma correction

使用这种伽马校正,我们现在得到了一个更加一致的从暗到亮的渐变:

 


 

这篇关于光线追踪8- Diffuse Materials 漫反射材料的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/782739

相关文章

Temu官方宣导务必将所有的点位材料进行检测-RSL资质检测

关于饰品类产品合规问题宣导: 产品法规RSL要求 RSL测试是根据REACH法规及附录17的要求进行测试。REACH法规是欧洲一项重要的法规,其中包含许多对化学物质进行限制的规定和高度关注物质。 为了确保珠宝首饰的安全性,欧盟REACH法规规定,珠宝首饰上架各大电商平台前必须进行RSLReport(欧盟禁限用化学物质检测报告)资质认证,以确保产品不含对人体有害的化学物质。 RSL-铅,

代码随想录训练营day37|52. 携带研究材料,518.零钱兑换II,377. 组合总和 Ⅳ,70. 爬楼梯

52. 携带研究材料 这是一个完全背包问题,就是每个物品可以无限放。 在一维滚动数组的时候规定了遍历顺序是要从后往前的,就是因为不能多次放物体。 所以这里能多次放物体只需要把遍历顺序改改就好了 # include<iostream># include<vector>using namespace std;int main(){int n,m;cin>>n>>m;std::vector<i

SpringCloud之Sleuth(Micrometer)+ZipKin分布式链路追踪

(学习笔记) 1、分布式链路追踪概述 问题:在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。 在分布式与微服务场景下,我们需要解决如下问题:   在大规模分布式与微服务集群下,如何实时观测系统的整体调用链路情况。

钓鱼邮件真相追踪:XDR见招拆招!

钓鱼陷阱,财富“蒸发” 如果一家规模5000人、业务遍布全球的企业之中有一位员工不小心点进了一个钓鱼邮件,会发生什么……?终端失陷?数据泄露?失去客户信任? 最让人破碎的当然是……核心资产泄露,钱没了!! 人有失手,"鱼"有逃命 某大型零售企业财务部门小张收到一封看似来自公司财务部的邮件,由于内容与其实际工作情况相符,小张打开了邮件中的附件,并点击了附件里的下载链接

没参加会议,还要 30000 字的会议材料写总结?用好 AI工具,30 分钟堵住领导的嘴

前段时间本来要参加总公司的重要会议,但由于临时出差错过了。 分公司老总,给了我 10 份会议材料内容,让我学习,并在节后梳理出要点。 结果,一过节就全都给忘记了,咋办?听说最近Kimi出了新玩法,能大幅提高使用效率,说干就干,研究看看多长时间能完成! 一、准备工具 KimiChat网址: https://kimi.moonshot.cn/ 即时工具: https://ww

用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化

概述 WWDC 24 一声炮响为我们送来 Swift 6.0 的同时,也颇为“低调”的推出了 SwiftData 2.0。在新版本的 SwiftData 中,苹果为其新增了多个激动人心的新特性,其中就包括历史记录追踪(History Trace)。 不过,历史记录追踪目前看起来似乎有些“白璧微瑕”,略微让人有些不爽。在这里就让我们看看如何利用 Swift 结构化并发中的异步序列(Asy

机器学习项目——基于机器学习(决策树 随机森林 朴素贝叶斯 SVM KNN XGBoost)的帕金森脑电特征识别研究(代码/报告材料)

完整的论文代码见文章末尾 以下为核心内容和部分结果 问题背景 帕金森病(Parkinson’s Disease, PD)是一种常见的神经退行性疾病,其主要特征是中枢神经系统的多巴胺能神经元逐渐丧失,导致患者出现运动障碍、震颤、僵硬等症状。然而,除运动症状外,帕金森病患者还常常伴有一系列非运动症状,其中睡眠障碍是最为显著的非运动症状之一。 脑电图(Electroencephalogram, E

编译原理项目——C++实现C语言编译器输出为8086级汇编(代码/报告材料)

完整的材料 代码见文章末尾 以下为核心内容和部分结果 项目介绍 一个小型的c语言编译器,实现的功能如下: 可以定义多个变量,并且能初始化。可以支持基本的加减乘除运算。可以支持带括号的多个变量的四则混合运算。可以支持单行注释和多行注释。可以输出%d格式的整数。可以定义int 类型。可以输出字符串。可以输出2位整数可以支持简单的if{}else{}语句。(大于和小于比较条件)以上支持的语法可以混

是噱头还是低成本新宠?加州大学用视觉追踪实现跨平台的机器手全掌控?

导读: 在当今科技飞速发展的时代,机器人的应用越来越广泛。从工业生产到医疗保健,从物流运输到家庭服务,机器人正在逐渐改变我们的生活方式。而机器人的有效操作和控制,离不开高效的遥操作系统。今天,我们要介绍的是UC San Diego 的新研究,一种创新的跨平台视觉外骨骼系统ACE。©️【深蓝AI】编译 1. ACE系统的背景与概述 近年来,机器人研究者们通常利用真实世界的机器人数据,来训练

代码随想录算法训练营第35天|背包问题基础、46. 携带研究材料(01背包二维解法)(01背包一维解法)(acm)、416. 分割等和子集

目录 0、背包问题基础01背包 46. 携带研究材料(01背包)1、题目描述2、思路3、code(二维解法)3-1、code(一维解法)4、复杂度分析 416. 分割等和子集1、题目描述2、思路3、code4、复杂度分析 0、背包问题基础 01背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能