games101作业6

本文描述了一次编程练习,涉及在光线追踪场景中实现关键功能,包括Render()函数中生成光线,Triangle::getIntersection()函数计算光线与三角形的交点,Bounds3::IntersectP()用于判断光线与包围盒是否相交,以及BVHAccel::getIntersection()在二叉空间划分树中加速求交过程。这些函数的实现涉及到向量运算、相交测试算法以及递归遍历。
摘要由CSDN通过智能技术生成

作业要求

  • Render() in Renderer.cpp: 将你的光线生成过程粘贴到此处,并且按照新框
    架更新相应调用的格式。
  • Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数
    粘贴到此处,并且按照新框架更新相应相交信息的格式。
    在本次编程练习中,你需要实现以下函数:
  • IntersectP(const Ray& ray, const Vector3f& invDir,
    const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp: 这个函数的
    作用是判断包围盒 BoundingBox 与光线是否相交,你需要按照课程介绍的算
    法实现求交过程。
  • getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: 建
    立 BVH 之后,我们可以用它加速求交过程。该过程递归进行,你将在其中调
    用你实现的 Bounds3::IntersectP.

具体实现

  • Render()函数,与之前不同的是,在这里定义了一个光线Ray类,所以在算出来光线的方向之后需要生成一个Ray类的对象,其中光线的原点就是相机的位置,具体代码如下:
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(-1, 5, 10);
    int m = 0;
    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;
            // TODO: Find the x and y positions of the current pixel to get the
            // direction
            //  vector that passes through it.
            // Also, don't forget to multiply both of them with the variable
            // *scale*, and x (horizontal) variable with the *imageAspectRatio*

            // Don't forget to normalize this direction!
            Vector3f dir = normalize(Vector3f(x, y, -1));
            Ray r(eye_pos,dir);
            framebuffer[m++] = scene.castRay(r, 0);

        }
        UpdateProgress(j / (float)scene.height);
    }
    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 * clamp(0, 1, framebuffer[i].x));
        color[1] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].y));
        color[2] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].z));
        fwrite(color, 1, 3, fp);
    }
    fclose(fp);    
}
  • Triangle::getIntersection()函数,与上个作业不同的是,在这里把交点的信息封装成了一个Intersection对象,所以在这 之前需要熟悉一下Intersection类里面的信息,如下所示。
struct Intersection
{
    Intersection(){
        happened=false;
        coords=Vector3f();
        normal=Vector3f();
        distance= std::numeric_limits<double>::max();
        obj =nullptr;
        m=nullptr;
    }
    bool happened;   //表示光线是否与物体相交
    Vector3f coords; //相交的交点坐标
    Vector3f normal;  //相交点所在平面的法向量
    double distance; //相交点距离原点也就是相机的距离
    Object* obj;  //相交物体的类型,通过给的代码可以看出,这些类型主要有Sphere,Triangle,MeshTriangle
    Material* m;  //相交物体的材质
};

具体实现如下:

inline Intersection Triangle::getIntersection(Ray ray)
{
    Intersection inter;

    if (dotProduct(ray.direction, normal) > 0)
        return inter;
    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 = dotProduct(tvec, pvec) * det_inv;
    if (u < 0 || u > 1)
        return inter;
    Vector3f qvec = crossProduct(tvec, e1);
    v = dotProduct(ray.direction, qvec) * det_inv;
    if (v < 0 || u + v > 1)
        return inter;
    t_tmp = dotProduct(e2, qvec) * det_inv;

    // TODO find ray triangle intersection
    if(t_tmp < 0)  //t小于0没有交点
    return  inter;

    Vector3f tmp = ray.origin + t_tmp*ray.direction;
    inter.happened = true;
    inter.coords = tmp;
    inter.distance = sqrtf(dotProduct(t_tmp*ray.direction,t_tmp*ray.direction));
    inter.normal = this->normal;
    inter.m = this->m;
    inter.obj = this;
    return inter;
}
  • Bounds3::IntersectP()函数,该函数是判断光线是否与包围盒相交,需要知道课上讲的实现过程以及这个函数所给参数意义
    • 原理
      我们以2D AABB为例子,因此只有x,y两对平面,3D情况可类推:
      在这里插入图片描述
      首先如上图最左边所示,求出光线与x平面的交点,将先进入的交点(偏小的那个)记为 tmin, 后出去的交点(偏大的那个)记为 tmax,紧接着如中间图所示计算出光线与y平面的两个交点同样记为另外一组tmin, tmax,当然计算的过程中要注意如果任意的 t < 0,那么这代表的是光线反向传播与对应平面的交点。找出tmin中的较大值记为tenter,tmax中的较小值记为texit。接着还需要继续判断tenter和texit之间的关系才能确定光线和包围盒是否相交,如下图所示:
      在这里插入图片描述
    • 参数意义:ray就是光线;invDir表示一个向量,这个向量和ray中direction向量有关系,如果direction是(x,y,z),那么invDir就是(1.0/x,1.0/y,1.0/z),方便后面计算光线与平面的交点;dirIsNeg表示光线的方向;
    • 计算光线与平面的交点
      知道原理和参数意义之后就剩下计算问题了,具体方法如下
      在这里插入图片描述
      式子中的Ox,dx都是知道的,其中dx可以用invDir来代替就可以写成乘法的形式。现在只剩Px不知道,我们来看Bounds3这个类,它为我们提供了pMin,pMax两个三维向量,存储的就是包围盒的两个顶点,pMin表示包围盒x,y,z值最小的顶点,pMax表示包围盒x,y,z值最大的顶点。把pMin里面的x值换成Px相当于求出光线到达包围盒最前面那个面的时间,pMax里面的x值换成Px相当于求出光线到达包围盒最后面那个面的时间,依次就可以求出光线到达最左最右面,最上最下面的时间。代码如下:
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
    float txmin = (pMin.x - ray.origin.x) * invDir.x;
    float txmax = (pMax.x - ray.origin.x) * invDir.x;
    float tymin = (pMin.y - ray.origin.y) * invDir.y;
    float tymax = (pMax.y - ray.origin.y) * invDir.y;
    float tzmin = (pMin.z - ray.origin.z) * invDir.z;
    float tzmax = (pMax.z - ray.origin.z) * invDir.z;

    //dirIsNeg表示光线的方向,如果是负的则为0,正的则为1
    if(!dirIsNeg[0])
    std::swap(txmin,txmax);
    if(!dirIsNeg[1])
    std::swap(tymin,tymax);
    if(!dirIsNeg[2])
    std::swap(tzmin,tzmax);

    float tenter = std::max(std::max(txmin,tymin),txmin);
    float texit = std::min(std::min(txmax,tymax),tzmax);

    if(texit >= 0.0 && texit > tenter)
    return true;
    return false;
}
  • BVHAccel::getIntersection()函数,该函数返回光线与包围盒内物体的交点信息,大致过程如下
    • 光线是否与包围盒相交,不相交直接返回
    • 相交的话判断是不是叶子结点,如果是就调用该节点中物体的getIntersection()得到交点信息;(根据函数上面BVHAccel::recursiveBuild()函数可知,叶子结点里面只有一个物体)
    • 不是叶子结点就继续递归下去,找出光线与两个子树的交点信息,返回距离最近的
      代码如下:
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    std::array<int, 3> dirIsNeg;
    dirIsNeg[0] = (ray.direction[0]>0);
    dirIsNeg[1] = (ray.direction[1]>0);
    dirIsNeg[2] = (ray.direction[2]>0);
    Intersection inter,interLeft,interRight;
    if(!node->bounds.IntersectP(ray,ray.direction_inv,dirIsNeg))  //没有交点
    return inter;
    if(node->left == nullptr && node->right == nullptr)  //是叶子结点
    {
        inter = node->object->getIntersection(ray);
        return inter;
    }

    //不是叶子结点
    interLeft = getIntersection(node->left,ray);
    interRight = getIntersection(node->right,ray);
    if(interLeft.distance < interRight.distance) return interLeft;
    else return interRight;
}

运行结果

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

译制片~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值