Three.js 体积渲染在链上数据可视化中的应用:体素化与 Ray Marching 实践
Three.js 体积渲染在链上数据可视化中的应用体素化与 Ray Marching 实践一、链上数据的可视化困境平面图表的信息密度天花板区块链数据天然是三维的——时间区块高度、空间地址间交互、价值交易金额构成三个互相正交的维度。但绝大多数 DApp 仪表盘仍然停留在二维图表折线图显示 TVL 变化、柱状图对比 DEX 交易量、热力图展示 Gas 分布。当需要展示地址簇的交互关系、多层 DeFi 的资金流向、MEV 套利的三明治攻击时序时二维可视化开始丢失信息。Scatter Plot 的第三维被映射为颜色或点大小但人脑对空间深度的感知远优于对色谱的分辨。更具体地说考虑一个套利机器人跨 5 个 DEX 执行原子交易的场景每笔交易的时间间隔在毫秒级资金在 5 个合约之间流动形成一个闭合环路。在二维散点图中你只能看到 5 个点按时间排序连线——信息全部压缩在平面上。而在三维体积渲染中X 轴是合约地址、Y 轴是时间区块高度、Z 轴是金额用户旋转视角就能在不同维度上解构这条套利环路。体积渲染提供了一种在不同维度上探索链上数据的方式。它不是取代二维图表的实用性而是在需要展示集合中个体的空间分布与密度时提供补充视角。二、体素化与 Ray Marching 的渲染原理flowchart LR subgraph 数据预处理 RD[链上原始数据br/交易/地址/金额] -- NORM[归一化br/映射到 0-1 空间] NORM -- VOX[体素化br/128x128x128 网格] end subgraph 渲染管线 VOX --|3D纹理| GPU[GPU Shader] GPU -- RM[Ray Marchingbr/逐步采样] RM -- ACC[累积颜色与透明度] ACC -- FRAMEBUFFER[帧缓冲] end subgraph 交互层 FRAMEBUFFER -- CAM[轨道相机控制] CAM -- SLICER[截面切割br/展示内部结构] SLICER -- DISPLAY[WebGL Canvas] end VOX --|密度值| COLOR[传递函数br/密度→颜色映射] COLOR -- ACC体素化是将连续空间离散化为规则网格的过程。链上数据映射到三维空间后每个体素存储该区域内的事件密度——可能是在该价格区间和区块范围交易的地址数量或者某对代币在不同时间点的流动性状态。Ray Marching 是体积渲染的核心算法。从相机向每个像素发射一条射线沿射线以固定步长前进每一步在体素网格中采样颜色和透明度alpha按从后到前或从前到后的顺序混合。关键优势是不同于表面渲染需要三角形网格Ray Marching 直接对体数据采样天然适合展示内部结构。三、Three.js WebGL 体积渲染实现// volume/VolumeRenderer.ts import * as THREE from three; /** * 链上数据体积渲染器 * 设计决策 * 1. 使用 THREE.js WebGL 后端不依赖 WebGPU兼容性优先 * 2. 体素数据存储在 DataTexture 中单通道 8-bit 密度 * 3. Ray Marching 在 Fragment Shader 中完全实现CPU 仅负责数据上传 */ export class ChainVolumeRenderer { private scene: THREE.Scene; private camera: THREE.PerspectiveCamera; private renderer: THREE.WebGLRenderer; private volumeMaterial: THREE.ShaderMaterial; private volumeData: Uint8Array; private gridSize: number 128; constructor(canvas: HTMLCanvasElement) { this.scene new THREE.Scene(); this.camera new THREE.PerspectiveCamera( 60, canvas.clientWidth / canvas.clientHeight, 0.1, 10 ); this.camera.position.set(1.5, 1.5, 1.5); this.camera.lookAt(0.5, 0.5, 0.5); this.renderer new THREE.WebGLRenderer({ canvas, antialias: true }); this.renderer.setSize(canvas.clientWidth, canvas.clientHeight); // 初始化空数据 this.volumeData new Uint8Array(this.gridSize ** 3); // 创建渲染体单位立方体 const geometry new THREE.BoxGeometry(1, 1, 1); this.volumeMaterial this.createVolumeMaterial(); const mesh new THREE.Mesh(geometry, this.volumeMaterial); this.scene.add(mesh); // 轨道控制 this.setupOrbitControls(canvas); } /** * 更新体素数据 * param data 原始数组gridSize^3 个 uint8 */ updateVolumeData(data: Uint8Array) { this.volumeData data; // 创建 3D 纹理 const texture new THREE.Data3DTexture( data, this.gridSize, this.gridSize, this.gridSize ); texture.format THREE.RedFormat; texture.type THREE.UnsignedByteType; texture.needsUpdate true; this.volumeMaterial.uniforms.volumeMap.value texture; } /** * 创建体积渲染 Shader * Ray Marching 在 fragment shader 中执行 */ private createVolumeMaterial(): THREE.ShaderMaterial { return new THREE.ShaderMaterial({ uniforms: { volumeMap: { value: null }, // 传递函数低密度→蓝色低活跃区高密度→橙红高活跃区 steps: { value: 256 }, // Ray 采样步数越多越精确但越慢 alphaScale: { value: 1.0 }, // 透明度缩放 cameraPos: { value: new THREE.Vector3() }, }, vertexShader: /* glsl */ varying vec3 vWorldPos; varying vec3 vLocalPos; void main() { vec4 worldPos modelMatrix * vec4(position, 1.0); vWorldPos worldPos.xyz; vLocalPos position; // 单位立方体 [-0.5, 0.5] gl_Position projectionMatrix * modelViewMatrix * vec4(position, 1.0); } , fragmentShader: /* glsl */ varying vec3 vWorldPos; varying vec3 vLocalPos; uniform sampler3D volumeMap; uniform int steps; uniform float alphaScale; uniform vec3 cameraPos; /** * 密度值到颜色的传递函数 * 低密度(交易少) - 蓝色系 * 中密度 - 品红 * 高密度(交易密集) - 橙红 */ vec3 densityToColor(float density) { vec3 lowColor vec3(0.1, 0.2, 0.8); // 蓝色 vec3 midColor vec3(0.8, 0.2, 0.6); // 品红 vec3 highColor vec3(1.0, 0.4, 0.1); // 橙红 if (density 0.5) { return mix(lowColor, midColor, density * 2.0); } else { return mix(midColor, highColor, (density - 0.5) * 2.0); } } void main() { // 射线方向从相机指向当前片元的世界位置 vec3 rayOrigin cameraPos; vec3 rayDir normalize(vWorldPos - cameraPos); // 计算射线与立方体的交点 vec3 boxMin vec3(-0.5); vec3 boxMax vec3(0.5); vec3 tMin (boxMin - rayOrigin) / rayDir; vec3 tMax (boxMax - rayOrigin) / rayDir; vec3 t1 min(tMin, tMax); vec3 t2 max(tMin, tMax); float tNear max(max(t1.x, t1.y), t1.z); float tFar min(min(t2.x, t2.y), t2.z); // Ray 不与立方体相交 if (tNear tFar || tFar 0.0) { discard; } tNear max(tNear, 0.0); // Ray Marching float stepSize (tFar - tNear) / float(steps); vec3 step rayDir * stepSize; vec3 pos rayOrigin rayDir * tNear; vec4 accumulatedColor vec4(0.0); for (int i 0; i 256; i) { if (i steps) break; // 将世界坐标映射到纹理坐标 [0, 1] vec3 texCoord pos 0.5; // 边界检查 if (any(lessThan(texCoord, vec3(0.0))) || any(greaterThan(texCoord, vec3(1.0)))) { break; } // 采样体素密度 float density texture(volumeMap, texCoord).r; if (density 0.01) { vec3 sampleColor densityToColor(density); float alpha density * alphaScale * stepSize * 30.0; // 从前到后混合accumulated sample * alpha * (1 - accumulated.a) accumulatedColor.rgb sampleColor * alpha * (1.0 - accumulatedColor.a); accumulatedColor.a alpha * (1.0 - accumulatedColor.a); } // 提前退出透明度接近饱和 if (accumulatedColor.a 0.98) break; pos step; } gl_FragColor accumulatedColor; // 没有可见内容的区域透明化 if (gl_FragColor.a 0.01) discard; } , transparent: true, depthWrite: false, // 体积渲染禁用深度写入保证半透明混合 }); } private setupOrbitControls(canvas: HTMLCanvasElement) { let isDragging false; let prevX 0; let prevY 0; let spherical { theta: Math.PI / 4, phi: Math.PI / 3, radius: 2.5 }; canvas.addEventListener(mousedown, (e) { isDragging true; prevX e.clientX; prevY e.clientY; }); canvas.addEventListener(mousemove, (e) { if (!isDragging) return; const dx (e.clientX - prevX) * 0.005; const dy (e.clientY - prevY) * 0.005; spherical.theta - dx; spherical.phi Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi dy)); prevX e.clientX; prevY e.clientY; this.updateCamera(spherical); }); canvas.addEventListener(mouseup, () { isDragging false; }); canvas.addEventListener(wheel, (e) { spherical.radius Math.max(1.0, Math.min(5.0, spherical.radius e.deltaY * 0.005)); this.updateCamera(spherical); }); this.updateCamera(spherical); } private updateCamera( spherical: { theta: number; phi: number; radius: number } ) { this.camera.position.set( spherical.radius * Math.sin(spherical.phi) * Math.cos(spherical.theta), spherical.radius * Math.cos(spherical.phi), spherical.radius * Math.sin(spherical.phi) * Math.sin(spherical.theta) ); this.camera.lookAt(0, 0, 0); this.volumeMaterial.uniforms.cameraPos.value.copy(this.camera.position); } render() { this.renderer.render(this.scene, this.camera); requestAnimationFrame(() this.render()); } }数据从链上事件映射到体素网格// volume/data-loader.ts /** * 将 DeFi 交易数据映射为体素密度 * 三维X代币对(按地址排序), Y价格区间, Z时间(区块) */ export function transactionsToVolume( transactions: Array{ tokenPair: string; price: number; blockNumber: number; volume: number; }, gridSize: number 128 ): Uint8Array { const volume new Float32Array(gridSize ** 3); const maxDensity new Float32Array(gridSize ** 3); // 构建离散化映射 const sortedPairs [...new Set(transactions.map(t t.tokenPair))].sort(); const pairMap new Map(sortedPairs.map((p, i) [p, i])); const prices transactions.map(t t.price); const priceMin Math.min(...prices); const priceMax Math.max(...prices); const blocks transactions.map(t t.blockNumber); const blockMin Math.min(...blocks); const blockMax Math.max(...blocks); // 归一化并累积 for (const tx of transactions) { const x Math.floor((pairMap.get(tx.tokenPair)! / sortedPairs.length) * (gridSize - 1)); const y Math.floor( ((tx.price - priceMin) / Math.max(priceMax - priceMin, 1)) * (gridSize - 1) ); const z Math.floor( ((tx.blockNumber - blockMin) / Math.max(blockMax - blockMin, 1)) * (gridSize - 1) ); const idx x y * gridSize z * gridSize * gridSize; volume[idx] tx.volume; maxDensity[idx] Math.max(maxDensity[idx], tx.volume); } // 归一化到 0-255 const maxVal Math.max(...volume, 1); const result new Uint8Array(gridSize ** 3); for (let i 0; i result.length; i) { result[i] Math.floor((volume[i] / maxVal) * 255); } return result; }四、Ray Marching 的性能边界采样步数与帧率的 trade-off。256 步的 Ray Marching 在中端 GPU 上可维持 60fps128^3 纹理但提升到 512 步后帧率可能降至 30fps 以下。解决方案是自适应步长对密度高的区域增加采样步数稀疏区域跳跃采样。空域跳跃Empty Space Skipping可以用一张低分辨率的占位网格预先标记空区域。体素分辨率的 GPU 内存限制。128^3 的 uint8 纹理占用 2MB VRAM256^3 占 16MB512^3 占 128MB。在移动端 GPU 上超过 256^3 可能导致内存不足。渲染质量不依赖更高分辨率——体素的视觉贡献取决于密度分布而非分辨率128-256 范围对大多数链上数据集足够。实时数据更新的瓶颈在数据传输。WebGL 的 texImage3D 调用耗时与纹理大小成正比256^3 的更新约需 10-20ms。如果链上数据以每秒多个区块的速度更新需要用 PBOPixel Buffer Object做异步上传或者降低更新频率为每 5-10 秒一次。多渲染目标的并行。体积渲染的 fragment shader 计算密集同时渲染其他 Three.js 场景对象会进一步拖低帧率。建议将数据可视化作为独立 canvas 层使用 OffscreenCanvas Worker 线程避免与 UI 层竞争 GPU 资源。传递函数的选择对可读性至关重要。简单的线性映射密度 → 颜色 → 透明度在数据分布不均匀时会产生热点区域过曝、冷区全透明的问题。建议用对数映射 手动定义颜色停靠点对密度 10% 分位数的体素设为几乎透明alpha0.05对 90% 分位数的体素设高不透明度alpha0.9中间段用曲线插值。这样低密度区域不会被完全隐藏高密度区域不会遮挡内部结构。五、总结体积渲染为链上数据可视化提供了一个有价值的补充维度。当二维图表的密度到达信息上限时第三维不是装饰——它是承载地址间关系、价格时间分布等真实数据维度的有效载体。Three.js 的 WebGL 后端 手写 Ray Marching shader 是当前兼容性最好的方案。在工程实践中核心挑战不是实现体积渲染本身逻辑相对标准的 Ray Marching 传递函数而是数据映射——将非结构化的链上事件转化为有意义的体素密度分布。这一层的设计质量直接决定了可视化是否能传递 insight而非仅仅看起来炫酷。

相关新闻

如何用AI智能图像分层工具快速提取PSD图层:layerdivider终极指南

如何用AI智能图像分层工具快速提取PSD图层:layerdivider终极指南

如何用AI智能图像分层工具快速提取PSD图层:layerdivider终极指南 【免费下载链接】layerdivider A tool to divide a single illustration into a layered structure. 项目地址: https://gitcode.com/gh_mirrors/la/layerdivider 你是否曾经面对一张精美的插…

2026/7/11 12:09:45阅读更多 →
特性速览:面对海量数字资产,Perforce P4 如何保障跨团队高效协作?

特性速览:面对海量数字资产,Perforce P4 如何保障跨团队高效协作?

Perforce P4 在游戏、半导体与制造领域的应用实践 本视频由Perforce中国授权合作伙伴——上海龙智翻译制作。 视频概述 当数字现实与物理世界并驾齐驱,创新正以光速演进。在这个追求极致协作与效能的时代,如何打破边界,让不可能成为可能&am…

2026/7/11 12:09:45阅读更多 →
NVIDIA RTX 6090技术解析:Rubin架构与DLSS6对开发者的影响

NVIDIA RTX 6090技术解析:Rubin架构与DLSS6对开发者的影响

最近在关注显卡市场的开发者们可能已经注意到,关于 NVIDIA 下一代旗舰显卡 RTX 6090 的各种爆料开始在网上流传。作为可能是 Rubin 架构的首发产品,这款被戏称为"800W核弹"的显卡引起了广泛关注,特别是对需要高性能计算、AI训练、图…

2026/7/11 12:09:45阅读更多 →
STM32L053R8与MCP3428高精度低功耗数据采集方案

STM32L053R8与MCP3428高精度低功耗数据采集方案

1. 为什么选择MCP3428与STM32L053R8组合在工业传感器网络和便携式设备中,数据采集系统的设计往往面临三个核心矛盾:精度要求与功耗限制的矛盾、接口复杂度与开发效率的矛盾、硬件成本与系统可靠性的矛盾。MCP3428STM32L053R8的组合恰好在这三个维度上达到…

2026/7/11 13:04:50阅读更多 →
生命涌现的小龙虾技能之【Elderly Medication Compliance (Pick-up / To-mouth / Swallow) | 老年人服药动作确认(取药/入口/吞咽)】简介

生命涌现的小龙虾技能之【Elderly Medication Compliance (Pick-up / To-mouth / Swallow) | 老年人服药动作确认(取药/入口/吞咽)】简介

💊 Elderly Medication Compliance (Pick-up / To-mouth / Swallow) | 老年人服药动作确认(取药/入口/吞咽) 智能分析中枢 图片/视频智能分析 结构化报告 历史报告云端查询 🧭 技能概览 | Overview 模块内容🏷️ 技…

2026/7/11 13:04:50阅读更多 →
免费下载Steam创意工坊模组:WorkshopDL 2.0.1使用完全指南

免费下载Steam创意工坊模组:WorkshopDL 2.0.1使用完全指南

免费下载Steam创意工坊模组:WorkshopDL 2.0.1使用完全指南 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 还在为GOG、Epic平台游戏无法使用Steam创意工坊模组而烦恼…

2026/7/11 13:04:50阅读更多 →
PyEcharts 柱状图性能优化:处理10万+数据点的3种渲染策略对比

PyEcharts 柱状图性能优化:处理10万+数据点的3种渲染策略对比

PyEcharts 柱状图性能优化:处理10万数据点的3种渲染策略对比当数据量突破10万级别时,传统的PyEcharts柱状图渲染会面临严重的性能瓶颈。浏览器卡顿、内存溢出、交互延迟等问题接踵而至。本文将深入探讨三种针对海量数据的优化方案,通过实测数…

2026/7/11 13:04:50阅读更多 →
【限时开源】DeepSeek-R1 API最佳实践白皮书:基于127个真实生产请求日志的性能压测报告(附Python/Go双语言SDK封装)

【限时开源】DeepSeek-R1 API最佳实践白皮书:基于127个真实生产请求日志的性能压测报告(附Python/Go双语言SDK封装)

更多请点击: https://codechina.net 第一章:DeepSeek-R1 API 调用入门与核心概念 DeepSeek-R1 是深度求索(DeepSeek)推出的高性能开源大语言模型,其官方 API 提供了标准化的 RESTful 接口,支持文本生成、多…

2026/7/11 13:04:49阅读更多 →
如何快速上手scala-notebook?5分钟搭建你的Web交互式Scala环境

如何快速上手scala-notebook?5分钟搭建你的Web交互式Scala环境

如何快速上手scala-notebook?5分钟搭建你的Web交互式Scala环境 【免费下载链接】scala-notebook Interactive Scala REPL in a browser 项目地址: https://gitcode.com/gh_mirrors/sc/scala-notebook scala-notebook是一个基于浏览器的交互式Scala REPL环境&…

2026/7/11 12:59:49阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/10 12:10:00阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/10 12:29:21阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/10 4:59:05阅读更多 →
Premiere Pro 2025安装失败原因与AGSIS验证绕过指南

Premiere Pro 2025安装失败原因与AGSIS验证绕过指南

1. 为什么2025版PR安装比以往更“磨人”?——从弹窗警告到路径陷阱的真实处境 Premiere Pro 2025版不是简单的一次版本迭代,它是一道分水岭。我从去年底开始帮影视工作室、高校剪辑实验室和自由职业者部署2025环境,累计处理了137台设备&#…

2026/7/11 0:03:43阅读更多 →
5款实用macOS系统优化工具:让你的Mac运行更流畅更高效

5款实用macOS系统优化工具:让你的Mac运行更流畅更高效

5款实用macOS系统优化工具:让你的Mac运行更流畅更高效 【免费下载链接】open-source-mac-os-apps 🚀 Awesome list of open source applications for macOS. https://t.me/s/opensourcemacosapps 项目地址: https://gitcode.com/gh_mirrors/op/open-so…

2026/7/11 0:03:43阅读更多 →
5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南 【免费下载链接】comfyui_controlnet_aux ComfyUIs ControlNet Auxiliary Preprocessors 项目地址: https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux 想要让AI图像生成真正听从你的指挥吗&…

2026/7/11 0:03:43阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/10 13:39:09阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/10 22:20:33阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/10 17:29:22阅读更多 →