漫射材料的奇怪光线追踪行为

时间:2019-04-16 19:14:00

标签: c++ raytracing

我一直在阅读并尝试彼得·雪莉(Peter Shirley)的“一个周末的射线追踪”。直到扩散的材料部分,一切都进行得很好。基本上,我的算法似乎不是散布材料,而是仅从特定角度投射阴影,而且我不知道问题可能来自何处。

我通常会一步一步地跟着本书走。 前面的部分给出了正确的结果,而我在最后一部分中添加到漫反射材料的唯一代码是下面的函数。

以下是漫反射材料代码的特定部分,它们基本上从平行于碰撞点的球体中选择了将射线反射到随机方向的射线(很抱歉,如果我的解释不够清楚)。

此功能是从与切点相切的球体中获取随机点。

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

谢谢!

2 个答案:

答案 0 :(得分:3)

问题很简单,每次生成随机向量时,都在使用新的,默认初始化的 psuedorandom数字生成器。随机数生成器包含某种状态,并且需要保留此状态,以便随着时间的推移看到不同的结果。

要解决此问题,只需以一种或另一种方式使您的随机数生成器静态:

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(可能)为生成器添加了一些现实世界的随机性。

答案 1 :(得分:2)

随机方向功能对我来说似乎是错误的。看起来它应该产生三个方向余弦(wx,wy,wz),它们在半径为1的球面上是均匀的,这样

wx 2 + wy 2 + wz 2 = 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作为参数,因此状态会发生变化。其次,现在可以在单位球面上正确采样点,并且可以将其用作随机方向。只需用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;
}

UPDATE II

第二个问题是您在单位球体内生成了均匀的点。所以问题不在于方向-您的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。您的基本几何规则刚刚失效。