闫令琪:Games101 现代计算机图形学-光线追踪(一):whitted ray trace光线追踪 & 作业Assignment05解析

1 相比光栅化,为什么需要光线追踪

在这里插入图片描述 这是课上闫老师举的例子和自己的注释,已经很能够说明光栅化的不足之处。
除了上面的三种之外,还有一种就是无法展示透明的玻璃杯、水……这种有折射的物体。

2 光线追踪的思路综述

2.1综述文章

探究光线追踪技术及UE4的实现

全局光照:光线追踪、路径追踪与GI技术进化编年史

光线追踪综述
这三篇文章的内容其实不局限whitted风格的光线追踪,包含后面的路径追踪,算是不错的综述文章。

2.2核心思路

光线追踪方法主要思想是从视点向成像平面上的像素发射光线,找到与该光线相交的最近物体的交点,如果该点处的表面是散射面,则计算光源直接照射该点产生的颜色;如果该点处表面是镜面或折射面,则继续向反射或折射方向跟踪另一条光线,如此递归下去,直到光线逃逸出场景或达到设定的最大递归深度。
在这里插入图片描述

2.3 基本假设

这三个假设适用后面的路径追踪。
在这里插入图片描述

2.4 算法过程

大致上,Whitted RayTrace其主要把光线分为四种

  • 视角光线,和之前的一样,没什么好说的
  • 反射光线。在表面沿镜面反射方向继续照射。反射的颜色由反射光线与场景中的对象的交点决定。
  • 折射光线,其创建与反射光线类似,只是它的方向是进入对象并最终可以退出对象。
  • 阴影光线,是通过创建从交点到所有灯光的阴影光线来计算的。如果阴影光线在到达灯光之前与某个对象相交,则该交点将从该特定灯光中阴影显示。
    (对应下图中的编号①②③④四种光线)
    在这里插入图片描述
    上图所示也是whitted的过程:
  • 从像素处发射一根光线,求与场景中物体的交点;
  • 如果是反射材质,就求反射光线
  • 如果是折射材质,就求折射光线,颜色占比按照菲涅尔折射定律确定比例
  • 如果是漫反射材质,就用布林冯或者其他的光照模型求颜色。光线传播结束。

伪代码表示如下:

遍历每一个像素
{
	从观察点到像素初始化一条射线;
	求摄像是否与场景物体有交点;(怎么求交点是一个很重要的部分,下一篇单独讲)
	if (无交点)
		返回背景色;
	else
		switch(材质)
		case (反射折射材质):
			求反射向量;
			求折射向量;
			菲涅尔系数kr;
			return kr*递归反射 + (1-kr)*递归折射
		case (反射材质)
			求菲涅尔系数kr;
			求反射向量;
			return 反射递归*kr
		case (漫反射材质)
			if(在阴影里)
				光源强度为0return 着色模型着色
	return 颜色
}

从这个whitted光线追踪的过程可以发现,它的缺点:
①没有模拟漫反射,无法更好地模拟全局光照;
②整个过程并没有遵循物理规律,比如折射和反射都是比较理想化的,并没有这种理想材质;
③依然无法表现软阴影

3 算法实现的技术关键点

3.1 光线与物体求交

真实的场景中,物体可能有成千上百个,三角面可能有几十万个,如果计算光线与具体哪个三角面有交点,这假设1080p的画面有1920x1080x100w个三角面,这个计算量太大了。
所以,计算光线与物体的交点,是整个光线追踪算法的核心之一,也是运算量的大占比之一。

求光线与物体的交点主要分为:①查找光线与哪个物体、进而与哪个三角面相交;②求光线与三角面的交点坐标

上面的第①步是下一篇文章,也是作业06重点关注的BVH算法。本小节重点介绍求光线与一个三角面的交点的算法Möller Trumbore算法。
算法描述
思路就是交点既可以用射线表示,又可以用重心坐标表示,联立方程求解即可。
作业任务之一就是求解这个,照着公式敲出来其实很容易,但是关键是判断条件的添加。
判断条件:
①t>0;
②0<b1 && 0<b2 && 0<1-b1-b2 均为true
③fabs(s1* e1) < EPSILON;分母不能太小

3.2 菲涅尔折射

作业中直接给出了代码,具体的推导参考这两篇博客即可。
计算光的折射向量

菲涅尔公式

3.3 递归的终止条件

递归终止
条件一:遇到漫反射物体;
条件二:设置一个最大光线传播次数,作业中是变量depth=5

4 作业代码

4.1 Möller Trumbore算法

bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1,
                          const Vector3f& v2, const Vector3f& orig,
                          const Vector3f& dir, float& tnear, float& u, float& v)
{
    Vector3f edge1 = v1 - v0;
    Vector3f edge2 = v2 - v0;
    Vector3f pvec = crossProduct(dir, edge2);
    float det = dotProduct(edge1, pvec);
    if (det  < EPSILON )
        return false;

    Vector3f tvec = orig - v0;
    u = dotProduct(tvec, pvec);
    if (u < 0 || u > det)
        return false;

    Vector3f qvec = crossProduct(tvec, edge1);
    v = dotProduct(dir, qvec);
    if (v < 0 || u + v > det)
        return false;

    float invDet = 1 / det;

    tnear = dotProduct(edge2, qvec) * invDet;
    u *= invDet;
    v *= invDet;

    return true;
}

4.2 初始化像素对应光线

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)
        {
			float xx;
			float yy;
			xx = (-1.0f + 2.0f / scene.width * i + 2.0f / scene.width * 0.5f) * imageAspectRatio;
			yy = 1.0f - 2.0f / scene.height * j - 2.0f / scene.height * 0.5f;
			float zz = -1.0f;
			float norm_my = sqrt(xx * xx + yy * yy + zz * zz);
			Vector3f dir = Vector3f(xx / norm_my, yy / norm_my, zz / norm_my); // Don't forget to normalize this direction!

            /*/网上的代码,比较两者的区别,上面是按照框架里的z=-1写的,好复杂
            float x = i + 0.5f - scene.width / 2;
            float y = scene.height / 2 - j - 0.5f;
            float z = -scene.height / 2;
            */
            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);    
}

4.3作业结果

光线弹射一次的结果:
在这里插入图片描述
光线弹射2次:
因为折射的光线此时还在球里面,还没出来
在这里插入图片描述
光线弹射3次:
在这里插入图片描述

光线最大弹射5次结果(即作业要求结果):
在这里插入图片描述

5 读取ppm文件

因为作业生成的是.ppm文件,该文件在Linux下可以点击查看,win 上不行,所以需要另外的插件读取,下面链接是一个小软件,安装后即可在win上查看ppm文件
读ppm文件

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在这部分的课程中,我们将专注于使用光线追踪来渲染图像。在光线追踪中 最重要的操作之一就是找到光线与物体的交点。一旦找到光线与物体的交点,就 可以执行着色并返回像素颜色。在这次作业中,我们需要实现两个部分:光线的 生成和光线与三角的相交。本次代码框架的工作流程为: 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 算法来更新的参数。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值