紧接上文,漫射材料的球体颜色是不是太黑了呢?为什么会这么黑?
接下来要debug啦!!!
第一步:取消“消锯齿”
首先想到的是:应该减少光线条数。所以,将“消锯齿”的哪个ns系数设置为1先,原本的系数是100。简单算一下200*100的图片是2万个像素点,为了消锯齿,每个像素点采样100次然后求平均值,也就是一共采样200万次(by the way, 最后一次还真尝试打出了200万条log信息,保存的log的txt文件接近50Mkb)。
ns=1(不消锯齿),
输出图片如下:
放大8倍看截图是这样的:
怎么会是这个鬼样子?尤其是小球的上半球,被光线首次撞击后的反射光线理论上是不可能再有机会撞击到大球的啊(是不考虑撞自己的,因为反射光线撞自己的话根就是0,而程序设置了根是大于0的),所以小球的上半球应该只会被撞击一次(即光线只反射一次),那它的颜色值的RGB值应该就是背景颜色的一半。但是,从实际输出的图片来看,明显不对吧。(注意还是只考虑小球的上半球)其一,颜色太深;其二,黑块太多;其三,确实看得不爽。
第二步:撤掉大球
可能大球太大(像地球一样大),小球上半球的反射光线还是有可能撞击大球的,然后光线再反射撞击小球,然后……反正就是有可能被多次反射的。
OK,现在撤掉大球,只画小球。
需要改的code不多。在main函数将原本对两个球的定义,改成只定义一个球,相应的list大小由2改成1。
int main(){
int nx = 200;
int ny = 100;
int ns = 100;
ofstream outfile(".\\results\\DiffuseMaterial_test.txt", ios_base::out);
outfile <<"P3\n" << nx << " " << ny <<"\n255\n";
std::cout <<"P3\n" << nx << " " << ny <<"\n255\n";
hitable *list[1];//原本是2,现在改成1
list[0] = newsphere(vec3(0,0,-1), 0.5);
// list[1] = new sphere(vec3(0,-100.5,-1), 100);
hitable *world = newhitable_list(list,1); //原本是2,现在改成1
……
一个球,看输出图片:
放大8倍看截图是这样的:
不科学,不科学,不科学!什么鬼,什么鬼,什么鬼!
大球都撤了,从小球上反射的光线还能撞谁?球的的颜色难道不应该是简简单单地被设置为背景色颜色值的一半么(因为值可能被反射一次)?但是,为什么还是这么深的颜色,还是有这么多的黑块。八个雅鹿,八个雅鹿,八个雅鹿!
第三步:分析光线只碰撞小球的情况
我想知道:
一共有多少条光线撞击了小球?
所有撞击小球的光线中,有多少条是来自原点(剩余的就是来自反射)?
每次撞击小球的光线的起点?
每次被撞击的小球的球心和半径?
每次撞击点的坐标及其对应方程的根?(撞击点的坐标即为反射光线的起点)
埋log所加的code及其位置:
20.3.1,定义几个全局变量,计数光线。
the number of all the rays(original raysand reflected rays): counter1
the number of the rays that hit the sphere: counter2
the number of the rays that hit the sphereand come from origin: counter3
the number of the rays that are set color: counter4
添加code:
main.cpp文件最前面定义这几个全局变量:
extern int counter1 = 0;
extern int counter2 = 0;
extern int counter3 = 0;
extern int counter4 = 0;
在color()函数中添加计数器计数:
vec3 color(const ray&r, hitable *world) {
counter1++;//5883059/59002/32920
hit_record rec;
if (world->hit(r,0.0, (numeric_limits<float>::max)(), rec)) {
counter2++;//3883059/39002/12920
if((r.origin().x() == (float)(0)) &&
(r.origin().y() == (float)(0)) &&
(r.origin().z() == (float)(0)))
counter3++;//1019651/10194/2615
ofstream outfile(".\\results\\log\\ns1_ball1_t0+[log].txt", ios_base::app);
outfile <<"counter2: " << counter2 << "===ray:origin: ( "<< r.origin().x()<< ", " << r.origin().y() <<", " << r.origin().z() << "), " <<"===center,radius: ( " << rec.c.x() << ", "<< rec.c.y() << ", " << rec.c.z() << "), " << rec.r << "===hitpoint(t): " << rec.t<< endl;
vec3 target =rec.p + rec.normal + random_in_unit_sphere();
return 0.5*color(ray(rec.p, target-rec.p), world);
}
else {
counter4++;//2000000/20000/20000
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
}
}
在main()函数的函数体最后添加log:
std::cout << "the number of all therays(original rays and reflected rays): counter1: "
<<counter1 << endl;
std::cout << "the number of the raysthat hit the sphere: counter2: "
<<counter2 << endl;
std::cout << "the number of the raysthat hit the sphere and come from origin: counter3: "
<<counter3 << endl;
std::cout << "the number of the raysthat are set color: counter4: "
<<counter4 << endl;
看结果:
读书少,数学不好。
让我先算算:
所有光线数目counter1=32920。尼玛,哪来怎么多?
原始光线数目不是20000么(像素点200*100)?等于最终设置颜色的光线数counter4=20000。
多出来这counter1-counter4=12920条光线就是反射光线咯。
一条反射光线对应着一次撞击,如所有撞击数目counter2,正好/必须/当然=counter1-counter4=12920。
这么多次撞击中,来自原始光线的撞击次数counter3=2615次。
等等,等等。有点迷糊。
12920次撞击中只有2615次来自原始光线,也就是有12920-2615=10305次撞击来自反射光线。
尼玛,2615条原始光线产生了10305条撞击小球的反射光线?也就是说平均每条撞击小球的原始光线产生大约四次能够再次撞击小球的反射光线。
考虑到空间里只有一个小球,哪来怎么多次撞击????????????????
我要知道:
每次撞击小球的光线的起点?
每次撞击点的坐标及其对应方程的根?
20.3.2,获取每次撞击的光线信息和撞点信息
考虑到发生了12920次撞击,也就是会有12920条log信息,我们将这些信息从文件输出(而不是屏幕)。
20.3.2.1,获取每次撞击的光线信息
其中每次撞击的光线的信息在color()函数的形参中传进来的,这个可以直接获取。通过在如下位置的红色字体代码可以获得每次撞击的光线的起点坐标,这样就可以区分原始光线和反射光线了。
vec3 color(const ray& r, hitable *world) {
counter1++;//5883059/59002/32920
hit_record rec;
if (world->hit(r, 0.0, (numeric_limits<float>::max)(), rec)) {
counter2++;//3883059/39002/12920
if((r.origin().x() == (float)(0)) &&
(r.origin().y() == (float)(0)) &&
(r.origin().z() == (float)(0)))
counter3++;//1019651/10194/2615
ofstream outfile( ".\\results\\log\\ns1_ball1_t0+[log].txt",ios_base::app);
outfile << "counter2:" << counter2<< "===ray:origin: ( " <<setprecision(18) <<r.origin().x()<< ", " <<r.origin().y() << ", " << r.origin().z() << ")," <<setprecision(6) << "===ray:direction: ( "<< r.direction().x() << ", " << r.direction().y()<< ", " << r.direction().z() << "),"<< "===center,radius: ( "<< rec.c.x() << ", " << rec.c.y() << "," << rec.c.z() << " ), " << rec.r<<"===hitpoint(t): " << rec.t << endl;
vec3 target =rec.p + rec.normal + random_in_unit_sphere();
return 0.5*color(ray(rec.p, target-rec.p), world);
}
else {
counter4++;//2000000/20000/20000
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
}
}
20.3.2.2获取每次撞击的撞点信息
撞点信息是通过hit()函数的形参rec返回来的。这个数据是在哪里填进来的呢??
20.3.2.2.1,理清hit()相关代码关系:
我们查查hit()的调用流程:
1,if (world->hit(r, 0.0,(numeric_limits<float>::max)(), rec)) {
/*有条语句,我们只能直到是由指针world调用hit()的。所以,我们要找world指针是怎么来的*/
2,vec3 color(const ray& r, hitable*world) {
/*world指针是作为color()函数的形参传进来的。去找color()函数被调用的地方*/
3,col += color(r, world);
/*这是main()函数调用color()函数的语句,传进了world指针*/
4,hitable *world = new hitable_list(list,1);
/*这是main()函数中定义world指针的地方。world指向的是一个hitable_list对象。Hitable_list是什么鬼?我们先要去hitable定义的地方看看它是什么鬼 */
5,
#ifndef HITABLE_LIST_H
#define HITABLE_LIST_H
#include"hitable.h"
class hitable_list: publichitable{
public:
hitable_list() {}
hitable_list(hitable **l, int n) {list= l; list_size = n; }
virtualbool hit(const ray& r, float tmin, float tmax, hit_record& rec) const;
hitable**list;
int list_size;
};
#endif // HITABLE_LIST_H
/*这是hitable_list.h中的代码。有三个地方我们需要注意:5.1,构造函数的赋值;5.2,hitable_list是从hitable类继承来的; 5.3,声明了成员方法hit();。接下来,我们分别了解:我们先看看构造函数的赋值*/
5.1,构造函数的赋值
hitable_list(hitable**l, int n) {list = l; list_size = n; }
/*形参l是指向hitable类型的指针的指针,也就是l指向的是hitable类型的指针。具体说,l是指向一个hitable类型的列表,这个列表的每一项放的是hitable类型的对象。形参n表示列表中hitable类型对象的个数。这个构造函数就是以l和n初始化一个新的hitable_list对象。(by the way,这个类有两个成员变量list和list_size,声明了有一个成员方法hit())。*/
5.2,hitable_list是从hitable类继承来的
#ifndef HITABLE_H
#define HITABLE_H
#include "ray.h"
struct hit_record{
float t;
vec3 p;
vec3 normal;
// vec3 c;
// float r;
};
class hitable
{
public:
virtualbool hit(const ray& r, float t_min, floatt_max, hit_record& rec) const = 0;
};
/*以上是hitable.h的代码。原来hitable是一个抽象类,只声明了虚函数hit()。也就是说,它的子类必须要实现hit()。看看hit()的参数,光线r在一定范围内(t_min, t_max)是否能够撞击,然后返回撞击数据。其中撞击数据是放在结构体hit_record中,数据包含实根t、撞点坐标向量p、撞点出的法向量normal。我们的目的不是获取rec返回的信息么?所以应该是来自这个结构体。现在我们要回到hitable_list.h*/
5.3,声明了成员方法hit();
#ifndef HITABLE_LIST_H
#define HITABLE_LIST_H
#include"hitable.h"
class hitable_list: public hitable{
public:
hitable_list() {}
hitable_list(hitable **l, int n) {list= l; list_size = n; }
virtual bool hit(const ray& r,float tmin, float tmax, hit_record& rec) const;
hitable**list;
int list_size;
};
#endif // HITABLE_LIST_H
/*hitable_list继承了抽象类hitable,就必须实现其虚函数hit()。Hitable_list实现hit()应该在hitable_list.cpp中*/
5.3.1 hitable_list实现成员方法
#include"hitable_list.h"
bool hitable_list::hit(const ray& r, float t_min, floatt_max, hit_record& rec) const {
hit_record temp_rec;
bool hit_anything = false;
double closest_so_far = t_max;
for (int i = 0; i < list_size; i++){
if (list[i]->hit(r, t_min,closest_so_far, temp_rec)){
hit_anything = true;
closest_so_far = temp_rec.t;
rec = temp_rec;
}
}
return hit_anything;
}
/*这里是hitable_list.cpp实现hit()的地方。截至当前,hitable_list的所有信息:父类、构造函数(成员变量)、成员方法都已了解清楚。接下来我们要回到4*/
4,hitable *world = new hitable_list(list,1);
/*根据5.1,有一个list,其中只有1个hitable对象,然后以这个list初始化一个hitable_list对象,然后将hitable_list对象的指针给了world。作为world,既然你把指针给了我,我就得知道:4.1你有什么数据;4.2你有什么方法。*/
4.2 新的这个hitable_list对象有什么方法呢?
/*这个,这个,这个就是5.3的内容*/
4.1 新的这个hitable_list对象有什么数据?他的数据就是来自list
hitable *list[1];
list[0] = newsphere(vec3(0,0,-1), 0.5);
/*这些代码(位置在4语句的上面)定义的list:这是一个只有一个hitable类型元素的列表,列表中放的是一个sphere对象(sphere对象放hitable类型的表里,这就说sphere和hitable有关系咯)。sphere对象是什么鬼?? */
6,
#ifndef SPHERE_H
#define SPHERE_H
#include"hitable.h"
class sphere: publichitable{
public:
sphere() {}
sphere(vec3 cen, float r) :center(cen), radius(r) {}
virtual bool hit(const ray& r,float tmin, float tmax, hit_record& rec) const;
vec3 center;
float radius;
};
#endif // SPHERE_H
/*这是sphere.h文件,定义了类sphere。从上面的code看到,sphere类和之前5中的hitable_list类比较相似。6.1,构造函数的初始化;6.2,继承于抽象hitable;6.3,声明了成员方法hit()*/
6.1,构造函数初始化
sphere(vec3 cen, float r) :center(cen), radius(r) {}
/*这里使用的初始化列表的构造函数。形参cen是一个向量,r是一个浮点数;用cen初始化center(球心),用r初始化radius(球半径)。*/
6.2,继承于抽象类hitable
/*同5.2*/
6.3,声明了成员方法hit()
/*sphere继承了抽象类hitable,就必须实现其虚函数hit()。spher实现hit()应该在hitable_list.cpp中*/
6.3.1 sphere实现成员方法
#include"sphere.h"
bool sphere::hit(const ray& r, float t_min, floatt_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.c = center;
// rec.r = radius;
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.c = center;
// rec.r = radius;
return true;
}
}
return false;
}
/*这里是sphere.cpp实现hit()的地方。截至当前,sphere的所有信息:父类、构造函数(成员变量)、成员方法都已了解清楚。*/
20.3.2.2.2,hit()的调用和rec数据的返回
到目前为止,我们算是已经将关系理清了。现在重新理一下:
一:1,if (world->hit(r, 0.0,(numeric_limits<float>::max)(), rec)) {
/*由4语句中对world的定义,我们直到world指向的是一个hitable_list对象,所以,world->hit()调用的是hitable_list类的成员方法hitable_list::hit (),所以跳到:5.3.1*/
二:5.3.1 hitable_list实现成员方法
#include"hitable_list.h"
bool hitable_list::hit(const ray& r, float t_min, floatt_max, hit_record& rec)const {
hit_record temp_rec;
bool hit_anything = false;
double closest_so_far = t_max;
for (int i = 0; i < list_size; i++){
if (list[i]->hit(r,t_min, closest_so_far, temp_rec)){
hit_anything = true;
closest_so_far = temp_rec.t;
rec = temp_rec;
}
}
return hit_anything;
}
/* hitable_list::hit ()使用的数据list[]是hitable_list对象的成员变量。有4,4.1 语句中对hitable_list对象的初始化,我们知道list[]中放的是sphere类型的对象。所以,上面list[i]->hit()调用的是sphere类的成员方法sphere::hit (),所以跳到:6.3.1*/
三:6.3.1 sphere实现成员方法
#include"sphere.h"
bool sphere::hit(const ray& r, float t_min, floatt_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.c = center;
// rec.r = radius;
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.c = center;
// rec.r = radius;
return true;
}
}
return false;
}
调用流程:
world->hit(r, 0.0, (numeric_limits<float>::max)(), rec) è hitable_list::hit(const ray& r,float t_min, float t_max, hit_record& rec) è sphere::hit(const ray& r, floatt_min, float t_max, hit_record& rec)
rec数据返回和调用流程是反过来的。数据主要是在sphere::hit()方法中填充的。程序中默认会填充hit_record结构体定义的三个数据(参考20.3.2.2.1中的5.2:实根t、撞点坐标向量p、撞点出的法向量normal)。
按照要求,我们还需要返回信息:每次被撞击的小球的球心和半径?
20.3.2.2.3,怎么让rec同时带回“被撞击小球的球心和半径信息”
首先,我们需要在hit_record结构体中添加球心center和半径radius成员。(参考20.3.2.2.1中的5.2,添加代码如红色字体)
5.2,hitable_list是从hitable类继承来的
#ifndef HITABLE_H
#defineHITABLE_H
#include"ray.h"
structhit_record{
float t;
vec3 p;
vec3 normal;
vec3 c;
float r;
};
然后,在sphere::hit()中填充数据。(参考20.3.2.2.1中的6.3.1,添加代码如红色字体)
6.3.1 sphere实现成员方法
#include"sphere.h"
boolsphere::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.c =center;
rec.r =radius;
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.c =center;
rec.r =radius;
return true;
}
}
return false;
}
最后就可以在
vec3color(const ray& r, hitable *world) {
counter1++;//5883059/59002/32920
hit_record rec;
if (world->hit(r, 0.0,(numeric_limits<float>::max)(), rec)) {
counter2++;//3883059/39002/12920
if ((r.origin().x() == (float)(0))&&
(r.origin().y() == (float)(0))&&
(r.origin().z() == (float)(0)))
counter3++;//1019651/10194/2615
ofstream outfile( ".\\results\\log\\ns1_ball1_t0[log].txt", ios_base::app);
outfile << "counter2: " <<counter2<< "===ray:origin: ( " << setprecision(18)<<r.origin().x()<< ", " << r.origin().y() <<", " << r.origin().z() << "), " <<setprecision(6) <<"===ray:direction: ( " << r.direction().x() << "," << r.direction().y() << ", " <<r.direction().z() << "), "<< "===center,radius: (" << rec.c.x() << ", " << rec.c.y() <<", " << rec.c.z() << " ), " <<rec.r<< "===hitpoint(t): " << rec.t << endl;
/*setprecision(18):是将浮点数float的精度设置到小数点后18位,通常是6位。调用该函数时需要#include <iomanip> */
vec3 target = rec.p + rec.normal + random_in_unit_sphere();
return 0.5*color( ray(rec.p,target-rec.p), world);
}
else {
counter4++;//2000000/20000/20000
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
}
}
以上黄底字体的代码就打出如下信息的log:
撞击次序;撞击光线的起点向量;被撞击的球的球心位置和半径;撞击点的有效实根。
接下来,要看log了!!!!!!!
20.3.3,撞击的log分析及修改
先回忆一下“20.3.1,定义几个全局变量,计数光线。”的测试结果:
12920次撞击中只有2615次来自原始光线,也就是有12920-2615=10305次撞击来自反射光线。
尼玛,2615条原始光线产生了10305条撞击小球的反射光线?也就是说平均每条撞击小球的原始光线产生大约四次能够再次撞击小球的反射光线。
我们现在就是要知道多出的10305次撞击是怎么发生的。
log贴出来:(太多,我们只贴最后一部分)
counter2: 12909
===ray:origin: ( 0, 0, 0),
===ray:direction: ( 0.0680001, -0.572, -1),
===center,radius: ( 0, 0, -1 ), 0.5
===hitpoint(t): 0.725463
counter2: 12910
===ray:origin: (0.0493315644562244415, -0.414965033531188965, -0.725463330745697021),
===ray:direction: (-0.161337, -1.76993, 0.589073),
===center,radius: ( 0, 0,-1 ), 0.5
===hitpoint(t):2.02195e-008
counter2: 12911
===ray:origin: (0.0493315607309341431, -0.414965063333511353, -0.725463330745697021),
===ray:direction: (0.0586631, -1.48993, 0.849073),
===center,radius: ( 0, 0,-1 ), 0.5
===hitpoint(t):3.85033e-009
counter2: 12912
===ray:origin: ( 0.0493315607309341431,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( -0.141337,-0.66993, 1.08907),
===center,radius: ( 0, 0,-1 ), 0.5
===hitpoint(t):1.30857e-008
counter2: 12913===ray:origin: ( 0.0493315607309341431,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( -0.341337,-0.96993, 1.12907), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):1.00913e-008
counter2: 12914===ray:origin: ( 0.0493315570056438446,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( -0.0813369,-0.72993, 1.20907), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):1.42611e-008
counter2: 12915===ray:origin: ( 0.0493315570056438446,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( -0.561337,-0.50993, 0.749073), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):1.62195e-008
counter2: 12916===ray:origin: ( 0.0493315495550632477,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( 0.0986631,0.0300699, 1.00907), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):2.38706e-008
counter2: 12917===ray:origin: ( 0.0493315532803535461,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( 0.0386631,-0.0499302, 0.0690733), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):1.71255e-007
counter2: 12918===ray:origin: ( 0.0493315607309341431,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( 0.538663,-0.22993, 0.789073), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):1.74277e-008
counter2: 12919===ray:origin: ( 0.0493315719068050385,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( -0.641337,-0.94993, 0.749073), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):5.77624e-009
counter2: 12920===ray:origin: ( 0.04933156818151474,-0.414965063333511353, -0.725463330745697021), ===ray:direction: ( -0.221337,-0.32993, 0.189073), ===center,radius: ( 0, 0, -1 ), 0.5===hitpoint(t):5.25433e-008
1,上面截取的是最后12次撞击(第12909次到12920次)的log信息;
2,第12909次撞击是来自原始光线,在此之后的11次撞击都是来自反射光线。也就是说最后一次原始光线(12909)撞击之后反射了11次之后才最终确定其颜色值,其颜色值将等于该位置背景颜色值*(1/2)12,这个就非常接近黑色了。这就解释了图片中为什么会有那么多黑块。
3,这12次撞击对象都是同一个小球(0,0, -1, 0.5)。这就是说,原始光线撞击小球后,反射光线有对该小球进行了11次撞击。这时怎么回事呢?直击问题吧,我们关注一下每次撞击的t值。
原始光线:===hitpoint(t):0.725463
反射光线:===hitpoint(t): 2.02195e-008
===hitpoint(t): 3.85033e-009
===hitpoint(t): 1.30857e-008
有什么问题?问题就是反射光线的实根(t)值都非常小,都是小于10-7的值。所以最后11次反射光线的起点都非常接近原始光线的那次撞点,从log来看,最后11次反射光线起点之间的差别是小于10-7的。
当然,每次方向都不一样(因为是随机的嘛)。
4,这个情况有点类似于:原始光线撞击小球后被困在在小球表面的撞点的那个坑里,来回碰壁了11次才逃脱出来。
问题怎么解决呢??
其实很简单,只要忽略很小的实根即可。
code的修改也非常简单:color()函数中的这个条件
if (world->hit(r, 0.0, (numeric_limits<float>::max)(), rec)) {
改成:
if (world->hit(r, 0.001,(numeric_limits<float>::max)(), rec)) {
修改后的log显示:
所有光线条数(原始光线+反射光线):22603条
撞击小球的光线条数(原始光线+反射光线):2603条
撞击小球的光线中原始光线的条数:2603条
设置颜色的光线的条数:20000条。
说明,只有那2603条原始光线撞击小球,没有反射光线撞击小球。也就是这2603条撞击小球的原始光线只被反射一次,这样每条光线的颜色值只要在背景颜色的基础上*1/2(颜色不会很深)。
另外,贴出最后几条文件中的log:
counter2: 2599===ray:origin: ( 0, 0, 0),===ray:direction: ( -0.0854, -0.5654, -1), ===center,radius: ( 0, 0, -1 ),0.5===hitpoint(t): 0.701537
counter2: 2600===ray:origin: ( 0, 0, 0),===ray:direction: ( -0.0238, -0.5638, -1), ===center,radius: ( 0, 0, -1 ),0.5===hitpoint(t): 0.678304
counter2: 2601===ray:origin: ( 0, 0, 0),===ray:direction: ( 0.0122001, -0.5678, -1), ===center,radius: ( 0, 0, -1 ),0.5===hitpoint(t): 0.688106
counter2: 2602===ray:origin: ( 0, 0, 0),===ray:direction: ( 0.0283999, -0.5716, -1), ===center,radius: ( 0, 0, -1 ),0.5===hitpoint(t): 0.703594
counter2: 2603===ray:origin: ( 0, 0, 0),===ray:direction: ( 0.0713999, -0.5686, -1), ===center,radius: ( 0, 0, -1 ),0.5===hitpoint(t): 0.707011
也说明撞击小球的光线都是起点在(0, 0, 0)(原始光线)
忽略很小的实根前后图片对比:(一个球时,不消锯齿)
忽略很小的实根前后图片对比:(一个球时,消锯齿)
忽略很小的实根前后图片对比:(两个球时,不消锯齿)
忽略很小的实根前后图片对比:(两个球时,消锯齿)