您当前的位置: 首页 >  flashinggg

GAMES101作业6-BVH完成全过程

flashinggg 发布时间:2022-06-07 13:56:40 ,浏览量:1

目录

作业要求

Render.cpp

TODO:需要的补充内容

Triangle.hpp

框架

Ray.hpp -> struct Ray

Intersection.hpp -> struct Intersection

判断有无交点

TODO:需要的补充内容

Bounds3.hpp

TODO:需要的补充内容

BVH.cpp

框架

BVHAccel::BVHAccel()

recursiveBuild()

框架

分类讨论&递归

TODO:getIntersection() 

遇到的问题汇总

报错C4996: 'fopen'不安全

解决方法

报错:Assertion failed

解决方法

结果展示

渲染时间过长的原因及解决方法

作业要求

一共有四个内容需要我们补充,其中前面的两个过程与作业5类似。 

Render.cpp

光线生成,这里跟作业5的基本相同,就不做过多的注释。

TODO:需要的补充内容
void Renderer::Render(const Scene& scene)
{
    std::vector 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));
            //castRay(const Ray ray, int depth)
            //Ray(ori, dir, _t=0)
            Ray ray(eye_pos, dir);
            framebuffer[m++] = scene.castRay(ray, 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.hpp

补充光线与三角形求交的getIntersection()函数.

框架
inline Intersection Triangle::getIntersection(Ray ray)
{
    Intersection inter;
    ...
    return inter;
}

其中包含的两个结构 Ray 和 Intersection:

Ray.hpp -> struct Ray
struct Ray{
    //Destination = origin + t*direction
    Vector3f origin;
    Vector3f direction, direction_inv;
    double t;//transportation time,
    double t_min, t_max;

    Ray(const Vector3f& ori, const Vector3f& dir, const double _t = 0.0): origin(ori), direction(dir),t(_t) {
        direction_inv = Vector3f(1./direction.x, 1./direction.y, 1./direction.z);
        t_min = 0.0;
        t_max = std::numeric_limits::max();

    }

    Vector3f operator()(double t) const{return origin+direction*t;}

    friend std::ostream &operatorleft = recursiveBuild(std::vector{objects[0]});
        node->right = recursiveBuild(std::vector{objects[1]});
        node->bounds = Union(node->left->bounds, node->right->bounds);
        return node;
    }
...

②只有两个物体:

...
//如果只有两个物体,则把两个物体分别作为左右子节点进行遍历,下一个recur这两个物体就可以分别作为size==1的叶节点了
    else if (objects.size() == 2) {
        node->left = recursiveBuild(std::vector{objects[0]});
        node->right = recursiveBuild(std::vector{objects[1]});
        node->bounds = Union(node->left->bounds, node->right->bounds);
        return node;
    }
...

③物体>2个:

 //>2个物体
    else {
        Bounds3 centroidBounds;
        for (int i = 0; i < objects.size(); ++i)
            //得到所有box的中心覆盖的范围AABB
            centroidBounds =
                Union(centroidBounds, objects[i]->getBounds().Centroid());//centroid()得到box的中心
        //得到覆盖范围最大的轴(x->0 y->1 z->2)
        int dim = centroidBounds.maxExtent();
        //在该轴进行排序,升序排列
        switch (dim) {
        case 0://x
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().x <
                       f2->getBounds().Centroid().x;
            });
            break;
        case 1://y
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().y <
                       f2->getBounds().Centroid().y;
            });
            break;
        case 2://z
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().z <
                       f2->getBounds().Centroid().z;
            });
            break;
        }
        //对半分
        auto beginning = objects.begin();
        auto middling = objects.begin() + (objects.size() / 2);
        auto ending = objects.end();
        //分成左右两份,分别进行
        auto leftshapes = std::vector(beginning, middling);
        auto rightshapes = std::vector(middling, ending);

        assert(objects.size() == (leftshapes.size() + rightshapes.size()));
        //开始递归了,把叶节点不断分成左右两个子节点,直到到了最后只剩一个物体
        node->left = recursiveBuild(leftshapes);
        node->right = recursiveBuild(rightshapes);

        node->bounds = Union(node->left->bounds, node->right->bounds);
    }
TODO:getIntersection() 

这里主要是要有个分类的思路,我在代码注释里展示出来了,具体看代码就好:

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    //递归调用Bounds3::intersectP
    Intersection res;
    // 首先判断这个ray与当前box是否有交点:
    // 1.如果没有交点 -> 那就不用继续了,因为再进行细分也没有意义,直接返回当前的intersection
    std::array dirIsNeg = { int(ray.direction.x>0),int(ray.direction.y > 0),int(ray.direction.z > 0) };
    if (!node->bounds.IntersectP(ray, ray.direction_inv, dirIsNeg)) {
        return res;
    }
    // 2.如果有交点 -> 2.1若该点无子节点,则返回该交点,该节点是没有子节点,可以进行BVN判断是否相交
    if (node->left == nullptr && node->right == nullptr) {
        res = node->object->getIntersection(ray);
        return res;
    }
    //2.1该点有子节点,则左右子节点分别判断,继续递归
    Intersection resleft, resright;
    resleft = getIntersection(node->left, ray);
    resright = getIntersection(node->right, ray);
    return resleft.distance < resright.distance ? resleft : resright;
}
遇到的问题汇总 报错C4996: 'fopen'不安全

解决方法

属性 -> C/C++ -> 预处理器 -> 定义 ->加上以下定义即可:

_CRT_SECURE_NO_WARNINGS

 

报错:Assertion failed

解决方法

检查main.cpp里obj模型的路径,如果是两个点,需要将model文件夹放在VS项目文件夹下面才行。

结果展示

注意,跟作业5一样,输出的是ppm文件,可以用PS等软件打开再保存成png等图片格式即可。 

吐槽:用时80min我真的会谢。。。电脑太拉了TAT,迟早给他换掉!!

渲染时间过长的原因及解决方法

这里是做完作业7后的我,又回来复盘了!

作业6渲染时长竟然有快80min,后来我发现大家大部分都是用了几秒就完成了,开始回来找问题,后来看到了这个问题:

作业6渲染时间过长是为什么 – 计算机图形学与混合现实在线平台 (games-cn.org)

其中,有两个人给了问题的解决方法:

我再回去看了一眼自己IntersectP函数:

inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
                                const std::array& dirIsNeg) const
{
    //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
    //判断包围盒Bounding Box与光线是否相交
    //tenter = max{tmin} texit = min{tmax}
    //先给个无穷初值
    double tenter = -std::numeric_limits::infinity();
    double texit = std::numeric_limits::infinity();
    for (int i = 0; i < 3; i++) {
        //求三个轴的tmin,tmax
        // invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z),
        double tmin = (pMin[i] - ray.origin[i]) * invDir[i];
        double tmax = (pMax[i] - ray.origin[i]) * invDir[i];
        //用dirIsNeg判断光线方向
        if (!dirIsNeg[i])//如果i= 0;
}

竟然用了一个循环!!! 

先甩一下把循环消除后的解决办法,用时11s

inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
    const std::array& 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 tmin = (pMin - ray.origin) * invDir;
    Vector3f tmax = (pMax - ray.origin) * invDir;
    Vector3f dir = ray.direction;
    if (dir.x < 0)
        std::swap(tmin.x, tmax.x);
    if (dir.y < 0)
        std::swap(tmin.y, tmax.y);
    if (dir.z < 0)
        std::swap(tmin.z, tmax.z);
    float texit = std::min(tmax.x, std::min(tmax.y, tmax.z));
    float tenter = std::max(tmin.x, std::max(tmin.y, tmin.z));
    return tenter < texit&& tenter > 0;
}

11s VS 80min 很多了!那么为什么会这样呢?道理其实很简单,递归如果有循环相当于指数增长了,用时也会爆炸增长,因此以后如果遇到这种需要取值的可以选择其他的方法,尽量避免循环的使用~【X】

【2023.6.13补充】其实时间差别不是循环的影响,我后来发现,循环用不用只是20s和10s的差别,真正原因是在tenter和texit那里max和min用错了导致的问题!

关注
打赏
1688896170
查看更多评论

flashinggg

暂无认证

  • 1浏览

    0关注

    83博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0473s