mcpField
B
bevy-voxel-pipeline

bevy-voxel-pipeline

github.com/chrisgliddon开发2680 次安装9 周前更新
代码仓库

关于 bevy-voxel-pipeline

面向 Bevy 0.19 的体素网格构建 Skill,说明如何用 block-mesh-rs 生成并转换网格,也涵盖工作线程任务的基础模式。适合实现分块体素地形的开发者;重点说明网格策略取舍、跨区块填充及生产调度边界。

主要功能

  1. 01支持 greedy_quads 与 visible_block_faces
  2. 02将 block-mesh-rs 网格接入 Bevy Mesh
  3. 03说明跨区块邻居所需填充
  4. 04提供异步网格任务与完成轮询模式
  5. 05介绍 UV、网格属性及常见陷阱

适用场景

  1. 01为静态地形选择较少面的网格方案
  2. 02为频繁编辑的区块选择快速重建方式
  3. 03将区块网格结果交给 Bevy 0.19
  4. 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