From 140f0c4c5019c58b2ebc826f7354c97e0822fd99 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 8 Mar 2023 19:42:31 -0300 Subject: [PATCH 001/201] Expose both main `Texture`s and `TextureView`s in `ViewTarget`. --- crates/bevy_core_pipeline/src/bloom/mod.rs | 4 +- .../bevy_core_pipeline/src/upscaling/node.rs | 2 +- crates/bevy_render/src/view/mod.rs | 139 +++++++++++------- 3 files changed, 91 insertions(+), 54 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 4653d107653de..f1c90ac05b7d6 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -216,7 +216,9 @@ impl Node for BloomNode { BindGroupEntry { binding: 0, // Read from main texture directly - resource: BindingResource::TextureView(view_target.main_texture()), + resource: BindingResource::TextureView( + view_target.main_texture_view(), + ), }, BindGroupEntry { binding: 1, diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index 8e66f1eb07f06..718901bca788e 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -74,7 +74,7 @@ impl Node for UpscalingNode { LoadOp::Clear(Default::default()) }; - let upscaled_texture = target.main_texture(); + let upscaled_texture = target.main_texture_view(); let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); let bind_group = match &mut *cached_bind_group { diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 2a15363fdaf31..7a709cb3a8cd6 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -198,13 +198,13 @@ pub struct PostProcessWrite<'a> { impl ViewTarget { pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float; - /// Retrieve this target's color attachment. This will use [`Self::sampled_main_texture`] and resolve to [`Self::main_texture`] if + /// Retrieve this target's color attachment. This will use [`Self::sampled_main_texture_view`] and resolve to [`Self::main_texture`] if /// the target has sampling enabled. Otherwise it will use [`Self::main_texture`] directly. pub fn get_color_attachment(&self, ops: Operations) -> RenderPassColorAttachment { - match &self.main_textures.sampled { - Some(sampled_texture) => RenderPassColorAttachment { - view: sampled_texture, - resolve_target: Some(self.main_texture()), + match &self.main_textures.sampled_view { + Some(sampled_texture_view) => RenderPassColorAttachment { + view: sampled_texture_view, + resolve_target: Some(self.main_texture_view()), ops, }, None => self.get_unsampled_color_attachment(ops), @@ -217,14 +217,14 @@ impl ViewTarget { ops: Operations, ) -> RenderPassColorAttachment { RenderPassColorAttachment { - view: self.main_texture(), + view: self.main_texture_view(), resolve_target: None, ops, } } /// The "main" unsampled texture. - pub fn main_texture(&self) -> &TextureView { + pub fn main_texture(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { &self.main_textures.a } else { @@ -238,7 +238,7 @@ impl ViewTarget { /// /// A use case for this is to be able to prepare a bind group for all main textures /// ahead of time. - pub fn main_texture_other(&self) -> &TextureView { + pub fn main_texture_other(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { &self.main_textures.b } else { @@ -246,11 +246,39 @@ impl ViewTarget { } } + /// The "main" unsampled texture. + pub fn main_texture_view(&self) -> &TextureView { + if self.main_texture.load(Ordering::SeqCst) == 0 { + &self.main_textures.a_view + } else { + &self.main_textures.b_view + } + } + + /// The _other_ "main" unsampled texture view. + /// In most cases you should use [`Self::main_texture_view`] instead and never this. + /// The textures will naturally be swapped when [`Self::post_process_write`] is called. + /// + /// A use case for this is to be able to prepare a bind group for all main textures + /// ahead of time. + pub fn main_texture_other_view(&self) -> &TextureView { + if self.main_texture.load(Ordering::SeqCst) == 0 { + &self.main_textures.b_view + } else { + &self.main_textures.a_view + } + } + /// The "main" sampled texture. - pub fn sampled_main_texture(&self) -> Option<&TextureView> { + pub fn sampled_main_texture(&self) -> Option<&Texture> { self.main_textures.sampled.as_ref() } + /// The "main" sampled texture view. + pub fn sampled_main_texture_view(&self) -> Option<&TextureView> { + self.main_textures.sampled_view.as_ref() + } + #[inline] pub fn main_texture_format(&self) -> TextureFormat { self.main_texture_format @@ -286,13 +314,13 @@ impl ViewTarget { // if the old main texture is a, then the post processing must write from a to b if old_is_a_main_texture == 0 { PostProcessWrite { - source: &self.main_textures.a, - destination: &self.main_textures.b, + source: &self.main_textures.a_view, + destination: &self.main_textures.b_view, } } else { PostProcessWrite { - source: &self.main_textures.b, - destination: &self.main_textures.a, + source: &self.main_textures.b_view, + destination: &self.main_textures.a_view, } } } @@ -343,9 +371,12 @@ fn prepare_view_uniforms( #[derive(Clone)] struct MainTargetTextures { - a: TextureView, - b: TextureView, - sampled: Option, + a: Texture, + a_view: TextureView, + b: Texture, + b_view: TextureView, + sampled: Option, + sampled_view: Option, /// 0 represents `main_textures.a`, 1 represents `main_textures.b` /// This is shared across view targets with the same render target main_texture: Arc, @@ -391,46 +422,50 @@ fn prepare_view_targets( dimension: TextureDimension::D2, format: main_texture_format, usage: TextureUsages::RENDER_ATTACHMENT - | TextureUsages::TEXTURE_BINDING, + | TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_SRC, // TODO: Consider changing this if main_texture_format is not sRGB view_formats: &[], }; + let a = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("main_texture_a"), + ..descriptor + }, + ); + let b = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("main_texture_b"), + ..descriptor + }, + ); + let (sampled_texture, sampled_view) = if msaa.samples() > 1 { + let sampled = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("main_texture_sampled"), + size, + mip_level_count: 1, + sample_count: msaa.samples(), + dimension: TextureDimension::D2, + format: main_texture_format, + usage: TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }, + ); + (Some(sampled.texture), Some(sampled.default_view)) + } else { + (None, None) + }; MainTargetTextures { - a: texture_cache - .get( - &render_device, - TextureDescriptor { - label: Some("main_texture_a"), - ..descriptor - }, - ) - .default_view, - b: texture_cache - .get( - &render_device, - TextureDescriptor { - label: Some("main_texture_b"), - ..descriptor - }, - ) - .default_view, - sampled: (msaa.samples() > 1).then(|| { - texture_cache - .get( - &render_device, - TextureDescriptor { - label: Some("main_texture_sampled"), - size, - mip_level_count: 1, - sample_count: msaa.samples(), - dimension: TextureDimension::D2, - format: main_texture_format, - usage: TextureUsages::RENDER_ATTACHMENT, - view_formats: &[], - }, - ) - .default_view - }), + a: a.texture, + a_view: a.default_view, + b: b.texture, + b_view: b.default_view, + sampled: sampled_texture, + sampled_view, main_texture: Arc::new(AtomicUsize::new(0)), } }); From d068a575e2f088ec42673e4a0a5752f246899c2a Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 8 Mar 2023 20:00:07 -0300 Subject: [PATCH 002/201] Introduce `ViewTransmissionTexture` --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 71cfaf6d402f3..7fb03c87a2e49 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -74,6 +74,11 @@ impl Plugin for Core3dPlugin { .in_set(RenderSet::Prepare) .after(bevy_render::view::prepare_windows), ) + .add_system( + prepare_core_3d_transmission_textures + .in_set(RenderSet::Prepare) + .after(bevy_render::view::prepare_windows), + ) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); @@ -326,3 +331,75 @@ pub fn prepare_core_3d_depth_textures( }); } } + +#[derive(Component)] +pub struct ViewTransmissionTexture { + pub texture: Texture, + pub view: TextureView, + pub sampler: Sampler, +} + +pub fn prepare_core_3d_transmission_textures( + mut commands: Commands, + mut texture_cache: ResMut, + render_device: Res, + views_3d: Query< + (Entity, &ExtractedCamera, &ExtractedView), + ( + With>, + With>, + With>, + ), + >, +) { + let mut textures = HashMap::default(); + for (entity, camera, view) in &views_3d { + let Some(physical_target_size) = camera.physical_target_size else { + continue; + }; + + let cached_texture = textures + .entry(camera.target.clone()) + .or_insert_with(|| { + let usage = TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST; + + // The size of the depth texture + let size = Extent3d { + depth_or_array_layers: 1, + width: physical_target_size.x, + height: physical_target_size.y, + }; + + let format = if view.hdr { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }; + + let descriptor = TextureDescriptor { + label: Some("view_transmission_texture"), + size, + mip_level_count: 1, + sample_count: 1, // msaa.samples(), + dimension: TextureDimension::D2, + format, + usage, + view_formats: &[], + }; + + texture_cache.get(&render_device, descriptor) + }) + .clone(); + + let sampler = render_device.create_sampler(&SamplerDescriptor { + label: Some("view_transmission_sampler"), + ..Default::default() + }); + + commands.entity(entity).insert(ViewTransmissionTexture { + texture: cached_texture.texture, + view: cached_texture.default_view, + sampler, + }); + } +} From 52233b36c2038e545551d504d60eb3d45fd2214b Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 8 Mar 2023 20:01:39 -0300 Subject: [PATCH 003/201] Expose `ViewTransmissionTexture` in mesh view uniforms --- crates/bevy_pbr/src/render/mesh.rs | 33 +++++++++++++++++++ .../src/render/mesh_view_bindings.wgsl | 5 +++ 2 files changed, 38 insertions(+) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 9b1239099bfd8..4472324421579 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -7,6 +7,7 @@ use crate::{ use bevy_app::{IntoSystemAppConfigs, Plugin}; use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; use bevy_core_pipeline::{ + core_3d::ViewTransmissionTexture, prepass::ViewPrepassTextures, tonemapping::{ get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, @@ -434,6 +435,25 @@ impl FromWorld for MeshPipeline { )); } + entries.extend_from_slice(&[ + BindGroupLayoutEntry { + binding: 18, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + multisampled: false, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 19, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + ]); + entries } @@ -947,6 +967,7 @@ pub fn queue_mesh_view_bind_groups( Option<&ViewPrepassTextures>, Option<&EnvironmentMapLight>, &Tonemapping, + &ViewTransmissionTexture, )>, images: Res>, mut fallback_images: FallbackImagesMsaa, @@ -976,6 +997,7 @@ pub fn queue_mesh_view_bind_groups( prepass_textures, environment_map, tonemapping, + transmission, ) in &views { let layout = if msaa.samples() > 1 { @@ -1058,6 +1080,17 @@ pub fn queue_mesh_view_bind_groups( )); } + entries.extend_from_slice(&[ + BindGroupEntry { + binding: 18, + resource: BindingResource::TextureView(&transmission.view), + }, + BindGroupEntry { + binding: 19, + resource: BindingResource::Sampler(&transmission.sampler), + }, + ]); + let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &entries, label: Some("mesh_view_bind_group"), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index e0890ea3ea3a6..a30439bc82b89 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -69,3 +69,8 @@ var depth_prepass_texture: texture_depth_2d; @group(0) @binding(17) var normal_prepass_texture: texture_2d; #endif + +@group(0) @binding(18) +var view_main_texture: texture_2d; +@group(0) @binding(19) +var view_main_sampler: sampler; From 675913dd7376221d8a21a012a6f3b84a44008075 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 8 Mar 2023 20:04:06 -0300 Subject: [PATCH 004/201] Copy main texture to transmission texture before transparent phase --- .../src/core_3d/main_pass_3d_node.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs index 5003fbfd538f1..7cf9f3b4657e8 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs @@ -8,14 +8,16 @@ use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::RenderPhase, - render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor}, + render_resource::{ + Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor, + }, renderer::RenderContext, view::{ExtractedView, ViewDepthTexture, ViewTarget}, }; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -use super::Camera3dDepthLoadOp; +use super::{Camera3dDepthLoadOp, ViewTransmissionTexture}; pub struct MainPass3dNode { query: QueryState< @@ -26,6 +28,7 @@ pub struct MainPass3dNode { &'static RenderPhase, &'static Camera3d, &'static ViewTarget, + &'static ViewTransmissionTexture, &'static ViewDepthTexture, Option<&'static DepthPrepass>, Option<&'static NormalPrepass>, @@ -67,6 +70,7 @@ impl Node for MainPass3dNode { transparent_phase, camera_3d, target, + transmission, depth, depth_prepass, normal_prepass, @@ -153,6 +157,17 @@ impl Node for MainPass3dNode { alpha_mask_phase.render(&mut render_pass, world, view_entity); } + let physical_target_size = camera.physical_target_size.unwrap(); + render_context.command_encoder().copy_texture_to_texture( + target.main_texture().as_image_copy(), + transmission.texture.as_image_copy(), + Extent3d { + width: physical_target_size.x, + height: physical_target_size.y, + depth_or_array_layers: 1, + }, + ); + if !transparent_phase.items.is_empty() { // Run the transparent pass, sorted back-to-front // NOTE: Scoped to drop the mutable borrow of render_context From af53dfeb987e7487ff216ff8f8286455a2b96d9a Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 8 Mar 2023 20:04:43 -0300 Subject: [PATCH 005/201] Add missing `use` items --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 7fb03c87a2e49..268fdee13f546 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -35,12 +35,12 @@ use bevy_render::{ RenderPhase, }, render_resource::{ - CachedRenderPipelineId, Extent3d, TextureDescriptor, TextureDimension, TextureFormat, - TextureUsages, + CachedRenderPipelineId, Extent3d, Sampler, SamplerDescriptor, Texture, TextureDescriptor, + TextureDimension, TextureFormat, TextureUsages, TextureView, }, renderer::RenderDevice, - texture::TextureCache, - view::ViewDepthTexture, + texture::{BevyDefault, TextureCache}, + view::{ExtractedView, ViewDepthTexture, ViewTarget}, Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_utils::{FloatOrd, HashMap}; From 3e0ef1a57f6567ad5ce3b8675e6bfb7f7f4306fe Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 8 Mar 2023 20:05:27 -0300 Subject: [PATCH 006/201] Add rudimentary light transmission implementation in PBR shader --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 45f42268c0e64..5e5b2cb811cf5 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -188,6 +188,7 @@ fn pbr( let diffuse_color = output_color.rgb * (1.0 - metallic); let R = reflect(-in.V, in.N); + let R2 = refract(-in.V, in.N, 0.6); let f_ab = F_AB(perceptual_roughness, NdotV); @@ -252,9 +253,16 @@ fn pbr( let emissive_light = emissive.rgb * output_color.a; + var transmitted_light: vec3 = vec3(0.0); + let alpha_mode = in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND { + transmitted_light = transmissive_light(in.frag_coord.xy, R2).rgb * in.material.base_color.rgb; + direct_light = vec3(0.0); + } + // Total light output_color = vec4( - direct_light + indirect_light + emissive_light, + transmitted_light + direct_light + indirect_light + emissive_light, output_color.a ); @@ -270,6 +278,14 @@ fn pbr( } #endif // NORMAL_PREPASS +fn transmissive_light(frag_coord: vec2, N: vec3) -> vec3 { + return textureSample( + view_main_texture, + view_main_sampler, + frag_coord.xy / view.viewport.zw + N.xy * 0.05, + ).rgb; +} + #ifdef DEBAND_DITHER fn dither(color: vec4, pos: vec2) -> vec4 { return vec4(color.rgb + screen_space_dither(pos.xy), color.a); From b1d466730f58c5d2d055ae3080efda3164ac70e7 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 00:05:30 -0300 Subject: [PATCH 007/201] Clean up light transmission shader implementation with more proper math/parameters --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 5e5b2cb811cf5..25a380ca99578 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -171,6 +171,15 @@ fn pbr( let metallic = in.material.metallic; let perceptual_roughness = in.material.perceptual_roughness; let roughness = perceptualRoughnessToRoughness(perceptual_roughness); + let ior = 1.5; + let thickness = 1.0; + var transmission = 0.0; + let alpha_mode = in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND) { + transmission = 1.0; + } + + let transmissive_color = transmission * in.material.base_color.rgb; let occlusion = in.occlusion; @@ -184,11 +193,10 @@ fn pbr( let reflectance = in.material.reflectance; let F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic; - // Diffuse strength inversely related to metallicity - let diffuse_color = output_color.rgb * (1.0 - metallic); + // Diffuse strength inversely related to metallicity and transmission + let diffuse_color = output_color.rgb * (1.0 - metallic) * (1.0 - transmission); let R = reflect(-in.V, in.N); - let R2 = refract(-in.V, in.N, 0.6); let f_ab = F_AB(perceptual_roughness, NdotV); @@ -253,11 +261,12 @@ fn pbr( let emissive_light = emissive.rgb * output_color.a; + // Transmitted Light var transmitted_light: vec3 = vec3(0.0); - let alpha_mode = in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; - if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND { - transmitted_light = transmissive_light(in.frag_coord.xy, R2).rgb * in.material.base_color.rgb; - direct_light = vec3(0.0); + if transmission > 0.0 { + transmitted_light = transmissive_light(in.frag_coord.xyz, in.N, in.V, ior, thickness, transmissive_color).rgb; + } else { + transmitted_light = vec3(0.0); } // Total light @@ -278,12 +287,39 @@ fn pbr( } #endif // NORMAL_PREPASS -fn transmissive_light(frag_coord: vec2, N: vec3) -> vec3 { - return textureSample( +fn transmissive_light(frag_coord: vec3, N: vec3, V: vec3, ior: f32, thickness: f32, transmissive_color: vec3) -> vec3 { + // Calculate view aspect ratio, used later to scale offset so that it's proportionate + let aspect = view.viewport.z / view.viewport.w; + + // Calculate the ratio between refaction indexes. Assume air/vacuum for the space outside the mesh + let eta = 1.0 / ior; + + // Calculate incidence vector (opposite to view vector) and its dot product with the mesh normal + let I = -V; + let NdotI = dot(N, I); + + // Calculate refracted direction using Snell's law + let k = 1.0 - eta * eta * (1.0 - NdotI * NdotI); + let T = eta * I - (eta * NdotI + sqrt(k)) * N; + + // Transform refracted direction into view space + let view_T = (view.inverse_view * vec4(T.xyz, 0.0)).xyz; + + // Calculate an offset position, by multiplying the refracted direction by the thickness and adding it to + // the fragment position scaled by viewport size. Use the aspect ratio calculated earlier stay proportionate + let offset_position = frag_coord.xy / view.viewport.zw + view_T.xy * thickness * vec2(1.0, -aspect); + + // Sample the view main texture at the offset position, to get the background color + // TODO: use depth prepass data to reject values that are in front of the current fragment + let background_sample = textureSample( view_main_texture, view_main_sampler, - frag_coord.xy / view.viewport.zw + N.xy * 0.05, + offset_position, ).rgb; + + // Calculate final color by applying transmissive color to background sample + // TODO: Add support for attenuationColor and attenuationDistance + return transmissive_color * background_sample; } #ifdef DEBAND_DITHER From 2999947f507e57c597dfb33099ae44d22a9fb56b Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 00:08:28 -0300 Subject: [PATCH 008/201] Move `transmissive_light()` function to `pbr_lighting.wgsl` --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 35 ------------------- crates/bevy_pbr/src/render/pbr_lighting.wgsl | 35 +++++++++++++++++++ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 25a380ca99578..e90e4aa0499c7 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -287,41 +287,6 @@ fn pbr( } #endif // NORMAL_PREPASS -fn transmissive_light(frag_coord: vec3, N: vec3, V: vec3, ior: f32, thickness: f32, transmissive_color: vec3) -> vec3 { - // Calculate view aspect ratio, used later to scale offset so that it's proportionate - let aspect = view.viewport.z / view.viewport.w; - - // Calculate the ratio between refaction indexes. Assume air/vacuum for the space outside the mesh - let eta = 1.0 / ior; - - // Calculate incidence vector (opposite to view vector) and its dot product with the mesh normal - let I = -V; - let NdotI = dot(N, I); - - // Calculate refracted direction using Snell's law - let k = 1.0 - eta * eta * (1.0 - NdotI * NdotI); - let T = eta * I - (eta * NdotI + sqrt(k)) * N; - - // Transform refracted direction into view space - let view_T = (view.inverse_view * vec4(T.xyz, 0.0)).xyz; - - // Calculate an offset position, by multiplying the refracted direction by the thickness and adding it to - // the fragment position scaled by viewport size. Use the aspect ratio calculated earlier stay proportionate - let offset_position = frag_coord.xy / view.viewport.zw + view_T.xy * thickness * vec2(1.0, -aspect); - - // Sample the view main texture at the offset position, to get the background color - // TODO: use depth prepass data to reject values that are in front of the current fragment - let background_sample = textureSample( - view_main_texture, - view_main_sampler, - offset_position, - ).rgb; - - // Calculate final color by applying transmissive color to background sample - // TODO: Add support for attenuationColor and attenuationDistance - return transmissive_color * background_sample; -} - #ifdef DEBAND_DITHER fn dither(color: vec4, pos: vec2) -> vec4 { return vec4(color.rgb + screen_space_dither(pos.xy), color.a); diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index d57af6e9bf102..e76633a485f69 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -280,3 +280,38 @@ fn directional_light(light_id: u32, roughness: f32, NdotV: f32, normal: vec3, N: vec3, V: vec3, ior: f32, thickness: f32, transmissive_color: vec3) -> vec3 { + // Calculate view aspect ratio, used later to scale offset so that it's proportionate + let aspect = view.viewport.z / view.viewport.w; + + // Calculate the ratio between refaction indexes. Assume air/vacuum for the space outside the mesh + let eta = 1.0 / ior; + + // Calculate incidence vector (opposite to view vector) and its dot product with the mesh normal + let I = -V; + let NdotI = dot(N, I); + + // Calculate refracted direction using Snell's law + let k = 1.0 - eta * eta * (1.0 - NdotI * NdotI); + let T = eta * I - (eta * NdotI + sqrt(k)) * N; + + // Transform refracted direction into view space + let view_T = (view.inverse_view * vec4(T.xyz, 0.0)).xyz; + + // Calculate an offset position, by multiplying the refracted direction by the thickness and adding it to + // the fragment position scaled by viewport size. Use the aspect ratio calculated earlier stay proportionate + let offset_position = frag_coord.xy / view.viewport.zw + view_T.xy * thickness * vec2(1.0, -aspect); + + // Sample the view main texture at the offset position, to get the background color + // TODO: use depth prepass data to reject values that are in front of the current fragment + let background_sample = textureSample( + view_main_texture, + view_main_sampler, + offset_position, + ).rgb; + + // Calculate final color by applying transmissive color to background sample + // TODO: Add support for attenuationColor and attenuationDistance + return transmissive_color * background_sample; +} From d0265bc54d15223e476d49a5c29450e98c5b61c0 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 00:09:20 -0300 Subject: [PATCH 009/201] Enable filtering for transmission texture --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 268fdee13f546..1021847d73ac4 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -35,8 +35,8 @@ use bevy_render::{ RenderPhase, }, render_resource::{ - CachedRenderPipelineId, Extent3d, Sampler, SamplerDescriptor, Texture, TextureDescriptor, - TextureDimension, TextureFormat, TextureUsages, TextureView, + CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor, Texture, + TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, }, renderer::RenderDevice, texture::{BevyDefault, TextureCache}, @@ -393,6 +393,7 @@ pub fn prepare_core_3d_transmission_textures( let sampler = render_device.create_sampler(&SamplerDescriptor { label: Some("view_transmission_sampler"), + mag_filter: FilterMode::Linear, ..Default::default() }); From 714f40a4ae374336d9520ed389c6d32808b77e04 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 00:11:24 -0300 Subject: [PATCH 010/201] Use consistent naming to refer to the view transmission texture --- crates/bevy_pbr/src/render/mesh_view_bindings.wgsl | 4 ++-- crates/bevy_pbr/src/render/pbr_lighting.wgsl | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index a30439bc82b89..61a82b261e471 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -71,6 +71,6 @@ var normal_prepass_texture: texture_2d; #endif @group(0) @binding(18) -var view_main_texture: texture_2d; +var view_transmission_texture: texture_2d; @group(0) @binding(19) -var view_main_sampler: sampler; +var view_transmission_sampler: sampler; diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index e76633a485f69..01815fd627403 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -303,11 +303,11 @@ fn transmissive_light(frag_coord: vec3, N: vec3, V: vec3, ior: f3 // the fragment position scaled by viewport size. Use the aspect ratio calculated earlier stay proportionate let offset_position = frag_coord.xy / view.viewport.zw + view_T.xy * thickness * vec2(1.0, -aspect); - // Sample the view main texture at the offset position, to get the background color + // Sample the view transmission texture at the offset position, to get the background color // TODO: use depth prepass data to reject values that are in front of the current fragment let background_sample = textureSample( - view_main_texture, - view_main_sampler, + view_transmission_texture, + view_transmission_sampler, offset_position, ).rgb; From 12b845a1528d507b2e61063e2d32c2b7e161cd4e Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 00:11:57 -0300 Subject: [PATCH 011/201] Fix comment capitalization --- crates/bevy_pbr/src/render/pbr_lighting.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 01815fd627403..91bdd761609d0 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -304,7 +304,7 @@ fn transmissive_light(frag_coord: vec3, N: vec3, V: vec3, ior: f3 let offset_position = frag_coord.xy / view.viewport.zw + view_T.xy * thickness * vec2(1.0, -aspect); // Sample the view transmission texture at the offset position, to get the background color - // TODO: use depth prepass data to reject values that are in front of the current fragment + // TODO: Use depth prepass data to reject values that are in front of the current fragment let background_sample = textureSample( view_transmission_texture, view_transmission_sampler, From c0ec1e3603a105444feef024c2fb0816bcfd9086 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 01:10:25 -0300 Subject: [PATCH 012/201] Expose transmission-related properties in `StandardMaterial` --- crates/bevy_pbr/src/pbr_material.rs | 61 +++++++++++++++++++ crates/bevy_pbr/src/render/pbr.wgsl | 8 +++ crates/bevy_pbr/src/render/pbr_functions.wgsl | 10 +-- crates/bevy_pbr/src/render/pbr_types.wgsl | 6 ++ 4 files changed, 78 insertions(+), 7 deletions(-) diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 6a4c81d2c20f3..8b4a2aab65b43 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -137,6 +137,55 @@ pub struct StandardMaterial { #[doc(alias = "specular_intensity")] pub reflectance: f32, + /// The amount of light transmitted through the material (via refraction) + /// + /// - When set to `0.0` (the default) no light is transmitted. + /// - When set to `1.0` all light is transmitted through the material. + /// + /// The material's [`StandardMaterial::base_color`] also modulates the transmitted light. + /// + /// **Important:** For transmission to have any effect, you must also set `alpha_mode` to [`AlphaMode::Blend`]. + /// **Note:** Typically used in conjunction with [`StandardMaterial::thickness`] and [`StandardMaterial::ior`]. + pub transmission: f32, + + /// Thickness of the volume beneath the material surface. + /// + /// When set to `0.0` (the default) the material appears as an infinitely-thin film, + /// transmitting light without distorting it. + /// + /// When set to any other value, the material distorts light like a volumetric lens. + /// + /// **Note:** Typically used in conjunction with [`StandardMaterial::transmission`] and [`StandardMaterial::ior`]. + pub thickness: f32, + + /// The index of refraction of the material + /// + /// | Material | Index of Refraction | + /// |:----------------|:---------------------| + /// | Vacuum | 1 | + /// | Air | 1.00 | + /// | Ice | 1.31 | + /// | Water | 1.33 | + /// | Eyes | 1.38 | + /// | Quartz | 1.46 | + /// | Olive Oil | 1.47 | + /// | Honey | 1.49 | + /// | Acrylic | 1.49 | + /// | Window Glass | 1.52 | + /// | Polycarbonate | 1.58 | + /// | Flint Glass | 1.69 | + /// | Ruby | 1.71 | + /// | Glycerine | 1.74 | + /// | Saphire | 1.77 | + /// | Cubic Zirconia | 2.15 | + /// | Diamond | 2.42 | + /// | Moissanite | 2.65 | + /// + /// **Note:** Typically used in conjunction with [`StandardMaterial::transmission`] and [`StandardMaterial::thickness`]. + #[doc(alias = "index_of_refraction")] + #[doc(alias = "refraction_index")] + pub ior: f32, + /// Used to fake the lighting of bumps and dents on a material. /// /// A typical usage would be faking cobblestones on a flat plane mesh in 3D. @@ -251,6 +300,9 @@ impl Default for StandardMaterial { // Expressed in a linear scale and equivalent to 4% reflectance see // reflectance: 0.5, + transmission: 0.0, + thickness: 0.0, + ior: 1.5, occlusion_texture: None, normal_map_texture: None, flip_normal_map_y: false, @@ -336,6 +388,12 @@ pub struct StandardMaterialUniform { /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] /// defaults to 0.5 which is mapped to 4% reflectance in the shader pub reflectance: f32, + /// Amount of light transmitted through the material + pub transmission: f32, + /// Thickness of the volume underneath the material surface + pub thickness: f32, + /// Index of Refraction + pub ior: f32, /// The [`StandardMaterialFlags`] accessible in the `wgsl` shader. pub flags: u32, /// When the alpha mode mask flag is set, any base color alpha above this cutoff means fully opaque, @@ -405,6 +463,9 @@ impl AsBindGroupShaderType for StandardMaterial { roughness: self.perceptual_roughness, metallic: self.metallic, reflectance: self.reflectance, + transmission: self.transmission, + thickness: self.thickness, + ior: self.ior, flags: flags.bits(), alpha_cutoff, } diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index b87208edca6b3..38625580dd4e1 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -36,6 +36,14 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { pbr_input.material.base_color = output_color; pbr_input.material.reflectance = material.reflectance; + var transmission = material.transmission; + if (material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS) != STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND { + // Disable transmission for `alpha_mode` values that are not `AlphaMode::Blend` + transmission = 0.0; + } + pbr_input.material.transmission = material.transmission; + pbr_input.material.thickness = material.thickness; + pbr_input.material.ior = material.ior; pbr_input.material.flags = material.flags; pbr_input.material.alpha_cutoff = material.alpha_cutoff; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index e90e4aa0499c7..6273d2ec534fc 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -171,13 +171,9 @@ fn pbr( let metallic = in.material.metallic; let perceptual_roughness = in.material.perceptual_roughness; let roughness = perceptualRoughnessToRoughness(perceptual_roughness); - let ior = 1.5; - let thickness = 1.0; - var transmission = 0.0; - let alpha_mode = in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; - if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND) { - transmission = 1.0; - } + let ior = in.material.ior; + let thickness = in.material.thickness; + let transmission = in.material.transmission; let transmissive_color = transmission * in.material.base_color.rgb; diff --git a/crates/bevy_pbr/src/render/pbr_types.wgsl b/crates/bevy_pbr/src/render/pbr_types.wgsl index 722f9b8411c21..ce43006ee6b5a 100644 --- a/crates/bevy_pbr/src/render/pbr_types.wgsl +++ b/crates/bevy_pbr/src/render/pbr_types.wgsl @@ -6,6 +6,9 @@ struct StandardMaterial { perceptual_roughness: f32, metallic: f32, reflectance: f32, + transmission: f32, + thickness: f32, + ior: f32, // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. flags: u32, alpha_cutoff: f32, @@ -40,6 +43,9 @@ fn standard_material_new() -> StandardMaterial { material.perceptual_roughness = 0.089; material.metallic = 0.01; material.reflectance = 0.5; + material.transmission = 0.0; + material.thickness = 0.0; + material.ior = 1.5; material.flags = STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE; material.alpha_cutoff = 0.5; From e040fc8ebaf36eeac9d4c86e913c9c0fa4049981 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 02:35:46 -0300 Subject: [PATCH 013/201] Make transmitted light blurry when material is rough, using dither + multiple taps --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 2 +- crates/bevy_pbr/src/render/pbr_lighting.wgsl | 41 ++++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 6273d2ec534fc..31774d2ae7cfa 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -260,7 +260,7 @@ fn pbr( // Transmitted Light var transmitted_light: vec3 = vec3(0.0); if transmission > 0.0 { - transmitted_light = transmissive_light(in.frag_coord.xyz, in.N, in.V, ior, thickness, transmissive_color).rgb; + transmitted_light = transmissive_light(in.frag_coord.xyz, in.N, in.V, ior, thickness, perceptual_roughness, transmissive_color).rgb; } else { transmitted_light = vec3(0.0); } diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 91bdd761609d0..e0d7991bf8f91 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -281,7 +281,7 @@ fn directional_light(light_id: u32, roughness: f32, NdotV: f32, normal: vec3, N: vec3, V: vec3, ior: f32, thickness: f32, transmissive_color: vec3) -> vec3 { +fn transmissive_light(frag_coord: vec3, N: vec3, V: vec3, ior: f32, thickness: f32, perceptual_roughness: f32, transmissive_color: vec3) -> vec3 { // Calculate view aspect ratio, used later to scale offset so that it's proportionate let aspect = view.viewport.z / view.viewport.w; @@ -301,17 +301,36 @@ fn transmissive_light(frag_coord: vec3, N: vec3, V: vec3, ior: f3 // Calculate an offset position, by multiplying the refracted direction by the thickness and adding it to // the fragment position scaled by viewport size. Use the aspect ratio calculated earlier stay proportionate - let offset_position = frag_coord.xy / view.viewport.zw + view_T.xy * thickness * vec2(1.0, -aspect); + let offset_position = frag_coord.xy / view.viewport.zw + view_T.xy * thickness * vec2(1.0, -aspect) ; - // Sample the view transmission texture at the offset position, to get the background color - // TODO: Use depth prepass data to reject values that are in front of the current fragment - let background_sample = textureSample( - view_transmission_texture, - view_transmission_sampler, - offset_position, - ).rgb; + // Fetch background color + let background_color = fetch_transmissive_background(offset_position, frag_coord, aspect, perceptual_roughness); - // Calculate final color by applying transmissive color to background sample + // Calculate final color by applying transmissive color to background // TODO: Add support for attenuationColor and attenuationDistance - return transmissive_color * background_sample; + return transmissive_color * background_color; +} + +fn fetch_transmissive_background(offset_position: vec2, frag_coord: vec3, aspect: f32, perceptual_roughness: f32) -> vec3 { + // Number of taps scale with perceptual roughness + // Minimum: 1, Maximum: 9 + let num_taps = i32(max(perceptual_roughness * 40.0, 8.0)) + 1; + var result = vec3(0.0, 0.0, 0.0); + for (var i: i32 = 0; i < num_taps; i = i + 1) { + // Magic numbers have been empirically chosen to produce blurry results that look “smooth” + let dither = screen_space_dither(frag_coord.xy + vec2(f32(i) * 4773.0, f32(i) * 1472.0)); + let dither_offset = perceptual_roughness * (30.0 * dither.yz + 0.03 * normalize(dither).xy) * vec2(1.0, -aspect); + + // Sample the view transmission texture at the offset position + dither offset, to get the background color + // TODO: Use depth prepass data to reject values that are in front of the current fragment + result += textureSample( + view_transmission_texture, + view_transmission_sampler, + offset_position + dither_offset, + ).rgb; + } + + result /= f32(num_taps); + + return result; } From c9db7aaa07bb33f0dca27cb99b6485d4d231a8ca Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 02:36:22 -0300 Subject: [PATCH 014/201] Unconditionally import tonemapping utils, ensuring dither is present --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 31774d2ae7cfa..dec30c41a4c3f 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -1,8 +1,5 @@ #define_import_path bevy_pbr::pbr_functions - -#ifdef TONEMAP_IN_SHADER #import bevy_core_pipeline::tonemapping -#endif #ifdef ENVIRONMENT_MAP #import bevy_pbr::environment_map From 27c928580b2e3184f6d2b6d16992935fa53bf2b4 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 10 Mar 2023 03:12:20 -0300 Subject: [PATCH 015/201] Add transmission example --- Cargo.toml | 10 ++ examples/3d/transmission.rs | 272 ++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 examples/3d/transmission.rs diff --git a/Cargo.toml b/Cargo.toml index ac0fd63b8cdd3..80d7509280800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -621,6 +621,16 @@ description = "Demonstrates transparency in 3d" category = "3D Rendering" wasm = true +[[example]] +name = "transmission" +path = "examples/3d/transmission.rs" + +[package.metadata.example.transmission] +name = "Transmission" +description = "Showcases light transmission in the PBR material" +category = "3D Rendering" +wasm = true + [[example]] name = "two_passes" path = "examples/3d/two_passes.rs" diff --git a/examples/3d/transmission.rs b/examples/3d/transmission.rs new file mode 100644 index 0000000000000..755aeba6381f6 --- /dev/null +++ b/examples/3d/transmission.rs @@ -0,0 +1,272 @@ +//! This example showcases light transmission +//! +//! ## Controls +//! +//! | Key Binding | Action | +//! |:-------------------|:------------------------------------------| +//! | `Q` / `W` | Decrease / Increase Transmission | +//! | `A` / `S` | Decrease / Increase Thickness | +//! | `Z` / `X` | Decrease / Increase IOR | +//! | `Down` / `Up` | Decrease / Increase Perceptual Roughness | +//! | `Left` / `Right` | Rotate Camera | +//! | `H` | Toggle HDR | +//! | `C` | Randomize Colors | + +use bevy::prelude::*; +use rand::random; + +fn main() { + let mut app = App::new(); + + app.add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(example_control_system); + + // Unfortunately, MSAA and HDR are not supported simultaneously under WebGL. + // Since this example uses HDR, we must disable MSAA for WASM builds, at least + // until WebGPU is ready and no longer behind a feature flag in Web browsers. + #[cfg(target_arch = "wasm32")] + app.insert_resource(Msaa::Off); + + app.run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + asset_server: Res, +) { + let icosphere_mesh = meshes.add( + Mesh::try_from(shape::Icosphere { + radius: 0.9, + subdivisions: 7, + }) + .unwrap(), + ); + + // Opaque + commands.spawn(( + PbrBundle { + mesh: icosphere_mesh.clone(), + material: materials.add(StandardMaterial { + base_color: Color::rgba(0.9, 0.2, 0.3, 1.0), + alpha_mode: AlphaMode::Opaque, + ..default() + }), + transform: Transform::from_xyz(-1.0, 0.0, 0.0), + ..default() + }, + ExampleControls { + color: true, + transmission: false, + }, + )); + + // Transmissive + commands.spawn(( + PbrBundle { + mesh: icosphere_mesh.clone(), + material: materials.add(StandardMaterial { + base_color: Color::WHITE, + transmission: 1.0, + thickness: 1.0, + ior: 1.5, + perceptual_roughness: 0.12, + alpha_mode: AlphaMode::Blend, + ..default() + }), + transform: Transform::from_xyz(1.0, 0.0, 0.0), + ..default() + }, + ExampleControls { + color: true, + transmission: true, + }, + )); + + // Chessboard Plane + let black_material = materials.add(Color::BLACK.into()); + let white_material = materials.add(Color::WHITE.into()); + + let plane_mesh = meshes.add(shape::Plane::from_size(2.0).into()); + + for x in -3..4 { + for z in -3..4 { + commands.spawn(( + PbrBundle { + mesh: plane_mesh.clone(), + material: if (x + z) % 2 == 0 { + black_material.clone() + } else { + white_material.clone() + }, + transform: Transform::from_xyz(x as f32 * 2.0, -1.0, z as f32 * 2.0), + ..default() + }, + ExampleControls { + color: true, + transmission: false, + }, + )); + } + } + + // Light + commands.spawn(PointLightBundle { + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + + // Camera + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(0.0, 2.5, 8.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // Controls Text + let text_style = TextStyle { + font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 18.0, + color: Color::BLACK, + }; + + commands.spawn( + TextBundle::from_section( + "Q / W - Decrease / Increase Transmission\nA / S - Decrease / Increase Thickness\nZ / X - Decrease / Increase IOR\nDown / Up - Decrease / Increase Perceptual Roughness\nLeft / Right - Rotate Camera\nH - Toggle HDR\nC - Randomize Colors", + text_style.clone(), + ) + .with_style(Style { + position_type: PositionType::Absolute, + position: UiRect { + top: Val::Px(10.0), + left: Val::Px(10.0), + ..default() + }, + ..default() + }), + ); + + commands.spawn(( + TextBundle::from_section("", text_style).with_style(Style { + position_type: PositionType::Absolute, + position: UiRect { + top: Val::Px(10.0), + right: Val::Px(10.0), + ..default() + }, + ..default() + }), + ExampleDisplay, + )); +} + +#[derive(Component)] +struct ExampleControls { + transmission: bool, + color: bool, +} + +struct ExampleState { + transmission: f32, + thickness: f32, + ior: f32, + perceptual_roughness: f32, +} + +#[derive(Component)] +struct ExampleDisplay; + +impl Default for ExampleState { + fn default() -> Self { + ExampleState { + transmission: 1.0, + thickness: 1.0, + ior: 1.5, + perceptual_roughness: 0.12, + } + } +} + +#[allow(clippy::too_many_arguments)] +fn example_control_system( + mut materials: ResMut>, + controllable: Query<(&Handle, &ExampleControls)>, + mut camera: Query<(&mut Camera, &mut Transform), With>, + mut display: Query<&mut Text, With>, + mut state: Local, + time: Res