一个简单的光线追踪渲染器

前言

本文参照自raytracing in one weekend教程,地址为:https://raytracing.github.io/books/RayTracingInOneWeekend.html

什么是光线追踪?

光线追踪模拟现实中的成像原理,通过模拟一条条直线在场景内反射折射,最终获知物体表面的颜色。现实世界中,光线最终射向相机,获得成像,光线追踪则是从相机出发,向场景中反向发射光线,从而推出相机“底片”中每个像素的颜色。

现实中的相机很发杂,包括多组透镜,在成像时不是光线直接射入相机,需要经过多次折射。我们这里的光线追踪更类似于在模拟小孔成像(只不过小孔成像获得的图像是反置的,我们直接得到正向的结果,相当于对反置图像做了反置),我们在小孔的位置放置相机。

在这里插入图片描述

光线追踪和光栅化是两种不同的渲染方式,光栅化相当于把物体表面直接反射或发射的颜色返回给相机“底片”,场景中的各种阴影、遮蔽等效果都是通过预计算等方法得出的,而光线追踪会考虑物体表面光线多次反射或折射的结果,直接可以得到场景中的阴影、遮蔽等细节效果。

在具体实现的过程中,我们会在相机的正前方设置一个虚拟画布,相当于相机的底片(正常来说相机底片应当是在相机背面的,不过为了直观以及便于确定每条光线的方向,直接在相机前方设置),虚拟画布上每个位置的颜色代表了最终渲染结果对应像素的颜色。在发射光线时,通常以相机为起点,虚拟画布上的每个位置为终点,构建一条射线,每个方向可以根据采样设置发射多条射线。每个方向的射线的平均结果为对应像素的最终颜色。

在这里插入图片描述

光栅器的构建过程

这一部分是我在学习raytracing in one weekend教程时,我认为重点部分的罗列。

图像格式

raytracing in one weekend教程中采用了ppm格式,这种格式很简单,可以用ASCII文本表示。详细介绍可以参考:https://zhuanlan.zhihu.com/p/609960339

基本的光追过程

简述一下光线追踪的过程:

  1. 屏幕上的每一个像素都进行光线投射。

  2. 光线的每次投射都需要判断交点,而且投射到交点后还可能产生反射、折射,那么就往相应的方向继续进行新的投射,直到投射到天空或者投射次数达到限制。

  3. 最后,将每个交点的受光照情况以一定权重综合起来,得到一束光线获得的颜色,根据采样次数,每个像素发出的多个颜色的平均值为该像素的颜色。

下图是一个示例。

在这里插入图片描述

光线追踪的伪代码:

RayTracing(Ray ray, hittable_list world, int depth){if(depth <= 0)return black color;hit_record rec; // 弹射点的属性记录if(Intersect(ray, world, out rec)){material = rec.mat; // 弹射点所在物体的材质normal = rec.normal;localColor = ShaderCalculate(ray, material, normal);out_ray = Get_outputRay(ray, material, normal);localColor = shaderCalculate(direction,hitpoint,normal);return localColor * RayTracing(out_ray, world, depth - 1);}else{return the color of background;}
}

抗锯齿

看到一个有趣的真相:每个小像素块不是正方形,参考文献,不过为了简单起见,我们假设每个小像素块是正方形。

这里为了进行抗锯齿,采用了一定程度的随机,即从相机向虚拟画布发射光线时,以像素为单位为射线的终点做随机扰动。

在代码中的体现如下:

ray get_ray(int i, int j) const {// 获取位置 i,j 处像素的随机采样光线。auto pixel_center = pixel00_loc + (i * pixel_delta_u) + (j * pixel_delta_v);auto pixel_sample = pixel_center + pixel_sample_square();auto ray_origin = center;auto ray_direction = pixel_sample - ray_origin;return ray(ray_origin, ray_direction);}vec3 pixel_sample_square() const {// 返回一个单位像素正方形周围的随机点。auto px = -0.5 + random_double();auto py = -0.5 + random_double();return (px * pixel_delta_u) + (py * pixel_delta_v);}

漫反射材质

(最简单的)漫反射材质,在光线射入表面后,会在法线半球随机射出。

而如何获得一个随机的单位球内的向量?文中给出的方法是在单位正方体内随机取点,将不在单位球内的点丢弃。而进一步删选是否在表面法线半球,则可以通过将获得的向量与法线点积,如果点积结果为正则采用,为负则丢弃。

为了让漫反射结果更真实,我们应该采用Lambertian Reflection。采用这种方法,在采样时越靠近法线处概率越高。我们可以在与表面交点相切的球体内采样反射光线,示意图如下:

在这里插入图片描述

gamma矫正

另外需要注意gamma矫正,简单来说,屏幕是处于gamma空间上的,而我们渲染的结果在未经处理时是在linear空间上的,为了使色彩不失真,我们需要将渲染的结果转换到gamma空间上。

以下是gamma矫正前和gamma矫正后的对比图:

在这里插入图片描述

金属材质

这里的金属材质与镜子比较类似,金属材质有一个fuzz参数,代表材质表面反射的模糊度。当反射模糊度为零时,这个材质就相当于一个带颜色的镜子。

fuzz参数:反射时,可以对反射光线加一个随机,表示模糊效果。具体随机方式如下图,对反射光线的末端,加一个半径为fuzz范围的球体随机。

在这里插入图片描述

金属材质的效果:

在这里插入图片描述

玻璃材质

引入了光线的折射,下面贴一下教程原文的计算表示过程。

在这里插入图片描述

在做折射时,需要注意全反射的情况。

另外,在现实生活中,当我们贴近玻璃表面时,玻璃会表现地像镜子,这个效果可以用Christophe Schlick给出的公式来模拟。

此时我们得到的是一个通过物体后光线颠倒的效果,这显然不真实。

在这里插入图片描述

有一个trick,我们可以镶嵌两层玻璃球(内层的玻璃球采用负半径,让表面法线颠倒),消除之前的颠倒效果。

在这里插入图片描述

散焦模糊(defocus blur)

这个概念是模仿相机的景深,指的是在相机拍摄时,焦距附近的图像会很清晰,而焦距之外的图像比较模糊。

要模仿真实的相机,我们还需要模拟相机内各种透镜的折射,这太复杂了。为了简单一点,教程中把我们的相机从发射点扩展为发射圆盘,即每次发射光线时从一个半径为r的圆盘中发射光线,穿过虚拟画布。这种模拟不是严格的相机成像,但是效果还不错,具体原理我没怎么搞清。渲染出来的结果如下图:

在这里插入图片描述

最终的渲染结果

最终作者给了一个大场景,我在机器上跑了十多个小时才跑出来。

在这里插入图片描述

完整代码

不想上传个单独的github项目了,就传在网盘上吧:

链接:https://pan.baidu.com/s/1TQUo7GbRUsR-tyLDOv_vOg?pwd=duxm
提取码:duxm
–来自百度网盘超级会员V6的分享

ps: 我只分享了源代码,没有什么依赖库,应该可以直接跑出图片。

参考

https://raytracing.github.io/books/RayTracingInOneWeekend.html

https://zhuanlan.zhihu.com/p/168791125

https://zhuanlan.zhihu.com/p/357142662

https://www.cnblogs.com/KillerAery/p/15106773.html

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

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

相关文章

秋招上岸记录咕咕咕了。

思考了一下&#xff0c;感觉并没有单独写这样一篇博客的必要。 能够写出来的&#xff0c;一些可能会对人有帮助的东西都做进了视频里面&#xff0c;未来会在blbl发布&#xff0c;目前剪辑正在施工中&#xff08;&#xff1f;&#xff09; 另外就是&#xff0c;那个视频里面使…

作为一个的软件测试工程师,想拿到自己想要的薪资,需要具备哪些能力?

如果只是想成为一名低薪的测试工程师&#xff0c;只要掌握功能测试就可以。 但是如果想成为一名高薪的测试工程师&#xff0c;那就要打造你的不可替代性。 可是&#xff0c;你可能会说&#xff1a;“我现在就是个普通职员啊&#xff0c;我就是个普通人&#xff0c;我目前还没有…

前端js实现将异步封装成promise然后用async await转同步

&#xff08;一&#xff09;需求背景&#xff1a; 哈喽 大家好啊&#xff0c;今天遇到一个问题&#xff0c;需要将异步请求转换成同步 &#xff08;二&#xff09;相关代码&#xff1a; function getInfo() {return new Promise((resolve,reject)> {setTimeout(()> {re…

CSS的基本选择器及高级选择器(附详细示例以及效果图)

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍HTML中CSS的基础选择及高级选择器&#xff08;详解&#xff09;以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xf…

JVM学习之运行时数据区

运行时数据区 概述 内存 内存是非常重要的系统资源&#xff0c;是硬盘和CPU的中间桥梁&#xff0c;承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请&#xff0c;分配&#xff0c;管理的策略&#xff0c;保证了JVM高效稳定运行。不同的JVM对于…

STL容器之string(上)

目录 什么是STL string类 string类常见接口 string类的常见构造函数 string类对象的容器操作 string类对象的访问及遍历操作 string类对象的修改操作 拓展 从本期开始&#xff0c;我们将正式学习C中的STL&#xff0c;美国的麦克阿瑟将军说过&#xff1a;“C不能没有STL就…

mipi dsi协议DBI/DPI接口

MIPI dsi协议中的DBI/DPI接口主要用于主机和display设备之间的数据传输&#xff0c;说的更通俗一点就是DSI RX控制器和实际的显示面板之间的接口&#xff1b;dsi 协议spec中对DBI/DPI有描述&#xff1a; DSI协议中对DBI 接口模式命名为command mode operation&#xff0c;对DP…

【物联网】EMQX(二)——docker快速搭建EMQX 和 MQTTX客户端使用

一、前言 在上一篇文章中&#xff0c;小编向大家介绍了物联网必然会用到的消息服务器EMQ&#xff0c;相信大家也对EMQ有了一定的了解&#xff0c;那么接下来&#xff0c;小编从这篇文章正式开始展开对EMQ的学习教程&#xff0c;本章节来记录一下如何对EMQ进行安装。 二、使用…

QT第一步

文章目录 软件下载软件安装QT的程序组新建项目 软件下载 qt下载网址&#xff1a;https://download.qt.io/archive/qt/   关于版本&#xff1a;     我选择的版本是5.14.2&#xff0c;这个版本是最后的二进制安装包的版本&#xff0c;在往后的版本就需要在线安装了。并且5…

单机架构到分布式架构的演变

目录 1.单机架构 2.应用数据分离架构 3.应用服务集群架构 4.读写分离 / 主从分离架构 5.引入缓存 —— 冷热分离架构 6.垂直分库 7.业务拆分 —— 微服务 8.容器化引入——容器编排架构 总结 1.单机架构 初期&#xff0c;我们需要利用我们精干的技术团队&#xff0c;快…

RocketMQ系统性学习-SpringCloud Alibaba集成RocketMQ以及批量发送消息、消息过滤实战

文章目录 批量发送消息消息过滤 批量发送消息 批量发送消息可以减少网络的 IO 开销&#xff0c;让多个消息通过 1 次网络开销就可以发送&#xff0c;提升数据发送的吞吐量 虽然批量发送消息可以减少网络 IO 开销&#xff0c;但是一次也不能发送太多消息 批量消息直接将多个消…

C#基础——类、对象和属性

类&#xff1a;是具有相同属性和行为特征的集合 对象&#xff1a;对象是类的实例化&#xff0c;它具有类定义的所有特征和行为。 类的语法格式&#xff1a; 访问修饰符 class关键字 类名 两种创建类的方式 第一种方式就是在类的下面再创建一个类 第二种方式是在文件中添加一个…

【员工工资册】————大一期末答辩近满分作业分享

前言 大家好吖&#xff0c;欢迎来到 YY 滴项目系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C语言的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; PS&#xff1a;以下内容是部分展示&am…

springboot升级到3.2导致mybatis-plus启动报错

在springboot升级到3.2时&#xff0c;服务启动报错 java.lang.IllegalArgumentException: Invalid value type for attribute ‘factoryBeanObjectType’: java.lang.String&#xff1a; java.lang.IllegalArgumentException: Invalid value type for attribute factoryBeanOb…

55 代码审计-JAVA项目注入上传搜索或插件挖掘

目录 必备知识点演示案例:简易Demo段SQL注入及预编译IDEA审计插件FindBugs安装使用Fortify_SCA代码自动审计神器使用Ofcms后台SQL注入-全局搜索关键字Ofcms后台任意文件上传-功能点测试 涉及资源&#xff1a; 我们一般针对java项目&#xff0c;进行漏洞分析的话&#xff0c;主要…

【计算机视觉--解耦视频分割跟踪任何物体】

UIUC&Adobe开源|无需监督&#xff0c;使用解耦视频分割跟踪任何物体&#xff01;视频分割的训练数据往往昂贵且需要大量的标注工作。这限制了将端到端算法扩展到新的视频分割任务&#xff0c;特别是在大词汇量的情况下。为了在不为每个个别任务训练视频数据的情况下实现“跟…

HPM6750系列--第九篇 GPIO详解(中断操作)

一、目的 在上篇中《HPM6750系列--第九篇 GPIO详解&#xff08;基本操作&#xff09;》我们讲解了GPIO的基本操作&#xff0c;本篇继续讲解GPIO的中断处理。 二、介绍 将一个引脚设置为中断涉及到以下几个步骤&#xff08;此处我们以PZ02举例&#xff09;&#xff1a; 1.设置IO…

全球汽车行业的数字化转型:产品和后端的渐进之旅

如何管理汽车行业的数字化转型?在我们本篇文章中了解更多有关如何设定长期目标的信息。 正在改变汽车行业的26个数字化主题 最近一篇关于汽车行业数字化转型的论文确定了26个数字技术主题&#xff08;论文详情请点击阅读原文&#xff09;&#xff0c;分为三个主要集群: 1)驾驶…

社交网络分析3:社交网络隐私攻击、保护的基本概念和方法 + 去匿名化技术 + 推理攻击技术 + k-匿名 + 基于聚类的隐私保护算法

社交网络分析3&#xff1a;社交网络隐私攻击、保护的基本概念和方法 去匿名化技术 推理攻击技术 k-匿名 基于聚类的隐私保护算法 写在最前面社交网络隐私泄露用户数据暴露的途径复杂行为的隐私风险技术发展带来的隐私挑战经济利益与数据售卖防范措施 社交网络 用户数据隐私…

centOS7 安装tailscale并启用子网路由

1、在centOS7上安装Tailscale客户端 #安装命令所在官网位置&#xff1a;https://tailscale.com/download/linux #具体命令为&#xff1a; curl -fsSL https://tailscale.com/install.sh | sh #命令执行后如下图所示2、设置允许IP转发和IP伪装。 安装后&#xff0c;您可以启动…