体素渲染:技术谱系、算法与 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-meshvisible_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)。解决方案:

  1. Transition Mesh:在 LOD 边界处生成过渡三角带
  2. Skirts / 裙摆:较低 LOD Chunk 边缘垂直延伸一个体素高度的裙边遮挡裂缝
  3. 强制统一 LOD:边界区域强制使用相同 LOD(最简单,但有性能浪费)

光照与氛围技术

顶点环境光遮蔽(Vertex AO)

体素场景的”便宜立体感”秘密。每个顶点根据相邻体素占据情况计算遮蔽值:

AO 强度 = f(相邻体素占据数)
通常4级:0(全亮)→ 3(最暗)

实现方式:

  • 烘焙进顶点色:mesh 生成时计算,零运行时开销
  • Shader remapao_value / 3.00-1 范围

关键细节(Andre Blunt 的实现):

  • 需要定义每个面的 8 个”邻居偏移”
  • 考虑 winding order(顺时针/逆时针)确保面朝向正确
  • 相邻面的 Corner + 两个 Side 体素共同决定顶点 AO

Voxel Cone Tracing(体素锥追踪)

CryEngine 的 SVOGI、Godot 的 SDFGI 背后的技术:

  1. 将场景体素化到稀疏八叉树(SVO)
  2. 对每个像素发射锥形光线,在 SVO 中步进采样
  3. 近处高精度、远处低精度 → 天然 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 方案

相关页面