25.1 当前问题分析
之前在“问题二”中说明过,有截图如下:
文件显示不出来,原因肯定是文件格式不对。
所有的文件都应包含如下文件头,该文件头的格式如下:
P3
200 100
255
没见显示不出来,可能的原因:
1,RGB值个数未到200*100个
2,RGB值个数刚好200*100个,但某个R值、G值或者B值缺失(也就是RGB值出现残疾)
3,RGB值不在[0,255]之间,可能是因为值溢出而出现负数。
25.2 在学习“问题四十六(superellipsoid)”时,添加本章节如下内容:
当像素点的值溢出时,其值输出为:
-2147483648 -2147483648 -2147483648
若某个变量x的值出现溢出,则该值在程序中表示为“nan(Not a Number)”,怎么判断x的值是否溢出呢?
If (x != x) {
//表示x值溢出,即为nan
}
或者:
If(isnan(x)) {
//表示x值溢出,即为nan
}
这条语句经常用于设置断点,在x值溢出时拦截程序,从而可以查看断点处程序的状态。
接下来我们在程序中反推是什么原因导致像素点的值出现溢出的:
main():
col += color(r, world, 0);
color():
return (1.0-t)*vec3(1.0,1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);//white, light blue
float t = 0.5*(unit_direction.y()+ 1.0);
vec3 unit_direction = unit_vector(r.direction());
传入color()函数的光线有两种情况:其一,原始光线;其二,反射/折射光线
对于第一种情况:一般不会出错。对应main()函数中的如下代码:
float u = float(i + random)/ float(nx);
float v = float(j + random)/ float(ny);
ray r = cam.get_ray(u, v);
对于第二种情况:
color():
return attenuation*color(scattered, world,depth+1);
if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered)) {
反射/折射光线是在对应的材料类的scatter()成员方法返回来的。下面我们分别看看
lambertian::scatter():
scattered =ray(rec.p, target-rec.p);
vec3 target =rec.p + rec.normal +random_in_unit_sphere();
metal::scatter():
scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere());
vec3 reflected= reflect(unit_vector(r_in.direction()), rec.normal);
dielectric::scatter():既有反射,也有折射:
反射:
scattered = ray(rec.p, reflected);
vec3 reflected= reflect(r_in.direction(), rec.normal);
折射:
scattered = ray(rec.p, refracted);
if (refract(r_in.direction(),outward_normal, ni_over_nt, refracted)) {
bool refract(const vec3& v, constvec3& n, floatni_over_nt, vec3& refracted) {
float dt = dot(uv, n);
refracted = ni_over_nt*(uv - n*dt) - n*sqrt(discriminat);
if(refract(r_in.direction(), outward_normal,ni_over_nt, refracted)) {
outward_normal = rec.normal;
如上,不管是什么材料,最终导致像素点的值溢出的是撞击点处的法向量normal的值。
这里以superellipsoid类为例,到其对应的hit()函数中看看normal的值的来源:
superellipsoid::hit():
rec.normal =unit_vector(vec3(nx,ny, nz));
nx = (p_e2/p_e1) * (pow((pow(fabs(pc.x()/intercept_x),(2/p_e2)) +
pow(fabs(pc.z()/intercept_z), (2/p_e2))), (p_e2/p_e1-1))) *
(2/p_e2)*(pow(fabs(pc.x()/intercept_x),(2/p_e2-1)))/intercept_x;
ny = (2/p_e1)*(pow(fabs(pc.y()/intercept_y),
(2/p_e1-1)))/intercept_y;
nz = (p_e2/p_e1) * (pow((pow(fabs(pc.x()/intercept_x),(2/p_e2)) +
pow(fabs(pc.z()/intercept_z), (2/p_e2))), (p_e2/p_e1-1))) *
(2/p_e2)*(pow(fabs(pc.z()/intercept_z),(2/p_e2-1)))/intercept_z;
从这里可以看到,可能导致nx,ny,nz值溢出的是pow()函数计算的值超出数据类型的范围(1,当指数为小数时,底数为负;2,底数为0;3,底数绝对值大于1,指数很大,然后求幂后超出数据类型能够表示的最大值)
25.3 单独分析pow()函数
从C++ Reference官网获知:
If the base is finite negative and the exponent is finite but not an integer value, it causes a domain error. 潜在错误一:底数为负时,指数为小数
If both base and exponent are zero, it may also cause a domain error on certain implementations.
If base is zero and exponent is negative, it may cause a domain error or a poleerror (or none, depending onthe library implementation). 潜在错误二:底数为0
The function may also cause a range error if the result is too great or toosmall to be represented by a value of the return type. 潜在错误三:计算结果超出数据类型范围
应对这三个潜在错误的措施:
1,单独考虑底数为负的情况,比如:
if (pc.x() < 0) {d_a1 = -intercept_x;}
if (pc.y() < 0) {d_a2 = -intercept_y;}
if (pc.z() < 0) {d_a3 = -intercept_z;}
2,单独考虑底数为0的情况,比如:
if (pc.x() == 0) {
nx = 0;
}
else {
nx = double(p_r)*pow(double(fabs(pc.x()/d_a1)),
double(p_r-1))/double(d_a1);
}
if (pc.y() == 0) {
ny = 0;
}
else {
ny = double(p_s)*pow(double(fabs(pc.y()/d_a2)),
double(p_s-1))/double(d_a2);
}
if (pc.z() == 0) {
}
else {
nz = double(p_t)*pow(double(fabs(pc.z()/d_a3)),
double(p_t-1))/double(d_a3);
}
3,将数据类型换成double类型,比如(同上)(double类型都溢出了,直接输出报错吧!数值太大了,亲)