光线跟踪(RayTracing)原理及c++实现

2023-11-06

Chapt1. Why to write a RayTracing Render

提到Computer Graphics,众所周知的是如OpenGL、Direct3D这样非常流行的光栅化渲染器。事实上,这些大部分应用于游戏制作的API主要为实时渲染(Real-time Rendering)而设置,而它们所采用的光栅化(Rasterization)的渲染方式,通过渲染大量的三角形(或者其他的几何图元种类(Primitive types)),是与本文介绍的光线跟踪相对的一种渲染方式。这种基于光栅化的渲染系统,往往只支持局部照明(Local Illumination)。局部照明在渲染几何图形的一个像素时,光照计算只能取得该像素的信息,而不能访问其他几何图形的信息。


图1.jpg

该图片出自《孤岛惊魂》,尽管看似水面显示出了远处山峰的倒影,却不能渲染植被、船骸等细节。

理论上,阴影(Shadow)、反射(Reflection)、折射(Refraction)均为全局照明(Global Illumination)效果,所以在实际应用中,栅格化渲染系统可以使用预处理(如阴影贴图(shadow mapping)、环境贴图(environment mapping))去模拟这些效果。

栅格化的最大优势是计算量比较小,适合实时渲染。相反,全局光照计算量大,一般也没有特殊硬件加速(通常只使用CPU而非GPU),所以只适合离线渲染(offline rendering),例如3D Studio Max、Maya等工具。其中一个支持全局光照的方法,称为光线追踪(ray tracing)。光线追踪能简单直接地支持阴影、反射、折射,实现起来亦非常容易。

Chapt2. Principles of RayTracing

由光源发出的光到达物体表面后,产生反射和折射,简单光照明模型和光透射模型模拟了这两种现象。在简单光照明模型中,反射被分为理想漫反射和镜面反射光,把透射光模型分为理想漫透射光和规则透射光。由广元发出的光成为直接光,物体对直接光的反射或折射成为直接反射和直接折射,相对的,把物体表面间对广德反射和折射成为间接光、间接反射、间接折射。光线在物体之间的传播方式是光线跟踪算法的基础。

最基本的光线跟踪算法是跟踪镜面反射和折射。从光源发出的光遇到物体的表面,发生反射和折射,光就改变方向,沿着反射方向和折射方向继续前进,知道遇到新的物体。但是光源发出光线,经过反射与折射,只有很少部分可以进入人的眼睛。因此实际光线跟踪算法的跟踪方向与光传播的方向是相反的(反向光线跟踪),称之为视线跟踪。由视点与像素(x,y)发出一根射线,与第一个物体相交后,在其反射与折射方向上进行跟踪,如图2所示


图2.gif

在光线跟踪算法中,我们有如下的四种光线:

  • 视线是由视点与像素(x,y)发出的射线;
  • 阴影测试线是物体表面上点与光源的连线;
  • 反射光线;
  • 折射光线

当光线 V与物体表面交于点P时,点P分为三部分,把这三部分光强相加,就是该条光线V在P点处的总的光强:

  • a) 由光源产生的直接的光线照射光强,是交点处的局部光强,可以由下式计算:

    式1.gif

  • b) 反射方向上由其它物体引起的间接光照光强,由IsKs' 计算,Is通过对反射光线的递归跟踪得到
  • c) 折射方向上由其它物体引起的间接光照光强,由ItKt' 计算,It通过对折射光线的递归跟踪得到

现在我们来讨论光线跟踪算法本身。我们将对一个由两个透明球和一个非透明物体组成的场景进行光线跟踪(图3)通过这个例子,可以把光线跟踪的基本过程解释清楚。


图3.gif

在我们的场景中,有一个点光源L,两个透明的球体O1与O2,一个不透明的物体O3。首先,从视点出发经过视屏一个像素点的视线E传播到达球体O1,与其交点为P1。从P1向光源L作一条阴影测试线S1,我们发现其间没有遮挡的物体,那么我们就用局部光照明模型计算光源对P1在其视线E的方向上的光强,作为该点的局部光强。同时我们还要跟踪该点处反射光线R1和折射光线T1,它们也对P1点的光强有贡献。在反射光线R1方向上,没有再与其他物体相交,那么就设该方向的光强为零,并结束这光线方向的跟踪。然后我们来对折射光线T1方向进行跟踪,来计算该光线的光强贡献。折射光线T1在物体O1内部传播,与O1相交于点P2,由于该点在物体内部,我们假设它的局部光强为零,同时,产生了反射光线R2和折射光线T2,在反射光线R2方向,我们可以继续递归跟踪下去计算它的光强,在这里就不再继续下去了。我们将继续对折射光线T2进行跟踪。T2与物体O3交于点P3,作P3与光源L的阴影测试线S3,没有物体遮挡,那么计算该处的局部光强,由于该物体是非透明的,那么我们可以继续跟踪反射光线R3方向的光强,结合局部光强,来得到P3处的光强。反射光线R3的跟踪与前面的过程类似,算法可以递归的进行下去。重复上面的过程,直到光线满足跟踪终止条件。这样我们就可以得到视屏上的一个象素点的光强,也就是它相应的颜色值。

上面的例子就是光线跟踪算法的基本过程,我们可以看出,光线跟踪算法实际上是光照明物理过程的近似逆过程,这一过程可以跟踪物体间的镜面反射光线和规则透射,模拟了理想表面的光的传播。

虽然在理想情况下,光线可以在物体之间进行无限的反射和折射,但是在实际的算法进行过程中,我们不可能进行无穷的光线跟踪,因而需要给出一些跟踪的终止条件。在算法应用的意义上,可以有以下的几种终止条件:

  • 该光线未碰到任何物体。
  • 该光线碰到了背景
  • 光线在经过许多次反射和折射以后,就会产生衰减,光线对于视点的光强贡献很小(小于某个设定值)
  • 光线反射或折射次数即跟踪深度大于一定值

Chapt3. Rasterization & RayTracing

了解了光线跟踪的原理之后,再来看一下在计算机上的实现。

光栅化渲染,简单地说,就是把大量三角形画到屏幕上。当中会采用深度缓冲(depth buffer, z-buffer),来解决多个三角形重叠时的前后问题。三角形数目影响效能,但三角形在屏幕上的总面积才是主要瓶颈。

光线追踪,简单地说,就是从摄影机的位置,通过影像平面上的像素位置(比较正确的说法是取样(sampling)位置),发射一束光线到场景,求光线和几何图形间最近的交点,再求该交点的著色。如果该交点的材质是反射性的,可以在该交点向反射方向继续追踪。光线追踪除了容易支持一些全局光照效果外,亦不局限于三角形作为几何图形的单位。任何几何图形,能与一束光线计算交点(intersection point),就能支持。


图4.png

上图显示了光线追踪的基本方式。要计算一点是否在阴影之内,也只须发射一束光线到光源,检测中间是否存在障碍物。

Chapt4. Source Code

1. Base Class

本例代码尝试使用基于物件(object-based)的方式编写

3D Vector
struct Vector {
    float x, y, z;
    Vector(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {}
    Vector(const Vector& r) : x(r.x), y(r.y), z(r.z) {}
    float sqrLength() const {
        return x  x + y  y + z  z;
    }
    float length() const {
        return sqrt(sqrLength());
    }
    Vector operator+(const Vector& r) const {
        return Vector(x + r.x, y + r.y, z + r.z);
    }
    Vector operator-(const Vector& r) const {
        return Vector(x - r.x, y - r.y, z - r.z);
    }
    Vector operator(float v) const {
        return Vector(v  x, v  y, v  z);
    }
    Vector operator/(float v) const {
        float inv = 1 / v;
        return this  inv;
    }
    Vector normalize() const {
        float invlen = 1 / length();
        return this  invlen;
    }
    float dot(const Vector& r) const {
        return x  r.x + y  r.y + z  r.z;
    }
    Vector cross(const Vector& r) const {
        return Vector(-z  r.y + y  r.z,
                      z  r.x - x  r.z,
                      -y  r.x + x  r.y);
    }
    static Vector zero() {
        return Vector(0, 0, 0);
    }
};
inline Vector operator(float l, const Vector& r) {return r  l;}

这些类方法(如normalize、dot、cross等),如果传回Vector对象,都会传回一个新建构的Vector。这些三维向量的功能很简单,不在此详述。

Vector zero()用作常量,避免每次重新构建。值得一提,这些常量必需在prototype设定之后才能定义。

Ray

即为光线类,所谓光线(ray),从一点向某方向发射也。数学上可用参数函数(parametric function)表示:


式2.png

当中,o即发谢起点(origin),d为方向。在本文的例子里,都假设d为单位向量(unit vector),因此t为距离。实现如下

struct Ray {
    Vector origin, direction;
    Ray(const Vector& o, const Vector& d) : origin(o), direction(d) {}
    Vector getPoint(float t) const {
        return origin + t * direction;
    }
};
Sphere

球体(sphere)是其中一个最简单的立体几何图形。这里只考虑球体的表面(surface),中心点为c、半径为r的球体表面可用等式(equation)表示:


式3.png

如前文所述,需要计算光线和球体的最近交点。只要把光线x = r(t)代入球体等式,把该等式求解就是交点。为简化方程,设v=o - c,则:


式4.png

因为d为单位向量,所以二次方的系数可以消去。 t的二次方程式的解为


式5.png
struct Sphere : public Geometry {
    Vector center;
    float radius, sqrRadius;
    Sphere(const Vector& c, float r, Material m = NULL) :
            Geometry(m), center(c), radius(r), sqrRadius(r  r) {}
    IntersectResult intersect(const Ray& ray) const {
        Vector v = ray.origin - center;
        float a0 = v.sqrLength() - sqrRadius;
        float DdotV = ray.direction.dot(v);
        if (DdotV <= 0.0) {
            float discr = DdotV * DdotV - a0;
            if (discr >= 0.0) {
                float d = -DdotV - sqrt(discr);
                Vector p = ray.getPoint(d);
                Vector n = (p - center).normalize();
                return IntersectResult(this, d, p, n);
            }
        }
        return IntersectResult();
    }
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

光线跟踪(RayTracing)原理及c++实现 的相关文章

  • C语言:数组的应用2——扫雷(递归实现地图变化)

    之前呢跟大家分享了二维数组实现的小游戏 三子棋 井字棋 大家都看懂了吗 今天给大家分享一下用数组实现的扫雷小游戏 先看看最终的效果吧 我设计的这个扫雷游戏 可以让玩家自己选择游戏难度 有简单 适中 困难三种模式 并利用递归的方式去改变地图
  • ThreadPoolExecutor类讲解

    一 ThreadPoolExecutor类讲解 1 线程池状态 五种状态 线程池 的状态 说明 RUNNING 允许提交并处理任务 SHUTDOWN 不允许提交新的任务 但是会处理完已提交的任务 STOP 不允许提交新的任务 也不会处理阻塞
  • 睿智的智能优化算法4——进化策略(Evolution Strategy)

    睿智的智能优化算法4 进化策略 Evolution Strategy 1 算法思路 1 1 杂交方式 1 2 基因突变 1 3 淘汰低适应度个体 2 与遗传算法对比 2 1 相同点 2 2 不同点 实现代码 GITHUB下载连接 遗传算法是
  • 字体文件只有本地服务器,解决字体文件Font跨域权限问题

    问题描述 Font from origin http trunk supply chain com has been blocked from loading by Cross Origin Resource Sharing policy
  • 海康、大华网络摄像机RTSP URL格式组成及参数配置

    经常有开发者咨询我们关于海康 大华网络摄像机RTSP url拼接规则和相关参数配置 虽然很简单 考虑到资料不全 写个博客记录下 1 海康摄像机 在IE浏览器输入网络摄像机的IP地址 输入配置的用户名 密码 登陆 视频 1 码率类型 主码流和
  • js逆向不用扣代码系列(2)—3分钟快速破解猿人学第16题(webpack初体验)

    网址 http match yuanrenxue com match 16 1 加密参数分析 进行翻页请求抓包 发现加密参数为m 打上xhr断点 调试堆栈 发现m加密代码位置在9431行 r m n e 528 btoa p s p s为时
  • Kubernetes CoreDNS Plugin/loop:Seen "HINFO IN xxxxxxx." more than twice,loop detected 问题解决方法

    前几天在自己的实验室环境里面搭建了一个简单的k8s环境 该环境是基于kubeadm搭建的single master节点 并且有两台计算节点 可是环境搭建好了以后 CoreDNS一直存在报错 查看coredns日志 报错内容如下 于此同时我还
  • temu的使用

    temu对进程的分析是建立在虚拟机上的 temu工具依赖于qemu qemu是模拟处理器 安装temu时qemu就已经安装好了 使用temu时 首先要制作一个映像 然后通过temu启动该映像进入虚拟机 需要注意的是 如果你在实际ubuntu
  • android 首页图标

  • 绘制单个条形图与多个条形图

    绘制单个条形图 案例 假设你获取到了某年内地电影票房前20的电影 列表a 和电影票房数据 列表b 那么如何更加直观的展示该数据 a 战狼2 速度与激情8 功夫瑜伽 西游伏妖篇 变形金刚5 最后的骑士 摔跤吧 爸爸 加勒比海盗5 死无对证 金
  • 使用JavaMail发送邮件时嵌入公司logo图片

    使用JavaMail发送邮件时嵌入公司logo图片 第一种方式 img 标签和 logo 图片链接 第二种方式 使用 img 标签和图片 base64 字符串 第三种方式 推荐 将 logo 当做附件一起发送并设置 ContentID 再使
  • [1120]Maven依赖冲突解决之exclusions

    1 背景 1 作为java生态下开发者 往往需要使用大量线程的第三方库 一般都是以jar包形式存在 2 maven作为事实上主流的jar包依赖管理工具 Idea和Eclipse都支持创建maven工程来管理jar包依赖 3 使用maven进
  • C语言实现原码补码输出

    今天复习了一下C中的原码补码的知识 顺便编程使用for while do while goto recursive 实现了补码和原码的输出 核心思想 借助一个字符串数组 将得到的原码和补码存储到相应数组中去 在计算原码的时候 由于存在负数的
  • 单片机C语言基础

    目录 前言 一 C语言基础 1 1 逻辑运算 1 2 字长定义 1 3 布尔型变量 1 4 符号 1 5 位段 1 6 typedef关键字 1 7 volatile关键字 二 代码规则 前言 介绍一下单片机开发的C语言使用 个人会慢慢完善
  • 第四次 python

    元组练习题 tup nihao wohao dajiahao a 计算元组长度并输出 b 获取元组第2个元素并输出 c 获取元素第2 3个元素并输出 d 使用for循环遍历输出元组 e 使用for len range输出元组的索引 f 将元
  • JAVA实现用户输出正整数重复出现的次数并打印(最大数字不超过10)

    package day1 实现用户输出正整数重复出现的次数并打印 public class TestSameNum public static void main String args int arr 1 2 3 4 5 6 7 1 2
  • vue使用动态样式与计算属性实现多变量判断

    一 动态class绑定 代码 class的计算属性着重于减少页面内含大量逻辑判断导致代码阅读性差
  • 垃圾分类图片数据集分享-约10w张数据集

    1 获取方式 点赞本博客 评论区留邮箱 博主在会发送 私信博主 访问的人太多了 需要私信联系 截至到2021 03 30评论区所有邮箱已无偿发送 图片数据集直接留邮箱即可 2 问题描述 最近在做一个相关项目 从网上整理了许许多多的有关于垃圾
  • 多维时序

    多维时序 MATLAB实现DNN深度神经网络多变量时间序列预测 考虑历史特征的影响 多指标 多图输出 目录 多维时序 MATLAB实现DNN深度神经网络多变量时间序列预测 考虑历史特征的影响 多指标 多图输出 预测效果 基本介绍 模型结构
  • .Net Core with 微服务 - 架构图

    上一次我们简单介绍了什么是微服务 NET Core with 微服务 什么是微服务 介绍了微服务的来龙去脉 一些基础性的概念 有大佬在评论区指出说这根本不是微服务 由于本人的能力有限 大概也只能理解到这个层次 先不管它到底是不是微服务吧 既

随机推荐

  • 「五度易链」助力园区招商,引优质企业 精准甄别 全程管理,促产业高质发展

    近年 无数产业园区已迈入数字化转型阶段 大数据招商被深度应用 缺乏招商线索的局面已一去不返 但面对鳞萃比栉 纷繁芜杂的各种招商标的信息 又是让园区招商工作者眼花缭乱 举棋不定 难以抉择 那么该如何进行项目甄别 判断企业落地价值呢 五度易链
  • 快手短视频微信小程序端自动下单工具

    快手小店微信小程序虽然关闭了 但是还有个快手短视频 其实就是快手小店的产品 在直播售卖 抓包短视频直播下单 根据小程序端下单 写个自动下单工具 众所周知 快手CK 很长时间都有效 打开软件 打开PC微信 快手短视频小程序 进入直播 随便找一
  • Wolfram Mathematica 安装与使用

    1 Wolfram Mathematica 9 安装 各大网站几乎都有Wolfram Mathematica 9学习软件 大学 高等数学 里面涉及的的函数图形几乎都可以使用GeoGebra 5来做图 GeoGebra的功能十分强大 而且是免
  • Centos 8二进制安装Mysql-8.0.23

    1 mysql tar包的下载 下载官网 https downloads mysql com archives community 2 解压 root cent8 yzil tar xf mysql 8 0 23 linux glibc2
  • 基于NB-IoT物联网智能运维箱整体解决方案

    一 方案背景 雪亮工程 智慧交通 水利监控 环保监控等系统大多安装于户外 设备量级大 分布广 易受到恶劣环境和人为破坏等因素影响 导致系统故障率高 可用性差 传统人工维保存在维护成本高 效率低的问题 难以保证设备的高在线率 在物联网 边缘计
  • 代码行数统计小工具

    一 先下载好SourceCounter小工具 解压 然后直接打开文件夹中的SourceCounter exe 如果没有找到此工具的下载链接 点这里下载 二 选择代码类型 勾选上所有类型 三 双击点开后 选择文件夹 就可以直接统计出字数了
  • 前端笔记(4)JavaScript宏观与微观任务

    宏观和微观任务 在 ES3 和更早的版本中 JavaScript 本身还没有异步执行代码的能力 这也就意味着 宿主环境传递给 JavaScript 引擎一段代码 引擎就把代码直接顺次执行了 这个任务也就是宿主发起的任务 但是 在 ES5 之
  • 顺序表的基本操作(C语言实现)

    顺序表 前言 本文主要讲线性表的其中一种 那就是顺序表 顺序表就是采用顺序储存方式来存储数据 所谓顺序存储 就是数据在内存上的地址同样为连续储存 中间不允许有空 有间隔 顺序表的优点在于支持随机访问 就是通过下标来访问元素 缺点是插入 删除
  • iBatis resultMap报错 nullValue完美解决

    错误信息 SQLErrorCodesFactory Database product name cached for DataSource org apache commons dbcp BasicDataSource 19c5048 na
  • OSWatcher.sh脚本说明

    OSWatcher sh脚本位于oswbb目录下 Oracle 19c数据库中脚本的路径是 u01 app oracle product 19 0 0 dbhome 1 suptools tfa release tfa home ext o
  • Maven中dependencyManagement作用说明

    备注 今天有好些实习的同事问到Maven中关于dependencyManagement和普通dependencies的区别 说多了 麻烦 记录一下 在Maven多模块的时候 管理依赖关系是非常重要的 各种依赖包冲突 查询问题起来非常复杂 于
  • python网络爬虫有那些实例_python爬虫经典例子有哪些

    python爬虫例子 首先导入爬虫的库 生成一个response对象 然后设置编码格式 并打印状态码 最后输出爬取的信息 代码为 print response text python爬虫例子 1 爬取强大的BD页面 打印页面信息 第一个爬虫
  • 毕业设计-基于机器视觉的焊缝图像处理研究- OpenCV

    目录 前言 课题背景和意义 实现技术思路 一 焊缝识别系统设计 二 焊缝图像预处理 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精力 近几年各个学校要
  • 设置读取plc时间_Visual Studio 2010-C#跟西门子1200(Sharp7)窗体控制④-循环读取

    Visual Studio 2010 C 跟西门子1200 Sharp7 窗体控制 循环读取 上期回顾 上期主要是对单个按钮 按下后能够同时置位PLC的多个位 本期做一个读取PLC的OK NOK的统计数据的C 标签 先创建一个标签 在设定一
  • SpringBoot:@Schedule定时任务

    一 Schedule SpringBoot内置了Sping Schedule定时框架 通过注解驱动方式添加所注解方法到定时任务 根据配置定时信息定时执行 二 定时任务实现 1 开启定时任务 package com gupao springb
  • 使用VBA在工作表中快速插入行

    在工作表中插入行 有需要用到代码吗 是不是杀鸡用牛刀的感觉 其实不是这样的 在很多复杂的应用场景中 插入行不再是简单的单击鼠标右键就可以即刻完成的 比如需要隔行插入空行 如果有一万行数据 是不是搞到手抽筋了 再比如插入空行的数量不是固定的
  • 自动记录数据录入时间不懂得VBA的朋友可以看看

    在日常工作中 经常会遇到需要实时记录数据录入的时间问题 有朋友会说了 用快捷键啊 按Ctrl 分号 可以返回当前的系统日期 按Ctrl Shift 分号 可以返回当前的系统时间 但是如果需要同时返回日期和时间又该怎么处理呢 对于懂得VBA的
  • 关于对Vue中slot插槽理解

    关于slot插槽理解 1 何时需要使用插槽 在开发中 我们需要将共性内容抽取到组件中 将不同的暴露为插槽 插槽的益处便是 一旦预留了插槽 使用者便可以根据自己的需求来决定插槽中插入的的内容 2 slot的基本使用 div div
  • 软件测试之网络协议基础

    软件测试之网络协议基础 前言 我会在此账号上持续更新 软件测试的文章 包括网络部分 前端代码部分 数据库部分 软件测试部分 互联网协议 osi 7层协议 tcp ip 5层协议 网络协议的存在是为了两者中间根据一定的协议沟通交流 每层运行常
  • 光线跟踪(RayTracing)原理及c++实现

    Chapt1 Why to write a RayTracing Render 提到Computer Graphics 众所周知的是如OpenGL Direct3D这样非常流行的光栅化渲染器 事实上 这些大部分应用于游戏制作的API主要为实