diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 1aa8ce0..35e9a23 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -8,6 +8,10 @@ All notable changes to the `bevy voxel plot` crate will be documented in this fi * ... +# 2.3.0 + +* Fixed the sorting for transparent voxels. + # 2.2.0 * Assume voxel vertices are real world coordinates in shader. diff --git a/Cargo.toml b/Cargo.toml index 12054d2..0d197c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_voxel_plot" -version = "2.2.0" +version = "2.3.0" edition = "2021" authors = ["Linus Leo Stöckli"] repository = "https://github.com/hacknus/bevy_voxel_plot" diff --git a/assets/shaders/instancing.wgsl b/assets/shaders/instancing.wgsl index 63b95f4..7fb290a 100644 --- a/assets/shaders/instancing.wgsl +++ b/assets/shaders/instancing.wgsl @@ -1,4 +1,3 @@ -#import bevy_pbr::mesh_functions::{mesh_position_local_to_world} #import bevy_pbr::view_transformations::position_world_to_clip struct Vertex { diff --git a/examples/bevy_egui.rs b/examples/bevy_egui.rs index f03d25b..c825010 100644 --- a/examples/bevy_egui.rs +++ b/examples/bevy_egui.rs @@ -86,11 +86,11 @@ fn voxel_plot_setup( let mut instances: Vec = instances.into_iter().collect(); // Sort by opacity (color alpha channel) descending - instances.sort_by(|a, b| { - b.color[3] - .partial_cmp(&a.color[3]) - .unwrap_or(std::cmp::Ordering::Equal) - }); + // instances.sort_by(|a, b| { + // b.color[3] + // .partial_cmp(&a.color[3]) + // .unwrap_or(std::cmp::Ordering::Equal) + // }); // Truncate to top 2 million most opaque points const MAX_INSTANCES: usize = 1_000_000; @@ -102,6 +102,7 @@ fn voxel_plot_setup( let threshold = instances.last().unwrap().color[3]; println!("Auto threshold for opacity was: {}", threshold); } + let first_pass_layer = RenderLayers::layer(0); commands.spawn(( Mesh3d(meshes.add(Cuboid::new(cube_width, cube_height, cube_depth))), @@ -153,7 +154,6 @@ fn voxel_plot_setup( commands.insert_resource(RenderImage(image_handle.clone())); // This specifies the layer used for the first pass, which will be attached to the first pass camera and cube. - let first_pass_layer = RenderLayers::layer(0); let pan_orbit_id = commands .spawn(( diff --git a/examples/simple_cubes.rs b/examples/simple_cubes.rs new file mode 100644 index 0000000..c6c4992 --- /dev/null +++ b/examples/simple_cubes.rs @@ -0,0 +1,52 @@ +use bevy::prelude::*; +use bevy::pbr::AmbientLight; +use bevy_voxel_plot::{InstanceData, InstanceMaterialData, VoxelMaterialPlugin}; +use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; + +fn setup(mut commands: Commands, mut meshes: ResMut>) { + // Two cubes: one red (alpha 1.0), one blue (alpha 0.5) + let instances = vec![ + InstanceData { + pos_scale: [0.0, 0.0, 0.0, 1.0], + color: LinearRgba::from(Color::srgba(1.0, 0.0, 0.0, 0.5)) + .to_f32_array() + }, + InstanceData { + pos_scale: [0.3, 0.0, 0.0, 1.0], + color: LinearRgba::from(Color::srgba(0.0, 1.0, 0.0, 0.5)) + .to_f32_array() + }, + InstanceData { + pos_scale: [0.6, 0.0, 0.0, 1.0], + color: LinearRgba::from(Color::srgba(0.0, 0.0, 1.0, 0.5)) + .to_f32_array() + }, + ]; + + let cube_mesh = meshes.add(Cuboid::new(0.2, 0.2, 0.2)); + + commands.spawn(( + Mesh3d(cube_mesh), + InstanceMaterialData { instances }, + )); + + commands.insert_resource(AmbientLight { + color: Color::WHITE, + brightness: 1.5, + affects_lightmapped_meshes: false, + }); + + // Camera + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(1.5, 1.0, 1.5).looking_at(Vec3::ZERO, Vec3::Y), + PanOrbitCamera::default(), + )); +} + +fn main() { + App::new() + .add_plugins((DefaultPlugins, VoxelMaterialPlugin, PanOrbitCameraPlugin)) + .add_systems(Startup, setup) + .run(); +} \ No newline at end of file diff --git a/src/bevy_voxel_plot.rs b/src/bevy_voxel_plot.rs index 3f80ebe..a068223 100644 --- a/src/bevy_voxel_plot.rs +++ b/src/bevy_voxel_plot.rs @@ -63,6 +63,8 @@ pub const SHADER_HANDLE: Handle = weak_handle!("123e4567-e89b-12d3-a456- impl Plugin for VoxelMaterialPlugin { fn build(&self, app: &mut App) { app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); // Add this line + app.sub_app_mut(RenderApp) .add_render_command::() .init_resource::>() @@ -86,6 +88,7 @@ impl Plugin for VoxelMaterialPlugin { } } + /// Single instance data containing position, scale and color. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] @@ -155,40 +158,59 @@ struct InstanceBuffer { length: usize, } +#[derive(Component, Clone)] +struct CameraPosition(Vec3); + +impl ExtractComponent for CameraPosition { + type QueryData = &'static GlobalTransform; + type QueryFilter = With; + type Out = Self; + + fn extract_component(transform: QueryItem<'_, Self::QueryData>) -> Option { + Some(CameraPosition(transform.translation())) + } +} + + /// Prepares instance buffers each frame, sorting instances by distance to camera. fn prepare_instance_buffers( mut commands: Commands, query: Query<(Entity, &InstanceMaterialData)>, - cameras: Query<&ExtractedView>, render_device: Res, + camera_query: Query<&CameraPosition>, ) { - let Some(camera) = cameras.iter().next() else { - return; - }; - let cam_pos = camera.world_from_view.transform_point(Vec3::ZERO); + let camera_pos = camera_query + .iter() + .next() + .map(|pos| pos.0) + .unwrap_or(Vec3::ZERO); - for (entity, instance_data) in &query { - let mut sorted_instances = instance_data.instances.clone(); + // // Debug: Print camera position + // println!("Camera position: {:?}", camera_pos); - if sorted_instances.is_empty() { + for (entity, instance_data) in &query { + if instance_data.instances.is_empty() { commands.entity(entity).remove::(); continue; } - // Sort back-to-front for proper alpha blending + let mut sorted_instances = instance_data.instances.clone(); sorted_instances.sort_by(|a, b| { - let a_pos = Vec3::new(a.pos_scale[0], a.pos_scale[1], a.pos_scale[2]); - let b_pos = Vec3::new(b.pos_scale[0], b.pos_scale[1], b.pos_scale[2]); - let a_dist = cam_pos.distance_squared(a_pos); - let b_dist = cam_pos.distance_squared(b_pos); - - b_dist - .partial_cmp(&a_dist) - .unwrap_or(std::cmp::Ordering::Equal) + let dist_a = camera_pos.distance_squared(Vec3::from_slice(&a.pos_scale[0..3])); + let dist_b = camera_pos.distance_squared(Vec3::from_slice(&b.pos_scale[0..3])); + dist_b.partial_cmp(&dist_a).unwrap_or(std::cmp::Ordering::Equal) }); + // Debug: Print instance order and distances + // println!("Sorted instances (far to near):"); + // for inst in &sorted_instances { + // let pos = Vec3::from_slice(&inst.pos_scale[0..3]); + // let dist = camera_pos.distance(pos); + // println!(" pos: {:?}, distance: {:.3}, color: {:?}", pos, dist, inst.color); + // } + let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - label: Some("sorted instance data buffer"), + label: Some("instance data buffer"), contents: bytemuck::cast_slice(sorted_instances.as_slice()), usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, }); @@ -200,6 +222,7 @@ fn prepare_instance_buffers( } } + /// Custom pipeline for instanced mesh rendering. #[derive(Resource)] struct CustomPipeline { @@ -242,7 +265,18 @@ impl SpecializedMeshPipeline for CustomPipeline { descriptor.fragment.as_mut().unwrap().targets[0] = Some(ColorTargetState { format: color_format, - blend: Some(BlendState::ALPHA_BLENDING), + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + }), write_mask: ColorWrites::ALL, });