GAMES101作业7

声明:使用的是vs2022版,以下内容如有问题,感谢各位大佬指正!

作业要求:

在本次实验中,我们将在上一次实验的基础上实现完整的 Path Tracing 算法

工作框架:

作业效果:

💡我们需要做的:

关键词:光线追踪;俄罗斯转盘算法;直接光照;间接光照


image

image

1.迁移Bounds3::IntersectP:

inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
                                const std::array<int, 3>& dirIsNeg) const
{
    // 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 t1 = (pMin - ray.origin) * invDir;
    Vector3f t2 = (pMax - ray.origin) * invDir;
    Vector3f tMin = Vector3f::Min(t1, t2);                  //每组平面的最小t值(进入时间)
    Vector3f tMax = Vector3f::Max(t1, t2);                  //每组平面的最大t值(离开时间)
    float tEnter = 0.0;
    float tExit = 0.0;
    tEnter = std::max(tMin.x, std::max(tMin.y, tMin.z));    // 所有进入时间的最大值
    tExit = std::min(tMax.x, std::min(tMax.y, tMax.z));     // 所有离开时间的最小值
    //相交条件
    return tEnter <= tExit && tExit >= 0;
}

2.迁移Triangle::getIntersection的 ray triangle intersection

inline Intersection Triangle::getIntersection(Ray ray)
{
    Intersection inter;
    //光线方向与三角形法向量点积为正,表示光线从背面入射,不相交
    if (dotProduct(ray.direction, normal) > 0)
        return inter;
    //若det≈0,说明光线与三角形平面平行或重合,无交点。
    double u, v, t_tmp = 0;
    Vector3f pvec = crossProduct(ray.direction, e2);
    double det = dotProduct(e1, pvec);
    if (fabs(det) < EPSILON)
        return inter;

    double det_inv = 1. / det;
    Vector3f tvec = ray.origin - v0;
    //重心坐标u
    u = dotProduct(tvec, pvec) * det_inv;
    // u超出[0,1],交点在三角形外
    if (u < 0 || u > 1)
        return inter;

    Vector3f qvec = crossProduct(tvec, e1);
    //重心坐标v
    v = dotProduct(ray.direction, qvec) * det_inv;
    //v超出范围或u+v>1,交点在三角形外
    if (v < 0 || u + v > 1)
        return inter;
    
    //计算光线交点
    t_tmp = dotProduct(e2, qvec) * det_inv;
    // TODO find ray triangle intersection
    // 交点在光线反方向,无效
    if (t_tmp < 0)
        return inter;
        
		// TODO find ray triangle intersection
    inter.happened = true;
    inter.coords = ray(t_tmp);  //计算交点坐标
    inter.normal = normal;      //三角形法向量
    inter.distance = t_tmp;     //交点距离
    inter.obj = this;           //相交物件指针
    inter.m = m;                //材质信息

    return inter;
}

3.迁移getIntersection

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    Intersection intersection;
    // 若节点为空或光线与当前包围盒不相交,直接返回
    if (node == nullptr || !node->bounds.IntersectP(ray, ray.direction_inv, { 0,0,0 }))
        return intersection;
    //与叶子节点中的物件(三角形)求交
    if (node->left == nullptr && node->right == nullptr)
    {
        return node->object->getIntersection(ray);          //这里调用的是obj子类:Triangle的getIntersection,即到叶子节点的包围盒开始与三角形求交
    }
    Intersection hit1 = getIntersection(node->left, ray);   //检查左子树
    Intersection hit2 = getIntersection(node->right, ray);  //检查右子树
    //返回距离最近的交点
    return  hit1.distance < hit2.distance ? hit1 : hit2;
}

4.然后修改Scene.cpp中的castRay

// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray& ray, int depth) const
{
    // TO DO Implement Path Tracing Algorithm here
    /*
    伪代码:
    shade(p, wo)
		    1.计算直接光照
        sampleLight(inter , pdf_light)
        Get x, ws, NN, emit from inter
        Shoot a ray from p to x
        If the ray is not blocked in the middle
            L_dir = emit * eval(wo, ws, N) * dot(ws, N) * dot(ws,NN) / |x-p|^2 / pdf_light
						 // 直接光照 = 光源发射光 * BRDF值 * 两个方向与法线的点积 * 距离衰减 * 概率密度倒数
				
				//2.计算间接光照
        L_indir = 0.0
        // 使用俄罗斯轮盘赌决定是否继续递归
        wi = sample(wo, N)
        Trace a ray r(p, wi)
        If ray r hit a non-emitting object at q
            L_indir = shade(q, wi) * eval(wo, wi, N) * dot(wi, N)/ pdf(wo, wi, N) / RussianRoulette
				// 间接光照 = 递归调用着色函数 * BRDF值 * 方向与法线的点积 * 概率密度倒数 * 轮盘赌概率倒数
        // 返回直接光照和间接光照的总和
        Return L_dir + L_indir
    */

    Vector3f L_dir(0, 0, 0), L_indir(0, 0, 0);
    // 1. 追踪光线与场景的交点
    Ray wo = ray;
    Intersection p_inter = this->intersect(wo);
    
    // 如果未击中任何物体,返回黑色
    if (!p_inter.happened) return L_dir;
    
    // 如果直接击中光源,返回光源颜色
    if (p_inter.m->hasEmission()) return p_inter.m->getEmission();

		// 2. 计算直接光照
    // 从所有光源中均匀采样一个点x
    Intersection x_inter; float x_pdf;
    sampleLight(x_inter, x_pdf);

    //获取x点位置、方向向量、法线和发射光
    Vector3f p = p_inter.coords;
    Vector3f x = x_inter.coords;
    Vector3f Np = p_inter.normal;
    Vector3f Nx = x_inter.normal;
    Vector3f emit = x_inter.emit;

    //从p点向x点发射一条光线
    Vector3f ws_dir = (x - p).normalized();
    Ray ws(p, ws_dir);
    Intersection ws_inter = this->intersect(ws);

    //如果光线未被遮挡,计算直接光照

    
    float px_dis = (x - p).norm(), ws_dis = ws_inter.distance;
    if (px_dis - ws_dis < 0.001)
    {
		    
        L_dir = emit
            * p_inter.m->eval(wo.direction, ws.direction, Np)
            * dotProduct(ws.direction, Np)      //归一化
            * dotProduct(-ws.direction, Nx)     //点积为余弦
            / pow(px_dis, 2)
            / x_pdf;
    } 
    //3. 计算间接光照(使用俄罗斯轮盘赌控制递归)
    float P_rand = get_random_float();
    if (P_rand < RussianRoulette) 
    {
        //采样一个新的反射方向
        Vector3f wi_dir = p_inter.m->sample(wo.direction, Np).normalized();
        Ray wi(p_inter.coords, wi_dir);
        //追踪新光线并计算间接光照
        Intersection wi_inter = this->intersect(wi);
        if (wi_inter.happened && !(wi_inter.m->hasEmission())) 
        {
            L_indir = castRay(wi, depth + 1)
                * p_inter.m->eval(wo.direction, wi.direction, Np)
                * dotProduct(wi.direction, Np)
                / p_inter.m->pdf(wo.direction, wi.direction, Np)
                / RussianRoulette;
        }
    }
    return L_dir + L_indir;
}

然后保存运行,就可以得到下面的图,不过时间有点久。效果也有很多的噪点(ノへ ̄、)

image


【提高项】:多线程(还没做)

posted @ 2025-07-17 11:35  鱼鱼莲  阅读(28)  评论(0)    收藏  举报