opengl 光线追踪_基于CUDA的GPU光线追踪

2023-10-30

先放最终效果:

实时光线追踪https://www.zhihu.com/video/1073144630408941568

十天前我打算用我之前的CPU渲染器渲染一张博客封面,大概是这张。

我之前的渲染器是使用C#写的,已经进行过多线程优化。但是为了渲染这张图我坐等两个多小时(7680*1159, i7-8700k)。坐等的时候我便决定,我要搞GPU渲染器...

之前有几位大佬使用OpenGL,Vulkan,Unity等用Shader的方式实现了GPU光线追踪:

Ubp.a:基于OpenGL的GPU光线追踪​zhuanlan.zhihu.com ycz:【PathTracing】实时光线追踪和BSSRDF的那些事​zhuanlan.zhihu.com

但是我这里为了学习并行计算使用的是CUDA。本文并不是一个教程,因为光线追踪部分与下面文章重复了。本文的重点是分享一些CUDA编写光线追踪渲染器时候遇见的一些坑。

破晓:【翻译】两天学会光线追踪(一)​zhuanlan.zhihu.com Asixa:《一周学习光线追踪》(一)序言及动态模糊​zhuanlan.zhihu.com

关于CUDA可以看看这里 我是小将:CUDA编程入门极简教程。简单地说,CUDA可以同时启动上千万个线程(Kernel)进行并行计算。但是CUDA的Kernel需要注意以下问题。

CUDA值得注意的特性(坑)

0.传递数据

CUDA在从内存传数据到显存的时候使用的是cudaMalloc和cudaMemcpy, 对象中以指针储存的字段都需要单独复制一遍。所以这个在传递二叉树和派生类的时候可能会有一些棘手。

  1. 不支持递归

由于GPU的目的是高并行运算,所以GPU的计算单元并没有CPU那么强的计算能力。最明显的一点就是GPU的Kernel的栈深度无法支持递归,经测试Kernel递归三四次后在就会崩掉...

解决办法就是讲所有递归代码改成循环。比如光线追踪射线采样,BVH查找这些高度依赖递归的函数都需要改成循环。

2. 不支持虚函数

比如B类继承自A,A类中定义了一个虚函数(Virtual function),而B类中覆盖(override)了这个函数。且B对象是在CPU端创建的(从Host创建)。那么在GPU上调用B的这个函数时,程序就会卡死。而且是那种用任务管理器,taskkill 都关不掉的卡死。

解决办法是在GPU端创建这个对象B(从device创建),我的写法是在B类中声明一个构造函数,参数是B*, 然后初始值全都从这个指针中取。在Host中的对象传到Device后重新构建一遍对象,这可能有一丢丢慢,但每次渲染只需要构建一次。

3. 不支持STL,但支持thrust

最开始在我构建BVH树的时候我是在GPU上构建的,因为懒得传二叉树。而这样做的弊端不仅是GPU的一个线程计算力远低于CPU。并且GPU不支持STL的qsort, Vector<T>. 无法排序就无法构建BVH。

但是英伟达提供了thrust库,理论上可以在GPU端上代替STL。

4. 显存层级

GPU上的显存共分为三个层级,Global Memory, Shared Memory, Local Memory.

读取耗时方面,L存 < S存 <<G存 << 内存。

local memory是最快的,但是需要注意一个问题,每个Kernel的local memory大小是650000字节,如果使用量超过了这个量,就会崩溃。

5.在Device慎用 new,malloc

这两个操作是在Device端创建一个Global Memory,这个弊端是很慢。我相信每个使用GPU加速的程序都是对效率敏感的。

6. 异常处理

可以在每次调用完Kernel写

auto error = cudaGetLastError();
if(error!=0)printf("error %dn", error);

来检测有没有Error抛出。(我每次使用Nsight调试整个电脑就会崩,可能是我自己的问题)

而在我写渲染器的时候出现最多的是Error 77“内存越界”,一般的内存越界很容易避免,但是我依然遇到很多很迷的崩溃然后抛出Error77,据我猜测应该包含但不限于以下两种情况:

  • kernel栈溢出

前面说过,Kernel的栈深度并不够用,第一种解决办法是消除递归,减少函数相互调用等。第二种是 将项目从Debug模式改成Release模式,这样编译器的优化就会发挥作用。

  • Local Memory超过了极限,

将不需要的对象及时的free掉,或者使用cudaDeviceSetLimitAPI设置最低Local Memory大小。

7. 随机数

在之前的项目中我的随机数使用的是drand48(),但是CUDA提供了一个更高效的随机数生成器curand。

curand提供多种随机数序列。我用的最多的是最普通的curand_uniform,在我的光线追踪采样中,我确保每个像素的采样序列都不一样,不然就会出现很多奇怪的效果

我为每个像素都创建了一个currandState

//Host
#include <curand_kernel.h>
//...

curandState *d_rng_states = nullptr;
cudaMalloc(reinterpret_cast<void **>(&d_rng_states), height * width * sizeof(curandState));

而种子方面,使用像素的唯一id。

//Device
const auto tidx = blockIdx.x * blockDim.x + threadIdx.x;
const auto tidy = blockIdx.y * blockDim.y + threadIdx.y;
curand_init(seed + tidx + tidy * d_width, 0, 0, &rngStates[tidx]);

这样在每次调用

curand_uniform(&rngStates[tid])  //tid = tidx + tidy * width

就可以生成一个0~1的随机浮点数了。

7. 纹理

在CPU渲染器中我使用byte[] 储存的纹理信息,如果在Cuda中也使用 unsigned char* 的话,会消耗很多的Global Memory,并且最重要的是,Global Memory很慢。

幸运的是Cuda提供了一个Texture解决方案,这个Texture储存在一个特定的显存区域可以极大地提高读取速度。

在Cuda的示例 0_Simple/simpleTexture项目中,项目实现了一个简单Texture,这个Texture通过绑定到了一部分显存提供更快的读取。甚至不需要传递指针到kernal即可当全局变量使用。

但是有两个问题:

第一个问题,这个Texture不能是数组或者指针数组。也就是说Texture的数量在编译的时候就是写死的。

解决方案:1. 将所有的纹理都合并到一张Atlas,这理论上是最快的,效果大概是这样:

图自Unity Form by gary_bbgames

第二个方案是使用Texture的BindlessTexture功能,这个在CUDA的示例 2_Graphics/bindlessTexture项目中有实现。而我采用的就是这种方法。

CudaTexture第二个问题是如何绑定RGB三通道,示例项目中的颜色通道只有一个,并且值类型是float,我尝试使用uchar3类型来储存三个RGB值但是没有成功。我最后使用的是LayeredTexture来创建三个层,代码在Cuda示例 0_Simple/simpleLayeredTexture项目。我不确定这是否是创建三通道纹理的最优方法,如果有其他写法,请让我知道谢谢。

三通道纹理的缓冲有点奇怪,是这样的,在创建之前需要修改一下。

//类型float
RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB

下面附Texture相关代码

//Host
inline void InitTextureList()
	{
		for (auto i = 0; i < TEXTURE_COUNT; i++) {
			//读取纹理,使用了stb_image库
			int width, height, depth;
			const auto tex_data = stbi_load(imageFilenames[i],&width, &height, &depth, 0);
			const auto size = width * height * depth;
			float* h_data = new float[size];
			printf("LoadTexture %d,%d,%dn", width, height, depth);
			for (unsigned int layer = 0; layer < 3; layer++)
				for (auto i = 0; i < static_cast<int>(width * height); i++)h_data[layer*width*height + i] = tex_data[i * 3 + layer] / 255.0;

			//cudaArray Descriptor
			cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat);
			//cuda Array
			cudaArray *d_cuArr;
			cudaMalloc3DArray(&d_cuArr, &channelDesc, make_cudaExtent(width, height, 3), cudaArrayLayered);


			cudaMemcpy3DParms myparms = { 0 };
			myparms.srcPos = make_cudaPos(0, 0, 0);
			myparms.dstPos = make_cudaPos(0, 0, 0);
			myparms.srcPtr = make_cudaPitchedPtr(h_data, width * sizeof(float), width, height);
			myparms.dstArray = d_cuArr;
			myparms.extent = make_cudaExtent(width, height, 3);
			myparms.kind = cudaMemcpyHostToDevice;
			cudaMemcpy3D(&myparms);
			

			cudaResourceDesc    texRes;
			memset(&texRes, 0, sizeof(cudaResourceDesc));
			texRes.resType = cudaResourceTypeArray;
			texRes.res.array.array = d_cuArr;
			cudaTextureDesc     texDescr;
			memset(&texDescr, 0, sizeof(cudaTextureDesc));
			texDescr.filterMode = cudaFilterModeLinear;
			texDescr.addressMode[0] = cudaAddressModeWrap;   // clamp
			texDescr.addressMode[1] = cudaAddressModeWrap;
			texDescr.addressMode[2] = cudaAddressModeWrap;
			texDescr.readMode = cudaReadModeElementType;
			texDescr.normalizedCoords = true;
			cudaCreateTextureObject(&textlist[i], &texRes, &texDescr, NULL);
		}
	}
//Device 
const auto albedo =Vec3(
		tex2DLayered<float>(texs[texid], rec.u, 1-rec.v, 0), //R
		tex2DLayered<float>(texs[texid], rec.u, 1 - rec.v, 1),//G
		tex2DLayered<float>(texs[texid], rec.u, 1 - rec.v, 2));//B

8. BVH层次包围盒

在Kernel写BVH真的是刺激....

首先正如前面所说,BVH必须在CPU创建,所以从Host向Device复制数据时候,需要复制一棵二叉树,二叉树的子节点还是个派生类的指针.....

由于我之前没单独学过C语言的内存管理,所以这部分消耗了我整整两天一夜的精力。

我最后的解决方案是将所有对象包括BVH节点放在一个父类指针数组(Hitable**)中先传到Device。每个对象都被赋予一个id,也就是在数组中的位置。而BVH树的左右节点只是个int对象。

二分查找部分,由于这部分原始代码高度依赖于递归,需要改成循环。这部分我参考了

https://devblogs.nvidia.com/thinking-parallel-part-i-collision-detection-gpu/​devblogs.nvidia.com Thinking Parallel, Part II: Tree Traversal on the GPU | NVIDIA Developer Blog​devblogs.nvidia.com Thinking Parallel, Part III: Tree Construction on the GPU | NVIDIA Developer Blog​devblogs.nvidia.com

其中在第II部分,Minimizing Divergence 部分中的traverseIterative函数中。我创建的是

int stack[64];

并且这部分在每个像素的最初始被创建,每次查找时只是重设为0,最后记得free掉这个数组。目前调试BVH依然有问题,渲染个茶壶是没有问题的,

但是换成Bunny就会抛出Error 77。目前还没有解决。


代码目前开源在:

Asixa/ALightGPU​github.com

由于之前没怎么写过C++项目,代码可能有些乱,深表歉意,明天开学,等过一阵子可能才开始修BUG和整理代码。

关于为什么我为什么全都写在头文件里,因为CUDA的编译器如果想要代码分离的话需要开启【generate relocatable device code】但是这样会导致编译器无法进行代码优化。似乎另一种解决方式是使用CUDA的*.cuh和*.cu文件进行代码分离,但是我目前还没有测试成功。如果这样可以的话之后整理代码的时候会进行代码分离。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

opengl 光线追踪_基于CUDA的GPU光线追踪 的相关文章

  • 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

    1 给定一个字符串 验证它是否是回文串 只考虑字母和数字字符 可以忽略字母的大小写 说明 本题中 我们将空字符串定义为有效的回文串 示例 1 输入 A man a plan a canal Panama 输出 true 示例 2 输入 ra
  • 环形缓冲器

    环形缓冲器 ringr buffer 也称作圆形队列 circular queue 循环缓冲区 cyclic buffer 圆形缓冲区 circula buffer 是一种用于表示一个固定尺寸 头尾相连的缓冲区的数据结构 适合缓存数据流 中
  • 编译出错总结

    1 incompatible types in assignment of char to char 64 可以使用strcpy 解决 2 error assigning to an array from an initializer li
  • Unity PlayerPrefs记录时间

    static public void SaveDateTimeInPlayerPrefs string key DateTime dt string timeStr dt ToString yyyy MM dd HH mm ss Playe
  • 时序预测

    时序预测 MATLAB实现基于BP神经网络的时间序列预测 递归预测未来 多指标评价 目录 时序预测 MATLAB实现基于BP神经网络的时间序列预测 递归预测未来 多指标评价 预测结果 基本介绍 程序设计 参考资料 预测结果 基本介绍 Mat
  • IOS7手势识别

    添加一个view 给view加上各种手势查看效果 UIView gestureTestView UIView alloc initWithFrame CGRectMake 100 100 200 200 gestureTestView ba
  • AI开源项目分享:pytorch增强图像数据、Tensorflow 2.0实现...

    文末免费送电子书 七月在线干货组最新 升级的 名企AI面试100题 免费送 项目一 igel 无需编写代码即可训练 测试和使用模型 一个令人愉悦的机器学习工具 可让您无需编写代码即可训练 拟合 测试和使用模型 该项目的目标是为技术用户和非技
  • Windows server 2008 r2关于SMB签名未配置的漏洞修复

    一 漏洞描述 SMB签名未配置漏洞是指 远程SMB服务器上未配置需要签名 这将导致未经身份验证的远程攻击者可以利用此漏洞对SMB服务器进行中间人攻击 SMB是一个协议名 全称是Server Message Block 服务器消息快协议 用于
  • 第二十三篇:UE Ceisum开发之如何加载本地地形及经纬度、空间坐标相互转化

    本篇是基于Cesium for Unreal这个插件 基本入门我这里就不多说了 不会的人可以先跟着这个教程学习一下 Cesium for Unreal快速入门 本篇重点讲述一下如何加载本地地形 1 准备工作 安装最新的Cesiumlab软件
  • JavaScript中的事件循环(event loop)机制

    聚沙成塔 每天进步一点点 专栏简介 调用栈 Call Stack 消息队列 Message Queue 事件循环 Event Loop 宏任务和微任务 示例 写在最后 专栏简介 前端入门之旅 探索Web开发的奇妙世界 记得点击上方或者右侧链
  • C#报错:线程间操作无效: 从不是创建控件“XXXX”的线程访问它

    看到一篇很好的博文 如果只是测试用的 不需要上位机多稳定 直接用第一种方法 非常nice 在zhihu上看到的一篇文章 C 线程间操作无效 从不是创建控件 dataGridView 的线程访问它 知乎 https zhuanlan zhih
  • Nginx启动时提示nginx: [emerg] still could not bind()

    今天跟着视频学习Nginx时 启动Nginx报这个nginx emerg still could not bind 于是在网上找了解决方法在这里分享出来 报错界面 解决方法 1 根据Nginx配置文件查看配置的端口默认的是80端口 然后我们
  • PTA 浙大版《C语言程序设计(第3版)》题目集 练习5-2

    本题要求对两个整数a和b 输出其中较大的数 函数接口定义 int max int a int b 其中a和b是用户传入的参数 函数返回的是两者中较大的数 裁判测试程序样例 include
  • 小博客练习

    博客 简单的综合运用 一 了解博客需求 1 主要的分为前台和后台 后台 注册登录 登录 登录 分类管理 显示分类 编辑分类 添加分类 删除分类 文章管理 显示文章 编辑文章 添加文章 删除文章 用户管理 标签管理 等等功能 前台 首页面 列
  • linux常用命令总结

    linux常用命令总结 1 在根目录下查找xxx文件 2 列出程序启动时搜索库路径的过程 3 查看程序或so的依赖 1 查看Test程序依赖的so文件 2 也可以查看so依赖的文件 4 SSH相关 5 查看当前登录的用户 6 查看重启 登录
  • Java面试题整理-高并发篇

    1 synchronized的实现原理以及锁优化 synchronized原理分析 遇见更好的自己 的博客 CSDN博客Java 高并发专题之synchronized关键字1 synchronized作为jvm关键字有三个作用域synchr
  • rancher部署

    Rancher 管理 Kubernetes 集群 Rancher 简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台 实现了 Kubernetes 集群在混合云 本地数据中心的集中部署与管理 以确保集群的安全性
  • 在脚本中 使用数组 获取列表中选中的复选框id + 获取选中的单选框的值

    var chk value 定义一个数组 input name communicateTeacher claIds checked each function 遍历每一个名字为interest的复选框 其中选中的执行函数 chk value
  • Pandas提取指定行列

    取列 1 通过列名称来提取指定列 推荐 列名 irline sentiment gold name negativereason gold retweet count text get data data airline sentiment

随机推荐

  • kafka基础学习(六):kafka 代码示例

    kafka 代码示例 生成者API在不同版本间无较大变动 消费者 Consumer 提供了两套API 低版本 8 0版本及其以前 API 高版本 8 0版本后 API 两种API的优缺点 高版本Consumer API 优点 高级API写起
  • Qt-UI 界面工具库简介

    一 关于Qt UI界面工具库 Qt UI界面工具库是武汉维仕杰科技有限公司基于Qt上进行扩展开发的控件包和界面工具 并且拥有完全自主的知识产权 得益于丰富的界面开发经验和强大的支持团队 使得Qt UI界面开发工具成为最专业 功能强大的Qt开
  • 他在 B 站有 178 万粉丝,今天免费带你玩转 Python

    近几年 编程越来越火 网上也是铺天盖地的免费教程 中小学生都开始投入到学习中 编程学习从娃娃抓起 甚至有些小学生都做起了 UP 主 教大家学编程 光从编程的难易度来说 Python 简单 易学 零基础 跨专业都很容易上手 想学全网超详细 P
  • Linux内核配置选项详解

    对于每一个配置选项 用户可以回答 y m 或 n 其中 y 表示将相应特性的支持或设备驱动程序编译进内核 m 表示将相应特性的支持或设备驱动程序编译成可加载模块 在需要时 可由系统或用户自行加入到内核中去 n 表示内核不提供相应特性或驱动程
  • Qt连接SQL server数据库

    Qt连接SQL server数据库 由于课程设计需要 需要用qt设计一个界面来操作数据库 在建立数据库连接时 期间遇到各种问题 Qt 连接SQL server数据大致可以概括为下图的三层模型 箭头代表他们之间的依赖关系 第一步 建立目标数据
  • FeignClient的参数传递给服务提供方的方式(简单数据类型、对象)

    1 简单数据类型的参数采用的restFull的方式 发送Get请求 服务提供方的controller 类名加了窄化请求 RequestMapping path house produces application json charset
  • 前端制作简单的“注册页面——阅读协议”页面及效果

    注 如对文中的scrollHeight scrollTop clientHeight属性有所一伙 请参考博文 JavaScript中元素client offset scroll相关属性的应用 链接 https blog csdn net m
  • 开源进展

    作为一个友好的 功能丰富的区块链中间件平台 WeBASE一直致力于降低区块链开发者的研发门槛 提高区块链开发效率 如今 WeBASE v1 5 4来了 此次更新新增区块链应用实训课程案例集 以及管理台操作指引与设计说明 助力社区开发者更快捷
  • 自定义指令 v-clickoutside 使用方法

    引入 import Clickoutside from element ui src utils clickoutside 声明 export default directives Clickoutside data function re
  • CentOS 安装Vim 编辑器

    在CentOS环境下使用vim提示 vim command not found时 说明系统还没有安装vim 安装步骤 1 检查是否已安装 查看一下你本机已经存在的包 确认一下你的VIM是否已经安装 输入 rpm qa grep vim 如果
  • 【详解】Thymeleaf中的基本表达式:@{},#{},${},*{}

    1 thymeleaf中的超链接表达式 话说例子如下 a a a a 2 thymeleaf中的消息表达式 或者是资源表达式 一般和 th text一起使用多一点 取出来的值取代了标签中的值 key 对应的value 如果标签中间已经有值
  • STM32的I2C主从机通信

    最近一段时间在做I2C通信协议 需要在两块STM32之间做I2C通信 定的是主机用IO口模拟 从机用的是STM32的硬件I2C 我的项目要求是需要主从机之间能够进行一串数据收发而不出错 实验时在主机方面 利用IO口模拟主机 只需要理解时序就
  • Java IDEA辅助键和快捷键

    快速生成main方法 psvm 回车 快速生成输出语句 sout 回车 内容辅助键 Ctrl Alt space 内容提示 代码补全等 格式化 Ctrl Alt L
  • java nio 编程

    转自 http yangguangfu iteye com blog 774194 Java代码 晚上学习了下Java 的 NIO Socket编程 写了下面这个小程序 包括服务器端与客户端 实现的功能为客户端向服务器端发送随即数目的消息
  • Java Executors(线程池)

    Sun在Java5中 对Java线程的类库做了大量的扩展 其中线程池就是Java5的新特征之一 除了线程池之外 还有很多多线程相关的内容 为多线程的编程带来了极大便利 为了编写高效稳定可靠的多线程程序 线程部分的新增内容显得尤为重要 有关J
  • PHP学习记录--基础篇

    目录 一 变量定义 二 循环结构 三 函数 四 文件加载原理 五 错误处理 六 字符串 七 数组 一 变量定义
  • C进阶之实现数据库(mysql 5.0)版本通讯录

    前言 断断续续历时两天百度的知识探索 继静态 动态 文件版本的通讯录之后 倔强的我开启了数据库版本通讯录 在动态版本通讯录的基础上将通讯人信息数据插入数据库中 初始化通讯录完成后 再从数据库中查询通讯人信息数据 的痛苦长路 想说脏话但是稍微
  • 在九天毕昇平台运行paddle出现fatal error: ‘Segmentation fault‘ is detected by the operating system

    我在九天毕昇平台使用paddlepaddle框架训练实例时 调用GPU出现下文错误 C Traceback most recent call last No stack trace in paddle may be caused by ex
  • SQL查询结果去重

    SQL查询结果去重 使用distinct关键字 去除重复的记录行 SELECT loc FROM dept SELECT DISTINCT loc FROM dept 案例 描述 题目 现在运营需要查看用户来自于哪些学校 请从用户信息表中取
  • opengl 光线追踪_基于CUDA的GPU光线追踪

    先放最终效果 实时光线追踪https www zhihu com video 1073144630408941568 十天前我打算用我之前的CPU渲染器渲染一张博客封面 大概是这张 我之前的渲染器是使用C 写的 已经进行过多线程优化 但是为