From 9c0234b0dc88fc43773c6774dc289db949597fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 6 Aug 2026 14:42:36 -0700 Subject: [PATCH 1/2] Material render settings. --- crates/processing_ffi/src/lib.rs | 55 ++++- crates/processing_pyo3/src/material.rs | 51 ++-- crates/processing_render/src/gltf.rs | 2 +- crates/processing_render/src/lib.rs | 100 ++++---- .../processing_render/src/material/custom.rs | 31 ++- crates/processing_render/src/material/mod.rs | 227 +++++++++++++++++- crates/processing_render/src/material/pbr.rs | 37 --- .../processing_render/src/render/material.rs | 2 +- crates/processing_render/src/render/mod.rs | 1 + crates/processing_wasm/src/lib.rs | 5 +- examples/particles_scatter.rs | 2 +- examples/particles_scatter_volume.rs | 2 +- 12 files changed, 367 insertions(+), 148 deletions(-) diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index 722af2f4..4b2b443c 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -2908,6 +2908,49 @@ pub extern "C" fn processing_material(window_id: u64, mat_id: u64) { error::check(|| graphics_record_command(window_entity, DrawCommand::Material(mat_entity))); } +#[unsafe(no_mangle)] +pub extern "C" fn processing_material_set_alpha_mode(mat_id: u64, mode: u8, cutoff: f32) { + error::clear_error(); + error::check(|| material_set_alpha_mode(Entity::from_bits(mat_id), mode, cutoff)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_material_set_double_sided(mat_id: u64, value: bool) { + error::clear_error(); + error::check(|| material_set_double_sided(Entity::from_bits(mat_id), value)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_material_set_unlit(mat_id: u64, value: bool) { + error::clear_error(); + error::check(|| material_set_unlit(Entity::from_bits(mat_id), value)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_material_set_depth_write(mat_id: u64, value: bool) { + error::clear_error(); + error::check(|| material_set_depth_write(Entity::from_bits(mat_id), value)); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_material_set_custom_blend_mode( + mat_id: u64, + color_src: u8, + color_dst: u8, + color_op: u8, + alpha_src: u8, + alpha_dst: u8, + alpha_op: u8, +) { + error::clear_error(); + error::check(|| { + let blend_state = custom_blend_state( + color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op, + )?; + material_set_custom_blend(Entity::from_bits(mat_id), blend_state) + }); +} + /// Create a shader from WGSL source. /// /// # Safety @@ -3583,18 +3626,6 @@ pub extern "C" fn processing_fill_buffer(graphics_id: u64, buffer_id: u64) { }); } -#[unsafe(no_mangle)] -pub extern "C" fn processing_material_set_albedo_color( - mat_id: u64, - r: f32, - g: f32, - b: f32, - a: f32, -) { - error::clear_error(); - error::check(|| material_set_albedo_color(Entity::from_bits(mat_id), [r, g, b, a])); -} - #[unsafe(no_mangle)] pub extern "C" fn processing_material_set_albedo_buffer(mat_id: u64, buffer_id: u64) { error::clear_error(); diff --git a/crates/processing_pyo3/src/material.rs b/crates/processing_pyo3/src/material.rs index 365ee778..2316ffd0 100644 --- a/crates/processing_pyo3/src/material.rs +++ b/crates/processing_pyo3/src/material.rs @@ -60,20 +60,18 @@ fn apply_albedo(entity: Entity, value: &Bound<'_, PyAny>) -> PyResult<()> { return material_set_albedo_buffer(entity, buf.entity) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))); } - if let Ok(c) = value.extract::>() { + let rgba = if let Ok(c) = value.extract::>() { let srgba: bevy::color::Srgba = c.0.into(); - return material_set_albedo_color( - entity, - [srgba.red, srgba.green, srgba.blue, srgba.alpha], - ) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))); - } - if let Ok(rgba) = value.extract::<[f32; 4]>() { - return material_set_albedo_color(entity, rgba) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))); - } - if let Ok(rgb) = value.extract::<[f32; 3]>() { - return material_set_albedo_color(entity, [rgb[0], rgb[1], rgb[2], 1.0]) + Some([srgba.red, srgba.green, srgba.blue, srgba.alpha]) + } else if let Ok(rgba) = value.extract::<[f32; 4]>() { + Some(rgba) + } else if let Ok(rgb) = value.extract::<[f32; 3]>() { + Some([rgb[0], rgb[1], rgb[2], 1.0]) + } else { + None + }; + if let Some(rgba) = rgba { + return material_set(entity, "color", shader_value::ShaderValue::Float4(rgba)) .map_err(|e| PyRuntimeError::new_err(format!("{e}"))); } Err(PyRuntimeError::new_err(format!( @@ -82,15 +80,29 @@ fn apply_albedo(entity: Entity, value: &Bound<'_, PyAny>) -> PyResult<()> { ))) } +fn py_truthy(value: &Bound<'_, PyAny>) -> PyResult { + value + .extract::() + .or_else(|_| value.extract::().map(|f| f > 0.5)) +} + fn apply_kwargs(entity: Entity, kwargs: &Bound<'_, PyDict>) -> PyResult<()> { for (key, value) in kwargs.iter() { let name: String = key.extract()?; - if name == "albedo" { - apply_albedo(entity, &value)?; - continue; + let rt = |e| PyRuntimeError::new_err(format!("{e}")); + match name.as_str() { + "albedo" => apply_albedo(entity, &value)?, + "unlit" => material_set_unlit(entity, py_truthy(&value)?).map_err(rt)?, + "double_sided" => material_set_double_sided(entity, py_truthy(&value)?).map_err(rt)?, + "depth_write" => material_set_depth_write(entity, py_truthy(&value)?).map_err(rt)?, + "alpha_mode" => { + material_set_alpha_mode(entity, value.extract::()?, 0.5).map_err(rt)? + } + _ => { + let v = py_to_shader_value(&value)?; + material_set(entity, &name, v).map_err(rt)?; + } } - let v = py_to_shader_value(&value)?; - material_set(entity, &name, v).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; } Ok(()) } @@ -127,8 +139,7 @@ impl Material { #[pyo3(signature = (**kwargs))] pub fn unlit(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult { let entity = material_create_pbr().map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; - material_set(entity, "unlit", shader_value::ShaderValue::Float(1.0)) - .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + material_set_unlit(entity, true).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; if let Some(kwargs) = kwargs { apply_kwargs(entity, kwargs)?; } diff --git a/crates/processing_render/src/gltf.rs b/crates/processing_render/src/gltf.rs index 5539383d..b85c8b25 100644 --- a/crates/processing_render/src/gltf.rs +++ b/crates/processing_render/src/gltf.rs @@ -243,7 +243,7 @@ pub fn material( .resource_mut::>() .add(ExtendedMaterial { base: standard, - extension: ProcessingMaterial { blend_state: None }, + extension: ProcessingMaterial { blend_state: None, depth_write: None }, }); let entity = world.spawn(UntypedMaterial(handle.untyped())).id(); Ok(entity) diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 6f4cc72e..20a8e20b 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -1721,65 +1721,16 @@ pub fn material_create_pbr() -> error::Result { /// `material_create_pbr` with `unlit = true` set on the base StandardMaterial. pub fn material_create_unlit() -> error::Result { let entity = material_create_pbr()?; - material_set(entity, "unlit", shader_value::ShaderValue::Float(1.0))?; + material_set_unlit(entity, true)?; Ok(entity) } /// set the albedo source to a constant srgba color. If the material is /// currently buffer-backed, swaps the asset back to plain PBR while /// preserving every other `StandardMaterial` field. -pub fn material_set_albedo_color(entity: Entity, color: [f32; 4]) -> error::Result<()> { - use crate::material::ProcessingMaterial; - use crate::particles::material::ParticlesMaterial; - use crate::render::material::UntypedMaterial; - use bevy::pbr::ExtendedMaterial; - - type DefaultMat = ExtendedMaterial; - - app_mut(|app| { - let untyped = app - .world() - .get::(entity) - .ok_or(error::ProcessingError::MaterialNotFound)? - .0 - .clone(); - let new_color = Color::srgba(color[0], color[1], color[2], color[3]); - - if let Ok(handle) = untyped.clone().try_typed::() { - let mut mats = app.world_mut().resource_mut::>(); - let mat = mats - .get_mut(&handle) - .ok_or(error::ProcessingError::MaterialNotFound)?; - mat.into_inner().base.base_color = new_color; - return Ok(()); - } - - let Ok(handle) = untyped.try_typed::() else { - return Err(error::ProcessingError::MaterialNotFound); - }; - let world = app.world_mut(); - let preserved = { - let mut mats = world.resource_mut::>(); - let mat = mats - .get(&handle) - .ok_or(error::ProcessingError::MaterialNotFound)?; - let mut base = mat.base.clone(); - base.base_color = new_color; - mats.remove(&handle); - base - }; - let new_handle = world - .resource_mut::>() - .add(ExtendedMaterial { - base: preserved, - extension: ProcessingMaterial { blend_state: None }, - }); - world - .entity_mut(entity) - .insert(UntypedMaterial(new_handle.untyped())); - Ok(()) - }) -} +// NOTE: constant albedo/emissive are plain PBR uniforms — set them via +// `material_set(entity, "color" | "emissive", Float4(..))`. Only the +// per-particle *buffer* variants are special (see material_set_*_buffer below). #[derive(Copy, Clone)] enum ParticlesBufferSlot { @@ -1893,6 +1844,49 @@ pub fn material_set( }) } +pub fn material_set_alpha_mode(entity: Entity, mode: u8, cutoff: f32) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(material::set_alpha_mode, (entity, mode, cutoff)) + .unwrap() + }) +} + +pub fn material_set_double_sided(entity: Entity, value: bool) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(material::set_double_sided, (entity, value)) + .unwrap() + }) +} + +pub fn material_set_unlit(entity: Entity, value: bool) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(material::set_unlit, (entity, value)) + .unwrap() + }) +} + +pub fn material_set_depth_write(entity: Entity, value: bool) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(material::set_depth_write, (entity, value)) + .unwrap() + }) +} + +pub fn material_set_custom_blend( + entity: Entity, + blend: bevy::render::render_resource::BlendState, +) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(material::set_custom_blend, (entity, blend)) + .unwrap() + }) +} + pub fn material_destroy(entity: Entity) -> error::Result<()> { app_mut(|app| { app.world_mut() diff --git a/crates/processing_render/src/material/custom.rs b/crates/processing_render/src/material/custom.rs index 443e9c83..7b7ae81c 100644 --- a/crates/processing_render/src/material/custom.rs +++ b/crates/processing_render/src/material/custom.rs @@ -38,7 +38,7 @@ use bevy::{ render_asset::RenderAssets, render_phase::DrawFunctions, render_resource::{ - BindGroupLayoutDescriptor, BindingResources, BlendState, UnpreparedBindGroup, + BindGroupLayoutDescriptor, BindingResources, BlendState, Face, UnpreparedBindGroup, }, renderer::RenderDevice, storage::GpuShaderBuffer, @@ -59,6 +59,8 @@ use processing_core::error::{ProcessingError, Result}; #[derive(Clone, Hash, PartialEq)] struct CustomMaterialKey { blend_state: Option, + double_sided: Option, + depth_write: Option, } fn specialize( @@ -67,12 +69,10 @@ fn specialize( _layout: &MeshVertexBufferLayoutRef, _pipeline_key: ErasedMaterialPipelineKey, ) -> std::result::Result<(), SpecializedMeshPipelineError> { - if let Some(key) = key.downcast_ref::() - && let Some(blend_state) = key.blend_state - && let Some(fragment_state) = &mut descriptor.fragment - { - for target in fragment_state.targets.iter_mut().flatten() { - target.blend = Some(blend_state); + if let Some(key) = key.downcast_ref::() { + crate::material::apply_pipeline_state(descriptor, key.blend_state, key.depth_write); + if let Some(double_sided) = key.double_sided { + descriptor.primitive.cull_mode = if double_sided { None } else { Some(Face::Back) }; } } Ok(()) @@ -85,6 +85,9 @@ pub struct CustomMaterial { pub has_vertex: bool, pub has_fragment: bool, pub blend_state: Option, + pub alpha_mode: AlphaMode, + pub double_sided: Option, + pub depth_write: Option, } #[derive(Component)] @@ -267,6 +270,9 @@ pub fn create_custom( has_vertex, has_fragment, blend_state: None, + alpha_mode: AlphaMode::Opaque, + double_sided: None, + depth_write: None, }; let handle = custom_materials.add(material); Ok(commands.spawn(UntypedMaterial(handle.untyped())).id()) @@ -455,12 +461,19 @@ impl ErasedRenderAsset for CustomMaterial { mesh_pipeline_key_bits: ErasedMeshPipelineKey::new(MeshPipelineKey::empty()), base_specialize: Some(base_specialize), material_layout: Some(bind_group_layout), - material_key: ErasedMaterialKey::new(CustomMaterialKey { blend_state }), + material_key: ErasedMaterialKey::new(CustomMaterialKey { + blend_state, + double_sided: source_asset.double_sided, + depth_write: source_asset.depth_write, + }), user_specialize: Some(specialize), + // A custom blend forces the sorted transparent phase (an arbitrary + // blend equation can't be assumed commutative); otherwise honor the + // explicitly-set alpha mode. alpha_mode: if blend_state.is_some() { AlphaMode::Blend } else { - AlphaMode::Opaque + source_asset.alpha_mode }, ..Default::default() }; diff --git a/crates/processing_render/src/material/mod.rs b/crates/processing_render/src/material/mod.rs index 6dfc067f..cd452ba6 100644 --- a/crates/processing_render/src/material/mod.rs +++ b/crates/processing_render/src/material/mod.rs @@ -11,7 +11,7 @@ use bevy::pbr::{ ExtendedMaterial, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline, }; use bevy::prelude::*; -use bevy::render::render_resource::{AsBindGroup, BlendState}; +use bevy::render::render_resource::{AsBindGroup, BlendState, Face}; use bevy::shader::ShaderRef; use bevy_naga_reflect::reflect::ParameterCategory; use processing_core::error::{self, ProcessingError}; @@ -51,7 +51,10 @@ pub fn create_pbr( cull_mode: None, ..default() }, - extension: ProcessingMaterial { blend_state: None }, + extension: ProcessingMaterial { + blend_state: None, + depth_write: None, + }, }); commands.spawn(UntypedMaterial(handle.untyped())).id() } @@ -137,6 +140,190 @@ pub fn set_property( Err(ProcessingError::MaterialNotFound) } + +type PbrMaterial = ExtendedMaterial; + +enum MaterialMut<'a> { + Pbr(&'a mut PbrMaterial), + Particles(&'a mut crate::particles::material::ParticlesMaterial), + Custom(&'a mut custom::CustomMaterial), +} + +fn edit_material( + entity: Entity, + material_handles: &Query<&UntypedMaterial>, + pbr: &mut Assets, + particles: &mut Assets, + custom: &mut Assets, + f: impl FnOnce(MaterialMut) -> error::Result<()>, +) -> error::Result<()> { + let untyped = material_handles + .get(entity) + .map_err(|_| ProcessingError::MaterialNotFound)?; + if let Ok(handle) = untyped.0.clone().try_typed::() { + let mat = pbr + .get_mut(&handle) + .ok_or(ProcessingError::MaterialNotFound)? + .into_inner(); + return f(MaterialMut::Pbr(mat)); + } + if let Ok(handle) = untyped + .0 + .clone() + .try_typed::() + { + let mat = particles + .get_mut(&handle) + .ok_or(ProcessingError::MaterialNotFound)? + .into_inner(); + return f(MaterialMut::Particles(mat)); + } + if let Ok(handle) = untyped.0.clone().try_typed::() { + let mat = custom + .get_mut(&handle) + .ok_or(ProcessingError::MaterialNotFound)? + .into_inner(); + return f(MaterialMut::Custom(mat)); + } + Err(ProcessingError::MaterialNotFound) +} + +fn resolve_alpha_mode(mode: u8, cutoff: f32) -> error::Result { + Ok(match mode { + 0 => AlphaMode::Opaque, + 1 => AlphaMode::Mask(cutoff), + 2 => AlphaMode::Blend, + 3 => AlphaMode::Premultiplied, + 4 => AlphaMode::Add, + 5 => AlphaMode::Multiply, + _ => { + return Err(ProcessingError::InvalidArgument(format!( + "unknown alpha_mode value: {mode}" + ))); + } + }) +} + +fn set_base_double_sided(base: &mut StandardMaterial, value: bool) { + base.double_sided = value; + base.cull_mode = if value { None } else { Some(Face::Back) }; +} + +pub fn set_alpha_mode( + In((entity, mode, cutoff)): In<(Entity, u8, f32)>, + material_handles: Query<&UntypedMaterial>, + mut pbr: ResMut>, + mut particles: ResMut>, + mut custom: ResMut>, +) -> error::Result<()> { + let alpha_mode = resolve_alpha_mode(mode, cutoff)?; + let opaque = matches!(alpha_mode, AlphaMode::Opaque); + edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| { + match m { + MaterialMut::Pbr(mat) => { + mat.base.alpha_mode = alpha_mode; + if opaque { + mat.extension.blend_state = None; + } + } + MaterialMut::Particles(mat) => mat.base.alpha_mode = alpha_mode, + MaterialMut::Custom(mat) => { + mat.alpha_mode = alpha_mode; + if opaque { + mat.blend_state = None; + } + } + } + Ok(()) + }) +} + +pub fn set_double_sided( + In((entity, value)): In<(Entity, bool)>, + material_handles: Query<&UntypedMaterial>, + mut pbr: ResMut>, + mut particles: ResMut>, + mut custom: ResMut>, +) -> error::Result<()> { + edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| { + match m { + MaterialMut::Pbr(mat) => set_base_double_sided(&mut mat.base, value), + MaterialMut::Particles(mat) => set_base_double_sided(&mut mat.base, value), + MaterialMut::Custom(mat) => mat.double_sided = Some(value), + } + Ok(()) + }) +} + +pub fn set_unlit( + In((entity, value)): In<(Entity, bool)>, + material_handles: Query<&UntypedMaterial>, + mut pbr: ResMut>, + mut particles: ResMut>, + mut custom: ResMut>, +) -> error::Result<()> { + edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| match m { + MaterialMut::Pbr(mat) => { + mat.base.unlit = value; + Ok(()) + } + MaterialMut::Particles(mat) => { + mat.base.unlit = value; + Ok(()) + } + MaterialMut::Custom(_) => Err(ProcessingError::InvalidArgument( + "unlit is not applicable to custom-shader materials; the shader defines lighting" + .to_string(), + )), + }) +} + +pub fn set_depth_write( + In((entity, value)): In<(Entity, bool)>, + material_handles: Query<&UntypedMaterial>, + mut pbr: ResMut>, + mut particles: ResMut>, + mut custom: ResMut>, +) -> error::Result<()> { + edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| match m { + MaterialMut::Pbr(mat) => { + mat.extension.depth_write = Some(value); + Ok(()) + } + MaterialMut::Particles(_) => Err(ProcessingError::InvalidArgument( + "depth-write is not yet configurable on particle materials".to_string(), + )), + MaterialMut::Custom(mat) => { + mat.depth_write = Some(value); + Ok(()) + } + }) +} + +pub fn set_custom_blend( + In((entity, blend)): In<(Entity, BlendState)>, + material_handles: Query<&UntypedMaterial>, + mut pbr: ResMut>, + mut particles: ResMut>, + mut custom: ResMut>, +) -> error::Result<()> { + edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| match m { + MaterialMut::Pbr(mat) => { + mat.extension.blend_state = Some(blend); + mat.base.alpha_mode = AlphaMode::Blend; + Ok(()) + } + MaterialMut::Particles(_) => Err(ProcessingError::InvalidArgument( + "custom blend is not yet configurable on particle materials".to_string(), + )), + MaterialMut::Custom(mat) => { + mat.blend_state = Some(blend); + mat.alpha_mode = AlphaMode::Blend; + Ok(()) + } + }) +} + pub fn destroy( In(entity): In, mut commands: Commands, @@ -165,18 +352,21 @@ pub fn destroy( #[bind_group_data(ProcessingMaterialKey)] pub struct ProcessingMaterial { pub blend_state: Option, + pub depth_write: Option, } #[repr(C)] #[derive(Eq, PartialEq, Hash, Copy, Clone)] pub struct ProcessingMaterialKey { blend_state: Option, + depth_write: Option, } impl From<&ProcessingMaterial> for ProcessingMaterialKey { fn from(mat: &ProcessingMaterial) -> Self { ProcessingMaterialKey { blend_state: mat.blend_state, + depth_write: mat.depth_write, } } } @@ -196,15 +386,30 @@ impl MaterialExtension for ProcessingMaterial { _layout: &MeshVertexBufferLayoutRef, key: MaterialExtensionKey, ) -> std::result::Result<(), SpecializedMeshPipelineError> { - if let Some(blend_state) = key.bind_group_data.blend_state - && let Some(fragment_state) = &mut descriptor.fragment - { - fragment_state.targets.iter_mut().for_each(|target| { - if let Some(target) = target { - target.blend = Some(blend_state); - } - }); - } + apply_pipeline_state( + descriptor, + key.bind_group_data.blend_state, + key.bind_group_data.depth_write, + ); Ok(()) } } + +pub(crate) fn apply_pipeline_state( + descriptor: &mut RenderPipelineDescriptor, + blend_state: Option, + depth_write: Option, +) { + if let Some(blend_state) = blend_state + && let Some(fragment_state) = &mut descriptor.fragment + { + for target in fragment_state.targets.iter_mut().flatten() { + target.blend = Some(blend_state); + } + } + if let Some(depth_write) = depth_write + && let Some(depth_stencil) = &mut descriptor.depth_stencil + { + depth_stencil.depth_write_enabled = Some(depth_write); + } +} diff --git a/crates/processing_render/src/material/pbr.rs b/crates/processing_render/src/material/pbr.rs index 20048bf8..f4fd86f8 100644 --- a/crates/processing_render/src/material/pbr.rs +++ b/crates/processing_render/src/material/pbr.rs @@ -50,43 +50,6 @@ pub fn set_property( }; material.emissive = LinearRgba::new(c[0], c[1], c[2], c[3]); } - "unlit" => { - let ShaderValue::Float(v) = value else { - return Err(ProcessingError::InvalidArgument(format!( - "'{name}' expects Float, got {value:?}" - ))); - }; - material.unlit = *v > 0.5; - } - "double_sided" => { - let ShaderValue::Float(v) = value else { - return Err(ProcessingError::InvalidArgument(format!( - "'{name}' expects Float, got {value:?}" - ))); - }; - material.double_sided = *v > 0.5; - } - "alpha_mode" => { - let ShaderValue::Int(v) = value else { - return Err(ProcessingError::InvalidArgument(format!( - "'{name}' expects Int, got {value:?}" - ))); - }; - material.alpha_mode = match v { - 0 => AlphaMode::Opaque, - // TODO: allow configuring the alpha cutoff value - 1 => AlphaMode::Mask(0.5), - 2 => AlphaMode::Blend, - 3 => AlphaMode::Premultiplied, - 4 => AlphaMode::Add, - 5 => AlphaMode::Multiply, - _ => { - return Err(ProcessingError::InvalidArgument(format!( - "unknown alpha_mode value: {v}" - ))); - } - }; - } "base_color_texture" | "texture" => { let Some(handle) = texture_handle else { return Err(ProcessingError::InvalidArgument(format!( diff --git a/crates/processing_render/src/render/material.rs b/crates/processing_render/src/render/material.rs index 15837391..423daeaa 100644 --- a/crates/processing_render/src/render/material.rs +++ b/crates/processing_render/src/render/material.rs @@ -169,7 +169,7 @@ impl MaterialKey { let base = self.to_standard_material(); let extended = ProcessingExtendedMaterial { base, - extension: ProcessingMaterial { blend_state }, + extension: ProcessingMaterial { blend_state, depth_write: None }, }; materials.add(extended).untyped() } diff --git a/crates/processing_render/src/render/mod.rs b/crates/processing_render/src/render/mod.rs index 75a57607..876c861e 100644 --- a/crates/processing_render/src/render/mod.rs +++ b/crates/processing_render/src/render/mod.rs @@ -1533,6 +1533,7 @@ fn add_shape3d( base, extension: ProcessingMaterial { blend_state: state.style.blend_state, + depth_write: None, }, }; res.materials.add(extended).untyped() diff --git a/crates/processing_wasm/src/lib.rs b/crates/processing_wasm/src/lib.rs index a05f7a30..fc3d9581 100644 --- a/crates/processing_wasm/src/lib.rs +++ b/crates/processing_wasm/src/lib.rs @@ -2376,9 +2376,10 @@ pub fn js_material_set_albedo_color( b: f32, a: f32, ) -> Result<(), JsValue> { - check(material_set_albedo_color( + check(material_set( Entity::from_bits(entity), - [r, g, b, a], + "color", + shader_value::ShaderValue::Float4([r, g, b, a]), )) } diff --git a/examples/particles_scatter.rs b/examples/particles_scatter.rs index dd046618..db68dffd 100644 --- a/examples/particles_scatter.rs +++ b/examples/particles_scatter.rs @@ -73,7 +73,7 @@ fn sketch() -> error::Result<()> { let aging = compute_create(age_shader)?; let mat = material_create_pbr()?; - material_set_albedo_color(mat, [0.9, 0.85, 1.0, 1.0])?; + material_set(mat, "color", shader_value::ShaderValue::Float4([0.9, 0.85, 1.0, 1.0]))?; let burst: u32 = 600; let dt: f32 = 1.0 / 60.0; diff --git a/examples/particles_scatter_volume.rs b/examples/particles_scatter_volume.rs index df8636b9..db2ec8e3 100644 --- a/examples/particles_scatter_volume.rs +++ b/examples/particles_scatter_volume.rs @@ -72,7 +72,7 @@ fn sketch() -> error::Result<()> { let aging = compute_create(age_shader)?; let mat = material_create_unlit()?; - material_set_albedo_color(mat, [1.0, 1.0, 1.0, 1.0])?; + material_set(mat, "color", shader_value::ShaderValue::Float4([1.0, 1.0, 1.0, 1.0]))?; let burst: u32 = 250; let dt: f32 = 1.0 / 60.0; From c1a4e7af3558b38426b7a2ce44cdb3cc0e59698f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 6 Aug 2026 16:41:11 -0700 Subject: [PATCH 2/2] Fmt. --- crates/processing_render/src/gltf.rs | 5 +- crates/processing_render/src/material/mod.rs | 166 +++++++++++------- .../processing_render/src/render/material.rs | 5 +- examples/particles_scatter.rs | 6 +- examples/particles_scatter_volume.rs | 6 +- 5 files changed, 118 insertions(+), 70 deletions(-) diff --git a/crates/processing_render/src/gltf.rs b/crates/processing_render/src/gltf.rs index b85c8b25..7b9397be 100644 --- a/crates/processing_render/src/gltf.rs +++ b/crates/processing_render/src/gltf.rs @@ -243,7 +243,10 @@ pub fn material( .resource_mut::>() .add(ExtendedMaterial { base: standard, - extension: ProcessingMaterial { blend_state: None, depth_write: None }, + extension: ProcessingMaterial { + blend_state: None, + depth_write: None, + }, }); let entity = world.spawn(UntypedMaterial(handle.untyped())).id(); Ok(entity) diff --git a/crates/processing_render/src/material/mod.rs b/crates/processing_render/src/material/mod.rs index cd452ba6..f449ab7d 100644 --- a/crates/processing_render/src/material/mod.rs +++ b/crates/processing_render/src/material/mod.rs @@ -140,7 +140,6 @@ pub fn set_property( Err(ProcessingError::MaterialNotFound) } - type PbrMaterial = ExtendedMaterial; enum MaterialMut<'a> { @@ -218,24 +217,31 @@ pub fn set_alpha_mode( ) -> error::Result<()> { let alpha_mode = resolve_alpha_mode(mode, cutoff)?; let opaque = matches!(alpha_mode, AlphaMode::Opaque); - edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| { - match m { - MaterialMut::Pbr(mat) => { - mat.base.alpha_mode = alpha_mode; - if opaque { - mat.extension.blend_state = None; + edit_material( + entity, + &material_handles, + &mut pbr, + &mut particles, + &mut custom, + |m| { + match m { + MaterialMut::Pbr(mat) => { + mat.base.alpha_mode = alpha_mode; + if opaque { + mat.extension.blend_state = None; + } } - } - MaterialMut::Particles(mat) => mat.base.alpha_mode = alpha_mode, - MaterialMut::Custom(mat) => { - mat.alpha_mode = alpha_mode; - if opaque { - mat.blend_state = None; + MaterialMut::Particles(mat) => mat.base.alpha_mode = alpha_mode, + MaterialMut::Custom(mat) => { + mat.alpha_mode = alpha_mode; + if opaque { + mat.blend_state = None; + } } } - } - Ok(()) - }) + Ok(()) + }, + ) } pub fn set_double_sided( @@ -245,14 +251,21 @@ pub fn set_double_sided( mut particles: ResMut>, mut custom: ResMut>, ) -> error::Result<()> { - edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| { - match m { - MaterialMut::Pbr(mat) => set_base_double_sided(&mut mat.base, value), - MaterialMut::Particles(mat) => set_base_double_sided(&mut mat.base, value), - MaterialMut::Custom(mat) => mat.double_sided = Some(value), - } - Ok(()) - }) + edit_material( + entity, + &material_handles, + &mut pbr, + &mut particles, + &mut custom, + |m| { + match m { + MaterialMut::Pbr(mat) => set_base_double_sided(&mut mat.base, value), + MaterialMut::Particles(mat) => set_base_double_sided(&mut mat.base, value), + MaterialMut::Custom(mat) => mat.double_sided = Some(value), + } + Ok(()) + }, + ) } pub fn set_unlit( @@ -262,20 +275,27 @@ pub fn set_unlit( mut particles: ResMut>, mut custom: ResMut>, ) -> error::Result<()> { - edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| match m { - MaterialMut::Pbr(mat) => { - mat.base.unlit = value; - Ok(()) - } - MaterialMut::Particles(mat) => { - mat.base.unlit = value; - Ok(()) - } - MaterialMut::Custom(_) => Err(ProcessingError::InvalidArgument( - "unlit is not applicable to custom-shader materials; the shader defines lighting" - .to_string(), - )), - }) + edit_material( + entity, + &material_handles, + &mut pbr, + &mut particles, + &mut custom, + |m| match m { + MaterialMut::Pbr(mat) => { + mat.base.unlit = value; + Ok(()) + } + MaterialMut::Particles(mat) => { + mat.base.unlit = value; + Ok(()) + } + MaterialMut::Custom(_) => Err(ProcessingError::InvalidArgument( + "unlit is not applicable to custom-shader materials; the shader defines lighting" + .to_string(), + )), + }, + ) } pub fn set_depth_write( @@ -285,19 +305,26 @@ pub fn set_depth_write( mut particles: ResMut>, mut custom: ResMut>, ) -> error::Result<()> { - edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| match m { - MaterialMut::Pbr(mat) => { - mat.extension.depth_write = Some(value); - Ok(()) - } - MaterialMut::Particles(_) => Err(ProcessingError::InvalidArgument( - "depth-write is not yet configurable on particle materials".to_string(), - )), - MaterialMut::Custom(mat) => { - mat.depth_write = Some(value); - Ok(()) - } - }) + edit_material( + entity, + &material_handles, + &mut pbr, + &mut particles, + &mut custom, + |m| match m { + MaterialMut::Pbr(mat) => { + mat.extension.depth_write = Some(value); + Ok(()) + } + MaterialMut::Particles(_) => Err(ProcessingError::InvalidArgument( + "depth-write is not yet configurable on particle materials".to_string(), + )), + MaterialMut::Custom(mat) => { + mat.depth_write = Some(value); + Ok(()) + } + }, + ) } pub fn set_custom_blend( @@ -307,21 +334,28 @@ pub fn set_custom_blend( mut particles: ResMut>, mut custom: ResMut>, ) -> error::Result<()> { - edit_material(entity, &material_handles, &mut pbr, &mut particles, &mut custom, |m| match m { - MaterialMut::Pbr(mat) => { - mat.extension.blend_state = Some(blend); - mat.base.alpha_mode = AlphaMode::Blend; - Ok(()) - } - MaterialMut::Particles(_) => Err(ProcessingError::InvalidArgument( - "custom blend is not yet configurable on particle materials".to_string(), - )), - MaterialMut::Custom(mat) => { - mat.blend_state = Some(blend); - mat.alpha_mode = AlphaMode::Blend; - Ok(()) - } - }) + edit_material( + entity, + &material_handles, + &mut pbr, + &mut particles, + &mut custom, + |m| match m { + MaterialMut::Pbr(mat) => { + mat.extension.blend_state = Some(blend); + mat.base.alpha_mode = AlphaMode::Blend; + Ok(()) + } + MaterialMut::Particles(_) => Err(ProcessingError::InvalidArgument( + "custom blend is not yet configurable on particle materials".to_string(), + )), + MaterialMut::Custom(mat) => { + mat.blend_state = Some(blend); + mat.alpha_mode = AlphaMode::Blend; + Ok(()) + } + }, + ) } pub fn destroy( diff --git a/crates/processing_render/src/render/material.rs b/crates/processing_render/src/render/material.rs index 423daeaa..d6ab6e4f 100644 --- a/crates/processing_render/src/render/material.rs +++ b/crates/processing_render/src/render/material.rs @@ -169,7 +169,10 @@ impl MaterialKey { let base = self.to_standard_material(); let extended = ProcessingExtendedMaterial { base, - extension: ProcessingMaterial { blend_state, depth_write: None }, + extension: ProcessingMaterial { + blend_state, + depth_write: None, + }, }; materials.add(extended).untyped() } diff --git a/examples/particles_scatter.rs b/examples/particles_scatter.rs index db68dffd..36dd715d 100644 --- a/examples/particles_scatter.rs +++ b/examples/particles_scatter.rs @@ -73,7 +73,11 @@ fn sketch() -> error::Result<()> { let aging = compute_create(age_shader)?; let mat = material_create_pbr()?; - material_set(mat, "color", shader_value::ShaderValue::Float4([0.9, 0.85, 1.0, 1.0]))?; + material_set( + mat, + "color", + shader_value::ShaderValue::Float4([0.9, 0.85, 1.0, 1.0]), + )?; let burst: u32 = 600; let dt: f32 = 1.0 / 60.0; diff --git a/examples/particles_scatter_volume.rs b/examples/particles_scatter_volume.rs index db2ec8e3..9b0f7b68 100644 --- a/examples/particles_scatter_volume.rs +++ b/examples/particles_scatter_volume.rs @@ -72,7 +72,11 @@ fn sketch() -> error::Result<()> { let aging = compute_create(age_shader)?; let mat = material_create_unlit()?; - material_set(mat, "color", shader_value::ShaderValue::Float4([1.0, 1.0, 1.0, 1.0]))?; + material_set( + mat, + "color", + shader_value::ShaderValue::Float4([1.0, 1.0, 1.0, 1.0]), + )?; let burst: u32 = 250; let dt: f32 = 1.0 / 60.0;