GAMES101作业5及框架梳理

闲言碎语

emmm,上一次写还是2022年4月份的事情了,真的有点恍如隔世,4月到9月主要是在准备保研的事情,然后10月到12月基本上是在适应实习生活(没错,保完研之后因为种种原因就直接开始实习了,害,好吧,其实基本上就是奢靡的摆烂生活==,因为压力忽然没了,基本就是在公司混日子,没干啥事),就有很多事情本来早就该弄了,但是自己一直拖拖拉拉,最近慢慢适应了工作节奏,就陆陆续续开始搞一些曾经想搞但一直没时间搞的东西,由于本科毕设是做渲染相关的,所以games101渲染部分的作业得好好做一做,争取在年前能够入个门。

好了回归正题,作业5总体来说不是很难,主要是把整个框架看懂花了点时间,实际上敲代码的话是比较容易的,感觉核心还是看懂整个框架,收获的东西相较于作业会更多

框架梳理

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

渲染流程大概就是先生成光线(视点与屏幕像素中心点的连线),然后光线与场景相交找到第一个与之相交的点(这里相交的方法是遍历空间中所有物体,依次与光线相交求出相交点,然后找到最近的相交点返回。其中,对于不同物体的求交方法是不一样的,比如对于球这种隐式表示的,就用解析解的方法直接求交点,对于三角网格这种显示表示的,就依次遍历其所有三角形,用线和三角形求交的方法求交点),之后根据各种渲染的方法对该点着色(不同材质的着色方法不一样,代码里面写了三种材质,对应三种不同的着色方法,并且这里还需要判断点是否处在阴影中,若在阴影中,则不需要着色),然后该点的颜色就会最终返回,作为最终渲染图像的一个像素值。射出全部光线就能得到全部像素值,最终得到渲染图像。

由于这次框架比较简单,这次就不一边梳理框架一边看代码了,因为我觉得代码千变万化,不同场景都会有变化,但是光线追踪核心的思想和流程是不变的,只要核心的思想懂了,代码怎么变都是可以看懂的。而且我觉得看框架的代码也是一个自主学习的过程,如果讲的太透,也没有什么意思,还是自主学习更加有趣,也会有更多的收获,我相信只要坚持看下去,每个人肯定是能把代码框架看懂的,遇到看不懂的地方可以再回来看一下我上面的思路,希望对你有一定帮助。

当你看懂框架的代码后,一定是能看懂我上面的这段话的

如果实在看不懂框架,可以参考:https://blog.youkuaiyun.com/qq_41835314/article/details/124969379(这位老哥可以说是讲的相当的细了==

任务一:生成光线

这个不难,本质上就是坐标转换,算出屏幕像素中心点在世界坐标系的坐标,与视点相减,就是光线方向。实际上可以这么理解,这里坐标转化的过程与透视投影那一部分几乎是一模一样的思路,可以把屏幕像素看作视锥的一个底面,脑子里要有一张图,然后视点就是视锥的尖端,按照这种角度去理解应该会更好理解一点,scale和imageAspectRatio的意义也容易明白。

可以参考:https://blog.youkuaiyun.com/Q_pril/article/details/123825665,已经讲的比较透彻了。

void Renderer::Render(const Scene& scene)
{
    std::vector<Vector3f> framebuffer(scene.width * scene.height);

    float scale = std::tan(deg2rad(scene.fov * 0.5f));
    float imageAspectRatio = scene.width / (float)scene.height;
                                                                                                 
    // Use this variable as the eye position to start your rays.
    Vector3f eye_pos(0);
    int m = 0;
    for (int j = 0; j < scene.height; ++j)
    {
        for (int i = 0; i < scene.width; ++i)
        {
            // generate primary ray direction
            // 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* 
            float x,world_scene_width;
            float y, world_scene_height;
            //世界坐标下屏幕的真实长度和宽度
            world_scene_width = 1 * scale * 2 * imageAspectRatio;
            world_scene_height = 1 * scale * 2;
            //x范围从  0-(scene.width-1) 转换为 0-1
            x = (i + 0.5) / (scene.width - 1);
            //x范围从  0-1 转换为 -1-1
            x = x * 2 - 1;
            //x范围从  -1-1 转换为 -world_scene_width/2-world_scene_width/2
            x = x * world_scene_width / 2;
            //y同理,其范围从0-(scene.height-1)最终转换为world_scene_height/2-(-world_scene_height/2)
            y= (-2 * (j + 0.5) / (scene.height-1) + 1)* world_scene_height/2;

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

    // 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] = (char)(255 * clamp(0, 1, framebuffer[i].x));
        color[1] = (char)(255 * clamp(0, 1, framebuffer[i].y));
        color[2] = (char)(255 * clamp(0, 1, framebuffer[i].z));
        fwrite(color, 1, 3, fp);
    }
    fclose(fp);    
}

任务二:光线与三角形求交

这个也很简单,就是把老师ppt上面的Moller Trumbore求交算法实现出来,实际上老师上课讲了两种方法,另外一种是解析几何的方法,这种算是一种直接求解法,本质上是通过重心坐标构造一个线性方程进行求解。按老师说法是比解析几何的方法要快的。个人理解就是解析几何的过程是先求平面和光线的交点,然后再通过叉乘判断交点是否在三角形内部。而Moller Trumbore算法是直接求出交点的重心坐标,通过简单的逻辑判断是否在三角形内部,相较于解析几何方法,不需要再进行一次叉乘计算判断,一步到位

bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
    const Vector3f& dir, float& tnear, float& u, float& v)
{
    // TODO: Implement this function that tests whether the triangle
    // that's specified bt v0, v1 and v2 intersects with the ray (whose
    // origin is *orig* and direction is *dir*)
    // Also don't forget to update tnear, u and v.

    //这里也不难,说白了就是把ppt上的Moller-Trumbore算法写出来就行
    Vector3f E1 = v1 - v0,
             E2 = v2 - v0,
             S = orig - v0,
             S1 = crossProduct(dir, E2),
             S2 = crossProduct(S, E1);

    float S1E1 = dotProduct(S1, E1);
    tnear  = dotProduct(S2, E2) / S1E1;
    u= dotProduct(S1, S) / S1E1;
    v= dotProduct(S2, dir) / S1E1;
    if (tnear < 0) return false;
    if ((1 - u - v) > 0 && u > 0 && v > 0) return true;

    return false;
}

最终效果

由于这次作业比较简单,没遇到bug,一次就出来了,最后效果看上去和老师给的差不多,还有点小激动==

在这里插入图片描述

感悟

好久没写算法了,前段时间都在写工程的一些东西,现在写这些简单的都有点生疏,慢慢拾起来吧,然后的话作业做完,看懂框架之后,感觉自己对光线追踪的过程有了进一步的理解,编程实践真的很重要==

参考链接

1.https://blog.youkuaiyun.com/Q_pril/article/details/123825665
2.https://blog.youkuaiyun.com/ycrsw/article/details/124199544
3.https://blog.youkuaiyun.com/qq_41835314/article/details/124969379

在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中 最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就 可以执行着色并返回像素颜色。在这次作业中,我们需要实现两个部分:光线的 生成和光线与三角的相交。本次代码框架的工作流程为: 1. 从 main 函数开始。我们定义场景的参数,添加物体(球体或三角形)到场景 中,并设置其材质,然后将光源添加到场景中。 2. 调用 Render(scene) 函数。在遍历所有像素的循环里,生成对应的光线并将 返回的颜色保存在帧缓冲区(framebuffer)中。在渲染过程结束后,帧缓冲 区中的信息将被保存为图像。 3. 在生成像素对应的光线后,我们调用 CastRay 函数,该函数调用 trace 来 查询光线与场景中最近的对象的交点。 4. 然后,我们在此交点执行着色。我们设置了三种不同的着色情况,并且已经 为你提供了代码。 你需要修改的函数是: • Renderer.cpp 中的 Render():这里你需要为每个像素生成一条对应的光 线,然后调用函数 castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相 应像素中。 • Triangle.hpp 中的 rayTriangleIntersect(): v0, v1, v2 是三角形的三个 顶点, orig 是光线的起点, dir 是光线单位化的方向向量。 tnear, u, v 是你需 要使用我们课上推导的 Moller-Trumbore 算法来更新的参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值