体素渲染:技术谱系、算法与 Bevy 实现
体素(Voxel = Volume + Pixel)渲染的核心矛盾:如何用离散的三维像素逼近连续世界,同时保持实时性能。
两条历史技术路线
| 路线 | 起源 | 代表应用 | 核心思想 |
|---|---|---|---|
| 表面网格化(Meshing) | 1987年 Marching Cubes(Lorensen & Cline,GE医疗) | Minecraft、大多数”体素风格”游戏 | 体素输入 → 三角网格输出,利用GPU光栅化管线 |
| 直接体渲染(Direct Volume Rendering) | 1980年代医疗CT可视化 | Teardown、SVO光线追踪 | 直接在GPU上遍历体素体积,无需三角化 |
关键洞察:Minecraft 和 Teardown 虽然都自称”体素引擎”,但底层渲染架构完全不同——前者是 体素输入 → 三角网格输出,后者是 体素输入 → 光线遍历输出。
网格化算法谱系
对于基于传统光栅化的引擎(如 Bevy),体素渲染的第一步几乎都是 网格化(Meshing)。
1. Naive Meshing(原始立方体)
每个可见体素生成6个面。顶点数 , = 体素数量。
2. Face Culling(面剔除)
只生成不与相邻体素接触的面。顶点数降至约 (仅表面)。
block-mesh 的 visible_block_faces 单核 ~4000万 quad/秒。
3. Greedy Meshing(贪心合并)
将共面且同材质的相邻面合并为大矩形。体素游戏的事实标准。
效果对比(球形数据):
- Normal: ~3× quad
- Greedy: ~1× quad — 减面 67%
- 速度: 约 3× 耗时
算法核心(Mikola Lysenko / 0fps.net):从上→下、左→右扫描,每个未覆盖位置取最宽可能的 quad,然后尽可能延伸高度。
4. Marching Cubes(等值面提取)
1987年经典算法,将标量场转换为平滑网格。
- 优势:适合地形/有机形状(平滑过渡)
- 劣势:不保留方块感、产生大量小三角面片
Bevy 生态中的体素解决方案
方案 A:全功能插件 bevy_voxel_world
仓库:splashdust/bevy_voxel_world(308★)
| 特性 | 说明 |
|---|---|
| 双层架构 | Procedural(无限地形函数)+ Persistent(HashMap存储修改) |
| 多线程网格化 | Bevy Task Pool 执行 greedy meshing |
| Chunk 管理 | 根据相机距离自动生成/销毁 Chunk |
| LOD 支持 | 自定义 chunk_lod 回调 |
| 材质系统 | 纹理数组 + 自定义 Shader |
| 射线检测 | 内置 raycast 用于鼠标拣取 |
方案 B:底层网格库 block-mesh + 自定义 Chunk 系统
如果需要完全控制地形生成逻辑、网络同步或特定游戏机制:
use block_mesh::{greedy_quads, GreedyQuadsBuffer, MergeVoxel, VoxelVisibility};
// 16³ chunk + 1-voxel padding 防止边界断裂
type ChunkShape = ConstShape3u32<18, 18, 18>;
let mut buffer = GreedyQuadsBuffer::new(voxels.len());
greedy_quads(
&voxels, &ChunkShape {},
[0; 3], [17; 3], // 排除 padding
&RIGHT_HANDED_Y_UP_CONFIG.faces,
&mut buffer
);何时选择方案 B:需要自定义地形生成(复杂洞穴系统)、特殊网络同步协议、或与 Bevy Virtual Geometry(Meshlet)管线集成。
Chunk 架构与 LOD 策略
体素世界管理的核心是 Chunk(块)切分 + 视距裁剪。
经典 Chunk 架构
World → HashMap<ChunkPos, Chunk>
Chunk → 16³ 或 32³ 体素数组
→ 可选:LOD0/LOD1/LOD2 多级 Mesh
LOD 接缝问题
不同 LOD 的 Chunk 相邻时,边界处会出现裂缝(Cracks)。解决方案:
- Transition Mesh:在 LOD 边界处生成过渡三角带
- Skirts / 裙摆:较低 LOD Chunk 边缘垂直延伸一个体素高度的裙边遮挡裂缝
- 强制统一 LOD:边界区域强制使用相同 LOD(最简单,但有性能浪费)
光照与氛围技术
顶点环境光遮蔽(Vertex AO)
体素场景的”便宜立体感”秘密。每个顶点根据相邻体素占据情况计算遮蔽值:
AO 强度 = f(相邻体素占据数)
通常4级:0(全亮)→ 3(最暗)
实现方式:
- 烘焙进顶点色:mesh 生成时计算,零运行时开销
- Shader remap:
ao_value / 3.0到0-1范围
关键细节(Andre Blunt 的实现):
- 需要定义每个面的 8 个”邻居偏移”
- 考虑 winding order(顺时针/逆时针)确保面朝向正确
- 相邻面的 Corner + 两个 Side 体素共同决定顶点 AO
Voxel Cone Tracing(体素锥追踪)
CryEngine 的 SVOGI、Godot 的 SDFGI 背后的技术:
- 将场景体素化到稀疏八叉树(SVO)
- 对每个像素发射锥形光线,在 SVO 中步进采样
- 近处高精度、远处低精度 → 天然 LOD
Bevy 现状:Bevy 0.15 尚未内置 VCT 或 SDFGI。如需要全局光照,目前方案为预烘焙 Lightmap(静态场景)或自定义 Compute Shader 锥追踪管线。
前沿案例:Teardown 的”无三角”渲染器
Teardown(Tuxedo Labs)代表了体素渲染的另一极:
| 特性 | 传统引擎 | Teardown |
|---|---|---|
| 几何表示 | 三角网格 | 规则网格体素体积 |
| 破坏 | 预 fractured 或视觉特效 | 物理真实的体积移除 |
| 渲染器 | 光栅化 | 光线遍历体素体积 |
| 加速结构 | BVH / KD-tree | 多层体素加速结构 |
| 全局光照 | 常需要 GI 方案 | 无 GI(光不二次反弹) |
光线遍历实现:GPU ray → march through voxel volumes → hit test per step
配合光线追踪 AO、软阴影、镜面遮蔽。Dennis Gustafsson 说明未做 GI 的原因是引擎设计时硬件光追尚未普及,选择在有限算力内把物理破坏做到极致。
Bevy 0.15 的潜在协同:Virtual Geometry
Bevy 0.15 引入了实验性的 Meshlet(虚拟几何),与 Nanite 同源:
- 自动 LOD(基于屏幕空间误差简化)
- GPU-Driven 剔除
- 5× 渲染性能提升(相比 0.14)
体素渲染 + Meshlet 的结合点:体素经 greedy meshing 生成的 mesh,可通过 MeshletMesh 转换器自动 cluster 成 meshlet,获得:
- 海量体素场景的 GPU 剔除
- 自动连续 LOD(远距离体素地形自动简化)
- 内存优化(只加载可见 meshlet)
JMS55 的博客记录了实现细节,包括 bytemuck 零拷贝反序列化(Stanford Bunny 5MB 加载 77ms → near-instant)。
技术选型决策树
需要体素渲染?
├── 是"方块世界"风格(Minecraft-like)?
│ ├── 需要快速出原型 → bevy_voxel_world 插件
│ └── 需要完全控制 → block-mesh + 自研 Chunk 系统
├── 是"可破坏一切"(Teardown-like)?
│ └── Bevy 目前无现成方案 → 需自研 SVO 光线遍历管线
│ (或降低目标:meshing + 运行时布尔减 mesh)
├── 是体素化静态场景(装饰/建筑)?
│ └── 使用 MagicaVoxel (.vox) → bevy_vox_scene 加载
└── 是体素地形 + 高保真视觉?
└── Greedy Meshing + Vertex AO + 待 Bevy GI 方案
相关页面
- greedy-meshing — 贪心网格化算法详解
- bevy_voxel_world — Bevy 全功能体素地形插件
- block-mesh — Rust 体素网格化底层库
- voxel-rendering-pipelines — 体素渲染双路线对比:表面网格化 vs 直接体渲染
- bevy — Bevy 引擎总览
- bevy-rendering — Bevy 渲染系统核心概念
- arpg-terrain-design — ARPG 地形设计(含体素地形章节)