漫反射材料奇怪的光线追踪行为
Weird raytracing behavior for diffuse materials
我一直在阅读和试用 Peter Shirley 的 "Ray tracing in one weekend"。在 diffuse material 部分之前一切都很顺利。基本上,我的算法似乎只是从特定角度投射阴影,而不是漫反射 material,我不知道问题可能来自哪里。
平时都是按照书上的步骤来的。
前面的部分给出了正确的结果,我从上一节添加到漫射 material 的唯一代码是下面的函数。
这里是漫反射代码的特定部分 material,它基本上将光线反射到随机方向,从与碰撞点相切的球体中选择(如果我的解释不是这样,抱歉够清楚了)。
这是从球体切线到碰撞点取随机点的函数。
vec3 random_in_unitSphere(){
vec3 p;
std::default_random_engine generator;
std::uniform_real_distribution<float> distribution(0.0, 1.0);
do{
p = 2.0*vec3(distribution(generator),distribution(generator),distribution(generator)) - vec3(1,1,1);
}while (p.squared_length() >= 1.0);
return p;
}
这是计算像素颜色的函数(通过投射光线直到它什么都没有)
vec3 color(const Ray& r,Hitable *world){
hit_record rec;
if(world->hit(r,0.0,FLT_MAX,rec)){
vec3 target = rec.p + rec.normal + random_in_unitSphere();
return 0.5*color(Ray(rec.p,target-rec.p),world);
}
else{
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);
}
}
这是负责为图像的每个像素投射光线的循环。
for(int j = ny-1 ; j >= 0 ; j--){
for(int i = 0; i < nx ; i++){
vec3 col(0,0,0);
for(int s = 0; s < ns ; s++){
float u = float(i+ distribution(generator)) / float(nx);
float v = float(j+ distribution(generator)) / float(ny);
Ray r = camera.getRay(u,v);
vec3 p = r.pointAt(2.0);
col += color(r,world);
}
col /= float(ns);
int ir = int (255.99*col.r());
int ig = int (255.99*col.g());
int ib = int (255.99*col.b());
outfile<< ir << " " << ig << " " << ib << std::endl;
}
}
这是预期的输出:https://imgur.com/im5HNEK
这是我得到的:https://imgur.com/heNjEVV
谢谢!
问题很简单,每次生成随机向量时,您都在使用新的、默认初始化的 伪随机数生成器。随机数生成器包含一些状态,需要保留此状态以便随着时间的推移看到不同的结果。
要解决此问题,只需以一种或另一种方式使您的随机数生成器保持静态即可:
vec3 random_in_unitSphere(){
vec3 p;
static std::default_random_engine generator{std::random_device{}()};
std::uniform_real_distribution<float> distribution(0.0, 1.0);
do{
p = 2.0*vec3(distribution(generator),distribution(generator),distribution(generator)) - vec3(1,1,1);
}while (p.squared_length() >= 1.0);
return p;
}
在这里,我还使用 std::random_device
来(可能)向生成器添加一些真实世界的随机性。
我觉得随机方向函数不对。看起来它应该产生三个方向余弦(wx、wy、wz),它们在半径为 1 的球体上是均匀的,这样
wx2+wy2+wz2 = 1
第一个问题:你每次进入函数的时候都构造随机引擎,所以你的值都是一样的。我只是把它放在 Visual Studio 2017,C++14.1,x64,Win10 中,产生了两个调用
-0.383666 -0.804919 0.0944412
-0.383666 -0.804919 0.0944412
第二个问题-不是随机维度,长度不等于1。
更新
根据 Wolfram 文章 http://mathworld.wolfram.com/SpherePointPicking.html,这里是解决这两个问题的代码 - 它确实有 RNG 作为参数,所以状态会改变。其次,点现在在单位球体上被正确采样,并且可以用作随机 direction.Just 用 vec3
替换元组
#include <iostream>
#include <random>
#include <tuple>
std::tuple<float,float,float> random_in_unitSphere(std::mt19937& rng) {
std::uniform_real_distribution<float> distribution{};
float x1, x2, l;
do {
x1 = 2.0f * distribution(rng) - 1.0f;
x2 = 2.0f * distribution(rng) - 1.0f;
l = x1 * x1 + x2 * x2;
} while (l >= 1.0f);
float s = sqrt(1.0f - l);
return std::make_tuple(2.0f*x1*s, 2.0f*x2*s, 1.0f - 2.0f*l);
}
int main() {
std::mt19937 rng{ 987654321ULL };
float wx, wy, wz, squared_length;
std::tie(wx, wy, wz) = random_in_unitSphere(rng);
std::cout << wx << " " << wy << " " << wz << '\n';
squared_length = wx * wx + wy * wy + wz * wz;
std::cout << squared_length << '\n';
std::tie(wx, wy, wz) = random_in_unitSphere(rng);
std::cout << wx << " " << wy << " " << wz << '\n';
squared_length = wx * wx + wy * wy + wz * wz;
std::cout << squared_length << '\n';
return 0;
}
更新二
第二个问题是您在单位球体内生成了均匀的点。所以问题不在于方向——你的 wx、wy、wz 是很好的 wrt 方向,但你的方向向量的长度。典型的光线追踪代码是这样的(在一些伪代码中)
auto [x0,y0,z0] = start_new_ray();
auto [wx,wy,wz] = sample_direction();
float path = compute_path_in_geometry(x0,y0,z0,wx,wy,wz); // compute path from start point 0 in the wx,wy,wz direction to next object
// move ray to new surface
x1 = x0 + wx*path;
y1 = y0 + wy*path;
z1 = z0 + wz*path;
// do scattering, illumination, ... at (x1,y1,z1)
如果 (wx,wy,wz) 长度不为 1,则长度计算为 sqrt((x1-x0)2 + (y1-y0)2+(z1-z0)2) 不会等于 path
。你的基本几何规则刚刚被打破。
我一直在阅读和试用 Peter Shirley 的 "Ray tracing in one weekend"。在 diffuse material 部分之前一切都很顺利。基本上,我的算法似乎只是从特定角度投射阴影,而不是漫反射 material,我不知道问题可能来自哪里。
平时都是按照书上的步骤来的。 前面的部分给出了正确的结果,我从上一节添加到漫射 material 的唯一代码是下面的函数。
这里是漫反射代码的特定部分 material,它基本上将光线反射到随机方向,从与碰撞点相切的球体中选择(如果我的解释不是这样,抱歉够清楚了)。
这是从球体切线到碰撞点取随机点的函数。
vec3 random_in_unitSphere(){
vec3 p;
std::default_random_engine generator;
std::uniform_real_distribution<float> distribution(0.0, 1.0);
do{
p = 2.0*vec3(distribution(generator),distribution(generator),distribution(generator)) - vec3(1,1,1);
}while (p.squared_length() >= 1.0);
return p;
}
这是计算像素颜色的函数(通过投射光线直到它什么都没有)
vec3 color(const Ray& r,Hitable *world){
hit_record rec;
if(world->hit(r,0.0,FLT_MAX,rec)){
vec3 target = rec.p + rec.normal + random_in_unitSphere();
return 0.5*color(Ray(rec.p,target-rec.p),world);
}
else{
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);
}
}
这是负责为图像的每个像素投射光线的循环。
for(int j = ny-1 ; j >= 0 ; j--){
for(int i = 0; i < nx ; i++){
vec3 col(0,0,0);
for(int s = 0; s < ns ; s++){
float u = float(i+ distribution(generator)) / float(nx);
float v = float(j+ distribution(generator)) / float(ny);
Ray r = camera.getRay(u,v);
vec3 p = r.pointAt(2.0);
col += color(r,world);
}
col /= float(ns);
int ir = int (255.99*col.r());
int ig = int (255.99*col.g());
int ib = int (255.99*col.b());
outfile<< ir << " " << ig << " " << ib << std::endl;
}
}
这是预期的输出:https://imgur.com/im5HNEK
这是我得到的:https://imgur.com/heNjEVV
谢谢!
问题很简单,每次生成随机向量时,您都在使用新的、默认初始化的 伪随机数生成器。随机数生成器包含一些状态,需要保留此状态以便随着时间的推移看到不同的结果。
要解决此问题,只需以一种或另一种方式使您的随机数生成器保持静态即可:
vec3 random_in_unitSphere(){
vec3 p;
static std::default_random_engine generator{std::random_device{}()};
std::uniform_real_distribution<float> distribution(0.0, 1.0);
do{
p = 2.0*vec3(distribution(generator),distribution(generator),distribution(generator)) - vec3(1,1,1);
}while (p.squared_length() >= 1.0);
return p;
}
在这里,我还使用 std::random_device
来(可能)向生成器添加一些真实世界的随机性。
我觉得随机方向函数不对。看起来它应该产生三个方向余弦(wx、wy、wz),它们在半径为 1 的球体上是均匀的,这样
wx2+wy2+wz2 = 1
第一个问题:你每次进入函数的时候都构造随机引擎,所以你的值都是一样的。我只是把它放在 Visual Studio 2017,C++14.1,x64,Win10 中,产生了两个调用
-0.383666 -0.804919 0.0944412
-0.383666 -0.804919 0.0944412
第二个问题-不是随机维度,长度不等于1。
更新
根据 Wolfram 文章 http://mathworld.wolfram.com/SpherePointPicking.html,这里是解决这两个问题的代码 - 它确实有 RNG 作为参数,所以状态会改变。其次,点现在在单位球体上被正确采样,并且可以用作随机 direction.Just 用 vec3
#include <iostream>
#include <random>
#include <tuple>
std::tuple<float,float,float> random_in_unitSphere(std::mt19937& rng) {
std::uniform_real_distribution<float> distribution{};
float x1, x2, l;
do {
x1 = 2.0f * distribution(rng) - 1.0f;
x2 = 2.0f * distribution(rng) - 1.0f;
l = x1 * x1 + x2 * x2;
} while (l >= 1.0f);
float s = sqrt(1.0f - l);
return std::make_tuple(2.0f*x1*s, 2.0f*x2*s, 1.0f - 2.0f*l);
}
int main() {
std::mt19937 rng{ 987654321ULL };
float wx, wy, wz, squared_length;
std::tie(wx, wy, wz) = random_in_unitSphere(rng);
std::cout << wx << " " << wy << " " << wz << '\n';
squared_length = wx * wx + wy * wy + wz * wz;
std::cout << squared_length << '\n';
std::tie(wx, wy, wz) = random_in_unitSphere(rng);
std::cout << wx << " " << wy << " " << wz << '\n';
squared_length = wx * wx + wy * wy + wz * wz;
std::cout << squared_length << '\n';
return 0;
}
更新二
第二个问题是您在单位球体内生成了均匀的点。所以问题不在于方向——你的 wx、wy、wz 是很好的 wrt 方向,但你的方向向量的长度。典型的光线追踪代码是这样的(在一些伪代码中)
auto [x0,y0,z0] = start_new_ray();
auto [wx,wy,wz] = sample_direction();
float path = compute_path_in_geometry(x0,y0,z0,wx,wy,wz); // compute path from start point 0 in the wx,wy,wz direction to next object
// move ray to new surface
x1 = x0 + wx*path;
y1 = y0 + wy*path;
z1 = z0 + wz*path;
// do scattering, illumination, ... at (x1,y1,z1)
如果 (wx,wy,wz) 长度不为 1,则长度计算为 sqrt((x1-x0)2 + (y1-y0)2+(z1-z0)2) 不会等于 path
。你的基本几何规则刚刚被打破。