Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
1 change: 0 additions & 1 deletion assets/shaders/instancing.wgsl
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions examples/bevy_egui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ fn voxel_plot_setup(
let mut instances: Vec<InstanceData> = 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;
Expand All @@ -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))),
Expand Down Expand Up @@ -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((
Expand Down
52 changes: 52 additions & 0 deletions examples/simple_cubes.rs
Original file line number Diff line number Diff line change
@@ -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<Assets<Mesh>>) {
// 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();
}
72 changes: 53 additions & 19 deletions src/bevy_voxel_plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ pub const SHADER_HANDLE: Handle<Shader> = weak_handle!("123e4567-e89b-12d3-a456-
impl Plugin for VoxelMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(ExtractComponentPlugin::<InstanceMaterialData>::default());
app.add_plugins(ExtractComponentPlugin::<CameraPosition>::default()); // Add this line

app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>()
.init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
Expand All @@ -86,6 +88,7 @@ impl Plugin for VoxelMaterialPlugin {
}
}


/// Single instance data containing position, scale and color.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
Expand Down Expand Up @@ -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<Camera3d>;
type Out = Self;

fn extract_component(transform: QueryItem<'_, Self::QueryData>) -> Option<Self> {
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<RenderDevice>,
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::<InstanceBuffer>();
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,
});
Expand All @@ -200,6 +222,7 @@ fn prepare_instance_buffers(
}
}


/// Custom pipeline for instanced mesh rendering.
#[derive(Resource)]
struct CustomPipeline {
Expand Down Expand Up @@ -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,
});

Expand Down