这里回顾GAMES101 HW7,这次作业的内容是路径追踪。

课程主页:

课程作业:

课程视频:

参考资料:

一些问题

首先回顾这次作业的问题。

问题1:depth参数的作用。

直接光照只要计算一次,depth是为了保证这点。

问题2:ndefined reference to pthread_create

在CMakeLists.txt中给编译选项添加-pthread。

问题3:结果全黑。

IntersectP中需要加eps,新代码如下:

inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
                                const std::array<int, 3>& dirIsNeg) const
{
    float eps = 1e-4;
    // invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z), use this because Multiply is faster that Division
    // dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
    // TODO test if ray bound intersects
    Vector3f origin = ray.origin;
    Vector3f t_enter = (pMin - origin) * invDir;
    Vector3f t_exit = (pMax - origin) * invDir;
    // 根据方向调整enter和exit
    if (dirIsNeg[0]) {
        float d = t_enter.x;
        t_enter.x = t_exit.x;
        t_exit.x = d;
    }
    if (dirIsNeg[1]) {
        float d = t_enter.y;
        t_enter.y = t_exit.y;
        t_exit.y = d;
    }
    if (dirIsNeg[2]) {
        float d = t_enter.z;
        t_enter.z = t_exit.z;
        t_exit.z = d;
    }
    float t_min = std::max(t_enter.x, std::max(t_enter.y, t_enter.z));
    float t_max = std::min(t_exit.x, std::min(t_exit.x, t_exit.z));
    // add
    t_max += eps;

    return (t_min < t_max && t_max >= 0);
}

问题4:结果偏黑。

分母pdf_light不要加eps。

castRay

// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    // TO DO Implement Path Tracing Algorithm here
    // 光线和场景的交点
    Intersection t1 = intersect(ray);
    // 没有交点
    if (!t1.happened) {
        return Vector3f();
    }
    // 如果物体发光
    if (t1.emit.norm() > 0) {
        // 物体发光部分只统计一次
        if (depth == 0) {
            return t1.emit;
        } else {
            return Vector3f();
        }
    }
    // 交点位置
    Vector3f p = t1.coords;
    // 场景法线
    Vector3f N = (t1.normal).normalized();
    // 入射方向
    Vector3f wo = (-ray.direction).normalized();
    // 材质
    Material *m1 = t1.m;
    // samplelight
    Intersection inter;
    float pdf_light;
    sampleLight(inter, pdf_light);
    // 光源位置
    Vector3f x = inter.coords;
    // 出射方向
    Vector3f ws = (x - p).normalized();
    // 光源法线
    Vector3f NN = (inter.normal).normalized();
    // Radiance Li
    Vector3f emit = inter.emit;
    // 距离
    float dist = (x - p).norm();
    // shoot a ray from p to x
    Ray px = Ray(p, ws);
    Intersection px_inter = intersect(px);
    // 交点位置
    Vector3f x_inter = px_inter.coords;
    // If the ray is not blocked in the middle
    Vector3f L_dir = Vector3f();
    if (px_inter.happened && ((x_inter - x).norm() < 1e-3)) {
        // 不要使用pdf_light + eps, 否则会全黑
        L_dir = emit * m1->eval(wo, ws, N) * dotProduct(ws, N) * dotProduct(-ws, NN) / (dist * dist) / pdf_light;
    }
    Vector3f L_indir = Vector3f();
    // Test Russian Roulette with probability RussianRoulette
    if (get_random_float() < RussianRoulette) {
        Vector3f wi = m1->sample(wo, N).normalized();
        // Trace a ray r(p, wi)
        Ray ray_new(p, wi);
        // If ray r hit a non -emitting object at q
        // 光线和场景的交点
        Intersection t2 = intersect(ray_new);
        Material *m2 = t1.m;
        float pdf = m1->pdf(wo, wi, N);
        L_indir = castRay(ray_new, depth + 1) * m2->eval(wo, wi, N) * dotProduct(wi, N) / pdf / RussianRoulette;
    }

    return L_dir + L_indir;
}

多进程版本

// The main render function. This where we iterate over all pixels in the image,
// generate primary rays and cast these rays into the scene. The content of the
// framebuffer is saved to a file.
void Renderer::Render(const Scene& scene)
{
    std::vector<Vector3f> framebuffer(scene.width * scene.height);

    float scale = tan(deg2rad(scene.fov * 0.5));
    float imageAspectRatio = scene.width / (float)scene.height;
    Vector3f eye_pos(278, 273, -800);
    int m = 0;

    // change the spp value to change sample ammount
    int spp = 16;
    std::cout << "SPP: " << spp << "\n";
    // for (uint32_t j = 0; j < scene.height; ++j) {
    //     for (uint32_t i = 0; i < scene.width; ++i) {
    //         // generate primary ray direction
    //         float x = (2 * (i + 0.5) / (float)scene.width - 1) *
    //                   imageAspectRatio * scale;
    //         float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;

    //         Vector3f dir = normalize(Vector3f(-x, y, 1));
    //         for (int k = 0; k < spp; k++){
    //             framebuffer[m] += scene.castRay(Ray(eye_pos, dir), 0) / spp;  
    //         }
    //         m++;
    //     }
    //     UpdateProgress(j / (float)scene.height);
    // }
    // UpdateProgress(1.f);
    // 整体进度
    int progress = 0;
    auto f = [&] (int x_start, int x_end, int y_start, int y_end) {
        for (uint32_t j = y_start; j < y_end; ++j) {
            int m = j * scene.width + x_start;
            for (uint32_t i = x_start; i < x_end; ++i) {
                // generate primary ray direction
                float x = (2 * (i + 0.5) / (float)scene.width - 1) *
                        imageAspectRatio * scale;
                float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;

                Vector3f dir = normalize(Vector3f(-x, y, 1));
                for (int k = 0; k < spp; k++){
                    framebuffer[m] += scene.castRay(Ray(eye_pos, dir), 0) / spp;  
                }
                m++;
                progress++;
            }
            std::lock_guard<std::mutex> lock(io_mutex);
            UpdateProgress(progress / (float)scene.height / scene.width);
        }
    };
    int hx = 5;
    int hy = 5;
    int hx_d = scene.height / hx;
    int hy_d = scene.width / hy;
    std::thread thread[hx][hy];
    for (int i = 0; i < hx; i++) {
        for (int j = 0; j < hy; j++) {
            int x_start = i * hx_d;
            int x_end = std::min(x_start + hx_d, scene.height);
            int y_start = j * hy_d;
            int y_end = std::min(y_start + hy_d, scene.width);
            thread[i][j] = std::thread(f, x_start, x_end, y_start, y_end);
        }
    }

    for (int i = 0; i < hx; i++) {
        for (int j = 0; j < hy; j++) {
            thread[i][j].join();
        }
    }
    UpdateProgress(1.f);

    // save framebuffer to file
    FILE* fp = fopen("binary.ppm", "wb");
    (void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
    for (auto i = 0; i < scene.height * scene.width; ++i) {
        static unsigned char color[3];
        color[0] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].x), 0.6f));
        color[1] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].y), 0.6f));
        color[2] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].z), 0.6f));
        fwrite(color, 1, 3, fp);
    }
    fclose(fp);    
}

效果