当前位置: 首页 > news >正文

[Ray Tracing in One Weekend] 笔记

前言

本文参照自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给出的公式来模拟。
在代码中,当F大于给定的值时,我们认为此时为反射。
F ( F 0 , θ i ) = F 0 + ( 1 − F 0 ) ( 1 − c o s θ i ) 5 F 0 = ( η 1 − η 2 η 1 + η 2 ) 2 = ( η − 1 η + 1 ) 2 F(F_0,\theta_i) = F_0 + (1 - F_0)(1 - cos\theta_i)^5 \\ F_0 = (\frac{\eta_1 - \eta_2}{\eta_1 + \eta_2})^2 = (\frac{\eta - 1}{\eta + 1})^2 F(F0,θi)=F0+(1F0)(1cosθi)5F0=(η1+η2η1η2)2=(η+1η1)2

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

在这里插入图片描述

有一个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

相关文章:

  • 基于CNN+数据增强+残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)+数据集+模型(二)
  • Halcon深度学习相关术语介绍
  • 02.Git常用基本操作
  • Spring boot+nacos+mysql通过修改配置密码自动刷新数据库连接
  • C++核心编程思路(1):①程序的内存模型②引用的作用
  • Debian系统设置SSH密钥登陆
  • 15 使用v-model绑定单选框
  • 基于Spring Boot 框架的试卷自动生成系统的设计与实现
  • C语言—每日选择题—Day52
  • EasyExcel合并相同内容单元格及动态标题功能的实现
  • 华为云CodeArts Pipeline常见问答汇总
  • 智能优化算法应用:基于学校优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码
  • 【golang】go执行shell命令行的方法( exec.Command )
  • 机器人制作开源方案 | 智能落叶清扫机器人
  • 【unity实战】一个通用的FPS枪支不同武器射击控制脚本
  • angular学习第一篇-----环境搭建
  • C++类中的特殊成员函数
  • iOS编译提示和导航提示
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • leetcode98. Validate Binary Search Tree
  • Linux各目录及每个目录的详细介绍
  • MobX
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • Promise面试题2实现异步串行执行
  • STAR法则
  • TypeScript实现数据结构(一)栈,队列,链表
  • ubuntu 下nginx安装 并支持https协议
  • 程序员最讨厌的9句话,你可有补充?
  • 第2章 网络文档
  • 机器学习中为什么要做归一化normalization
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 赢得Docker挑战最佳实践
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (1)bark-ml
  • (Forward) Music Player: From UI Proposal to Code
  • (function(){})()的分步解析
  • (附源码)基于SpringBoot和Vue的厨到家服务平台的设计与实现 毕业设计 063133
  • (九十四)函数和二维数组
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .net framework 4.0中如何 输出 form 的name属性。
  • .Net MVC4 上传大文件,并保存表单
  • .Net8 Blazor 尝鲜
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .NET连接MongoDB数据库实例教程
  • .NET轻量级ORM组件Dapper葵花宝典
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • .NET中GET与SET的用法
  • .pop ----remove 删除
  • [17]JAVAEE-HTTP协议
  • [acwing周赛复盘] 第 69 场周赛20220917
  • [Android] Implementation vs API dependency