问题二十一:怎么模拟ray tracing图形中不同材料的颜色(diffuse and metal)
在漫射材料章节,我们将多个球都模拟成漫射材料的颜色。那么问题来了,我们能不能将不同的球模拟成不同材料的颜色呢?可以哈!我们这一章节就干这事。
21.1总结一下设置颜色的几种方法
我们还是先回忆一下:ray tracing画图时,我们用过的设置颜色的方法:
1,第一张图(背景):
vec3 unit_direction = unit_vector(r.direction());
float t = 0.5*(unit_direction.y() + 1.0);
return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);//white,light blue
将“(原始)光线的方向向量的标准向量的某一坐标(x、y、z都可以)”变换到(0,1)内,然后用变换后的值对两个固定颜色值进行插值,得到的结果便作为当前(原始)光线对应位置的颜色值。
2,第二张图(背景+一个球)
if(hit_sphere(vec3(0,0,-1), 0.5, r))
return vec3(1, 0, 0);
vec3 unit_direction =unit_vector(r.direction());
float t =0.5*(unit_direction.y() + 1.0);
return(1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);//white, light blue
背景颜色和1中是一样的画法;
球的颜色直接设置为红色。
3,第三张图(背景+一个球)
float t =hit_sphere(vec3(0,0,-1), 0.5, r);
if (t > 0.0){
vec3 N =unit_vector(r.point_at_parameter(t) - vec3(0,0,-1));
return 0.5*vec3(N.x()+1, N.y()+1, N.z()+1);
}
vec3 unit_direction =unit_vector(r.direction());
t =0.5*(unit_direction.y() + 1.0);
return(1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);//white, light blue
背景颜色和1中是一样的画法;
球的颜色为光线和球交点处的的单位法向量的色彩表映射值。
4,第四张图(背景+两个球)
if (world->hit(r,0.0, (numeric_limits<float>::max)(), rec)) {
return 0.5*vec3(rec.normal.x()+1, rec.normal.y()+1, rec.normal.z()+1);
}
else {
vec3unit_direction = unit_vector(r.direction());
float t =0.5*(unit_direction.y() + 1.0);
return(1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);//white, light blue
}
背景颜色和1中是一样的画法;
球的颜色为光线和球交点处的的单位法向量的色彩表映射值。(和3中画一个球时一样,只不过此处的交点为光线与所有球的所有交点中最近的那个交点)
5,第五张图(背景+两个漫射材料图)
vec3 color(const ray&r, hitable *world) {
hit_record rec;
if (world->hit(r,0.001, (numeric_limits<float>::max)(), rec)) {
vec3 target =rec.p + rec.normal + random_in_unit_sphere();
return 0.5*color( ray(rec.p, target-rec.p), world);
}
else {
vec3unit_direction = unit_vector(r.direction());
float t =0.5*(unit_direction.y() + 1.0);
return(1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);//white, light blue
}
}
背景颜色和1中是一样的画法:将“(原始)光线的方向向量的标准向量的某一坐标(x、y、z都可以)”变换到(0,1)内,然后用变换后的值对两个固定颜色值进行插值,得到的结果便作为当前(原始)光线对应位置的颜色值。
球的颜色:(注意啦~注意啦~注意啦~)和背景颜色的画法非常类似,只是不是基于原始光线,而是基于“最后一次反射的反射光线”,另外再乘以每次反射系数的累计乘积。将“最后一次反射的反射光线的方向向量的标准向量的某一坐标(x、y、z都可以)”变换到(0,1)内,然后用变换后的值对两个固定颜色值进行插值,(再乘以每次反射系数的累计乘积)得到的结果便作为当前(原始)光线对应位置的颜色值。物理意义即是:原始光线和漫射材料球交点处呈现的颜色是最后一次反射光线“采集”的背景颜色经过(多次)随机的反射衰减后的颜色。
总结一下设置颜色的几种方法:
法1,设置固定颜色;
法2,原始光线的方向向量的映射;
法3,交点处法向量的映射;
法4,反射光线的方向向量的映射;
21.2 模拟不同材料的颜色
前面章节,我们已经知道漫射材料颜色的设置:return 0.5*color( ray(rec.p, target-rec.p), world);也就是反射光线方向向量的映射值和衰减系数的乘积。
根据不同的反射方式,可将材料分为漫反射材料和镜面反射材料。各种反射材料又有不同的反射衰减系数。所以,我们有必要先定义一个material的抽象类,这个类包含反射光线和衰减系数。
----------------------------------------------material.h------------------------------------------
material.h
#ifndef MATERIAL_H
#define MATERIAL_H#include "hitable.h"class material
{public:virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};#endif // MATERIAL_H
然后,定义lambertian漫反射类和metal镜面反射类,这两个类继承于material类。
----------------------------------------------lambertian.h------------------------------------------
lambertian.h
#ifndef LAMBERTIAN_H
#define LAMBERTIAN_H#include <material.h>class lambertian : public material
{public:lambertian(const vec3& a): albedo(a) {}virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const;/*scatter()获取反射光线和衰减系数*/vec3 albedo;/*保存衰减系数*/
};#endif // LAMBERTIAN_H
----------------------------------------------lambertian.cpp------------------------------------------
lambertian.cpp
#include "lambertian.h"vec3 random_in_unit_sphere() {
/*漫射材料章节中有介绍过这个函数。这个函数产生一个“起点在原点,长度小于1,方向随机”的向量,该向量和交点处单位法向量相加就得到交点处反射光线随机的方向向量*/vec3 p;do {p = 2.0*vec3((rand()%(100)/(float)(100)),(rand()%(100)/(float)(100)),(rand()%(100)/(float)(100)))- vec3(1,1,1);} while (p.squared_length() >= 1.0);return p;
}bool lambertian::scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
/*这里具体实现lambertian::scatter()。做两件事情:获取漫反射的反射光线;获取材料的衰减系数。 */vec3 target = rec.p + rec.normal + random_in_unit_sphere();scattered = ray(rec.p, target-rec.p);attenuation = albedo;return true;
}
----------------------------------------------metal.h------------------------------------------
metal.h
#ifndef METAL_H
#define METAL_H#include <material.h>class metal : public material
{public:metal(const vec3& a) : albedo(a) {}virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const;vec3 albedo;
};#endif // METAL_H
----------------------------------------------metal.cpp------------------------------------------
metal.cpp
#include "metal.h"vec3 reflect(const vec3& v, const vec3& n) {
/*获取镜面反射的反射光线的方向向量。具体计算,后面解释*/vec3 vp;return v - 2*vp.dot(v,n)*n;
}bool metal::scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
/*这里具体实现metal::scatter()。做两件事情:获取镜面反射的反射光线;获取材料的衰减系数。 */vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);scattered = ray(rec.p, reflected);attenuation = albedo;return (reflected.dot(scattered.direction(), rec.normal) > 0);
}
镜面反射的反射的方向向量的计算:
接下来需要将“材料属性”添加到sphere类中。因为不同的球被撞击后,由于其材料不一样,反射光线方程不一样,还有就是光线的衰减系数不一样。
需要改动的code如下:
----------------------------------------------hitable.cpp------------------------------------------
hitable.cpp
#ifndef HITABLE_H
#define HITABLE_H
#include "ray.h"
class material;
struct hit_record{
/*在hit_record结构体中添加material类型的成员。所以接下来在用到过hit_record的地方都要做相应的添加。主要是:撞击小球时对其“填数据”。*/
float t;
vec3 p;
vec3 normal;
material*mat_ptr;
// vec3 c;
// float r;
};
----------------------------------------------sphere.cpp------------------------------------------
sphere.cpp
#include "sphere.h"
bool sphere::hit(const ray& r, float t_min, float t_max,hit_record& rec) const {
vec3 oc = r.origin() -center;
float a =oc.dot(r.direction(), r.direction());
float b = 2.0 * oc.dot(oc, r.direction());
float c = oc.dot(oc,oc) - radius*radius;
float discriminant =b*b - 4*a*c;
if (discriminant >0) {
float temp = (-b -sqrt(discriminant)) / (2.0*a);
if (temp <t_max && temp > t_min) {
rec.t = temp;
rec.p =r.point_at_parameter(rec.t);
rec.normal =(rec.p - center) / radius;
rec.mat_ptr = ma;
/*这个“ma”是哪来的呢?ma是初始化sphere对象时保存的材料信息,所以,接下来需要在sphere类定义中添加ma成员和通过构造函数对其赋值*/
return true;
}
temp = (-b +sqrt(discriminant)) / (2.0*a);
if (temp <t_max && temp > t_min) {
rec.t = temp;
rec.p =r.point_at_parameter(rec.t);
rec.normal =(rec.p - center) / radius;
rec.mat_ptr = ma;
return true;
}
}
return false;
}
----------------------------------------------sphere.h------------------------------------------
sphere.h
#ifndef SPHERE_H
#define SPHERE_H
#include "hitable.h"
#include "material.h"
class sphere: public hitable{
public:
sphere() {}
sphere(vec3 cen, floatr, material *m) : center(cen), radius(r), ma(m) {}
/*sphere的构造函数变了,所以之前创建的sphere对象的地方都会报错。都要添加材料属性*/
virtual bool hit(constray& r, float tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
material *ma;
};
#endif // SPHERE_H
----------------------------------------------main.cpp------------------------------------------
main.cpp
int main(){
int nx = 200;
int ny = 100;
int ns = 100;
ofstream outfile(".\\results\\metal.txt", ios_base::out);
outfile <<"P3\n" << nx << " " << ny <<"\n255\n";
std::cout <<"P3\n" << nx << " " << ny <<"\n255\n";
hitable *list[2];
list[0] = newsphere(vec3(0,0,-1), 0.5, new lambertian(vec3(1, 1, 1)));
list[1] = newsphere(vec3(0,-100.5,-1), 100, new metal(vec3(1, 1, 1)));
/* new metal(vec3(0.8, 0.8,0.0)):创建一个反射衰减向量为(1, 1,1)的metal镜面材料对象。然后将这个对象的指针通过sphere类的构造函数的形参方式传递给sphere对象。*/
……
截至当前,我们已经将material的信息添加到球体对象上。接下来我们要做的是,怎么根据不同的材料,模拟其颜色。
----------------------------------------------main.cpp------------------------------------------
main.cpp
vec3 color(const ray&r, hitable *world, int depth) {
hit_record rec;
if (world->hit(r,0.001, (numeric_limits<float>::max)(), rec)) {
/*这个“rec”会在sphere::hit ()中带上来被撞击球的材料属性(指向一个材料对象的指针mat_ptr)。根据这个指针可以获取材料对象的成员方法scatter()和成员变量albedo(反射衰减向量)*/
ray scattered;
vec3 attenuation;
if (depth < 50 && rec.mat_ptr->scatter(r, rec,attenuation, scattered)) {
/*获取反射光线向量scattered和反射衰减向量attenuation*/
return attenuation*color(scattered, world, depth+1);
/*反射光线的强度需要乘以反射衰减向量(对应坐标相乘作为新的向量)。然后反射光线就扮演之前“原始光线”的角色。如果再次撞击到小球,就再次反射,直到不再撞击到任何球为止*/
}
else {
returnvec3(0,0,0);
}
}
else {
vec3unit_direction = unit_vector(r.direction());
float t =0.5*(unit_direction.y() + 1.0);
return(1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);//white, light blue
/*注意这里,原始光线和反射光线最后都会跑到这里来。
背景的颜色:原始光线的方向向量的映射
漫反射材料和镜面材料的颜色:最后一次反射光线的方向向量的映射 * 所有反射衰减系数的乘积。漫反射和镜面反射的区别在于,漫反射的每次反射方向是随机的。*/
}
}
乘以衰减系数的效果是什么呢?
考虑到,不管是原始光线还是反射光线的方向向量映射后的颜色都是两个固定颜色的插值(此处为白色和浅蓝色)。
但是,衰减系数是材料的特有属性。一方面,每次反射后的光强度肯定是弱了;另一方面每反射一次都会加入被撞物体的颜色。
我们可以这样理解:像漫反射球,原始光线碰撞漫反射球后经过多次无规则的反射进入环境,漫反射球的颜色就是环境颜色(最后一次反射光线方向向量的映射)和所有被碰撞到的其他物体颜色(衰减向量中隐含该信息)的叠加。
我们发现,镜面反射球上一般会有旁边物体的镜像,正是因为上述原因。但是漫反射球上不会有?因为其反射光线方向不规则嘛,所以成像太模糊了。
疑问?这么说的话,如果我们将漫反射球和镜面反射球的反射衰减系数都设置为(1, 1, 1),是不是就看不到球了呢?不会。为什么?
虽然反射衰减系数的乘积都是1,但是,最后一次反射光线的方向向量和原始光线的方向向量是不一样的(虽然最终颜色都是两个固定颜色的插值),所以是可以看出球的轮廓的。
下图两球的反射衰减系数都设置为(1, 1, 1),左球是漫反射球,右图是镜面反射球。
(左球确实看不太清哈,因为漫反射球的反射方向随机嘛,所以能够比较好地融入环境哈。右球就是一个完全的镜面球。)
书上是设置如下四个球:
list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.8, 0.3,0.3)));
list[1] = new sphere(vec3(0,-100.5,-1), 100, new lambertian(vec3(0.8,0.8, 0.0)));
list[2] = new sphere(vec3(1,0,-1), 0.5, new metal(vec3(0.8, 0.6, 0.2)));
list[3] = new sphere(vec3(-1,0,-1), 0.5, new metal(vec3(0.8, 0.8,0.8)));
运行结果:
(左右两个球是镜面材料球,每个球中都有其他两个球的镜像。注意是镜像,不是透明)
我们四个球的材料属性换一下:
list[0] = new sphere(vec3(0,0,-1), 0.5, new lambertian(vec3(0.8, 0.3,0.3)));
list[1] = new sphere(vec3(0,-100.5,-1), 100, new metal(vec3(0.8, 0.8, 0.0)));
list[2] = new sphere(vec3(1,0,-1), 0.5, new metal(vec3(0.8, 0.6, 0.2)));
list[3] = new sphere(vec3(-1,0,-1), 0.5, new lambertian(vec3(0.8,0.8, 0.8)));
效果是这样的:
另外,镜面反射球的清晰程度是可以调节的,如果觉得镜面反射球上的其他球的镜像太清晰了,可以使它模糊化。
我们可以在反射向量的方向向量上添加一个“起点在原点,长度小于1,方向随机”的向量(相当于添加一个fuzziness parameter,随机向量的产生方式可以参考“问题十九”)。(长度为零,说明没有模糊)
需要添加的code如下:
random_in_unit_sphere()实在lambertian.cpp中实现的。在lambertian.h中声明了,然后如果要调用该函数,只需要#include “lambertian.h”
将两个镜面反射球的模糊系数都设置为0.3前后的对比图:
问题二十一:怎么模拟ray tracing图形中不同材料的颜色(diffuse and metal)相关推荐
- 问题二十四:怎么模拟ray tracing图形中介质材料的颜色(dielectric)
这里的"介质"是指光可以通过的物质.比如,水,玻璃等.也就是我们常说的具有一定透明度的物质. 24.1 预备知识 24.1.1 反射和折射光线的方向向量 反射光线的方向向量: 漫反 ...
- 问题四十五:怎么画ray tracing图形中的blending and joining surface
当两个曲面在空间相交时,怎么连接相交处? 比如,两个相互垂直的柱面在空间相交如下: 怎么将黑色圈内的相交处画得平滑些?也就是将相交处包起来,添加一个类似水管的四通接口. 45.1 数学推导 45.2 ...
- 《Ray Tracing in One Weekend》——Chapter 7: Diffuse materials
总结<Ray Tracing in One Weekend>全文 第一部分:学习总结 问题十九:怎么模拟ray tracing中漫射材料球体的颜色(diffuse materials) 当 ...
- 问题十九:怎么模拟ray tracing中漫射材料球体的颜色(diffuse materials)
前面画一个球时,球体的颜色设置为红色: 前面画多个球时,球体的颜色设置为球在该点的单位法向量的色彩表映射值: 现在画多个漫射材料的球,球体的颜色设置为背景颜色的系数倍.(姑且表述为"背景颜色 ...
- 问题二十:C++全局debug “ray tracing图形”实例
紧接上文,漫射材料的球体颜色是不是太黑了呢?为什么会这么黑? 接下来要debug啦!!! 第一步:取消"消锯齿" 首先想到的是:应该减少光线条数.所以,将"消锯齿&quo ...
- 问题四十二:怎么用ray tracing画任意圆环片段
42.1 数学推导 引入两个参数theta1.theta2 撞击点P和中心点O连线的向量OP与+X轴的夹角theta在[theta1, theta2]范围内时,才有可能撞击到圆环段. 当然,考虑到th ...
- 问题三十二:怎么用ray tracing画多边形(polygon, triangle)
画多边形主要分为两步: 1,光线和多边形所在的平面相交,求得交点: 2,判断交点是否在多边形内: 32.1 光线和多边形所在的平面相交 分别定义光线.多边形和多边形所在平面.光线和平面的方程如下:(注 ...
- 问题二十八:ray tracing中的散焦模糊(defocus blur)
"散焦模糊"在摄影上又称"景深". 在现实的相机中,我们需要做"散焦模糊"的原因是:我们需要一个更大的孔来收集光线增加图片的亮度(而不是&q ...
- 问题十八:怎么对ray tracing图形进行消锯齿
先将上一章节的图放大8倍,看看局部: 边界的锯齿很清晰很明显很刺眼啊,有木有?我们现在要做的就是使得这些锯齿不那么清晰不那么明显不那么刺眼.简单地说,使边界模糊. 之前的图是每个像素点设置一个颜色值, ...
最新文章
- R语言回归模型协方差分析(Analysis of Covariance)
- 上周新闻回顾:Vista麻烦不断 Office波澜再起(2.26-3.4)
- SQL SERVER 2005 通过链接服务器 访问 ORACLE 的快速设定方法
- Docker容器的文件系统管理
- 使用7z命名行备份VS项目
- 中国最好的电子商务平台,75商务网成功上线
- 生活随笔:大学需要确立自己的方向
- 数据结构之广度优先搜索(队列实现)问题
- Java并发编程实战~volatile
- linux检测不到readline,在Linux中编译Lua时“找不到-lreadline”错误
- mysql数据库登录历史_mysql数据库查看历史记录
- java反编译工具那个好用_Java反编译工具有哪些,Java反编译工具好用推荐
- 用pentbox-1.8在kali中设置蜜罐记录攻击者行为
- 计算机英语单词练习五
- js中文汉字转拼音详细教程
- SwiftUI学习笔记[path绘制]
- Pyecharts--第一个培训数据dashboard(不太完美)
- 最好的肠道菌群科普记录片
- python:os.chdir()
- Android监听手机软键盘的弹起和关闭