体渲染原理及WebGL实现【Volume Rendering】

体渲染(Volume Rendering)是NeRF神经场辐射AI模型的基础,与传统渲染使用三角形来显示 3D 图形不同,体渲染使用其他方法,例如体积光线投射 (Volume Ray Casting)。本文介绍体渲染的原理并提供Three.js实现代码,源代码可以从Github下载。

在这里插入图片描述

推荐:用 NSDT编辑器 快速搭建可编程3D场景。

1、体渲染基础

体渲染是基于图像的方法,通过沿 3D 体积投射光线,将 3D 标量场渲染为 2D 图像。 我们在屏幕上看到的每个像素都是光线穿过立方体并按一定间隔从体素获取强度样本的结果。

但是我们如何投射光线呢?

一个简单的选择是使用大小为 (1,1,1) 的 3D 网格立方体,并在两个不同的渲染通道中渲染正面和背面(启用和禁用背面剔除)。

对于屏幕中生成的每个立方体片段,我们可以创建一条从立方体正面开始并在背面结束的射线。 有了光线的起点和终点,我们就可以开始对体素进行采样,以生成最终的片段颜色。

在这里插入图片描述

标量场表示为包含每个 (x,y,z) 位置处的强度值的体素

面我们将解释使用 WebGL 和 ThreeJS 实现体渲染的实现步骤。

2、准备原始文件

原始文件是非常简单的文件,仅包含体素强度,它们没有标头或元数据,并且通常每个体素具有按 X、Y 和 Z 顺序排列的 8 位(或 16 位)强度值。

在 OpenGL 或 DirectX 中,我们可以将所有这些数据加载到专门设计的 3D 纹理中,但由于 WebGL 目前不支持存储或采样 3D 纹理,因此我们必须以 2D 纹理可以使用的方式存储它 。 因此,我们可以存储一个 png 图像文件,其中所有 Z 切片一个挨着一个,形成 2D 切片的马赛克。 我开发了一个非常简单的转换器工具,其中包含源代码。 该工具获取原始文件并生成一个 png 图像马赛克,对 alpha 通道中每个体素的强度进行编码(尽管理想的是将 png 存储为 A8 格式只是为了节省一些空间)。

一旦 png 文件作为 2D 纹理加载到内存中,我们就可以使用我们自己的自定义 SampleAs3DTexture 函数对其进行采样,就好像它是 3D 纹理一样。

在本文末尾的参考资料部分中查找更多要测试的原始文件。

3、第一个渲染通道

在第二步中,我们打算生成用作光线终点的片段。 因此,对于第一个渲染通道,我们不是绘制背面颜色,而是将片段的世界空间位置存储在渲染纹理中,作为 RGB 片段颜色内的 x、y、z 坐标值(此处 RGB 被编码为浮点值)。

请注意 worldSpaceCoords 如何用于存储立方体背面位置的世界空间位置。

顶点着色器第一遍:

varying vec3 worldSpaceCoords;void main()
{
//Set the world space coordinates of the back faces vertices as output.
worldSpaceCoords = position + vec3(0.5, 0.5, 0.5); //move it from [-0.5;0.5] to [0,1]
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

片段着色器第一遍:

varying vec3 worldSpaceCoords;void main()
{
//The fragment's world space coordinates as fragment output.
gl_FragColor = vec4( worldSpaceCoords.x , worldSpaceCoords.y, worldSpaceCoords.z, 1 );
}

在这里插入图片描述

左:正面颜色坐标 右:背面颜色坐标

4、第2个渲染通道

该渲染通道是实际执行体积光线投射的通道,它首先绘制立方体的正面,其中正面的每个点都将是光线起点。

顶点着色器创建两个输出:投影坐标(片段的 2D 屏幕坐标)和世界空间坐标。

世界空间坐标将用作光线起点,而投影坐标将用于对存储立方体背面位置的纹理进行采样。

顶点着色器第二遍:

varying vec3 worldSpaceCoords;
varying vec4 projectedCoords;void main()
{
worldSpaceCoords = (modelMatrix * vec4(position + vec3(0.5, 0.5,0.5), 1.0 )).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
projectedCoords = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

第二个渲染通道的片段着色器有点复杂,因此我们将分部分进行介绍。

在这里插入图片描述

在此示例中,光线 R0 到 R4 从立方体的正面片段位置(f0 到 f4)投射,并在背面位置(I0 到 I4)结束

4.1 获取光线结束位置

基于上一步的位置,我们对纹理进行采样,得到背面片段的世界空间位置。

请注意我们如何通过除以 W 将投影坐标转换为 NDC(标准化设备坐标),然后如何将其转换为 [0;1] 范围,以便将其用作 UV 坐标。 当我们对先前渲染通道中生成的 2D 纹理进行采样时,可以获得光线的结束位置。

片段着色器第二遍第 1 部分:

//Transform the coordinates it from [-1;1] to [0;1]
vec2 texc = vec2(((projectedCoords.x / projectedCoords.w) + 1.0 ) / 2.0,
((projectedCoords.y / projectedCoords.w) + 1.0 ) / 2.0 );//The back position is the world space position stored in the texture.
vec3 backPos = texture2D(tex, texc).xyz;//The front position is the world space position of the second render pass.
vec3 frontPos = worldSpaceCoords;//The direction from the front position to back position.
vec3 dir = backPos - frontPos;float rayLength = length(dir);

4.2 设置射线

有了前面和后面的位置,我们现在可以创建一条从 frontPos 开始并在 backPos 结束的射线。

片段着色器第二遍第 2 部分

//Calculate how long to increment in each step.
float delta = 1.0 / steps;//The increment in each direction for each step.
vec3 deltaDirection = normalize(dir) * delta;
float deltaDirectionLength = length(deltaDirection);//Start the ray casting from the front position.
vec3 currentPosition = frontPos;//The color accumulator.
vec4 accumulatedColor = vec4(0.0);//The alpha value accumulated so far.
float accumulatedAlpha = 0.0;//How long has the ray travelled so far.
float accumulatedLength = 0.0;vec4 colorSample;
float alphaSample;

4.3 光线行进

一旦设置了射线,我们就开始从起始位置行进射线并将射线当前位置向方向推进。

在每个步骤中,我们都会对纹理进行采样以搜索体素强度。 值得注意的是,体素仅包含强度值,因此到目前为止它们没有任何有关颜色的信息。 每个体素的颜色由变换函数给出。 可以查看 sampleAs3DTexture函数代码来了解变换函数是如何工作的。

在获得由 sampleAs3DTexture 给出的体素颜色后,将通过 alphaCorrection 参数对其进行校正。 可以在线调整该值并查看不同的结果。

每次迭代的重要部分是实际的颜色组合,其中根据 alpha 值将累积颜色值添加到先前存储的值之上。 我们还保留了一个 alphaAccumulator,它可以让我们知道何时停止光线行进。

迭代不断进行,直到满足以下三个条件之一:

  • 光线传播的距离达到了假定的光线长度。 请记住,射线从 startPos 到 endPos。
  • 累计alpha值达到100%
  • 迭代达到最大常数 MAX_STEPS

最后,片段着色器返回所遍历的体素值的合成结果。

片段着色器第二遍第 3 部分

//Perform the ray marching iterations
for(int i = 0 ; i < MAX_STEPS ; i++)
{
//Get the voxel intensity value from the 3D texture.
colorSample = sampleAs3DTexture( currentPosition );//Allow the alpha correction customization
alphaSample = colorSample.a * alphaCorrection;//Perform the composition.
accumulatedColor += (1.0 - accumulatedAlpha) * colorSample * alphaSample;//Store the alpha accumulated so far.
accumulatedAlpha += alphaSample;//Advance the ray.
currentPosition += deltaDirection;
accumulatedLength += deltaDirectionLength;//If the length traversed is more than the ray length, or if the alpha accumulated reaches 1.0 then exit.
if(accumulatedLength >= rayLength || accumulatedAlpha >= 1.0 )
break;}gl_FragColor = accumulatedColor;

如果你可以更改每条射线完成的最大迭代次数,则更改控件中的步骤,并且你可能必须相应地调整 alphaCorrection 值。


原文链接:体渲染Three.js实现 — BimAnt

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

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

相关文章

使用CST仿真软件完成单站雷达目标RCS仿真

第一步&#xff1a;新建模版 第二步&#xff1a;选择微波与射频/光学应用 第三步&#xff1a;选择雷达应用 第四步&#xff1a;选择单站雷达静态RCS 第五步&#xff1a;选择小目标工作流。&#xff08;这里的小目标要求小于10倍波长&#xff0c;否则需要选大目标工作流&#x…

golang协程池库tunny实践

前言 线程池大家都听过&#xff0c;其主要解决的是线程频繁创建销毁带来的性能影响&#xff0c;控制线程数量。 go协程理论上支持百万协程并发&#xff0c;协程创建调度的消耗极低&#xff0c;但毕竟也是消耗对吧。 而且协程池可以做一些额外的功能&#xff0c;比如限制并发&…

[数据集][目标检测]道路坑洼目标检测数据集VOC格式1510张2类别

数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;1510 标注数量(xml文件个数)&#xff1a;1510 标注类别数&#xff1a;2 标注类别名称:["keng","…

百度智能云:千帆大模型平台接入Llama 2等33个大模型,上线103个Prompt模板

大家好&#xff0c;我是herosunly。985院校硕士毕业&#xff0c;现担任算法研究员一职&#xff0c;热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名&#xff0c;CCF比赛第二名&#xff0c;科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的…

Git全栈体系(五)

第八章 IDEA 集成 GitHub 一、设置 GitHub 账号 如果出现 401 等情况连接不上的&#xff0c;是因为网络原因&#xff0c;可以使用以下方式连接&#xff1a; 然后去 GitHub 账户上设置 token。 点击生成 token。 复制红框中的字符串到 idea 中。 点击登录。 二、分享工程…

Stephen Wolfram:让 ChatGPT 真正起作用的是什么?

What Really Lets ChatGPT Work? 让 ChatGPT 真正起作用的是什么&#xff1f; Human language—and the processes of thinking involved in generating it—have always seemed to represent a kind of pinnacle of complexity. And indeed it’s seemed somewhat remarkabl…

最新版本的Anaconda环境配置、Cuda、cuDNN以及pytorch环境一键式配置流程

本教程是最新的深度学习入门环境配置教程&#xff0c;跟着本教程可以帮你解决入门深度学习之前的环境配置问题。同时&#xff0c;本教程拒绝琐碎&#xff0c;大部分以图例形式进行教程。这里我们安装的都是最新版本~ 文章目录 一、Anaconda的安装1.1 下载1.2 安装1.3 环境配置…

掌握Python的X篇_27_Python中标准库文档查阅方法介绍

前面的博文介绍了python的基本语法、模块及其导入方法。前人将各种方法封装成模块、库、函数供我们使用&#xff0c;如何去使用前人做好的东西&#xff0c;那就需要去查阅文档。今天就介绍python中官方文档的查阅方式。对于初学者而言&#xff0c;python自带的文档就已经足够好…

[软件工具][原创]OCR识字找图关键词找图以文搜图工具使用教程

OCR识字找图工具功能简介&#xff1a; 当你有一批图片但是想提取图片里面包含关键词的的图片&#xff0c;以前都是手工肉眼打开去找&#xff0c;其实这个大可不必&#xff0c;现在只需输入关键词&#xff0c;软件会自动搜索所有图片&#xff0c;只要包含指定关键词就会复制或者…

Vue3自定义简单的Swiper滑动组件-触控板滑动鼠标滑动左右箭头滑动-demo

代码实现了一个基本的滑动功能&#xff0c;通过鼠标按下、鼠标松开和鼠标移动事件来监听滑动操作。 具体实现逻辑如下&#xff1a; 在 onMounted 钩子函数中&#xff0c;我们为滚动容器添加了三个事件监听器&#xff1a;mousedown 事件&#xff1a;当鼠标按下时&#xff0c;设置…

C# Blazor 学习笔记(12):css样式设置

文章目录 前言添加引入css保证razor和css的对应关系手动引入css文件 前言 由于Blazor UI库暂时还不完善&#xff0c;有时候需要我们自己写css。Razor做好了css动态隔离的设置。 ASP.NET Core Blazor CSS 隔离 C#小轮子&#xff1a;Visual Studio自动编译Sass文件 添加引入c…

在Centos环境中搭建Nginx环境

一、Nginx概念简介 Nginx是一个轻量级的高性能HTTP反向代理服务器&#xff0c;同时它也是一个通用类型的代理服务器&#xff0c;支持绝大部分协议&#xff0c;如TCP、UDP、SMTP、HTTPS等。 Nginx与redis相同&#xff0c;都是基于多路复用模型构建出的产物&#xff0c;因此它与R…

【MySQL】并发执行事务可能存在的问题, 事物的四种隔离级别

文章目录 前言一、并发执行事务可能存在的问题1, 脏读问题2, 不可重复读3, 幻读 二、MySQL 的四种隔离级别1, READ UNCOMMITTED 读未提交2, READ COMMITTED 读已提交3, REPEATABLE READ 可重复读 (MySQL 的默认事务隔离级别)4, SERIALIZABLE 串行化 总结 前言 各位读者好, 我是…

idea如何上传项目到github(超详细)

idea如何上传项目到github 1、IDEA配置2、项目上传到本地仓库2.1、创建本地git仓库2.2、Add操作2.3、Commit操作 3、项目上传到Github4、拿到登录Github的token 1、IDEA配置 File-Settings-VersionControl-Git Git的安装路径下bin目录下的git.exe可执行文件 可以直接点 Gene…

QGIS3.28的二次开发六:VS不借助QT插件创建UI界面

上一篇博客我们说了在VS中如何使用QT插件来创建UI界面&#xff0c;但是我们二次开发QGIS的第一篇博客就说了&#xff0c;最好使用OSGeo4W中自动下载的QT进行QGIS二次开发&#xff0c;这样兼容性是最好的&#xff0c;那么该如何在VS中不使用外部安装的QT以及QT的VS插件情况下进行…

【vue3】vue3中父子组件传参:

文章目录 一、父传子&#xff1a;二、父调用子方法&#xff1a;三、子组件发送emit方法给父组件&#xff1a; 一、父传子&#xff1a; 【1】父组件传值&#xff1a; 【2】子组件接收&#xff1a; 二、父调用子方法&#xff1a; 【1】父组件调用&#xff1a; 【2】子组件暴…

RabbitMQ在CentOS下的安装

RabbitMQ的版本是3.8.2 1.环境配置&#xff1a;CentOs 7.6以上版本&#xff0c;我的版本是7.9&#xff0c;不要对yum换源&#xff0c;否则可能会安装失败。 echo "export LC_ALLen_US.UTF-8" >> /etc/profile source /etc/profile 以上命令&#xff0c;是…

vue使用ElementUI

1.安装 npm i element-ui -S 2.引入 2.1完整引入 import Vue from vue; import ElementUI from element-ui; import element-ui/lib/theme-chalk/index.css; import App from ./App.vue;Vue.use(ElementUI); 2.2按需引入 说明&#xff1a;为了输入时候有提示&#xff0c;建…

变形金刚在图像识别方面比CNN更好吗?

链接到文 — https://arxiv.org/pdf/2010.11929.pdf 一、说明 如今&#xff0c;在自然语言处理&#xff08;NLP&#xff09;任务中&#xff0c;转换器已成为goto架构&#xff08;例如BERT&#xff0c;GPT-3等&#xff09;。另一方面&#xff0c;变压器在计算机视觉任务中的使用…

Practices9(双指针)|283. 移动零、11. 盛最多水的容器、15. 三数之和

283. 移动零 1.题目&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,…