GAMES101作业6及课程总结(重点解决SAH扩展作业)

news2025/5/16 0:48:45

这次作业相对于作业5会麻烦一点点,而且框架相较于作业五的也麻烦了一点,当然作业的难点其实主要还是在扩展作业SAH那块

目录

    • 课程总结与理解(光线追踪)
    • 框架梳理
    • 作业一:光线生成
    • 作业二:光线-三角形相交
    • 作业三:包围盒与光线相交
    • 作业四:使用BVH求交
    • 扩展:使用SAH规则构建BVH,从而加速BVH求交
    • 最终效果
    • 感悟
    • 参考链接

课程总结与理解(光线追踪)

光线追踪流程,从全局来看就是先往scene里面添加物体(包括物体本身的材质信息和各种属性)和光源,其中物体既可以是显示表示(三角网格),也可以是隐式表示(球,用方程)。添加完之后就调用Render函数进行渲染以上就是对应图形学两个环节,建模和渲染,如果要让他们动起来的话就是还要加个模拟。

渲染流程大概就是先生成光线(视点与屏幕像素中心点的连线),然后光线与场景相交找到第一个与之相交的点,这里就用到了包围盒的思想,加速求交过程,涉及到三个部分,如何构建包围盒(左下角和右上角,与三个轴平行的包围盒),如何计算包围盒与光线求交(和裁剪那边有点类似,本质上就是相似三角形构造比例),以及如何使用包围盒:均匀网格,空间划分,物体划分(BAH)其中物体划分的规则涉及到了NAIVE规则和SAH规则。当光线与包围盒求交判断有交点之后,就继续与其子包围盒求交,直到找到最后的子包围盒(树的叶子节点),然后让直线与在该叶子节点对应的子包围盒中的全部物体求交,找到最近交点(其中,对于不同物体的求交方法是不一样的,比如对于球这种隐式表示的,就用解析解的方法直接求交点,对于三角网格这种显示表示的,就依次遍历其所有三角形,用线和三角形求交的方法求交点)。之后根据各种渲染的方法对该点着色(不同材质的着色方法不一样,代码里面写了三种材质,对应三种不同的着色方法,并且这里还需要判断点是否处在阴影中,若在阴影中,则不需要着色),然后该点的颜色就会最终返回,作为最终渲染图像的一个像素值。射出全部光线就能得到全部像素值,最终得到渲染图像。

框架梳理

上次作业5由于框架比较好理解,所以没有去理框架,这次为了方便后面作业,就简单理一理。

首先看main函数,总的来看就两个部分,添加模型和光源,然后渲染场景。

int main(int argc, char** argv)
{
    Scene scene(1280, 960);

    MeshTriangle bunny("../models/bunny/bunny.obj");

    scene.Add(&bunny);
    scene.Add(std::make_unique<Light>(Vector3f(-20, 70, 20), 1));
    scene.Add(std::make_unique<Light>(Vector3f(20, 70, 20), 1));
    scene.buildBVH();

    Renderer r;

    auto start = std::chrono::system_clock::now();
    r.Render(scene);
    auto stop = std::chrono::system_clock::now();

    std::cout << "Render complete: \n";
    std::cout << "Time taken: " << std::chrono::duration_cast<std::chrono::hours>(stop - start).count() << " hours\n";
    std::cout << "          : " << std::chrono::duration_cast<std::chrono::minutes>(stop - start).count() << " minutes\n";
    std::cout << "          : " << std::chrono::duration_cast<std::chrono::seconds>(stop - start).count() << " seconds\n";

    return 0;
}

然后进入Render函数,总的来看就是很简单的生成光线,然后通过castRay函数求出该光线射出去计算得到并最终返回给像素的颜色,得到所有像素颜色之后就能得到最终的渲染图像。

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 = Vector3f(x, y, -1); // Don't forget to normalize this direction!
            dir = normalize(dir);
            Ray 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);    
}

然后进入castRay函数,这个函数也不难,就通过intersect函数求出光线与场景物体的最近交点,然后就根据不同的材质使用不同的着色方法对该点着色即可。
着色的细节可以参考https://blog.csdn.net/qq_41835314/article/details/124969379,这里不重点阐述这部分,主要重点阐述如何得到这个交点。

Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    if (depth > this->maxDepth) {
        return Vector3f(0.0,0.0,0.0);
    }
    Intersection intersection = Scene::intersect(ray);
    Material *m = intersection.m;
    Object *hitObject = intersection.obj;
    Vector3f hitColor = this->backgroundColor;
//    float tnear = kInfinity;
    Vector2f uv;
    uint32_t index = 0;
    if(intersection.happened) {

        Vector3f hitPoint = intersection.coords;
        Vector3f N = intersection.normal; // normal
        Vector2f st; // st coordinates
        hitObject->getSurfaceProperties(hitPoint, ray.direction, index, uv, N, st);
//        Vector3f tmp = hitPoint;
        switch (m->getType()) {
            case REFLECTION_AND_REFRACTION:
            {
                Vector3f reflectionDirection = normalize(reflect(ray.direction, N));
                Vector3f refractionDirection = normalize(refract(ray.direction, N, m->ior));
                Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
                                             hitPoint - N * EPSILON :
                                             hitPoint + N * EPSILON;
                Vector3f refractionRayOrig = (dotProduct(refractionDirection, N) < 0) ?
                                             hitPoint - N * EPSILON :
                                             hitPoint + N * EPSILON;
                Vector3f reflectionColor = castRay(Ray(reflectionRayOrig, reflectionDirection), depth + 1);
                Vector3f refractionColor = castRay(Ray(refractionRayOrig, refractionDirection), depth + 1);
                float kr;
                fresnel(ray.direction, N, m->ior, kr);
                hitColor = reflectionColor * kr + refractionColor * (1 - kr);
                break;
            }
            case REFLECTION:
            {
                float kr;
                fresnel(ray.direction, N, m->ior, kr);
                Vector3f reflectionDirection = reflect(ray.direction, N);
                Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
                                             hitPoint + N * EPSILON :
                                             hitPoint - N * EPSILON;
                hitColor = castRay(Ray(reflectionRayOrig, reflectionDirection),depth + 1) * kr;
                break;
            }
            default:
            {
                // [comment]
                // We use the Phong illumation model int the default case. The phong model
                // is composed of a diffuse and a specular reflection component.
                // [/comment]
                Vector3f lightAmt = 0, specularColor = 0;
                Vector3f shadowPointOrig = (dotProduct(ray.direction, N) < 0) ?
                                           hitPoint + N * EPSILON :
                                           hitPoint - N * EPSILON;
                // [comment]
                // Loop over all lights in the scene and sum their contribution up
                // We also apply the lambert cosine law
                // [/comment]
                for (uint32_t i = 0; i < get_lights().size(); ++i)
                {
                    auto area_ptr = dynamic_cast<AreaLight*>(this->get_lights()[i].get());
                    if (area_ptr)
                    {
                        // Do nothing for this assignment
                    }
                    else
                    {
                        Vector3f lightDir = get_lights()[i]->position - hitPoint;
                        // square of the distance between hitPoint and the light
                        float lightDistance2 = dotProduct(lightDir, lightDir);
                        lightDir = normalize(lightDir);
                        float LdotN = std::max(0.f, dotProduct(lightDir, N));
                        Object *shadowHitObject = nullptr;
                        float tNearShadow = kInfinity;
                        // is the point in shadow, and is the nearest occluding object closer to the object than the light itself?
                        bool inShadow = bvh->Intersect(Ray(shadowPointOrig, lightDir)).happened;
                        lightAmt += (1 - inShadow) * get_lights()[i]->intensity * LdotN;
                        Vector3f reflectionDirection = reflect(-lightDir, N);
                        specularColor += powf(std::max(0.f, -dotProduct(reflectionDirection, ray.direction)),
                                              m->specularExponent) * get_lights()[i]->intensity;
                    }
                }
                hitColor = lightAmt * (hitObject->evalDiffuseColor(st) * m->Kd + specularColor * m->Ks);
                break;
            }
        }
    }

    return hitColor;
}

这个时候再进一步看intersect函数,这里实际上就是去使用构造好的bvh求交点了。(怎么构造BVH,后面会详细阐述,先继续看下去

Intersection Scene::intersect(const Ray &ray) const
{
    return this->bvh->Intersect(ray);
}

继续往下看,此时会调用下面的函数,其中getIntersection函数里的内容就是作业之一(这部分后面会详细阐述),简单来说就是利用BVH求出交点

Intersection BVHAccel::Intersect(const Ray& ray) const
{
    Intersection isect;
    if (!root)
        return isect;
    isect = BVHAccel::getIntersection(root, ray);
    return isect;
}

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    std::array<int, 3> dirIsNeg = { (ray.direction.x > 0), (ray.direction.y > 0), (ray.direction.z > 0) };
    Intersection isect;
    isect.happened = node->bounds.IntersectP(ray, ray.direction_inv, dirIsNeg);
    if (!isect.happened) return isect;
    if (node->left == nullptr && node->right == nullptr)
        return node->object->getIntersection(ray);
    Intersection isect1,isect2;
    isect1 = getIntersection(node->left, ray);
    isect2 = getIntersection(node->right, ray);
    if (isect1.distance < isect2.distance)
        return isect1;
    else
        return isect2;
}

然后还有上面遗留的问题,就是BVH是如何构造的,主要就是这一段代码,构造方法也很简单,就是老师ppt里面提到的那个NAIVE规则,按最长轴切分,取中间的三角形为划分界限,划出两个子包围盒作为左右节点

然后有个小细节,就是BVH是在代码中什么时候被构造的,其实这里有两个地方进行了BVH的构造,分别是在MeshTriangle(const std::string& filename)和void Scene::buildBVH()中,简单来说就是对模型整体构造了一个BVH,然后对整个场景又构造了一次BVH(该BVH只包含了一个模型,因此是叶子节点)。在渲染求交点的时候,会先利用场景的BVH进行判断,此时场景的BVH是叶子节点,如果和模型的包围盒有交点,就会通过node->object->getIntersection(ray)去调用模型的BVH继续求交点,之所以这么设计,是因为模型由三角形组成,可以说是内部又嵌套了一层。

BVHAccel::BVHAccel(std::vector<Object*> p, int maxPrimsInNode,
                   SplitMethod splitMethod)
    : maxPrimsInNode(std::min(255, maxPrimsInNode)), splitMethod(splitMethod),
      primitives(std::move(p))
{
    time_t start, stop;
    time(&start);
    if (primitives.empty())
        return;

    if (splitMethod == SplitMethod::NAIVE)
    {
        root = recursiveBuild(primitives);
        printf("Using NAIVE SplitMethod\n");
    }
    else if (splitMethod == SplitMethod::SAH)
    {
        root = recursiveBuildBySAH(primitives);
        printf("Using SAH SplitMethod\n");
    }

    time(&stop);
    double diff = difftime(stop, start);
    int hrs = (int)diff / 3600;
    int mins = ((int)diff / 60) - (hrs * 60);
    int secs = (int)diff - (hrs * 3600) - (mins * 60);

    printf(
        "\rBVH Generation complete: \nTime Taken: %i hrs, %i mins, %i secs\n\n",
        hrs, mins, secs);
}

BVHBuildNode* BVHAccel::recursiveBuild(std::vector<Object*> objects)
{
    BVHBuildNode* node = new BVHBuildNode();

    // Compute bounds of all primitives in BVH node
    Bounds3 bounds;
    for (int i = 0; i < objects.size(); ++i)
        bounds = Union(bounds, objects[i]->getBounds());
    if (objects.size() == 1) {
        // Create leaf _BVHBuildNode_
        node->bounds = objects[0]->getBounds();
        node->object = objects[0];
        node->left = nullptr;
        node->right = nullptr;
        return node;
    }
    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;
    }
    else {
        Bounds3 centroidBounds;
        for (int i = 0; i < objects.size(); ++i)
            centroidBounds =
                Union(centroidBounds, objects[i]->getBounds().Centroid());
        int dim = centroidBounds.maxExtent();
        switch (dim) {
        case 0:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().x <
                       f2->getBounds().Centroid().x;
            });
            break;
        case 1:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().y <
                       f2->getBounds().Centroid().y;
            });
            break;
        case 2:
            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<Object*>(beginning, middling);
        auto rightshapes = std::vector<Object*>(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);
    }

    return node;
}

作业一:光线生成

就是把作业5的改改。

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 = Vector3f(x, y, -1); // Don't forget to normalize this direction!
            dir = normalize(dir);
            Ray 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);    
}

作业二:光线-三角形相交

一样,就是把作业5的改改。

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)
        return inter;

    //去看Sphere那个函数的写法就行
    inter.happened = true;
    inter.coords = ray(t_tmp);
    inter.normal = normal;
    inter.distance = t_tmp;
    inter.obj = this;
    inter.m = this->m;
    return inter;
}

作业三:包围盒与光线相交

不难,老师的ppt已经讲得很清楚了。核心本质就是当光线进入盒子中时,必然点进入了所有的对面(对应max(tmin)),当光线离开盒子时,必然离开了某一个对面(对应min(tmax)),所以才会有max和min的处理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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
    //注:dirIsNeg我觉得没必要用,我感觉我这种逻辑更容易理解,更小的t肯定是min
    float t1,t2,t_min_x, t_max_x, t_min_y, t_max_y, t_min_z, t_max_z, t_enter, t_exit;
    t1 = (pMin.x - ray.origin.x) * invDir.x; t2 = (pMax.x - ray.origin.x) * invDir.x;
    t_min_x = fminf(t1, t2); t_max_x = fmaxf(t1, t2);
    t1 = (pMin.y - ray.origin.y) * invDir.y; t2 = (pMax.y - ray.origin.y) * invDir.y;
    t_min_y = fminf(t1, t2); t_max_y = fmaxf(t1, t2);
    t1 = (pMin.z - ray.origin.z) * invDir.z; t2 = (pMax.z - ray.origin.z) * invDir.z;
    t_min_z = fminf(t1, t2); t_max_z = fmaxf(t1, t2);

    t_exit = fminf(fminf(t_max_x, t_max_y), t_max_z);
    if (t_exit < 0) return false;
    t_enter = fmaxf(fmaxf(t_min_x, t_min_y), t_min_z);
    if (t_enter >= t_exit)return false;
    return true;
}

作业四:使用BVH求交

也不难,主要是看懂框架,然后照着老师ppt的伪代码写就行。
在这里插入图片描述

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    std::array<int, 3> dirIsNeg = { (ray.direction.x > 0), (ray.direction.y > 0), (ray.direction.z > 0) };
    Intersection isect;
    isect.happened = node->bounds.IntersectP(ray, ray.direction_inv, dirIsNeg);
    if (!isect.happened) return isect;
    if (node->left == nullptr && node->right == nullptr)
        return node->object->getIntersection(ray);
    Intersection isect1,isect2;
    isect1 = getIntersection(node->left, ray);
    isect2 = getIntersection(node->right, ray);
    if (isect1.distance < isect2.distance)
        return isect1;
    else
        return isect2;
}

扩展:使用SAH规则构建BVH,从而加速BVH求交

但其实上述代码使用的NAIVE划分方法在面对不均匀场景的时候效果一般(会有很多空间冗余,光线与这个包围盒相交,但是真正与物体相交的概率却不大),如下图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kPyt759B-1672851153723)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20230104202307647.png)]

SAH划分方法实际上是NAIVE划分方法的进一步优化,SAH划分也很简单其实,就是在待定的划分中,通过计算相应划分的cost,取最小cost,从而得到最优划分。这就涉及到两个环节第一:怎么得到待定的划分?它这里的方法就是依次遍历三个轴,然后每个轴根据物体的数量划分成特定数量的桶,利用桶就能得到一个又一个的划分。第二:怎么计算cost,也就是如何评价这次划分好还是不好?cost的计算方法已经由下图给出,还是比较简单的,就是要抽象出划分之后,直线与这个大包围盒相交的代价(可以简单理解为计算开销),下图公式将其抽象成了三个部分,第一是一些基本的固定开销(直线和大包围盒的相交判断,直线和两个子包围盒的相交判断),然后第二和第三加起来就是划分出来的两个子包围盒带来的期望开销(就是说如果直线与子包围盒有相交,那就要计算直线与子包围盒里面全部物体相交所带来的开销,但是直线是不一定与子包围盒相交的,所以前面要乘上直线与子包围盒相交的概率),其中直线与子包围盒相交的概率由大包围盒与子包围盒的表面积之比求出(有点奇怪为啥不用体积,体积不是更合适点嘛==),直线与子包围盒内全部物体相交带来的开销可以自定义(代码里面就简化为了包围盒里物体的数量×1,当然这边可以更复杂,比如不同的物体会带来不同的开销)。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

BVHBuildNode* BVHAccel::recursiveBuildBySAH(std::vector<Object*> objects)
{
    int Bucket_num = 10;
    if(objects.size()< Bucket_num)
        return recursiveBuild(objects);

    BVHBuildNode* node = new BVHBuildNode();
    double minCost = std::numeric_limits<double>::infinity();
    int mindim, split_index;
    for (int dim = 0; dim < 3; dim++)
    {
        //按x,y,z轴排序
        switch (dim) {
        case 0:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().x <
                    f2->getBounds().Centroid().x;
                });
            break;
        case 1:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().y <
                    f2->getBounds().Centroid().y;
                });
            break;
        case 2:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().z <
                    f2->getBounds().Centroid().z;
                });
            break;
        }

        Bounds3 bounds;
        for (int i = 0; i < objects.size(); ++i)
            bounds = Union(bounds, objects[i]->getBounds());

        Bounds3 bucketA;
        int p_in_bucket_num , p_in_bucketA_num, p_in_bucketB_num;
        p_in_bucket_num = objects.size() / Bucket_num;
        p_in_bucketA_num = 0;
        p_in_bucketB_num = 0;
        for (int Bucket_index = 0; Bucket_index < Bucket_num-1; Bucket_index++)
        {
            int i;
            for (i = 0; i < p_in_bucket_num; i++)
            {
                bucketA = Union(bucketA, objects[Bucket_index* p_in_bucket_num+i]->getBounds());
                p_in_bucketA_num++;
            }
            Bounds3 bucketB;
            for (int j = Bucket_index * p_in_bucket_num + i; j < objects.size(); j++)
            {
                bucketB = Union(bucketB, objects[j]->getBounds());
            }
            p_in_bucketB_num = objects.size() - p_in_bucketA_num;
            double cost = (bucketA.SurfaceArea() * p_in_bucketA_num + bucketB.SurfaceArea() * p_in_bucketB_num) / bounds.SurfaceArea();
            if (minCost > cost)
            {
                minCost = cost;
                mindim = dim;
                split_index = p_in_bucketA_num;
            }
        }
    }
    switch (mindim) {
    case 0:
        std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
            return f1->getBounds().Centroid().x <
                f2->getBounds().Centroid().x;
            });
        break;
    case 1:
        std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
            return f1->getBounds().Centroid().y <
                f2->getBounds().Centroid().y;
            });
        break;
    case 2:
        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() + (split_index);
    auto ending = objects.end();

    auto leftshapes = std::vector<Object*>(beginning, middling);
    auto rightshapes = std::vector<Object*>(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);
    return node;
}

看得更深一点,就是实际上这个cost的作用(从公式本身去看他),就是让子包围盒的表面积尽可能小(减少冗余空间,说明光线若与这个包围盒相交,那么我们会希望交点大概率是在这个包围盒内部),同时,在物体必须全部被两个子包围盒囊括的情况下,小的子包围盒倾向于装更多且相交开销大的物体(光线与小的包围盒求交,相交概率不大,由此就能尽可能避免与内部物体相交产生的额外开销),另一个大点的子包围盒倾向于装更少且相交开销小的物体(光线与大的包围盒求交,相交概率大,由此可以最小化与内部物体相交产生的开销)。一个原则,把光线与物体的相交计算用在真正正确的地方

至于为什么这样的cost会解决不均匀的问题,好吧,我尝试思考了一个晚上,回答不了,想的头痛,就这样吧。因为我在思考的时候遇到了一个矛盾的点,一直想不通,擦,就是均匀意味着大的子包围盒要有更多的物体(不会有空间冗余,但是计算开销大),而这个cost方法会倾向于让大的子包围盒有更少的物体(会有空间冗余,但是计算开销小)。那你说,tmd,哪个更好呢?我擦,虽然这是简单逻辑,但其实并不好想通,也许,这就是两个矛盾的,想通不了==

等下,这有点感觉像博弈,就是说第一种情况的话,我倾向于高风险高收益(认为大包围盒大概率能找到交点,就想在大包围盒里面找到交点,所以加很多物体进去),第二种情况的话我倾向于低风险低收益(认为大包围盒大概率找不到交点,想早点跳过大包围盒,所以加尽可能少的物体进去)。

算了,不想了,头晕。

tmd,就先这么理解,就是说,让子包围盒的表面积尽可能小这一步就已经让其实现了均匀划分的效果(包围盒被尽可能的压缩了),然后之后的大的子包围盒有更少的物体这个倾向就是SAH在保持均匀划分效果下的进一步优化。

最终效果

至于渲染速度的话,读者有时间可以用自己电脑对比一下优化前和优化后,我的电脑太老了,渲染时间都得接近30,我这边优化前和优化后的速度差别不大,偶尔差个一两秒,看其他博主结果好像快的也不多,可能是场景还不够复杂没有充分体现出来==
在这里插入图片描述

感悟

tmd,写到半夜1点,本来是不想写的,但是想了想可能会对其他后来人有用,还是坚持写完了,害,可能我的理解会有一些错误,如有错误,还望读者见谅==

参考链接

1.https://zhuanlan.zhihu.com/p/477316706
2.http://15462.courses.cs.cmu.edu/fall2015content/lectures/10_acceleration/10_acceleration_slides.pdf
3.https://blog.csdn.net/onion23/article/details/126625653(感觉这个老哥代码写的不错)
4.https://blog.csdn.net/qq_41835314/article/details/125073507
5.https://zhuanlan.zhihu.com/p/50720158
6.https://blog.csdn.net/weixin_44491423/article/details/127485933
7.https://blog.csdn.net/ycrsw/article/details/124331686

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Neo4j图数据库 批量写入与查询

1 前言 1-1 简介 工作中需要对所有的实体数据进行存储构建实体知识图谱&#xff0c;为基于知识图谱的问答提供数据基础。选择使用Neo4j作为数据库进行存储。以下是关于Neo4j的简介。 1-2 任务背景 将处理好的实体数据(共计1100万)写入图数据库中&#xff0c;并且提供查询接口…

量子计算(二十):量子算法简介

文章目录 量子算法简介 一、概述 二、量子经典混合算法 量子算法简介 一、概述 量子算法是在现实的量子计算模型上运行的算法&#xff0c;最常用的模型是计算的量子电路模型。经典&#xff08;或非量子&#xff09;算法是一种有限的指令序列&#xff0c;或一步地解决问题的…

乐视--996、内卷、裁员环境下一朵“奇葩”

在2022.12.28日我们发表了一篇“为什么四天工作制才是企业良药&#xff0c;而非裁员”,大家认为四天工作制与我们的距离就像实现“一个小目标”一样&#xff0c;不太可能。这不他来了&#xff0c;乐视来了&#xff0c;他真的来了&#xff0c;“鸡毛真的上天了”。他来了他来了他…

SQL技巧:使用AVG()函数计算占比

计算方式对比 一般计算占比&#xff0c;比如转换率、留存率等&#xff0c;都是先分组求和再相除得到结果&#xff0c;但是在一定的条件下&#xff0c;可以直接使用AVG()求出百分比。 比如&#xff0c;要求统计报名转化率&#xff0c;报名转化率公式为转化率报名人数/浏览人数…

内核解读之内存管理(8)内存模型

文章目录基本的术语CONFIG_FLATMEM&#xff08;平坦内存模型&#xff09;稀疏的内存模型基本的术语 在介绍内存模型之前需要了解一些基本的知识。 1、什么是page frame&#xff1f; 在linux操作系统中&#xff0c;物理内存被分成一页页的page frame来管理&#xff0c;具体pa…

c++11 标准模板(STL)(std::deque)(八)

定义于头文件 <deque> std::deque 修改器 擦除元素 std::deque<T,Allocator>::erase iterator erase( iterator pos ); (1)(C11 前) iterator erase( const_iterator pos ); (C11 起) iterator erase( iterator first, iterator last ); (2)(C11 前) iterator …

即时编译助力人大金仓KES分析能力飞跃

随着数字化技术对各行各业的不断渗透&#xff0c;人大金仓在金融、能源、电信等行业逐步进入深水区&#xff0c;面临越来越多的核心类系统改造升级&#xff0c;这些系统不仅需要满足在线交易系统运行的高实时性要求&#xff0c;还需要保证高效分析能力以帮助客户进行业务决策。…

红米pro14笔记本系统故障怎么U盘重装系统?

红米pro14笔记本系统故障怎么U盘重装系统&#xff1f;今天和大家一起来分享如何使用U盘重装系统的方法分享。有用户的红米pro14笔记本系统出现了一些问题需要进行重新安装&#xff0c;那么今天我们就一起来分享看看怎么U盘重装系统的方法吧。 准备工作&#xff1a; 1、U盘一个&…

Java执行Linux命令死锁阻塞挂起,Runtime.getRuntime().exec阻塞卡死问题解决

1、前言&#xff1a; 最近在做一个需求需要调用linux下的ffmpeg来对处理视频&#xff0c;很简单的需求&#xff0c;我像往常一样写下如下的代码片段&#xff1a; Process process Runtime.getRuntime().exec(cmd); process.waitFor(); But当我运行代码时&#xff0c;发现代码执…

前端笔记 ---- document.execCommand 函数整理

1. 语法 使用语法 bool document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)返回值 一个 Boolean &#xff0c;如果是 false 则表示操作不被支持或未被启用。 备注&#xff1a; 在调用一个命令前&#xff0c;不要尝试使用返回值去校验浏览器的兼容性 2. 参…

基于Vue和SpringBoot的宾馆管理系统的设计和实现

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发&#xff08;Vue、SpringBoot和微信小程序&#xff09;、系统定制、远程技术指导。CSDN学院、蓝桥云…

树形结构——红黑树

前言 在 JDK1.8 之后&#xff0c;HashMap 的底层是由数组、链表、红黑树来实现的&#xff0c;当数组长度到 64 的时候&#xff0c;或者链表长度到 8 的时候&#xff0c;会调用 treeifyBin 转换为红黑树实现。因为红黑树是小伙伴们面试的时候经常被考到的知识点&#xff0c;因此…

OSPF-MGRE实验

1.首先配ip [r6]int g 0/0/1 [r6-GigabitEthernet0/0/1]ip add 192.168.1.2 24 [r6-GigabitEthernet0/0/1]int g 0/0/0 [r6-GigabitEthernet0/0/0]ip add 192.168.2.2 24 [r6-GigabitEthernet0/0/0]int g 0/0/2 [r6-GigabitEthernet0/0/2]ip add 192.168.3.2 24 [r6-GigabitEt…

git pull 和git fetch

1.git fetch 用户一&#xff1a;本地初始化项目&#xff0c;创建文件&#xff0c;保存本地仓库&#xff0c;提交远程仓库 $ git init $ touch file.txt $ git add . $ git commit -m "创建了file.txt文件" [master (root-commit) 4dcee36] 创建了file.txt文件1 file …

简单又好用的财务分析工具有哪些?

什么样的财务分析工具才能算是简单又好用&#xff1f;是能够快速完成组合多变的财务指标运算分析&#xff1b;能够充分发挥企业经营健康晴雨表作用&#xff0c;反映企业财务健康状态&#xff1b;还是能够支持多维度动态分析、自助分析&#xff1b;或者是轻松合并账套&#xff0…

跨域与JSONP

1、同源策略 1.1、什么是同源 如果两个页面的协议&#xff0c;域名和端口都相同&#xff0c;则两个页面具有相同的源。 例如&#xff0c;下表给出了相对于 http://www.test.com/index.html 页面的同源检测&#xff1a; URL 是否同源 原因 http://www.test.com/other.html…

智慧图书馆中的“智慧”体现在哪些方面?

在信息时代背景下&#xff0c;各个领域都发生了巨大变革&#xff0c;图书馆也不例外&#xff0c;开始逐步向着现代化方向发展。传统图书馆存在较多的缺陷&#xff0c;已经无法满足人们的借阅需求&#xff0c;引进信息化技术&#xff0c;打造智慧图书馆是目前图书馆的必然发展趋…

Linux学习记录——오 vim基本知识

** Linux开发工具 ** Linux开发工具——vim vim最小集 vim是一个多模式编辑器&#xff0c;vi也一样&#xff0c;但vim兼容了vi的所有指令&#xff0c;还有一些独有的特性&#xff0c;本篇只针对vim展开。vim有各种模式&#xff0c;每个模式的用法都有差别&#xff0c;模式…

【数据在内存中的存储】肝货满满

前言 我们知道在C语言中的基本内置类型&#xff1a; char //字符数据类型short //短整型int //整形long //长整形long long //更长的整形float //单精度浮点型double //双精度浮点型 那么这些类型是如何存储的呢&#xff1f; 回顾指针类型&#xff1a; *int pi*char pc*float…

String的讲解(Java系列9)

目录 前言&#xff1a; 1.String 1.1字符串的构造 1.2Sting对象的比较 1.3字符串的查找 1.4字符串的转化 1.4.1数值和字符转换 1.4.2大小写转换 1.4.3字符串转数组 1.4.4格式化 1.5字符串的替换 1.6字符串拆分 1.7字符串截取 1.8字符串去空格 1.9字符串的不可变…