Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I draw multiple(10000) cubes in the bevy game engine for my 3D voxel game?

When i create a 100 x 100 chunk of cubes in bevy it is only able to maintain like 10 fps.
Even if i replace the cubes with something more simple like planes i dont get any better performance out of it.
I benchmarked it with mangohud and it says, that my cpu and gpu are only sitting at about 20% usage.

Here is the code I use to generate a 32 x 32 chunk with OpenSimplex noise

    commands: &mut Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    asset_server: Res<AssetServer>,
    seed: Res<Seed>,
) {
    let noise = OpenSimplex::new();

    commands
        .spawn(PbrBundle {
            mesh: meshes.add(Mesh::from(shape::Plane{ size: 1.0 })),
            material: materials.add(Color::rgb(0.5, 0.5, 1.0).into()),
            transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
            ..Default::default()
        })
        .with(Chunk)
        .with_children(|parent| {

        let texture_handle = asset_server.load("textures/dirt.png");
    
        for x in -32 .. 32 {
            for z in -32 .. 32 {
                let y = (noise.get([
                    ( x as f32 / 20. ) as f64, 
                    ( z as f32 / 20. ) as f64,
                    seed.value,
                ]) * 15. + 16.0) as u32;


                parent
                    .spawn(PbrBundle {
                        mesh: meshes.add(Mesh::from(shape::Cube{ size: 1.0 })),
                        material: materials.add(StandardMaterial { albedo: Color::rgba(1.0, 1.0, 1.0, 1.0), albedo_texture: Some(texture_handle.clone()), ..Default::default() }),
                        transform: Transform::from_translation(Vec3::new(x as f32, y as f32, z as f32)),
                        ..Default::default()
                    })
                .with(Cube);
            }
        }
    });
}

But 32 x 32 is the absolute maximum for a playable experience. What do I have to do, to be able to draw multiple chunks at the same time?

System specs:
cpu: Intel Core i7-6820HQ CPU @ 2.70GHz
igpu:Intel HD Graphics 530
dgpu: Nvidia Quadro M2000M

But when offloading to the more powerfull dgpu I dont get any better performance.

like image 435
Bruno Wallner Avatar asked Nov 01 '25 15:11

Bruno Wallner


2 Answers

The hidden surface removal and not naively generating a cube mesh per voxel is the real answer; however, there is a piece of low hanging fruit to optimize that might explain why the performance is so low: you only need one mesh and one material to render all those cubes.

    commands: &mut Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    asset_server: Res<AssetServer>,
    seed: Res<Seed>,
) {
    let noise = OpenSimplex::new();

    commands
        .spawn(PbrBundle {
            mesh: meshes.add(Mesh::from(shape::Plane{ size: 1.0 })),
            material: materials.add(Color::rgb(0.5, 0.5, 1.0).into()),
            transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
            ..Default::default()
        })
        .with(Chunk)
        .with_children(|parent| {

        let texture_handle = asset_server.load("textures/dirt.png");
        let mesh_handle = meshes.add(Mesh::from(shape::Cube{ size: 1.0 }));
        let material_handle = materials.add(StandardMaterial { albedo: Color::rgba(1.0, 1.0, 1.0, 1.0), albedo_texture: Some(texture_handle.clone()), ..Default::default() });
        for x in -32 .. 32 {
            for z in -32 .. 32 {
                let y = (noise.get([
                    ( x as f32 / 20. ) as f64, 
                    ( z as f32 / 20. ) as f64,
                    seed.value,
                ]) * 15. + 16.0) as u32;


                parent
                    .spawn(PbrBundle {
                        mesh: mesh_handle,
                        material: material_handle,
                        transform: Transform::from_translation(Vec3::new(x as f32, y as f32, z as f32)),
                        ..Default::default()
                    })
                .with(Cube);
            }
        }
    });
}
like image 155
Shane Celis Avatar answered Nov 03 '25 10:11

Shane Celis


Some optimizations that are immediately visible:

  • Convert your nested for-loop algorithm into a single for-loop.
    • It's more cache friendly.
    • Use math to split the now-single index into x/y/z values to determine position.
  • Hidden surface removal.
    • During mesh creation, instead of creating a whole new cube to add to the mesh (6 faces, 12 triangles, 24 verts) only add the faces (2 triangles) to the mesh that are actually visible. I.e. those that do not have a neighboring opaque (not air) block in that direction.
  • Use Indexed drawing instead of Vertex-based
  • Use a TextureAtlas.
    • Use one big texture for every every cube instead of a single texture per cube.
like image 22
Casey Avatar answered Nov 03 '25 11:11

Casey



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!