Games101 Homework7 优化

本次作业其实就是在 whitted-style 的 ray tracing 上进行改进,实现更常见的 path tracing,原理部分闫老师的课程和作业文档中已经讲地十分清楚,不再赘述(如有一些细节上的疑惑,可以参考官网的论坛作业7发布公告 – 计算机图形学与混合现实在线平台 (games-cn.org) 或者这篇文章Games 101 | 作业7 + 路径追踪 Path Tracing + 多线程 - 知乎 (zhihu.com)),这里主要分享几个可以优化的点。

优化一:多线程加速

对于屏幕上的每个像素,因为不考虑光线之间的碰撞,从每个像素发出的追踪光线其实是相互独立的,故而可以给每个像素使用一个线程用于光线追踪。

在实际中,由于硬件限制,可以考虑在一个线程中绘制多行像素,达到多线程加速的目的。

具体做法是,使用一个绘制函数,新开线程给函数传参,告诉它要绘制那几行像素,使用隐函数会比较方便,参考代码如下:

int num_threads = 64;
std::thread th[num_threads];
int thread_height = scene.height/num_threads;

auto renderRows = [&](uint32_t start_height, uint32_t end_height) {
    for (uint32_t j = start_height; j < end_height; ++j) {
        for (uint32_t i = 0; i < scene.width; ++i) {
            // generate primary ray direction
            // random SSAA
            for (int k = 0; k < spp; k++){
                // trace light
            }
         }
         progressMtx.lock();
         progress++;
         UpdateProgress(progress / (float)scene.height);
         progressMtx.unlock();
     }};
    
for (int t = 0; t < num_threads; ++t) {
    th[t] = std::thread(renderRows, t*thread_height, (t+1)*thread_height);
}
for (int t = 0; t < num_threads; ++t) {
    th[t].join();
}

其中,加锁是为了 progress 的正常显示,不影响最终结果。

优化二:随机超采样

原来的代码中使用的超采样全都是从像素中心发出光线做平均,光线没有分散开来,这样并不能真正体现超采样的优点,也无法达到抗锯齿的效果。可以考虑使用 0~1 之间的均匀随机位置发出光线,然后再平均,当然也可以采用更好的平均算法,如高斯加权平均等。

// generate primary ray direction
// random SSAA
for (int k = 0; k < spp; k++){
    float x = (2 * (i + get_random_float()) / (float)scene.width - 1) * imageAspectRatio * scale;
    float y = (1 - 2 * (j + get_random_float()) / (float)scene.height) * scale;
    Vector3f dir = normalize(Vector3f(-x, y, 1));
    framebuffer[(int)(j*scene.width+i)] += scene.castRay(Ray(eye_pos, dir), 0) / spp;  
}

 优化三:光线遮挡判断

在判断光线是否被遮挡时,需要从当前点往采样的光源方向发出一个射线,然后判断射线所交的物体是否为光源。很多人的做法是通过距离进行判断,但这样的判断往往存在浮点数误差问题,需要引入一个 EPSILON 放宽限制。

在这里,其实可以直接判断光线所交物体是否为采样的光源,只需要改一个地方:

在三角形求交时需要存储 Object 指针;

inline Intersection Triangle::getIntersection(Ray ray)
{
    // ...

    // TODO find ray triangle intersection
    if(t_tmp <= 0) {
        return inter;
    }
    inter.happened = true;
    inter.coords = ray(t_tmp);
    inter.normal = normal;
    inter.distance = t_tmp;
    inter.m = m;
    inter.obj = this;
    return inter;
}

然后就行可以直接判断,所交物体是否为采样的光源;

Intersection lightDirBlock = intersect(Ray(p, lightDir));
Vector3f L_dir;
//if(lightDirBlock.distance - (p-lightPos).norm() > -10 * EPSILON) {
if(lightInter.obj == lightDirBlock.obj) {
    L_dir = lightEmit * pInter.m->eval(ray.direction, lightDir, pNormal) *
        dotProduct(lightDir, pNormal) * dotProduct(-lightDir, lightNormal) /
        pow((p-lightPos).norm(), 2) / lightPdf;
}

优化四:随机数生成

在提供的随机数生成函数中,使用局部变量来存储随机分布的相关变量,这样会使得每次生成都需要重新初始化变量,会带来一些性能瓶颈。

可以直接把所需要的变量设为静态,这样就只会进行一次初始化。

inline float get_random_float()
{
    static std::random_device dev;
    static std::mt19937 rng(dev());
    static std::uniform_real_distribution<float> dist(0.f, 1.f); // distribution in range [0, 1]

    return dist(rng);
}

优化结果

最后,使用SPP=256,64线程,在8G内存8核CPU的条件下,共运行了23分钟,效果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值