关于 bevy-voxel-pipeline
面向 Bevy 0.19 的体素网格构建 Skill,说明如何用 block-mesh-rs 生成并转换网格,也涵盖工作线程任务的基础模式。适合实现分块体素地形的开发者;重点说明网格策略取舍、跨区块填充及生产调度边界。
主要功能
- 01支持 greedy_quads 与 visible_block_faces
- 02将 block-mesh-rs 网格接入 Bevy Mesh
- 03说明跨区块邻居所需填充
- 04提供异步网格任务与完成轮询模式
- 05介绍 UV、网格属性及常见陷阱
适用场景
- 01为静态地形选择较少面的网格方案
- 02为频繁编辑的区块选择快速重建方式
- 03将区块网格结果交给 Bevy 0.19
- 04规划区块任务与生产调度的边界
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ chrisgliddon/ bevy-skills --skill bevy-voxel-pipeline安装后在 Claude Code 中输入/bevy-voxel-pipeline确认已加载。
详细介绍
Bevy 0.19 — 体素网格生成管线
何时使用此技能
- 构建采用分块网格生成的 Minecraft 风格体素世界。
- 在
greedy_quads(四边形更少、更大,最适合静态地形)和visible_block_faces(每个面一个四边形,最适合快速重新网格化编辑)之间做出选择。 - 将
block-mesh-rs的输出接入 Bevy 0.19 的Mesh。 - 为
bevy-voxel-runtime实现纯工作线程侧的网格构建,以便进行调度。
标准模式
use bevy::asset::RenderAssetUsages;
use bevy::mesh::{Indices, PrimitiveTopology};
use bevy::prelude::*;
use block_mesh::ndshape::{ConstShape, ConstShape3u32};
use block_mesh::{
greedy_quads, GreedyQuadsBuffer, MergeVoxel, Voxel, VoxelVisibility,
RIGHT_HANDED_Y_UP_CONFIG,
};
// 18^3 — the standard "chunk plus padding" block-mesh expects.
// Each axis gets 1 cell of padding on each side so neighbour lookups are
// in-bounds. The user-visible chunk is 16^3.
type ChunkShape = ConstShape3u32<18, 18, 18>;
#[derive(Clone, Copy, Eq, PartialEq, Default)]
pub struct BlockId(pub u16);
impl Voxel for BlockId {
fn get_visibility(&self) -> VoxelVisibility {
if self.0 == 0 {
VoxelVisibility::Empty
} else {
VoxelVisibility::Opaque
}
}
}
impl MergeVoxel for BlockId {
type MergeValue = u16;
fn merge_value(&self) -> Self::MergeValue { self.0 }
}
/// Build a Bevy 0.19 Mesh from a padded chunk of blocks.
/// Returns `None` for an entirely empty chunk so callers can skip spawning.
pub fn mesh_chunk(blocks: &[BlockId]) -> Option<Mesh> {
assert_eq!(blocks.len(), ChunkShape::SIZE as usize);
let mut buffer = GreedyQuadsBuffer::new(blocks.len());
greedy_quads(
blocks,
&ChunkShape {},
[0; 3],
[17, 17, 17],
&RIGHT_HANDED_Y_UP_CONFIG.faces,
&mut buffer,
);
if buffer.quads.num_quads() == 0 {
return None;
}
let num_indices = buffer.quads.num_quads() * 6;
let num_vertices = buffer.quads.num_quads() * 4;
let mut indices = Vec::with_capacity(num_indices);
let mut positions = Vec::with_capacity(num_vertices);
let mut normals = Vec::with_capacity(num_vertices);
for (group, face) in buffer.quads.groups.iter().zip(RIGHT_HANDED_Y_UP_CONFIG.faces.iter()) {
for quad in group.iter() {
indices.extend_from_slice(&face.quad_mesh_indices(positions.len() as u32));
positions.extend_from_slice(&face.quad_mesh_positions(quad, 1.0));
normals.extend_from_slice(&face.quad_mesh_normals());
}
}
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
mesh.try_insert_attribute(Mesh::ATTRIBUTE_POSITION, positions).ok()?;
mesh.try_insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals).ok()?;
mesh.insert_indices(Indices::U32(indices));
Some(mesh)
}
仅限工作线程的原语——不是生产级编辑调度器
以下示例展示了任务所有权和非阻塞完成轮询,但会有意不应用结果。生产级、编辑频繁的游戏必须先验证区块段版本/生命周期令牌,合并重复工作,并遵守工作线程、结果、上传和碰撞体预算上限。将其接入世界之前,请先阅读 bevy-voxel-runtime。
use bevy::prelude::*;
use bevy::tasks::{futures::check_ready, AsyncComputeTaskPool, Task};
#[derive(Clone, Copy)]
struct MeshToken {
section: IVec3,
revision: u64,
epoch: u64,
}
struct MeshBuild {
token: MeshToken,
mesh: Option<Mesh>,
}
#[derive(Component)]
struct MeshTask(Task<MeshBuild>);
#[derive(Resource, Default)]
struct CompletedMeshes(Vec<MeshBuild>);
# fn _kick(mut commands: Commands) {
let pool = AsyncComputeTaskPool::get();
let blocks = vec![crate::BlockId::default(); 18 * 18 * 18]; // replace from chunk store
let token = MeshToken { section: IVec3::ZERO, revision: 7, epoch: 2 };
let task = pool.spawn(async move {
MeshBuild { token, mesh: crate::mesh_chunk(&blocks) }
});
commands.spawn(MeshTask(task));
# }
# fn _poll(mut commands: Commands, mut completed: ResMut<CompletedMeshes>, mut q: Query<(Entity, &mut MeshTask)>) {
for (entity, mut task) in &mut q {
if let Some(result) = check_ready(&mut task.0) {
commands.entity(entity).remove::<MeshTask>();
completed.0.push(result); // validate token in the bounded apply stage
}
}
# }
注意事项
- 填充不可省略。
block-mesh需要每个轴各填充一个单元,以便正确计算跨区块的邻居可见性。“16-cube”区块实际存储为 18×18×18;对区块的编辑也会影响其六个邻居的填充区域。 - 贪心网格与简单网格。 此 crate 附带的球形输入基准测试显示,
greedy_quads生成的四边形数量约为三分之一,但网格生成时间约为三倍。请针对你的方块分布重新进行基准测试;简单网格适合编辑频繁的区块,而贪心网格通常适合稳定地形。 - UV 并非开箱即用。
block-mesh不会生成 UV,需要你根据四边形朝向和方块 ID 自行计算。标准做法是使用纹理图集和每个四边形的偏移(参见bevy-voxel-data)。 block-mesh-rs使用独立于 Bevy 的 crate 0.2.0(上次更新于 2022 年)。它依赖ilattice和ndshape,两者均为纯 Rust 库。由于完全不依赖 Bevy,因此与 Bevy 0.19 兼容。- 不要在单个区块网格中使用
par_iter——block-mesh本身已经很快。并行处理的收益来自跨区块,而不是区块内部。使用AsyncComputeTaskPool生成 N 个区块网格任务。 - 任务池不是调度器。 绝不要为每次编辑都生成一个任务,也不要仅根据坐标应用结果。请使用
bevy-voxel-runtime处理脏区块的任务扩散、版本管理、过期结果拒绝、任务合并、优先级、背压以及网格/碰撞体原子切换。 - 在 Bevy 0.19 中,
try_insert_attribute返回Result<(), MeshAccessError>。新构建的网格不可能出现ExtractedToRenderWorld,但仍应处理或传递该结果,以确保后续网格所有权变更时依然安全。 RenderAssetUsages位于bevy::asset(从bevy::render::render_asset重新导出,但为私有)。公开的重新导出路径是bevy::asset::RenderAssetUsages。
另请参阅
bevy-voxel-data— 稳定的方块 ID、稠密调色板和图集数据。bevy-voxel-runtime— 生产级调度、版本验证、受限上传、碰撞体切换和遥测。bevy-assets— 加载区块数据并管理网格句柄。
评论
支持 Markdown