体渲染原理及WebGL实现(webgl渲染引擎)
liuian 2025-05-08 02:45 26 浏览
体渲染(Volume Rendering)是NeRF神经场辐射AI模型的基础,与传统渲染使用三角形来显示 3D 图形不同,体渲染使用其他方法,例如体积光线投射 (Volume Ray Casting)。
本文的源代码可以从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 值。
原文链接:
http://www.bimant.com/blog/volume-rendering-with-threejs/
相关推荐
- Springboot 整合 Websocket 轻松实现IM及时通讯
-
一、方案实践集成分为三步:添加依赖、增加配置类和消息核心类、前端集成。1.1、添加依赖<dependency><groupId>org.springframework...
- SpringBoot扩展——应用Web Socket!
-
应用WebSocket目前,网络上的即时通信App有很多,如QQ、微信和飞书等,按照以往的技术来说,即时功能通常会采用服务器轮询和Comet技术来解决。HTTP是非持久化、单向的网络协议,在建立连接...
- 【Spring Boot】WebSocket 的 6 种集成方式
-
介绍由于前段时间我实现了一个库【SpringCloud】一个配置注解实现WebSocket集群方案以至于我对WebSocket的各种集成方式做了一些研究目前我所了解到的就是下面这些了(就一个破w...
- SpringBoot生产级WebSocket集群实践,支持10万连接!
-
1、问题背景智慧门诊系统旨在从一定程度上解决患者面临的三长一短(挂号、看病、取药时间长,医生问诊时间短)的问题。实现“诊前、诊中、诊后”实时智能一体化,整合完善医院工作流程。围绕门诊看病的各个环节,让...
- Spring Boot3 中 WebSocket 实现数据实时通信全解析
-
各位互联网大厂的开发同仁们,在如今的互联网应用开发中,实时通信功能越来越重要。比如在线聊天、数据推送、实时通知等场景,都离不开高效的实时通信技术。而WebSocket作为一种高效的双向通信协议,在...
- Java WebSocket 示例(java nio websocket)
-
一、环境准备1.依赖配置(Maven)在pom.xml中添加WebSocket依赖:xml<!--SpringBootWebSocket--><dependen...
- Spring Boot整合WebSocket:开启实时通信之旅
-
SpringBoot整合WebSocket:开启实时通信之旅今天咱们来聊聊SpringBoot整合WebSocket这件大事儿。说到实时通信,你是不是第一时间想到QQ、微信这些聊天工具?没错,We...
- Spring Boot3 竟能如此轻松整合 WebSocket 技术,你还不知道?
-
在当今互联网大厂的软件开发领域,实时通信的需求愈发迫切。无论是在线聊天应用、实时数据更新,还是协同办公系统,都离不开高效的实时通信技术支持。而WebSocket作为一种能够实现浏览器与服务器之间持...
- Spring Boot集成WebSocket(springboot集成websocket)
-
一、基础配置依赖引入<dependency><groupId>org.springframework.boot</groupId><artifactId>...
- Springboot下的WebSocket开发(springboot websocket server)
-
今天遇到一个需求,需要对接第三方扫码跳转。一种方案是前端页面轮询后端服务,但是这种空轮询会虚耗资源,实时性比较差而且也不优雅。所以决定使用另一种方案,websocket。以前就知道websocket,...
- springboot websocket开发(java spring boot websocket)
-
maven依赖SpringBoot2.0对WebSocket的支持简直太棒了,直接就有包可以引入<dependency><groupId>org....
- Python界面(GUI)编程PyQt5窗体小部件
-
一、简介在Qt(和大多数用户界面)中,“小部件”是用户可以与之交互的UI组件的名称。用户界面由布置在窗口内的多个小部件组成。Qt带有大量可用的小部件,也允许您创建自己的自定义和自定义小部件。二、小部件...
- 实战PyQt5: 014-下拉列表框控件QComboBox
-
QComboBox简介QComboBox下拉列表框,是一个集按钮和下拉列表选项于一体的部件。QComboBox提供了一种向用户呈现选项列表的方式,其占用最小量的屏幕空间。QComboBox中的常用方法...
- Python小白逆袭!7天吃透PyQt6,独立开发超酷桌面应用
-
PythonGUI编程:PyQt6从入门到实战的全面指南在Python的庞大生态系统中,PyQt6作为一款强大的GUI(GraphicalUserInterface,图形用户界面)编程框架,为开...
- 如何用 PyQt6 打造一个功能完善的 SQLite 数据库管理工具
-
如何使用PyQt6和qt_material库,打造一个功能完善的SQLite数据库管理工具,轻松管理和查询SQLite数据库。一、目标数据库连接与表管理:支持连接SQLite数据库...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
飞牛OS入门安装遇到问题,如何解决?
-
- 最近发表
-
- Springboot 整合 Websocket 轻松实现IM及时通讯
- SpringBoot扩展——应用Web Socket!
- 【Spring Boot】WebSocket 的 6 种集成方式
- SpringBoot生产级WebSocket集群实践,支持10万连接!
- Spring Boot3 中 WebSocket 实现数据实时通信全解析
- Java WebSocket 示例(java nio websocket)
- Spring Boot整合WebSocket:开启实时通信之旅
- Spring Boot3 竟能如此轻松整合 WebSocket 技术,你还不知道?
- Spring Boot集成WebSocket(springboot集成websocket)
- Springboot下的WebSocket开发(springboot websocket server)
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- table.render (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)