mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 06:00:20 +00:00
StandardMaterial
Light Transmission (#8015)
# Objective
<img width="1920" alt="Screenshot 2023-04-26 at 01 07 34"
src="https://user-images.githubusercontent.com/418473/234467578-0f34187b-5863-4ea1-88e9-7a6bb8ce8da3.png">
This PR adds both diffuse and specular light transmission capabilities
to the `StandardMaterial`, with support for screen space refractions.
This enables realistically representing a wide range of real-world
materials, such as:
- Glass; (Including frosted glass)
- Transparent and translucent plastics;
- Various liquids and gels;
- Gemstones;
- Marble;
- Wax;
- Paper;
- Leaves;
- Porcelain.
Unlike existing support for transparency, light transmission does not
rely on fixed function alpha blending, and therefore works with both
`AlphaMode::Opaque` and `AlphaMode::Mask` materials.
## Solution
- Introduces a number of transmission related fields in the
`StandardMaterial`;
- For specular transmission:
- Adds logic to take a view main texture snapshot after the opaque
phase; (in order to perform screen space refractions)
- Introduces a new `Transmissive3d` phase to the renderer, to which all
meshes with `transmission > 0.0` materials are sent.
- Calculates a light exit point (of the approximate mesh volume) using
`ior` and `thickness` properties
- Samples the snapshot texture with an adaptive number of taps across a
`roughness`-controlled radius enabling “blurry” refractions
- For diffuse transmission:
- Approximates transmitted diffuse light by using a second, flipped +
displaced, diffuse-only Lambertian lobe for each light source.
## To Do
- [x] Figure out where `fresnel_mix()` is taking place, if at all, and
where `dielectric_specular` is being calculated, if at all, and update
them to use the `ior` value (Not a blocker, just a nice-to-have for more
correct BSDF)
- To the _best of my knowledge, this is now taking place, after
964340cdd
. The fresnel mix is actually "split" into two parts in our
implementation, one `(1 - fresnel(...))` in the transmission, and
`fresnel()` in the light implementations. A surface with more
reflectance now will produce slightly dimmer transmission towards the
grazing angle, as more of the light gets reflected.
- [x] Add `transmission_texture`
- [x] Add `diffuse_transmission_texture`
- [x] Add `thickness_texture`
- [x] Add `attenuation_distance` and `attenuation_color`
- [x] Connect values to glTF loader
- [x] `transmission` and `transmission_texture`
- [x] `thickness` and `thickness_texture`
- [x] `ior`
- [ ] `diffuse_transmission` and `diffuse_transmission_texture` (needs
upstream support in `gltf` crate, not a blocker)
- [x] Add support for multiple screen space refraction “steps”
- [x] Conditionally create no transmission snapshot texture at all if
`steps == 0`
- [x] Conditionally enable/disable screen space refraction transmission
snapshots
- [x] Read from depth pre-pass to prevent refracting pixels in front of
the light exit point
- [x] Use `interleaved_gradient_noise()` function for sampling blur in a
way that benefits from TAA
- [x] Drill down a TAA `#define`, tweak some aspects of the effect
conditionally based on it
- [x] Remove const array that's crashing under HLSL (unless a new `naga`
release with https://github.com/gfx-rs/naga/pull/2496 comes out before
we merge this)
- [ ] Look into alternatives to the `switch` hack for dynamically
indexing the const array (might not be needed, compilers seem to be
decent at expanding it)
- [ ] Add pipeline keys for gating transmission (do we really want/need
this?)
- [x] Tweak some material field/function names?
## A Note on Texture Packing
_This was originally added as a comment to the
`specular_transmission_texture`, `thickness_texture` and
`diffuse_transmission_texture` documentation, I removed it since it was
more confusing than helpful, and will likely be made redundant/will need
to be updated once we have a better infrastructure for preprocessing
assets_
Due to how channels are mapped, you can more efficiently use a single
shared texture image
for configuring the following:
- R - `specular_transmission_texture`
- G - `thickness_texture`
- B - _unused_
- A - `diffuse_transmission_texture`
The `KHR_materials_diffuse_transmission` glTF extension also defines a
`diffuseTransmissionColorTexture`,
that _we don't currently support_. One might choose to pack the
intensity and color textures together,
using RGB for the color and A for the intensity, in which case this
packing advice doesn't really apply.
---
## Changelog
- Added a new `Transmissive3d` render phase for rendering specular
transmissive materials with screen space refractions
- Added rendering support for transmitted environment map light on the
`StandardMaterial` as a fallback for screen space refractions
- Added `diffuse_transmission`, `specular_transmission`, `thickness`,
`ior`, `attenuation_distance` and `attenuation_color` to the
`StandardMaterial`
- Added `diffuse_transmission_texture`, `specular_transmission_texture`,
`thickness_texture` to the `StandardMaterial`, gated behind a new
`pbr_transmission_textures` cargo feature (off by default, for maximum
hardware compatibility)
- Added `Camera3d::screen_space_specular_transmission_steps` for
controlling the number of “layers of transparency” rendered for
transmissive objects
- Added a `TransmittedShadowReceiver` component for enabling shadows in
(diffusely) transmitted light. (disabled by default, as it requires
carefully setting up the `thickness` to avoid self-shadow artifacts)
- Added support for the `KHR_materials_transmission`,
`KHR_materials_ior` and `KHR_materials_volume` glTF extensions
- Renamed items related to temporal jitter for greater consistency
## Migration Guide
- `SsaoPipelineKey::temporal_noise` has been renamed to
`SsaoPipelineKey::temporal_jitter`
- The `TAA` shader def (controlled by the presence of the
`TemporalAntiAliasSettings` component in the camera) has been replaced
with the `TEMPORAL_JITTER` shader def (controlled by the presence of the
`TemporalJitter` component in the camera)
- `MeshPipelineKey::TAA` has been replaced by
`MeshPipelineKey::TEMPORAL_JITTER`
- The `TEMPORAL_NOISE` shader def has been consolidated with
`TEMPORAL_JITTER`
This commit is contained in:
parent
d67fbd5e90
commit
44928e0df4
34 changed files with 1977 additions and 60 deletions
13
Cargo.toml
13
Cargo.toml
|
@ -260,6 +260,9 @@ shader_format_glsl = ["bevy_internal/shader_format_glsl"]
|
|||
# Enable support for shaders in SPIR-V
|
||||
shader_format_spirv = ["bevy_internal/shader_format_spirv"]
|
||||
|
||||
# Enable support for transmission-related textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs
|
||||
pbr_transmission_textures = ["bevy_internal/pbr_transmission_textures"]
|
||||
|
||||
# Enable some limitations to be able to use WebGL2. If not enabled, it will default to WebGPU in Wasm. Please refer to the [WebGL2 and WebGPU](https://github.com/bevyengine/bevy/tree/latest/examples#webgl2-and-webgpu) section of the examples README for more information on how to run Wasm builds with WebGPU.
|
||||
webgl2 = ["bevy_internal/webgl"]
|
||||
|
||||
|
@ -800,6 +803,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"
|
||||
|
|
|
@ -25,6 +25,31 @@ pub struct Camera3d {
|
|||
pub depth_load_op: Camera3dDepthLoadOp,
|
||||
/// The texture usages for the depth texture created for the main 3d pass.
|
||||
pub depth_texture_usages: Camera3dDepthTextureUsage,
|
||||
/// How many individual steps should be performed in the [`Transmissive3d`](crate::core_3d::Transmissive3d) pass.
|
||||
///
|
||||
/// Roughly corresponds to how many “layers of transparency” are rendered for screen space
|
||||
/// specular transmissive objects. Each step requires making one additional
|
||||
/// texture copy, so it's recommended to keep this number to a resonably low value. Defaults to `1`.
|
||||
///
|
||||
/// ### Notes
|
||||
///
|
||||
/// - No copies will be performed if there are no transmissive materials currently being rendered,
|
||||
/// regardless of this setting.
|
||||
/// - Setting this to `0` disables the screen-space refraction effect entirely, and falls
|
||||
/// back to refracting only the environment map light's texture.
|
||||
/// - If set to more than `0`, any opaque [`clear_color`](Camera3d::clear_color) will obscure the environment
|
||||
/// map light's texture, preventing it from being visible “through” transmissive materials. If you'd like
|
||||
/// to still have the environment map show up in your refractions, you can set the clear color's alpha to `0.0`.
|
||||
/// Keep in mind that depending on the platform and your window settings, this may cause the window to become
|
||||
/// transparent.
|
||||
pub screen_space_specular_transmission_steps: usize,
|
||||
/// The quality of the screen space specular transmission blur effect, applied to whatever's “behind” transmissive
|
||||
/// objects when their `roughness` is greater than `0.0`.
|
||||
///
|
||||
/// Higher qualities are more GPU-intensive.
|
||||
///
|
||||
/// **Note:** You can get better-looking results at any quality level by enabling TAA. See: [`TemporalAntiAliasPlugin`](crate::experimental::taa::TemporalAntiAliasPlugin).
|
||||
pub screen_space_specular_transmission_quality: ScreenSpaceTransmissionQuality,
|
||||
}
|
||||
|
||||
impl Default for Camera3d {
|
||||
|
@ -33,6 +58,8 @@ impl Default for Camera3d {
|
|||
clear_color: ClearColorConfig::Default,
|
||||
depth_load_op: Default::default(),
|
||||
depth_texture_usages: TextureUsages::RENDER_ATTACHMENT.into(),
|
||||
screen_space_specular_transmission_steps: 1,
|
||||
screen_space_specular_transmission_quality: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +104,37 @@ impl From<Camera3dDepthLoadOp> for LoadOp<f32> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The quality of the screen space transmission blur effect, applied to whatever's “behind” transmissive
|
||||
/// objects when their `roughness` is greater than `0.0`.
|
||||
///
|
||||
/// Higher qualities are more GPU-intensive.
|
||||
///
|
||||
/// **Note:** You can get better-looking results at any quality level by enabling TAA. See: [`TemporalAntiAliasPlugin`](crate::experimental::taa::TemporalAntiAliasPlugin).
|
||||
#[derive(Resource, Default, Clone, Copy, Reflect, PartialEq, PartialOrd, Debug)]
|
||||
#[reflect(Resource)]
|
||||
pub enum ScreenSpaceTransmissionQuality {
|
||||
/// Best performance at the cost of quality. Suitable for lower end GPUs. (e.g. Mobile)
|
||||
///
|
||||
/// `num_taps` = 4
|
||||
Low,
|
||||
|
||||
/// A balanced option between quality and performance.
|
||||
///
|
||||
/// `num_taps` = 8
|
||||
#[default]
|
||||
Medium,
|
||||
|
||||
/// Better quality. Suitable for high end GPUs. (e.g. Desktop)
|
||||
///
|
||||
/// `num_taps` = 16
|
||||
High,
|
||||
|
||||
/// The highest quality, suitable for non-realtime rendering. (e.g. Pre-rendered cinematics and photo mode)
|
||||
///
|
||||
/// `num_taps` = 32
|
||||
Ultra,
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct Camera3dBundle {
|
||||
pub camera: Camera,
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
use super::{Camera3d, ViewTransmissionTexture};
|
||||
use crate::core_3d::Transmissive3d;
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
|
||||
render_phase::RenderPhase,
|
||||
render_resource::{
|
||||
Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor,
|
||||
},
|
||||
renderer::RenderContext,
|
||||
view::{ViewDepthTexture, ViewTarget},
|
||||
};
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
use std::ops::Range;
|
||||
|
||||
/// A [`bevy_render::render_graph::Node`] that runs the [`Transmissive3d`] [`RenderPhase`].
|
||||
#[derive(Default)]
|
||||
pub struct MainTransmissivePass3dNode;
|
||||
|
||||
impl ViewNode for MainTransmissivePass3dNode {
|
||||
type ViewQuery = (
|
||||
&'static ExtractedCamera,
|
||||
&'static Camera3d,
|
||||
&'static RenderPhase<Transmissive3d>,
|
||||
&'static ViewTarget,
|
||||
Option<&'static ViewTransmissionTexture>,
|
||||
&'static ViewDepthTexture,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
(camera, camera_3d, transmissive_phase, target, transmission, depth): QueryItem<
|
||||
Self::ViewQuery,
|
||||
>,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.view_entity();
|
||||
|
||||
let physical_target_size = camera.physical_target_size.unwrap();
|
||||
|
||||
let render_pass_descriptor = RenderPassDescriptor {
|
||||
label: Some("main_transmissive_pass_3d"),
|
||||
// NOTE: The transmissive pass loads the color buffer as well as overwriting it where appropriate.
|
||||
color_attachments: &[Some(target.get_color_attachment(Operations {
|
||||
load: LoadOp::Load,
|
||||
store: true,
|
||||
}))],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||
view: &depth.view,
|
||||
// NOTE: The transmissive main pass loads the depth buffer and possibly overwrites it
|
||||
depth_ops: Some(Operations {
|
||||
load: LoadOp::Load,
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
};
|
||||
|
||||
// Run the transmissive pass, sorted back-to-front
|
||||
// NOTE: Scoped to drop the mutable borrow of render_context
|
||||
#[cfg(feature = "trace")]
|
||||
let _main_transmissive_pass_3d_span = info_span!("main_transmissive_pass_3d").entered();
|
||||
|
||||
if !transmissive_phase.items.is_empty() {
|
||||
let screen_space_specular_transmission_steps =
|
||||
camera_3d.screen_space_specular_transmission_steps;
|
||||
if screen_space_specular_transmission_steps > 0 {
|
||||
let transmission =
|
||||
transmission.expect("`ViewTransmissionTexture` should exist at this point");
|
||||
|
||||
// `transmissive_phase.items` are depth sorted, so we split them into N = `screen_space_specular_transmission_steps`
|
||||
// ranges, rendering them back-to-front in multiple steps, allowing multiple levels of transparency.
|
||||
//
|
||||
// Note: For the sake of simplicity, we currently split items evenly among steps. In the future, we
|
||||
// might want to use a more sophisticated heuristic (e.g. based on view bounds, or with an exponential
|
||||
// falloff so that nearby objects have more levels of transparency available to them)
|
||||
for range in split_range(
|
||||
0..transmissive_phase.items.len(),
|
||||
screen_space_specular_transmission_steps,
|
||||
) {
|
||||
// Copy the main texture to the transmission texture, allowing to use the color output of the
|
||||
// previous step (or of the `Opaque3d` phase, for the first step) as a transmissive color input
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
||||
let mut render_pass =
|
||||
render_context.begin_tracked_render_pass(render_pass_descriptor.clone());
|
||||
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
render_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
|
||||
// render items in range
|
||||
transmissive_phase.render_range(&mut render_pass, world, view_entity, range);
|
||||
}
|
||||
} else {
|
||||
let mut render_pass =
|
||||
render_context.begin_tracked_render_pass(render_pass_descriptor);
|
||||
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
render_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
|
||||
transmissive_phase.render(&mut render_pass, world, view_entity);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits a [`Range`] into at most `max_num_splits` sub-ranges without overlaps
|
||||
///
|
||||
/// Properly takes into account remainders of inexact divisions (by adding extra
|
||||
/// elements to the initial sub-ranges as needed)
|
||||
fn split_range(range: Range<usize>, max_num_splits: usize) -> impl Iterator<Item = Range<usize>> {
|
||||
let len = range.end - range.start;
|
||||
assert!(len > 0, "to be split, a range must not be empty");
|
||||
assert!(max_num_splits > 0, "max_num_splits must be at least 1");
|
||||
let num_splits = max_num_splits.min(len);
|
||||
let step = len / num_splits;
|
||||
let mut rem = len % num_splits;
|
||||
let mut start = range.start;
|
||||
|
||||
(0..num_splits).map(move |_| {
|
||||
let extra = if rem > 0 {
|
||||
rem -= 1;
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = (start + step + extra).min(range.end);
|
||||
let result = start..end;
|
||||
start = end;
|
||||
result
|
||||
})
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod camera_3d;
|
||||
mod main_opaque_pass_3d_node;
|
||||
mod main_transmissive_pass_3d_node;
|
||||
mod main_transparent_pass_3d_node;
|
||||
|
||||
pub mod graph {
|
||||
|
@ -15,6 +16,7 @@ pub mod graph {
|
|||
pub const END_PREPASSES: &str = "end_prepasses";
|
||||
pub const START_MAIN_PASS: &str = "start_main_pass";
|
||||
pub const MAIN_OPAQUE_PASS: &str = "main_opaque_pass";
|
||||
pub const MAIN_TRANSMISSIVE_PASS: &str = "main_transmissive_pass";
|
||||
pub const MAIN_TRANSPARENT_PASS: &str = "main_transparent_pass";
|
||||
pub const END_MAIN_PASS: &str = "end_main_pass";
|
||||
pub const BLOOM: &str = "bloom";
|
||||
|
@ -48,17 +50,18 @@ use bevy_render::{
|
|||
RenderPhase,
|
||||
},
|
||||
render_resource::{
|
||||
CachedRenderPipelineId, Extent3d, TextureDescriptor, TextureDimension, TextureFormat,
|
||||
TextureUsages,
|
||||
CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor, Texture,
|
||||
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::TextureCache,
|
||||
view::ViewDepthTexture,
|
||||
texture::{BevyDefault, TextureCache},
|
||||
view::{ExtractedView, ViewDepthTexture, ViewTarget},
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::{nonmax::NonMaxU32, tracing::warn, FloatOrd, HashMap};
|
||||
|
||||
use crate::{
|
||||
core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode,
|
||||
deferred::{
|
||||
copy_lighting_id::CopyDeferredLightingIdNode, node::DeferredGBufferPrepassNode,
|
||||
AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT,
|
||||
|
@ -91,6 +94,7 @@ impl Plugin for Core3dPlugin {
|
|||
render_app
|
||||
.init_resource::<DrawFunctions<Opaque3d>>()
|
||||
.init_resource::<DrawFunctions<AlphaMask3d>>()
|
||||
.init_resource::<DrawFunctions<Transmissive3d>>()
|
||||
.init_resource::<DrawFunctions<Transparent3d>>()
|
||||
.init_resource::<DrawFunctions<Opaque3dPrepass>>()
|
||||
.init_resource::<DrawFunctions<AlphaMask3dPrepass>>()
|
||||
|
@ -103,12 +107,14 @@ impl Plugin for Core3dPlugin {
|
|||
(
|
||||
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Transmissive3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Opaque3dPrepass>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<AlphaMask3dPrepass>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Opaque3dDeferred>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<AlphaMask3dDeferred>.in_set(RenderSet::PhaseSort),
|
||||
prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources),
|
||||
prepare_core_3d_transmission_textures.in_set(RenderSet::PrepareResources),
|
||||
prepare_prepass_textures.in_set(RenderSet::PrepareResources),
|
||||
),
|
||||
);
|
||||
|
@ -131,6 +137,10 @@ impl Plugin for Core3dPlugin {
|
|||
CORE_3D,
|
||||
MAIN_OPAQUE_PASS,
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<MainTransmissivePass3dNode>>(
|
||||
CORE_3D,
|
||||
MAIN_TRANSMISSIVE_PASS,
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<MainTransparentPass3dNode>>(
|
||||
CORE_3D,
|
||||
MAIN_TRANSPARENT_PASS,
|
||||
|
@ -148,6 +158,7 @@ impl Plugin for Core3dPlugin {
|
|||
END_PREPASSES,
|
||||
START_MAIN_PASS,
|
||||
MAIN_OPAQUE_PASS,
|
||||
MAIN_TRANSMISSIVE_PASS,
|
||||
MAIN_TRANSPARENT_PASS,
|
||||
END_MAIN_PASS,
|
||||
TONEMAPPING,
|
||||
|
@ -282,6 +293,78 @@ impl CachedRenderPipelinePhaseItem for AlphaMask3d {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Transmissive3d {
|
||||
pub distance: f32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub entity: Entity,
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_range: Range<u32>,
|
||||
pub dynamic_offset: Option<NonMaxU32>,
|
||||
}
|
||||
|
||||
impl PhaseItem for Transmissive3d {
|
||||
// NOTE: Values increase towards the camera. Back-to-front ordering for transmissive means we need an ascending sort.
|
||||
type SortKey = FloatOrd;
|
||||
|
||||
/// For now, automatic batching is disabled for transmissive items because their rendering is
|
||||
/// split into multiple steps depending on [`Camera3d::screen_space_specular_transmission_steps`],
|
||||
/// which the batching system doesn't currently know about.
|
||||
///
|
||||
/// Having batching enabled would cause the same item to be drawn multiple times across different
|
||||
/// steps, whenever the batching range crossed a step boundary.
|
||||
///
|
||||
/// Eventually, we could add support for this by having the batching system break up the batch ranges
|
||||
/// using the same logic as the transmissive pass, but for now it's simpler to just disable batching.
|
||||
const AUTOMATIC_BATCHING: bool = false;
|
||||
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
FloatOrd(self.distance)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_function(&self) -> DrawFunctionId {
|
||||
self.draw_function
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
radsort::sort_by_key(items, |item| item.distance);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_range(&self) -> &Range<u32> {
|
||||
&self.batch_range
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn batch_range_mut(&mut self) -> &mut Range<u32> {
|
||||
&mut self.batch_range
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dynamic_offset(&self) -> Option<NonMaxU32> {
|
||||
self.dynamic_offset
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dynamic_offset_mut(&mut self) -> &mut Option<NonMaxU32> {
|
||||
&mut self.dynamic_offset
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for Transmissive3d {
|
||||
#[inline]
|
||||
fn cached_pipeline(&self) -> CachedRenderPipelineId {
|
||||
self.pipeline
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Transparent3d {
|
||||
pub distance: f32,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
|
@ -352,6 +435,7 @@ pub fn extract_core_3d_camera_phases(
|
|||
commands.get_or_spawn(entity).insert((
|
||||
RenderPhase::<Opaque3d>::default(),
|
||||
RenderPhase::<AlphaMask3d>::default(),
|
||||
RenderPhase::<Transmissive3d>::default(),
|
||||
RenderPhase::<Transparent3d>::default(),
|
||||
));
|
||||
}
|
||||
|
@ -424,6 +508,7 @@ pub fn prepare_core_3d_depth_textures(
|
|||
(
|
||||
With<RenderPhase<Opaque3d>>,
|
||||
With<RenderPhase<AlphaMask3d>>,
|
||||
With<RenderPhase<Transmissive3d>>,
|
||||
With<RenderPhase<Transparent3d>>,
|
||||
),
|
||||
>,
|
||||
|
@ -484,6 +569,96 @@ 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<TextureCache>,
|
||||
render_device: Res<RenderDevice>,
|
||||
views_3d: Query<
|
||||
(
|
||||
Entity,
|
||||
&ExtractedCamera,
|
||||
&Camera3d,
|
||||
&ExtractedView,
|
||||
&RenderPhase<Transmissive3d>,
|
||||
),
|
||||
(
|
||||
With<RenderPhase<Opaque3d>>,
|
||||
With<RenderPhase<AlphaMask3d>>,
|
||||
With<RenderPhase<Transparent3d>>,
|
||||
),
|
||||
>,
|
||||
) {
|
||||
let mut textures = HashMap::default();
|
||||
for (entity, camera, camera_3d, view, transmissive_3d_phase) in &views_3d {
|
||||
let Some(physical_target_size) = camera.physical_target_size else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Don't prepare a transmission texture if the number of steps is set to 0
|
||||
if camera_3d.screen_space_specular_transmission_steps == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't prepare a transmission texture if there are no transmissive items to render
|
||||
if transmissive_3d_phase.items.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cached_texture = textures
|
||||
.entry(camera.target.clone())
|
||||
.or_insert_with(|| {
|
||||
let usage = TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST;
|
||||
|
||||
// The size of the transmission 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, // No need for MSAA, as we'll only copy the main texture here
|
||||
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"),
|
||||
mag_filter: FilterMode::Linear,
|
||||
min_filter: FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
commands.entity(entity).insert(ViewTransmissionTexture {
|
||||
texture: cached_texture.texture,
|
||||
view: cached_texture.default_view,
|
||||
sampler,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Disable MSAA and warn if using deferred rendering
|
||||
pub fn check_msaa(
|
||||
mut msaa: ResMut<Msaa>,
|
||||
|
|
|
@ -316,3 +316,12 @@ fn tone_mapping(in: vec4<f32>, color_grading: ColorGrading) -> vec4<f32> {
|
|||
return vec4(color, in.a);
|
||||
}
|
||||
|
||||
// This is an **incredibly crude** approximation of the inverse of the tone mapping function.
|
||||
// We assume here that there's a simple linear relationship between the input and output
|
||||
// which is not true at all, but useful to at least preserve the overall luminance of colors
|
||||
// when sampling from an already tonemapped image. (e.g. for transmissive materials when HDR is off)
|
||||
fn approximate_inverse_tone_mapping(in: vec4<f32>, color_grading: ColorGrading) -> vec4<f32> {
|
||||
let out = tone_mapping(in, color_grading);
|
||||
let approximate_ratio = length(in.rgb) / length(out.rgb);
|
||||
return vec4(in.rgb * approximate_ratio, in.a);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ repository = "https://github.com/bevyengine/bevy"
|
|||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[features]
|
||||
pbr_transmission_textures = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_animation = { path = "../bevy_animation", version = "0.12.0-dev", optional = true }
|
||||
|
@ -30,6 +33,9 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0-dev" }
|
|||
# other
|
||||
gltf = { version = "1.3.0", default-features = false, features = [
|
||||
"KHR_lights_punctual",
|
||||
"KHR_materials_transmission",
|
||||
"KHR_materials_ior",
|
||||
"KHR_materials_volume",
|
||||
"KHR_materials_unlit",
|
||||
"KHR_materials_emissive_strength",
|
||||
"extras",
|
||||
|
|
|
@ -763,6 +763,56 @@ fn load_material(
|
|||
texture_handle(load_context, &info.texture())
|
||||
});
|
||||
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
let (specular_transmission, specular_transmission_texture) =
|
||||
material.transmission().map_or((0.0, None), |transmission| {
|
||||
let transmission_texture: Option<Handle<Image>> = transmission
|
||||
.transmission_texture()
|
||||
.map(|transmission_texture| {
|
||||
// TODO: handle transmission_texture.tex_coord() (the *set* index for the right texcoords)
|
||||
texture_handle(load_context, &transmission_texture.texture())
|
||||
});
|
||||
|
||||
(transmission.transmission_factor(), transmission_texture)
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "pbr_transmission_textures"))]
|
||||
let specular_transmission = material
|
||||
.transmission()
|
||||
.map_or(0.0, |transmission| transmission.transmission_factor());
|
||||
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
let (thickness, thickness_texture, attenuation_distance, attenuation_color) = material
|
||||
.volume()
|
||||
.map_or((0.0, None, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| {
|
||||
let thickness_texture: Option<Handle<Image>> =
|
||||
volume.thickness_texture().map(|thickness_texture| {
|
||||
// TODO: handle thickness_texture.tex_coord() (the *set* index for the right texcoords)
|
||||
texture_handle(load_context, &thickness_texture.texture())
|
||||
});
|
||||
|
||||
(
|
||||
volume.thickness_factor(),
|
||||
thickness_texture,
|
||||
volume.attenuation_distance(),
|
||||
volume.attenuation_color(),
|
||||
)
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "pbr_transmission_textures"))]
|
||||
let (thickness, attenuation_distance, attenuation_color) =
|
||||
material
|
||||
.volume()
|
||||
.map_or((0.0, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| {
|
||||
(
|
||||
volume.thickness_factor(),
|
||||
volume.attenuation_distance(),
|
||||
volume.attenuation_color(),
|
||||
)
|
||||
});
|
||||
|
||||
let ior = material.ior().unwrap_or(1.5);
|
||||
|
||||
StandardMaterial {
|
||||
base_color: Color::rgba_linear(color[0], color[1], color[2], color[3]),
|
||||
base_color_texture,
|
||||
|
@ -782,6 +832,19 @@ fn load_material(
|
|||
emissive: Color::rgb_linear(emissive[0], emissive[1], emissive[2])
|
||||
* material.emissive_strength().unwrap_or(1.0),
|
||||
emissive_texture,
|
||||
specular_transmission,
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
specular_transmission_texture,
|
||||
thickness,
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
thickness_texture,
|
||||
ior,
|
||||
attenuation_distance,
|
||||
attenuation_color: Color::rgb_linear(
|
||||
attenuation_color[0],
|
||||
attenuation_color[1],
|
||||
attenuation_color[2],
|
||||
),
|
||||
unlit: material.unlit(),
|
||||
alpha_mode: alpha_mode(material),
|
||||
..Default::default()
|
||||
|
|
|
@ -72,6 +72,9 @@ x11 = ["bevy_winit/x11"]
|
|||
# enable rendering of font glyphs using subpixel accuracy
|
||||
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
|
||||
|
||||
# Transmission textures in `StandardMaterial`:
|
||||
pbr_transmission_textures = ["bevy_pbr?/pbr_transmission_textures", "bevy_gltf?/pbr_transmission_textures"]
|
||||
|
||||
# Optimise for WebGL2
|
||||
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl", "bevy_gizmos?/webgl", "bevy_sprite?/webgl"]
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ keywords = ["bevy"]
|
|||
|
||||
[features]
|
||||
webgl = []
|
||||
pbr_transmission_textures = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
|
|
|
@ -199,6 +199,10 @@ impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
|
|||
B::depth_bias(&self.base)
|
||||
}
|
||||
|
||||
fn reads_view_transmission_texture(&self) -> bool {
|
||||
B::reads_view_transmission_texture(&self.base)
|
||||
}
|
||||
|
||||
fn opaque_render_method(&self) -> crate::OpaqueRendererMethod {
|
||||
B::opaque_render_method(&self.base)
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ pub const PBR_BINDINGS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(56
|
|||
pub const UTILS_HANDLE: Handle<Shader> = Handle::weak_from_u128(1900548483293416725);
|
||||
pub const CLUSTERED_FORWARD_HANDLE: Handle<Shader> = Handle::weak_from_u128(166852093121196815);
|
||||
pub const PBR_LIGHTING_HANDLE: Handle<Shader> = Handle::weak_from_u128(14170772752254856967);
|
||||
pub const PBR_TRANSMISSION_HANDLE: Handle<Shader> = Handle::weak_from_u128(77319684653223658032);
|
||||
pub const SHADOWS_HANDLE: Handle<Shader> = Handle::weak_from_u128(11350275143789590502);
|
||||
pub const SHADOW_SAMPLING_HANDLE: Handle<Shader> = Handle::weak_from_u128(3145627513789590502);
|
||||
pub const PBR_FRAGMENT_HANDLE: Handle<Shader> = Handle::weak_from_u128(2295049283805286543);
|
||||
|
@ -135,6 +136,12 @@ impl Plugin for PbrPlugin {
|
|||
"render/pbr_lighting.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
PBR_TRANSMISSION_HANDLE,
|
||||
"render/pbr_transmission.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
SHADOWS_HANDLE,
|
||||
|
|
|
@ -598,9 +598,23 @@ impl Default for AmbientLight {
|
|||
#[reflect(Component, Default)]
|
||||
pub struct NotShadowCaster;
|
||||
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not receive shadows.
|
||||
///
|
||||
/// **Note:** If you're using diffuse transmission, setting [`NotShadowReceiver`] will
|
||||
/// cause both “regular” shadows as well as diffusely transmitted shadows to be disabled,
|
||||
/// even when [`TransmittedShadowReceiver`] is being used.
|
||||
#[derive(Component, Reflect, Default)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct NotShadowReceiver;
|
||||
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) using a PBR material with [`diffuse_transmission`](crate::pbr_material::StandardMaterial::diffuse_transmission)`> 0.0`
|
||||
/// receive shadows on its diffuse transmission lobe. (i.e. its “backside”)
|
||||
///
|
||||
/// Not enabled by default, as it requires carefully setting up [`thickness`](crate::pbr_material::StandardMaterial::thickness)
|
||||
/// (and potentially even baking a thickness texture!) to match the geometry of the mesh, in order to avoid self-shadow artifacts.
|
||||
///
|
||||
/// **Note:** Using [`NotShadowReceiver`] overrides this component.
|
||||
#[derive(Component, Reflect, Default)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct TransmittedShadowReceiver;
|
||||
|
||||
/// Add this component to a [`Camera3d`](bevy_core_pipeline::core_3d::Camera3d)
|
||||
/// to control how to anti-alias shadow edges.
|
||||
|
|
|
@ -2,8 +2,10 @@ use crate::*;
|
|||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
|
||||
experimental::taa::TemporalAntiAliasSettings,
|
||||
core_3d::{
|
||||
AlphaMask3d, Camera3d, Opaque3d, ScreenSpaceTransmissionQuality, Transmissive3d,
|
||||
Transparent3d,
|
||||
},
|
||||
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
|
||||
tonemapping::{DebandDither, Tonemapping},
|
||||
};
|
||||
|
@ -15,6 +17,7 @@ use bevy_ecs::{
|
|||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{
|
||||
camera::Projection,
|
||||
camera::TemporalJitter,
|
||||
extract_instances::{ExtractInstancesPlugin, ExtractedInstances},
|
||||
extract_resource::ExtractResource,
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
|
@ -124,6 +127,15 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized {
|
|||
0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture).
|
||||
///
|
||||
/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
|
||||
/// rendering to take place in a separate [`Transmissive3d`] pass.
|
||||
fn reads_view_transmission_texture(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader
|
||||
/// will be used.
|
||||
///
|
||||
|
@ -203,6 +215,7 @@ where
|
|||
render_app
|
||||
.init_resource::<DrawFunctions<Shadow>>()
|
||||
.add_render_command::<Shadow, DrawPrepass<M>>()
|
||||
.add_render_command::<Transmissive3d, DrawMaterial<M>>()
|
||||
.add_render_command::<Transparent3d, DrawMaterial<M>>()
|
||||
.add_render_command::<Opaque3d, DrawMaterial<M>>()
|
||||
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
|
||||
|
@ -418,10 +431,30 @@ const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey {
|
|||
}
|
||||
}
|
||||
|
||||
const fn screen_space_specular_transmission_pipeline_key(
|
||||
screen_space_transmissive_blur_quality: ScreenSpaceTransmissionQuality,
|
||||
) -> MeshPipelineKey {
|
||||
match screen_space_transmissive_blur_quality {
|
||||
ScreenSpaceTransmissionQuality::Low => {
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW
|
||||
}
|
||||
ScreenSpaceTransmissionQuality::Medium => {
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM
|
||||
}
|
||||
ScreenSpaceTransmissionQuality::High => {
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH
|
||||
}
|
||||
ScreenSpaceTransmissionQuality::Ultra => {
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_material_meshes<M: Material>(
|
||||
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
|
||||
transmissive_draw_functions: Res<DrawFunctions<Transmissive3d>>,
|
||||
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
|
||||
material_pipeline: Res<MaterialPipeline<M>>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
|
||||
|
@ -446,10 +479,12 @@ pub fn queue_material_meshes<M: Material>(
|
|||
Has<MotionVectorPrepass>,
|
||||
Has<DeferredPrepass>,
|
||||
),
|
||||
Option<&TemporalAntiAliasSettings>,
|
||||
Option<&Camera3d>,
|
||||
Option<&TemporalJitter>,
|
||||
Option<&Projection>,
|
||||
&mut RenderPhase<Opaque3d>,
|
||||
&mut RenderPhase<AlphaMask3d>,
|
||||
&mut RenderPhase<Transmissive3d>,
|
||||
&mut RenderPhase<Transparent3d>,
|
||||
)>,
|
||||
) where
|
||||
|
@ -464,15 +499,18 @@ pub fn queue_material_meshes<M: Material>(
|
|||
shadow_filter_method,
|
||||
ssao,
|
||||
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
|
||||
taa_settings,
|
||||
camera_3d,
|
||||
temporal_jitter,
|
||||
projection,
|
||||
mut opaque_phase,
|
||||
mut alpha_mask_phase,
|
||||
mut transmissive_phase,
|
||||
mut transparent_phase,
|
||||
) in &mut views
|
||||
{
|
||||
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
|
||||
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial<M>>();
|
||||
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial<M>>();
|
||||
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial<M>>();
|
||||
|
||||
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
|
||||
|
@ -494,9 +532,10 @@ pub fn queue_material_meshes<M: Material>(
|
|||
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
||||
}
|
||||
|
||||
if taa_settings.is_some() {
|
||||
view_key |= MeshPipelineKey::TAA;
|
||||
if temporal_jitter.is_some() {
|
||||
view_key |= MeshPipelineKey::TEMPORAL_JITTER;
|
||||
}
|
||||
|
||||
let environment_map_loaded = environment_map.is_some_and(|map| map.is_loaded(&images));
|
||||
|
||||
if environment_map_loaded {
|
||||
|
@ -534,6 +573,11 @@ pub fn queue_material_meshes<M: Material>(
|
|||
if ssao.is_some() {
|
||||
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
|
||||
}
|
||||
if let Some(camera_3d) = camera_3d {
|
||||
view_key |= screen_space_specular_transmission_pipeline_key(
|
||||
camera_3d.screen_space_specular_transmission_quality,
|
||||
);
|
||||
}
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for visible_entity in &visible_entities.entities {
|
||||
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
||||
|
@ -588,7 +632,16 @@ pub fn queue_material_meshes<M: Material>(
|
|||
+ material.properties.depth_bias;
|
||||
match material.properties.alpha_mode {
|
||||
AlphaMode::Opaque => {
|
||||
if forward {
|
||||
if material.properties.reads_view_transmission_texture {
|
||||
transmissive_phase.add(Transmissive3d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_transmissive_pbr,
|
||||
pipeline: pipeline_id,
|
||||
distance,
|
||||
batch_range: 0..1,
|
||||
dynamic_offset: None,
|
||||
});
|
||||
} else if forward {
|
||||
opaque_phase.add(Opaque3d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_opaque_pbr,
|
||||
|
@ -600,7 +653,16 @@ pub fn queue_material_meshes<M: Material>(
|
|||
}
|
||||
}
|
||||
AlphaMode::Mask(_) => {
|
||||
if forward {
|
||||
if material.properties.reads_view_transmission_texture {
|
||||
transmissive_phase.add(Transmissive3d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_transmissive_pbr,
|
||||
pipeline: pipeline_id,
|
||||
distance,
|
||||
batch_range: 0..1,
|
||||
dynamic_offset: None,
|
||||
});
|
||||
} else if forward {
|
||||
alpha_mask_phase.add(AlphaMask3d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_alpha_mask_pbr,
|
||||
|
@ -688,6 +750,11 @@ pub struct MaterialProperties {
|
|||
/// for meshes with equal depth, to avoid z-fighting.
|
||||
/// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
|
||||
pub depth_bias: f32,
|
||||
/// Whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture).
|
||||
///
|
||||
/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
|
||||
/// rendering to take place in a separate [`Transmissive3d`] pass.
|
||||
pub reads_view_transmission_texture: bool,
|
||||
}
|
||||
|
||||
/// Data prepared for a [`Material`] instance.
|
||||
|
@ -863,6 +930,7 @@ fn prepare_material<M: Material>(
|
|||
properties: MaterialProperties {
|
||||
alpha_mode: material.alpha_mode(),
|
||||
depth_bias: material.depth_bias(),
|
||||
reads_view_transmission_texture: material.reads_view_transmission_texture(),
|
||||
render_method: method,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -138,6 +138,152 @@ pub struct StandardMaterial {
|
|||
#[doc(alias = "specular_intensity")]
|
||||
pub reflectance: f32,
|
||||
|
||||
/// The amount of light transmitted _diffusely_ through the material (i.e. “translucency”)
|
||||
///
|
||||
/// Implemented as a second, flipped [Lambertian diffuse](https://en.wikipedia.org/wiki/Lambertian_reflectance) lobe,
|
||||
/// which provides an inexpensive but plausible approximation of translucency for thin dieletric objects (e.g. paper,
|
||||
/// leaves, some fabrics) or thicker volumetric materials with short scattering distances (e.g. porcelain, wax).
|
||||
///
|
||||
/// For specular transmission usecases with refraction (e.g. glass) use the [`StandardMaterial::specular_transmission`] and
|
||||
/// [`StandardMaterial::ior`] properties instead.
|
||||
///
|
||||
/// - When set to `0.0` (the default) no diffuse light is transmitted;
|
||||
/// - When set to `1.0` all diffuse light is transmitted through the material;
|
||||
/// - Values higher than `0.5` will cause more diffuse light to be transmitted than reflected, resulting in a “darker”
|
||||
/// appearance on the side facing the light than the opposite side. (e.g. plant leaves)
|
||||
///
|
||||
/// ## Notes
|
||||
///
|
||||
/// - The material's [`StandardMaterial::base_color`] also modulates the transmitted light;
|
||||
/// - To receive transmitted shadows on the diffuse transmission lobe (i.e. the “backside”) of the material,
|
||||
/// use the [`TransmittedShadowReceiver`] component.
|
||||
#[doc(alias = "translucency")]
|
||||
pub diffuse_transmission: f32,
|
||||
|
||||
/// A map that modulates diffuse transmission via its alpha channel. Multiplied by [`StandardMaterial::diffuse_transmission`]
|
||||
/// to obtain the final result.
|
||||
///
|
||||
/// **Important:** The [`StandardMaterial::diffuse_transmission`] property must be set to a value higher than 0.0,
|
||||
/// or this texture won't have any effect.
|
||||
#[texture(17)]
|
||||
#[sampler(18)]
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
pub diffuse_transmission_texture: Option<Handle<Image>>,
|
||||
|
||||
/// The amount of light transmitted _specularly_ through the material (i.e. 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.
|
||||
///
|
||||
/// **Note:** Typically used in conjunction with [`StandardMaterial::thickness`], [`StandardMaterial::ior`] and [`StandardMaterial::perceptual_roughness`].
|
||||
///
|
||||
/// ## Performance
|
||||
///
|
||||
/// Specular transmission is implemented as a relatively expensive screen-space effect that allows ocluded objects to be seen through the material,
|
||||
/// with distortion and blur effects.
|
||||
///
|
||||
/// - [`Camera3d::screen_space_specular_transmission_steps`](bevy_core_pipeline::core_3d::Camera3d::screen_space_specular_transmission_steps) can be used to enable transmissive objects
|
||||
/// to be seen through other transmissive objects, at the cost of additional draw calls and texture copies; (Use with caution!)
|
||||
/// - If a simplified approximation of specular transmission using only environment map lighting is sufficient, consider setting
|
||||
/// [`Camera3d::screen_space_specular_transmission_steps`](bevy_core_pipeline::core_3d::Camera3d::screen_space_specular_transmission_steps) to `0`.
|
||||
/// - If purely diffuse light transmission is needed, (i.e. “translucency”) consider using [`StandardMaterial::diffuse_transmission`] instead,
|
||||
/// for a much less expensive effect.
|
||||
/// - Specular transmission is rendered before alpha blending, so any material with [`AlphaMode::Blend`], [`AlphaMode::Premultiplied`], [`AlphaMode::Add`] or [`AlphaMode::Multiply`]
|
||||
/// won't be visible through specular transmissive materials.
|
||||
#[doc(alias = "refraction")]
|
||||
pub specular_transmission: f32,
|
||||
|
||||
/// A map that modulates specular transmission via its red channel. Multiplied by [`StandardMaterial::specular_transmission`]
|
||||
/// to obtain the final result.
|
||||
///
|
||||
/// **Important:** The [`StandardMaterial::specular_transmission`] property must be set to a value higher than 0.0,
|
||||
/// or this texture won't have any effect.
|
||||
#[texture(13)]
|
||||
#[sampler(14)]
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
pub specular_transmission_texture: Option<Handle<Image>>,
|
||||
|
||||
/// 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 thick lens.
|
||||
///
|
||||
/// **Note:** Typically used in conjunction with [`StandardMaterial::specular_transmission`] and [`StandardMaterial::ior`], or with
|
||||
/// [`StandardMaterial::diffuse_transmission`].
|
||||
#[doc(alias = "volume")]
|
||||
#[doc(alias = "thin_walled")]
|
||||
pub thickness: f32,
|
||||
|
||||
/// A map that modulates thickness via its green channel. Multiplied by [`StandardMaterial::thickness`]
|
||||
/// to obtain the final result.
|
||||
///
|
||||
/// **Important:** The [`StandardMaterial::thickness`] property must be set to a value higher than 0.0,
|
||||
/// or this texture won't have any effect.
|
||||
#[texture(15)]
|
||||
#[sampler(16)]
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
pub thickness_texture: Option<Handle<Image>>,
|
||||
|
||||
/// The [index of refraction](https://en.wikipedia.org/wiki/Refractive_index) of the material.
|
||||
///
|
||||
/// Defaults to 1.5.
|
||||
///
|
||||
/// | 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::specular_transmission`] and [`StandardMaterial::thickness`].
|
||||
#[doc(alias = "index_of_refraction")]
|
||||
#[doc(alias = "refraction_index")]
|
||||
#[doc(alias = "refractive_index")]
|
||||
pub ior: f32,
|
||||
|
||||
/// How far, on average, light travels through the volume beneath the material's
|
||||
/// surface before being absorbed.
|
||||
///
|
||||
/// Defaults to [`f32::INFINITY`], i.e. light is never absorbed.
|
||||
///
|
||||
/// **Note:** To have any effect, must be used in conjunction with:
|
||||
/// - [`StandardMaterial::attenuation_color`];
|
||||
/// - [`StandardMaterial::thickness`];
|
||||
/// - [`StandardMaterial::diffuse_transmission`] or [`StandardMaterial::specular_transmission`].
|
||||
#[doc(alias = "absorption_distance")]
|
||||
#[doc(alias = "extinction_distance")]
|
||||
pub attenuation_distance: f32,
|
||||
|
||||
/// The resulting (non-absorbed) color after white light travels through the attenuation distance.
|
||||
///
|
||||
/// Defaults to [`Color::WHITE`], i.e. no change.
|
||||
///
|
||||
/// **Note:** To have any effect, must be used in conjunction with:
|
||||
/// - [`StandardMaterial::attenuation_distance`];
|
||||
/// - [`StandardMaterial::thickness`];
|
||||
/// - [`StandardMaterial::diffuse_transmission`] or [`StandardMaterial::specular_transmission`].
|
||||
#[doc(alias = "absorption_color")]
|
||||
#[doc(alias = "extinction_color")]
|
||||
pub attenuation_color: Color,
|
||||
|
||||
/// 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.
|
||||
|
@ -343,6 +489,18 @@ impl Default for StandardMaterial {
|
|||
// Expressed in a linear scale and equivalent to 4% reflectance see
|
||||
// <https://google.github.io/filament/Material%20Properties.pdf>
|
||||
reflectance: 0.5,
|
||||
diffuse_transmission: 0.0,
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
diffuse_transmission_texture: None,
|
||||
specular_transmission: 0.0,
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
specular_transmission_texture: None,
|
||||
thickness: 0.0,
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
thickness_texture: None,
|
||||
ior: 1.5,
|
||||
attenuation_color: Color::WHITE,
|
||||
attenuation_distance: f32::INFINITY,
|
||||
occlusion_texture: None,
|
||||
normal_map_texture: None,
|
||||
flip_normal_map_y: false,
|
||||
|
@ -401,6 +559,10 @@ bitflags::bitflags! {
|
|||
const FLIP_NORMAL_MAP_Y = (1 << 7);
|
||||
const FOG_ENABLED = (1 << 8);
|
||||
const DEPTH_MAP = (1 << 9); // Used for parallax mapping
|
||||
const SPECULAR_TRANSMISSION_TEXTURE = (1 << 10);
|
||||
const THICKNESS_TEXTURE = (1 << 11);
|
||||
const DIFFUSE_TRANSMISSION_TEXTURE = (1 << 12);
|
||||
const ATTENUATION_ENABLED = (1 << 13);
|
||||
const ALPHA_MODE_RESERVED_BITS = (Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS); // ← Bitmask reserving bits for the `AlphaMode`
|
||||
const ALPHA_MODE_OPAQUE = (0 << Self::ALPHA_MODE_SHIFT_BITS); // ← Values are just sequential values bitshifted into
|
||||
const ALPHA_MODE_MASK = (1 << Self::ALPHA_MODE_SHIFT_BITS); // the bitmask, and can range from 0 to 7.
|
||||
|
@ -435,6 +597,18 @@ 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 diffuse light transmitted through the material
|
||||
pub diffuse_transmission: f32,
|
||||
/// Amount of specular light transmitted through the material
|
||||
pub specular_transmission: f32,
|
||||
/// Thickness of the volume underneath the material surface
|
||||
pub thickness: f32,
|
||||
/// Index of Refraction
|
||||
pub ior: f32,
|
||||
/// How far light travels through the volume underneath the material surface before being absorbed
|
||||
pub attenuation_distance: f32,
|
||||
/// Color white light takes after travelling through the attenuation distance underneath the material surface
|
||||
pub attenuation_color: Vec4,
|
||||
/// 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,
|
||||
|
@ -481,6 +655,18 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||
if self.depth_map.is_some() {
|
||||
flags |= StandardMaterialFlags::DEPTH_MAP;
|
||||
}
|
||||
#[cfg(feature = "pbr_transmission_textures")]
|
||||
{
|
||||
if self.specular_transmission_texture.is_some() {
|
||||
flags |= StandardMaterialFlags::SPECULAR_TRANSMISSION_TEXTURE;
|
||||
}
|
||||
if self.thickness_texture.is_some() {
|
||||
flags |= StandardMaterialFlags::THICKNESS_TEXTURE;
|
||||
}
|
||||
if self.diffuse_transmission_texture.is_some() {
|
||||
flags |= StandardMaterialFlags::DIFFUSE_TRANSMISSION_TEXTURE;
|
||||
}
|
||||
}
|
||||
let has_normal_map = self.normal_map_texture.is_some();
|
||||
if has_normal_map {
|
||||
let normal_map_id = self.normal_map_texture.as_ref().map(|h| h.id()).unwrap();
|
||||
|
@ -514,12 +700,22 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||
AlphaMode::Multiply => flags |= StandardMaterialFlags::ALPHA_MODE_MULTIPLY,
|
||||
};
|
||||
|
||||
if self.attenuation_distance.is_finite() {
|
||||
flags |= StandardMaterialFlags::ATTENUATION_ENABLED;
|
||||
}
|
||||
|
||||
StandardMaterialUniform {
|
||||
base_color: self.base_color.as_linear_rgba_f32().into(),
|
||||
emissive: self.emissive.as_linear_rgba_f32().into(),
|
||||
roughness: self.perceptual_roughness,
|
||||
metallic: self.metallic,
|
||||
reflectance: self.reflectance,
|
||||
diffuse_transmission: self.diffuse_transmission,
|
||||
specular_transmission: self.specular_transmission,
|
||||
thickness: self.thickness,
|
||||
ior: self.ior,
|
||||
attenuation_distance: self.attenuation_distance,
|
||||
attenuation_color: self.attenuation_color.as_linear_rgba_f32().into(),
|
||||
flags: flags.bits(),
|
||||
alpha_cutoff,
|
||||
parallax_depth_scale: self.parallax_depth_scale,
|
||||
|
@ -602,8 +798,25 @@ impl Material for StandardMaterial {
|
|||
self.depth_bias
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reads_view_transmission_texture(&self) -> bool {
|
||||
self.specular_transmission > 0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn opaque_render_method(&self) -> OpaqueRendererMethod {
|
||||
self.opaque_render_method
|
||||
match self.opaque_render_method {
|
||||
// For now, diffuse transmission doesn't work under deferred rendering as we don't pack
|
||||
// the required data into the GBuffer. If this material is set to `Auto`, we report it as
|
||||
// `Forward` so that it's rendered correctly, even when the `DefaultOpaqueRendererMethod`
|
||||
// is set to `Deferred`.
|
||||
//
|
||||
// If the developer explicitly sets the `OpaqueRendererMethod` to `Deferred`, we assume
|
||||
// they know what they're doing and don't override it.
|
||||
OpaqueRendererMethod::Auto if self.diffuse_transmission > 0.0 => {
|
||||
OpaqueRendererMethod::Forward
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -818,6 +818,12 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
| AlphaMode::Multiply => continue,
|
||||
}
|
||||
|
||||
if material.properties.reads_view_transmission_texture {
|
||||
// No-op: Materials reading from `ViewTransmissionTexture` are not rendered in the `Opaque3d`
|
||||
// phase, and are therefore also excluded from the prepass much like alpha-blended materials.
|
||||
continue;
|
||||
}
|
||||
|
||||
let forward = match material.properties.render_method {
|
||||
OpaqueRendererMethod::Forward => true,
|
||||
OpaqueRendererMethod::Deferred => false,
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
#ifdef DEPTH_PREPASS
|
||||
fn prepass_depth(frag_coord: vec4<f32>, sample_index: u32) -> f32 {
|
||||
#ifdef MULTISAMPLED
|
||||
let depth_sample = textureLoad(view_bindings::depth_prepass_texture, vec2<i32>(frag_coord.xy), i32(sample_index));
|
||||
#else
|
||||
let depth_sample = textureLoad(view_bindings::depth_prepass_texture, vec2<i32>(frag_coord.xy), 0);
|
||||
#endif
|
||||
return depth_sample;
|
||||
return textureLoad(view_bindings::depth_prepass_texture, vec2<i32>(frag_coord.xy), i32(sample_index));
|
||||
#else // MULTISAMPLED
|
||||
return textureLoad(view_bindings::depth_prepass_texture, vec2<i32>(frag_coord.xy), 0);
|
||||
#endif // MULTISAMPLED
|
||||
}
|
||||
#endif // DEPTH_PREPASS
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use bevy_app::{Plugin, PostUpdate};
|
||||
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::{AlphaMask3d, Opaque3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
|
||||
core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
|
||||
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},
|
||||
};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
|
@ -132,6 +132,7 @@ impl Plugin for MeshRenderPlugin {
|
|||
(
|
||||
(
|
||||
batch_and_prepare_render_phase::<Opaque3d, MeshPipeline>,
|
||||
batch_and_prepare_render_phase::<Transmissive3d, MeshPipeline>,
|
||||
batch_and_prepare_render_phase::<Transparent3d, MeshPipeline>,
|
||||
batch_and_prepare_render_phase::<AlphaMask3d, MeshPipeline>,
|
||||
batch_and_prepare_render_phase::<Shadow, MeshPipeline>,
|
||||
|
@ -221,12 +222,13 @@ impl From<&MeshTransforms> for MeshUniform {
|
|||
bitflags::bitflags! {
|
||||
#[repr(transparent)]
|
||||
pub struct MeshFlags: u32 {
|
||||
const SHADOW_RECEIVER = (1 << 0);
|
||||
const SHADOW_RECEIVER = (1 << 0);
|
||||
const TRANSMITTED_SHADOW_RECEIVER = (1 << 1);
|
||||
// Indicates the sign of the determinant of the 3x3 model matrix. If the sign is positive,
|
||||
// then the flag should be set, else it should not be set.
|
||||
const SIGN_DETERMINANT_MODEL_3X3 = (1 << 31);
|
||||
const NONE = 0;
|
||||
const UNINITIALIZED = 0xFFFF;
|
||||
const SIGN_DETERMINANT_MODEL_3X3 = (1 << 31);
|
||||
const NONE = 0;
|
||||
const UNINITIALIZED = 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,6 +259,7 @@ pub fn extract_meshes(
|
|||
Option<&PreviousGlobalTransform>,
|
||||
&Handle<Mesh>,
|
||||
Has<NotShadowReceiver>,
|
||||
Has<TransmittedShadowReceiver>,
|
||||
Has<NotShadowCaster>,
|
||||
Has<NoAutomaticBatching>,
|
||||
)>,
|
||||
|
@ -270,6 +273,7 @@ pub fn extract_meshes(
|
|||
previous_transform,
|
||||
handle,
|
||||
not_receiver,
|
||||
transmitted_receiver,
|
||||
not_caster,
|
||||
no_automatic_batching,
|
||||
)| {
|
||||
|
@ -283,6 +287,9 @@ pub fn extract_meshes(
|
|||
} else {
|
||||
MeshFlags::SHADOW_RECEIVER
|
||||
};
|
||||
if transmitted_receiver {
|
||||
flags |= MeshFlags::TRANSMITTED_SHADOW_RECEIVER;
|
||||
}
|
||||
if transform.matrix3.determinant().is_sign_positive() {
|
||||
flags |= MeshFlags::SIGN_DETERMINANT_MODEL_3X3;
|
||||
}
|
||||
|
@ -487,7 +494,7 @@ bitflags::bitflags! {
|
|||
const ENVIRONMENT_MAP = (1 << 8);
|
||||
const SCREEN_SPACE_AMBIENT_OCCLUSION = (1 << 9);
|
||||
const DEPTH_CLAMP_ORTHO = (1 << 10);
|
||||
const TAA = (1 << 11);
|
||||
const TEMPORAL_JITTER = (1 << 11);
|
||||
const MORPH_TARGETS = (1 << 12);
|
||||
const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state
|
||||
const BLEND_OPAQUE = (0 << Self::BLEND_SHIFT_BITS); // ← Values are just sequential within the mask, and can range from 0 to 3
|
||||
|
@ -514,6 +521,11 @@ bitflags::bitflags! {
|
|||
const VIEW_PROJECTION_PERSPECTIVE = 1 << Self::VIEW_PROJECTION_SHIFT_BITS;
|
||||
const VIEW_PROJECTION_ORTHOGRAPHIC = 2 << Self::VIEW_PROJECTION_SHIFT_BITS;
|
||||
const VIEW_PROJECTION_RESERVED = 3 << Self::VIEW_PROJECTION_SHIFT_BITS;
|
||||
const SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS = Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
|
||||
const SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW = 0 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
|
||||
const SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM = 1 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
|
||||
const SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH = 2 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
|
||||
const SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA = 3 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -541,6 +553,10 @@ impl MeshPipelineKey {
|
|||
const VIEW_PROJECTION_SHIFT_BITS: u32 =
|
||||
Self::SHADOW_FILTER_METHOD_SHIFT_BITS - Self::VIEW_PROJECTION_MASK_BITS.count_ones();
|
||||
|
||||
const SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS: u32 = 0b11;
|
||||
const SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS: u32 = Self::VIEW_PROJECTION_SHIFT_BITS
|
||||
- Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS.count_ones();
|
||||
|
||||
pub fn from_msaa_samples(msaa_samples: u32) -> Self {
|
||||
let msaa_bits =
|
||||
(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
|
||||
|
@ -661,6 +677,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(5));
|
||||
}
|
||||
|
||||
if cfg!(feature = "pbr_transmission_textures") {
|
||||
shader_defs.push("PBR_TRANSMISSION_TEXTURES_SUPPORTED".into());
|
||||
}
|
||||
|
||||
let mut bind_group_layout = vec![self.get_view_layout(key.into()).clone()];
|
||||
|
||||
if key.msaa_samples() > 1 {
|
||||
|
@ -794,8 +814,8 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||
shader_defs.push("ENVIRONMENT_MAP".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::TAA) {
|
||||
shader_defs.push("TAA".into());
|
||||
if key.contains(MeshPipelineKey::TEMPORAL_JITTER) {
|
||||
shader_defs.push("TEMPORAL_JITTER".into());
|
||||
}
|
||||
|
||||
let shadow_filter_method =
|
||||
|
@ -808,6 +828,20 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||
shader_defs.push("SHADOW_FILTER_METHOD_JIMENEZ_14".into());
|
||||
}
|
||||
|
||||
let blur_quality =
|
||||
key.intersection(MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS);
|
||||
|
||||
shader_defs.push(ShaderDefVal::Int(
|
||||
"SCREEN_SPACE_SPECULAR_TRANSMISSION_BLUR_TAPS".into(),
|
||||
match blur_quality {
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW => 4,
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM => 8,
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH => 16,
|
||||
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA => 32,
|
||||
_ => unreachable!(), // Not possible, since the mask is 2 bits, and we've covered all 4 cases
|
||||
},
|
||||
));
|
||||
|
||||
let format = if key.contains(MeshPipelineKey::HDR) {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
|
|
|
@ -29,5 +29,6 @@ struct MorphWeights {
|
|||
#endif
|
||||
|
||||
const MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u;
|
||||
const MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT: u32 = 2u;
|
||||
// 2^31 - if the flag is set, the sign is positive, else it is negative
|
||||
const MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT: u32 = 2147483648u;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::array;
|
||||
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::ViewTransmissionTexture,
|
||||
prepass::ViewPrepassTextures,
|
||||
tonemapping::{
|
||||
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
|
||||
|
@ -20,7 +21,7 @@ use bevy_render::{
|
|||
TextureFormat, TextureSampleType, TextureViewDimension,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::{BevyDefault, FallbackImageCubemap, FallbackImageMsaa, Image},
|
||||
texture::{BevyDefault, FallbackImageCubemap, FallbackImageMsaa, FallbackImageZero, Image},
|
||||
view::{Msaa, ViewUniform, ViewUniforms},
|
||||
};
|
||||
|
||||
|
@ -295,6 +296,7 @@ fn layout_entries(
|
|||
let tonemapping_lut_entries = get_lut_bind_group_layout_entries([15, 16]);
|
||||
entries.extend_from_slice(&tonemapping_lut_entries);
|
||||
|
||||
// Prepass
|
||||
if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|
||||
|| (cfg!(all(feature = "webgl", target_arch = "wasm32"))
|
||||
&& !layout_key.contains(MeshPipelineViewLayoutKey::MULTISAMPLED))
|
||||
|
@ -305,6 +307,26 @@ fn layout_entries(
|
|||
));
|
||||
}
|
||||
|
||||
// View Transmission Texture
|
||||
entries.extend_from_slice(&[
|
||||
BindGroupLayoutEntry {
|
||||
binding: 21,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
sample_type: TextureSampleType::Float { filterable: true },
|
||||
multisampled: false,
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 22,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
]);
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
|
@ -356,13 +378,15 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
&ViewClusterBindings,
|
||||
Option<&ScreenSpaceAmbientOcclusionTextures>,
|
||||
Option<&ViewPrepassTextures>,
|
||||
Option<&ViewTransmissionTexture>,
|
||||
Option<&EnvironmentMapLight>,
|
||||
&Tonemapping,
|
||||
)>,
|
||||
(images, mut fallback_images, fallback_cubemap): (
|
||||
(images, mut fallback_images, fallback_cubemap, fallback_image_zero): (
|
||||
Res<RenderAssets<Image>>,
|
||||
FallbackImageMsaa,
|
||||
Res<FallbackImageCubemap>,
|
||||
Res<FallbackImageZero>,
|
||||
),
|
||||
msaa: Res<Msaa>,
|
||||
globals_buffer: Res<GlobalsBuffer>,
|
||||
|
@ -387,6 +411,7 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
cluster_bindings,
|
||||
ssao_textures,
|
||||
prepass_textures,
|
||||
transmission_texture,
|
||||
environment_map,
|
||||
tonemapping,
|
||||
) in &views
|
||||
|
@ -443,7 +468,18 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
{
|
||||
entries = entries.extend_with_indices(((index, binding),));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let transmission_view = transmission_texture
|
||||
.map(|transmission| &transmission.view)
|
||||
.unwrap_or(&fallback_image_zero.texture_view);
|
||||
|
||||
let transmission_sampler = transmission_texture
|
||||
.map(|transmission| &transmission.sampler)
|
||||
.unwrap_or(&fallback_image_zero.sampler);
|
||||
|
||||
entries =
|
||||
entries.extend_with_indices(((21, transmission_view), (22, transmission_sampler)));
|
||||
|
||||
commands.entity(entity).insert(MeshViewBindGroup {
|
||||
value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries),
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
@group(0) @binding(16) var dt_lut_sampler: sampler;
|
||||
|
||||
#ifdef MULTISAMPLED
|
||||
|
||||
#ifdef DEPTH_PREPASS
|
||||
@group(0) @binding(17) var depth_prepass_texture: texture_depth_multisampled_2d;
|
||||
#endif // DEPTH_PREPASS
|
||||
|
@ -72,3 +71,6 @@
|
|||
#ifdef DEFERRED_PREPASS
|
||||
@group(0) @binding(20) var deferred_prepass_texture: texture_2d<u32>;
|
||||
#endif // DEFERRED_PREPASS
|
||||
|
||||
@group(0) @binding(21) var view_transmission_texture: texture_2d<f32>;
|
||||
@group(0) @binding(22) var view_transmission_sampler: sampler;
|
||||
|
|
|
@ -15,3 +15,11 @@
|
|||
@group(1) @binding(10) var normal_map_sampler: sampler;
|
||||
@group(1) @binding(11) var depth_map_texture: texture_2d<f32>;
|
||||
@group(1) @binding(12) var depth_map_sampler: sampler;
|
||||
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
@group(1) @binding(13) var specular_transmission_texture: texture_2d<f32>;
|
||||
@group(1) @binding(14) var specular_transmission_sampler: sampler;
|
||||
@group(1) @binding(15) var thickness_texture: texture_2d<f32>;
|
||||
@group(1) @binding(16) var thickness_sampler: sampler;
|
||||
@group(1) @binding(17) var diffuse_transmission_texture: texture_2d<f32>;
|
||||
@group(1) @binding(18) var diffuse_transmission_sampler: sampler;
|
||||
#endif
|
||||
|
|
|
@ -100,8 +100,10 @@ fn pbr_input_from_standard_material(
|
|||
|
||||
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
||||
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
||||
|
||||
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
|
||||
pbr_input.material.ior = pbr_bindings::material.ior;
|
||||
pbr_input.material.attenuation_color = pbr_bindings::material.attenuation_color;
|
||||
pbr_input.material.attenuation_distance = pbr_bindings::material.attenuation_distance;
|
||||
pbr_input.material.alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
||||
|
||||
// emissive
|
||||
|
@ -128,6 +130,34 @@ fn pbr_input_from_standard_material(
|
|||
pbr_input.material.metallic = metallic;
|
||||
pbr_input.material.perceptual_roughness = perceptual_roughness;
|
||||
|
||||
var specular_transmission: f32 = pbr_bindings::material.specular_transmission;
|
||||
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_SPECULAR_TRANSMISSION_TEXTURE_BIT) != 0u) {
|
||||
specular_transmission *= textureSample(pbr_bindings::specular_transmission_texture, pbr_bindings::specular_transmission_sampler, uv).r;
|
||||
}
|
||||
#endif
|
||||
pbr_input.material.specular_transmission = specular_transmission;
|
||||
|
||||
var thickness: f32 = pbr_bindings::material.thickness;
|
||||
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_THICKNESS_TEXTURE_BIT) != 0u) {
|
||||
thickness *= textureSample(pbr_bindings::thickness_texture, pbr_bindings::thickness_sampler, uv).g;
|
||||
}
|
||||
#endif
|
||||
// scale thickness, accounting for non-uniform scaling (e.g. a “squished” mesh)
|
||||
thickness *= length(
|
||||
(transpose(mesh[in.instance_index].model) * vec4(pbr_input.N, 0.0)).xyz
|
||||
);
|
||||
pbr_input.material.thickness = thickness;
|
||||
|
||||
var diffuse_transmission = pbr_bindings::material.diffuse_transmission;
|
||||
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DIFFUSE_TRANSMISSION_TEXTURE_BIT) != 0u) {
|
||||
diffuse_transmission *= textureSample(pbr_bindings::diffuse_transmission_texture, pbr_bindings::diffuse_transmission_sampler, uv).a;
|
||||
}
|
||||
#endif
|
||||
pbr_input.material.diffuse_transmission = diffuse_transmission;
|
||||
|
||||
// occlusion
|
||||
// TODO: Split into diffuse/specular occlusion?
|
||||
var occlusion: vec3<f32> = vec3(1.0);
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
mesh_view_bindings as view_bindings,
|
||||
mesh_view_types,
|
||||
lighting,
|
||||
transmission,
|
||||
clustered_forward as clustering,
|
||||
shadows,
|
||||
ambient,
|
||||
mesh_types::MESH_FLAGS_SHADOW_RECEIVER_BIT,
|
||||
mesh_types::{MESH_FLAGS_SHADOW_RECEIVER_BIT, MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT},
|
||||
utils::E,
|
||||
}
|
||||
|
||||
#ifdef ENVIRONMENT_MAP
|
||||
|
@ -18,7 +20,6 @@
|
|||
|
||||
#import bevy_core_pipeline::tonemapping::{screen_space_dither, powsafe, tone_mapping}
|
||||
|
||||
|
||||
fn alpha_discard(material: pbr_types::StandardMaterial, output_color: vec4<f32>) -> vec4<f32> {
|
||||
var color = output_color;
|
||||
let alpha_mode = material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
|
||||
|
@ -156,6 +157,12 @@ fn apply_pbr_lighting(
|
|||
let metallic = in.material.metallic;
|
||||
let perceptual_roughness = in.material.perceptual_roughness;
|
||||
let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);
|
||||
let ior = in.material.ior;
|
||||
let thickness = in.material.thickness;
|
||||
let diffuse_transmission = in.material.diffuse_transmission;
|
||||
let specular_transmission = in.material.specular_transmission;
|
||||
|
||||
let specular_transmissive_color = specular_transmission * in.material.base_color.rgb;
|
||||
|
||||
let occlusion = in.occlusion;
|
||||
|
||||
|
@ -167,8 +174,14 @@ fn apply_pbr_lighting(
|
|||
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 is inversely related to metallicity, specular and diffuse transmission
|
||||
let diffuse_color = output_color.rgb * (1.0 - metallic) * (1.0 - specular_transmission) * (1.0 - diffuse_transmission);
|
||||
|
||||
// Diffuse transmissive strength is inversely related to metallicity and specular transmission, but directly related to diffuse transmission
|
||||
let diffuse_transmissive_color = output_color.rgb * (1.0 - metallic) * (1.0 - specular_transmission) * diffuse_transmission;
|
||||
|
||||
// Calculate the world position of the second Lambertian lobe used for diffuse transmission, by subtracting material thickness
|
||||
let diffuse_transmissive_lobe_world_position = in.world_position - vec4<f32>(in.world_normal, 0.0) * thickness;
|
||||
|
||||
let R = reflect(-in.V, in.N);
|
||||
|
||||
|
@ -176,6 +189,9 @@ fn apply_pbr_lighting(
|
|||
|
||||
var direct_light: vec3<f32> = vec3<f32>(0.0);
|
||||
|
||||
// Transmitted Light (Specular and Diffuse)
|
||||
var transmitted_light: vec3<f32> = vec3<f32>(0.0);
|
||||
|
||||
let view_z = dot(vec4<f32>(
|
||||
view_bindings::view.inverse_view[0].z,
|
||||
view_bindings::view.inverse_view[1].z,
|
||||
|
@ -195,6 +211,25 @@ fn apply_pbr_lighting(
|
|||
}
|
||||
let light_contrib = lighting::point_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color);
|
||||
direct_light += light_contrib * shadow;
|
||||
|
||||
if diffuse_transmission > 0.0 {
|
||||
// NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
|
||||
// world position, inverted normal and view vectors, and the following simplified
|
||||
// values for a fully diffuse transmitted light contribution approximation:
|
||||
//
|
||||
// roughness = 1.0;
|
||||
// NdotV = 1.0;
|
||||
// R = vec3<f32>(0.0) // doesn't really matter
|
||||
// f_ab = vec2<f32>(0.1)
|
||||
// F0 = vec3<f32>(0.0)
|
||||
var transmitted_shadow: f32 = 1.0;
|
||||
if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)
|
||||
&& (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal);
|
||||
}
|
||||
let light_contrib = lighting::point_light(diffuse_transmissive_lobe_world_position.xyz, light_id, 1.0, 1.0, -in.N, -in.V, vec3<f32>(0.0), vec3<f32>(0.0), vec2<f32>(0.1), diffuse_transmissive_color);
|
||||
transmitted_light += light_contrib * transmitted_shadow;
|
||||
}
|
||||
}
|
||||
|
||||
// Spot lights (direct)
|
||||
|
@ -208,6 +243,25 @@ fn apply_pbr_lighting(
|
|||
}
|
||||
let light_contrib = lighting::spot_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, f_ab, diffuse_color);
|
||||
direct_light += light_contrib * shadow;
|
||||
|
||||
if diffuse_transmission > 0.0 {
|
||||
// NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
|
||||
// world position, inverted normal and view vectors, and the following simplified
|
||||
// values for a fully diffuse transmitted light contribution approximation:
|
||||
//
|
||||
// roughness = 1.0;
|
||||
// NdotV = 1.0;
|
||||
// R = vec3<f32>(0.0) // doesn't really matter
|
||||
// f_ab = vec2<f32>(0.1)
|
||||
// F0 = vec3<f32>(0.0)
|
||||
var transmitted_shadow: f32 = 1.0;
|
||||
if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)
|
||||
&& (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
transmitted_shadow = shadows::fetch_spot_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal);
|
||||
}
|
||||
let light_contrib = lighting::spot_light(diffuse_transmissive_lobe_world_position.xyz, light_id, 1.0, 1.0, -in.N, -in.V, vec3<f32>(0.0), vec3<f32>(0.0), vec2<f32>(0.1), diffuse_transmissive_color);
|
||||
transmitted_light += light_contrib * transmitted_shadow;
|
||||
}
|
||||
}
|
||||
|
||||
// directional lights (direct)
|
||||
|
@ -223,22 +277,104 @@ fn apply_pbr_lighting(
|
|||
light_contrib = shadows::cascade_debug_visualization(light_contrib, i, view_z);
|
||||
#endif
|
||||
direct_light += light_contrib * shadow;
|
||||
|
||||
if diffuse_transmission > 0.0 {
|
||||
// NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
|
||||
// world position, inverted normal and view vectors, and the following simplified
|
||||
// values for a fully diffuse transmitted light contribution approximation:
|
||||
//
|
||||
// roughness = 1.0;
|
||||
// NdotV = 1.0;
|
||||
// R = vec3<f32>(0.0) // doesn't really matter
|
||||
// f_ab = vec2<f32>(0.1)
|
||||
// F0 = vec3<f32>(0.0)
|
||||
var transmitted_shadow: f32 = 1.0;
|
||||
if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)
|
||||
&& (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
transmitted_shadow = shadows::fetch_directional_shadow(i, diffuse_transmissive_lobe_world_position, -in.world_normal, view_z);
|
||||
}
|
||||
let light_contrib = lighting::directional_light(i, 1.0, 1.0, -in.N, -in.V, vec3<f32>(0.0), vec3<f32>(0.0), vec2<f32>(0.1), diffuse_transmissive_color);
|
||||
transmitted_light += light_contrib * transmitted_shadow;
|
||||
}
|
||||
}
|
||||
|
||||
// Ambient light (indirect)
|
||||
var indirect_light = ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, occlusion);
|
||||
|
||||
if diffuse_transmission > 0.0 {
|
||||
// NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
|
||||
// world position, inverted normal and view vectors, and the following simplified
|
||||
// values for a fully diffuse transmitted light contribution approximation:
|
||||
//
|
||||
// perceptual_roughness = 1.0;
|
||||
// NdotV = 1.0;
|
||||
// F0 = vec3<f32>(0.0)
|
||||
// occlusion = vec3<f32>(1.0)
|
||||
transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3<f32>(0.0), 1.0, vec3<f32>(1.0));
|
||||
}
|
||||
|
||||
// Environment map light (indirect)
|
||||
#ifdef ENVIRONMENT_MAP
|
||||
let environment_light = environment_map::environment_map_light(perceptual_roughness, roughness, diffuse_color, NdotV, f_ab, in.N, R, F0);
|
||||
indirect_light += (environment_light.diffuse * occlusion) + environment_light.specular;
|
||||
|
||||
// we'll use the specular component of the transmitted environment
|
||||
// light in the call to `specular_transmissive_light()` below
|
||||
var specular_transmitted_environment_light = vec3<f32>(0.0);
|
||||
|
||||
if diffuse_transmission > 0.0 || specular_transmission > 0.0 {
|
||||
// NOTE: We use the diffuse transmissive color, inverted normal and view vectors,
|
||||
// and the following simplified values for the transmitted environment light contribution
|
||||
// approximation:
|
||||
//
|
||||
// diffuse_color = vec3<f32>(1.0) // later we use `diffuse_transmissive_color` and `specular_transmissive_color`
|
||||
// NdotV = 1.0;
|
||||
// R = T // see definition below
|
||||
// F0 = vec3<f32>(1.0)
|
||||
// occlusion = 1.0
|
||||
//
|
||||
// (This one is slightly different from the other light types above, because the environment
|
||||
// map light returns both diffuse and specular components separately, and we want to use both)
|
||||
|
||||
let T = -normalize(
|
||||
in.V + // start with view vector at entry point
|
||||
refract(in.V, -in.N, 1.0 / ior) * thickness // add refracted vector scaled by thickness, towards exit point
|
||||
); // normalize to find exit point view vector
|
||||
|
||||
let transmitted_environment_light = bevy_pbr::environment_map::environment_map_light(perceptual_roughness, roughness, vec3<f32>(1.0), 1.0, f_ab, -in.N, T, vec3<f32>(1.0));
|
||||
transmitted_light += transmitted_environment_light.diffuse * diffuse_transmissive_color;
|
||||
specular_transmitted_environment_light = transmitted_environment_light.specular * specular_transmissive_color;
|
||||
}
|
||||
#else
|
||||
// If there's no environment map light, there's no transmitted environment
|
||||
// light specular component, so we can just hardcode it to zero.
|
||||
let specular_transmitted_environment_light = vec3<f32>(0.0);
|
||||
#endif
|
||||
|
||||
let emissive_light = emissive.rgb * output_color.a;
|
||||
|
||||
if specular_transmission > 0.0 {
|
||||
transmitted_light += transmission::specular_transmissive_light(in.world_position, in.frag_coord.xyz, view_z, in.N, in.V, F0, ior, thickness, perceptual_roughness, specular_transmissive_color, specular_transmitted_environment_light).rgb;
|
||||
}
|
||||
|
||||
if (in.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ATTENUATION_ENABLED_BIT) != 0u {
|
||||
// We reuse the `atmospheric_fog()` function here, as it's fundamentally
|
||||
// equivalent to the attenuation that takes place inside the material volume,
|
||||
// and will allow us to eventually hook up subsurface scattering more easily
|
||||
var attenuation_fog: mesh_view_types::Fog;
|
||||
attenuation_fog.base_color.a = 1.0;
|
||||
attenuation_fog.be = pow(1.0 - in.material.attenuation_color.rgb, vec3<f32>(E)) / in.material.attenuation_distance;
|
||||
// TODO: Add the subsurface scattering factor below
|
||||
// attenuation_fog.bi = /* ... */
|
||||
transmitted_light = bevy_pbr::fog::atmospheric_fog(
|
||||
attenuation_fog, vec4<f32>(transmitted_light, 1.0), thickness,
|
||||
vec3<f32>(0.0) // TODO: Pass in (pre-attenuated) scattered light contribution here
|
||||
).rgb;
|
||||
}
|
||||
|
||||
// Total light
|
||||
output_color = vec4<f32>(
|
||||
direct_light + indirect_light + emissive_light,
|
||||
transmitted_light + direct_light + indirect_light + emissive_light,
|
||||
output_color.a
|
||||
);
|
||||
|
||||
|
|
181
crates/bevy_pbr/src/render/pbr_transmission.wgsl
Normal file
181
crates/bevy_pbr/src/render/pbr_transmission.wgsl
Normal file
|
@ -0,0 +1,181 @@
|
|||
#define_import_path bevy_pbr::transmission
|
||||
|
||||
#import bevy_pbr::{
|
||||
lighting,
|
||||
prepass_utils,
|
||||
utils::{PI, interleaved_gradient_noise},
|
||||
utils,
|
||||
mesh_view_bindings as view_bindings,
|
||||
};
|
||||
|
||||
#import bevy_core_pipeline::tonemapping::{
|
||||
approximate_inverse_tone_mapping
|
||||
};
|
||||
|
||||
fn specular_transmissive_light(world_position: vec4<f32>, frag_coord: vec3<f32>, view_z: f32, N: vec3<f32>, V: vec3<f32>, F0: vec3<f32>, ior: f32, thickness: f32, perceptual_roughness: f32, specular_transmissive_color: vec3<f32>, transmitted_environment_light_specular: vec3<f32>) -> vec3<f32> {
|
||||
// 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;
|
||||
|
||||
// Calculate the exit position of the refracted ray, by propagating refacted direction through thickness
|
||||
let exit_position = world_position.xyz + T * thickness;
|
||||
|
||||
// Transform exit_position into clip space
|
||||
let clip_exit_position = view_bindings::view.view_proj * vec4<f32>(exit_position, 1.0);
|
||||
|
||||
// Scale / offset position so that coordinate is in right space for sampling transmissive background texture
|
||||
let offset_position = (clip_exit_position.xy / clip_exit_position.w) * vec2<f32>(0.5, -0.5) + 0.5;
|
||||
|
||||
// Fetch background color
|
||||
var background_color: vec4<f32>;
|
||||
if perceptual_roughness == 0.0 {
|
||||
// If the material has zero roughness, we can use a faster approach without the blur
|
||||
background_color = fetch_transmissive_background_non_rough(offset_position, frag_coord);
|
||||
} else {
|
||||
background_color = fetch_transmissive_background(offset_position, frag_coord, view_z, perceptual_roughness);
|
||||
}
|
||||
|
||||
// Dot product of the refracted direction with the exit normal (Note: We assume the exit normal is the entry normal but inverted)
|
||||
let MinusNdotT = dot(-N, T);
|
||||
|
||||
// Calculate 1.0 - fresnel factor (how much light is _NOT_ reflected, i.e. how much is transmitted)
|
||||
let F = vec3(1.0) - lighting::fresnel(F0, MinusNdotT);
|
||||
|
||||
// Calculate final color by applying fresnel multiplied specular transmissive color to a mix of background color and transmitted specular environment light
|
||||
return F * specular_transmissive_color * mix(transmitted_environment_light_specular, background_color.rgb, background_color.a);
|
||||
}
|
||||
|
||||
fn fetch_transmissive_background_non_rough(offset_position: vec2<f32>, frag_coord: vec3<f32>) -> vec4<f32> {
|
||||
var background_color = textureSample(
|
||||
view_bindings::view_transmission_texture,
|
||||
view_bindings::view_transmission_sampler,
|
||||
offset_position,
|
||||
);
|
||||
|
||||
#ifdef DEPTH_PREPASS
|
||||
// Use depth prepass data to reject values that are in front of the current fragment
|
||||
if prepass_utils::prepass_depth(vec4<f32>(offset_position * view_bindings::view.viewport.zw, 0.0, 0.0), 0u) > frag_coord.z {
|
||||
background_color.a = 0.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TONEMAP_IN_SHADER
|
||||
background_color = approximate_inverse_tone_mapping(background_color, view_bindings::view.color_grading);
|
||||
#endif
|
||||
|
||||
return background_color;
|
||||
}
|
||||
|
||||
fn fetch_transmissive_background(offset_position: vec2<f32>, frag_coord: vec3<f32>, view_z: f32, perceptual_roughness: f32) -> vec4<f32> {
|
||||
// Calculate view aspect ratio, used to scale offset so that it's proportionate
|
||||
let aspect = view_bindings::view.viewport.z / view_bindings::view.viewport.w;
|
||||
|
||||
// Calculate how “blurry” the transmission should be.
|
||||
// Blur is more or less eyeballed to look approximately “right”, since the “correct”
|
||||
// approach would involve projecting many scattered rays and figuring out their individual
|
||||
// exit positions. IRL, light rays can be scattered when entering/exiting a material (due to
|
||||
// roughness) or inside the material (due to subsurface scattering). Here, we only consider
|
||||
// the first scenario.
|
||||
//
|
||||
// Blur intensity is:
|
||||
// - proportional to the square of `perceptual_roughness`
|
||||
// - proportional to the inverse of view z
|
||||
let blur_intensity = (perceptual_roughness * perceptual_roughness) / view_z;
|
||||
|
||||
#ifdef SCREEN_SPACE_SPECULAR_TRANSMISSION_BLUR_TAPS
|
||||
let num_taps = #{SCREEN_SPACE_SPECULAR_TRANSMISSION_BLUR_TAPS}; // Controlled by the `Camera3d::screen_space_specular_transmission_quality` property
|
||||
#else
|
||||
let num_taps = 8; // Fallback to 8 taps, if not specified
|
||||
#endif
|
||||
let num_spirals = i32(ceil(f32(num_taps) / 8.0));
|
||||
#ifdef TEMPORAL_JITTER
|
||||
let random_angle = interleaved_gradient_noise(frag_coord.xy, view_bindings::globals.frame_count);
|
||||
#else
|
||||
let random_angle = interleaved_gradient_noise(frag_coord.xy, 0u);
|
||||
#endif
|
||||
// Pixel checkerboard pattern (helps make the interleaved gradient noise pattern less visible)
|
||||
let pixel_checkboard = (
|
||||
#ifdef TEMPORAL_JITTER
|
||||
// 0 or 1 on even/odd pixels, alternates every frame
|
||||
(i32(frag_coord.x) + i32(frag_coord.y) + i32(view_bindings::globals.frame_count)) % 2
|
||||
#else
|
||||
// 0 or 1 on even/odd pixels
|
||||
(i32(frag_coord.x) + i32(frag_coord.y)) % 2
|
||||
#endif
|
||||
);
|
||||
|
||||
var result = vec4<f32>(0.0);
|
||||
for (var i: i32 = 0; i < num_taps; i = i + 1) {
|
||||
let current_spiral = (i >> 3u);
|
||||
let angle = (random_angle + f32(current_spiral) / f32(num_spirals)) * 2.0 * PI;
|
||||
let m = vec2(sin(angle), cos(angle));
|
||||
let rotation_matrix = mat2x2(
|
||||
m.y, -m.x,
|
||||
m.x, m.y
|
||||
);
|
||||
|
||||
// Get spiral offset
|
||||
var spiral_offset: vec2<f32>;
|
||||
switch i & 7 {
|
||||
// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135)
|
||||
// TODO: Figure out a more reasonable way of doing this, as WGSL
|
||||
// seems to only allow constant indexes into constant arrays at the moment.
|
||||
// The downstream shader compiler should be able to optimize this into a single
|
||||
// constant when unrolling the for loop, but it's still not ideal.
|
||||
case 0: { spiral_offset = utils::SPIRAL_OFFSET_0_; } // Note: We go even first and then odd, so that the lowest
|
||||
case 1: { spiral_offset = utils::SPIRAL_OFFSET_2_; } // quality possible (which does 4 taps) still does a full spiral
|
||||
case 2: { spiral_offset = utils::SPIRAL_OFFSET_4_; } // instead of just the first half of it
|
||||
case 3: { spiral_offset = utils::SPIRAL_OFFSET_6_; }
|
||||
case 4: { spiral_offset = utils::SPIRAL_OFFSET_1_; }
|
||||
case 5: { spiral_offset = utils::SPIRAL_OFFSET_3_; }
|
||||
case 6: { spiral_offset = utils::SPIRAL_OFFSET_5_; }
|
||||
case 7: { spiral_offset = utils::SPIRAL_OFFSET_7_; }
|
||||
default: {}
|
||||
}
|
||||
|
||||
// Make each consecutive spiral slightly smaller than the previous one
|
||||
spiral_offset *= 1.0 - (0.5 * f32(current_spiral + 1) / f32(num_spirals));
|
||||
|
||||
// Rotate and correct for aspect ratio
|
||||
let rotated_spiral_offset = (rotation_matrix * spiral_offset) * vec2(1.0, aspect);
|
||||
|
||||
// Calculate final offset position, with blur and spiral offset
|
||||
let modified_offset_position = offset_position + rotated_spiral_offset * blur_intensity * (1.0 - f32(pixel_checkboard) * 0.1);
|
||||
|
||||
// Sample the view transmission texture at the offset position + noise offset, to get the background color
|
||||
var sample = textureSample(
|
||||
view_bindings::view_transmission_texture,
|
||||
view_bindings::view_transmission_sampler,
|
||||
modified_offset_position,
|
||||
);
|
||||
|
||||
#ifdef DEPTH_PREPASS
|
||||
// Use depth prepass data to reject values that are in front of the current fragment
|
||||
if prepass_utils::prepass_depth(vec4<f32>(modified_offset_position * view_bindings::view.viewport.zw, 0.0, 0.0), 0u) > frag_coord.z {
|
||||
sample = vec4<f32>(0.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
// As blur intensity grows higher, gradually limit *very bright* color RGB values towards a
|
||||
// maximum length of 1.0 to prevent stray “firefly” pixel artifacts. This can potentially make
|
||||
// very strong emissive meshes appear much dimmer, but the artifacts are noticeable enough to
|
||||
// warrant this treatment.
|
||||
let normalized_rgb = normalize(sample.rgb);
|
||||
result += vec4(min(sample.rgb, normalized_rgb / saturate(blur_intensity / 2.0)), sample.a);
|
||||
}
|
||||
|
||||
result /= f32(num_taps);
|
||||
|
||||
#ifdef TONEMAP_IN_SHADER
|
||||
result = approximate_inverse_tone_mapping(result, view_bindings::view.color_grading);
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
|
@ -6,6 +6,12 @@ struct StandardMaterial {
|
|||
perceptual_roughness: f32,
|
||||
metallic: f32,
|
||||
reflectance: f32,
|
||||
diffuse_transmission: f32,
|
||||
specular_transmission: f32,
|
||||
thickness: f32,
|
||||
ior: f32,
|
||||
attenuation_distance: f32,
|
||||
attenuation_color: vec4<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,
|
||||
|
@ -30,6 +36,10 @@ const STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 64u;
|
|||
const STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 128u;
|
||||
const STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT: u32 = 256u;
|
||||
const STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT: u32 = 512u;
|
||||
const STANDARD_MATERIAL_FLAGS_SPECULAR_TRANSMISSION_TEXTURE_BIT: u32 = 1024u;
|
||||
const STANDARD_MATERIAL_FLAGS_THICKNESS_TEXTURE_BIT: u32 = 2048u;
|
||||
const STANDARD_MATERIAL_FLAGS_DIFFUSE_TRANSMISSION_TEXTURE_BIT: u32 = 4096u;
|
||||
const STANDARD_MATERIAL_FLAGS_ATTENUATION_ENABLED_BIT: u32 = 8192u;
|
||||
const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS: u32 = 3758096384u; // (0b111u32 << 29)
|
||||
const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0u32 << 29)
|
||||
const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 536870912u; // (1u32 << 29)
|
||||
|
@ -51,6 +61,12 @@ fn standard_material_new() -> StandardMaterial {
|
|||
material.perceptual_roughness = 0.5;
|
||||
material.metallic = 0.00;
|
||||
material.reflectance = 0.5;
|
||||
material.diffuse_transmission = 0.0;
|
||||
material.specular_transmission = 0.0;
|
||||
material.thickness = 0.0;
|
||||
material.ior = 1.5;
|
||||
material.attenuation_distance = 1.0;
|
||||
material.attenuation_color = vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||
material.flags = STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE;
|
||||
material.alpha_cutoff = 0.5;
|
||||
material.parallax_depth_scale = 0.1;
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
#import bevy_pbr::{
|
||||
mesh_view_bindings as view_bindings,
|
||||
utils::PI,
|
||||
utils::{PI, interleaved_gradient_noise},
|
||||
utils,
|
||||
}
|
||||
|
||||
// Do the lookup, using HW 2x2 PCF and comparison
|
||||
|
@ -70,13 +71,6 @@ fn sample_shadow_map_castano_thirteen(light_local: vec2<f32>, depth: f32, array_
|
|||
return sum * (1.0 / 144.0);
|
||||
}
|
||||
|
||||
// https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence
|
||||
fn interleaved_gradient_noise(pixel_coordinates: vec2<f32>) -> f32 {
|
||||
let frame = f32(view_bindings::globals.frame_count % 64u);
|
||||
let xy = pixel_coordinates + 5.588238 * frame;
|
||||
return fract(52.9829189 * fract(0.06711056 * xy.x + 0.00583715 * xy.y));
|
||||
}
|
||||
|
||||
fn map(min1: f32, max1: f32, min2: f32, max2: f32, value: f32) -> f32 {
|
||||
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
||||
}
|
||||
|
@ -84,7 +78,7 @@ fn map(min1: f32, max1: f32, min2: f32, max2: f32, value: f32) -> f32 {
|
|||
fn sample_shadow_map_jimenez_fourteen(light_local: vec2<f32>, depth: f32, array_index: i32, texel_size: f32) -> f32 {
|
||||
let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
|
||||
|
||||
let random_angle = 2.0 * PI * interleaved_gradient_noise(light_local * shadow_map_size);
|
||||
let random_angle = 2.0 * PI * interleaved_gradient_noise(light_local * shadow_map_size, view_bindings::globals.frame_count);
|
||||
let m = vec2(sin(random_angle), cos(random_angle));
|
||||
let rotation_matrix = mat2x2(
|
||||
m.y, -m.x,
|
||||
|
@ -96,14 +90,14 @@ fn sample_shadow_map_jimenez_fourteen(light_local: vec2<f32>, depth: f32, array_
|
|||
let uv_offset_scale = f / (texel_size * shadow_map_size);
|
||||
|
||||
// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135)
|
||||
let sample_offset1 = (rotation_matrix * vec2(-0.7071, 0.7071)) * uv_offset_scale;
|
||||
let sample_offset2 = (rotation_matrix * vec2(-0.0000, -0.8750)) * uv_offset_scale;
|
||||
let sample_offset3 = (rotation_matrix * vec2( 0.5303, 0.5303)) * uv_offset_scale;
|
||||
let sample_offset4 = (rotation_matrix * vec2(-0.6250, -0.0000)) * uv_offset_scale;
|
||||
let sample_offset5 = (rotation_matrix * vec2( 0.3536, -0.3536)) * uv_offset_scale;
|
||||
let sample_offset6 = (rotation_matrix * vec2(-0.0000, 0.3750)) * uv_offset_scale;
|
||||
let sample_offset7 = (rotation_matrix * vec2(-0.1768, -0.1768)) * uv_offset_scale;
|
||||
let sample_offset8 = (rotation_matrix * vec2( 0.1250, 0.0000)) * uv_offset_scale;
|
||||
let sample_offset1 = (rotation_matrix * utils::SPIRAL_OFFSET_0_) * uv_offset_scale;
|
||||
let sample_offset2 = (rotation_matrix * utils::SPIRAL_OFFSET_1_) * uv_offset_scale;
|
||||
let sample_offset3 = (rotation_matrix * utils::SPIRAL_OFFSET_2_) * uv_offset_scale;
|
||||
let sample_offset4 = (rotation_matrix * utils::SPIRAL_OFFSET_3_) * uv_offset_scale;
|
||||
let sample_offset5 = (rotation_matrix * utils::SPIRAL_OFFSET_4_) * uv_offset_scale;
|
||||
let sample_offset6 = (rotation_matrix * utils::SPIRAL_OFFSET_5_) * uv_offset_scale;
|
||||
let sample_offset7 = (rotation_matrix * utils::SPIRAL_OFFSET_6_) * uv_offset_scale;
|
||||
let sample_offset8 = (rotation_matrix * utils::SPIRAL_OFFSET_7_) * uv_offset_scale;
|
||||
|
||||
var sum = 0.0;
|
||||
sum += sample_shadow_map_hardware(light_local + sample_offset1, depth, array_index);
|
||||
|
|
|
@ -48,3 +48,22 @@ fn octahedral_decode(v: vec2<f32>) -> vec3<f32> {
|
|||
n = vec3(n.xy + w, n.z);
|
||||
return normalize(n);
|
||||
}
|
||||
|
||||
// https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence
|
||||
fn interleaved_gradient_noise(pixel_coordinates: vec2<f32>, frame: u32) -> f32 {
|
||||
let xy = pixel_coordinates + 5.588238 * f32(frame % 64u);
|
||||
return fract(52.9829189 * fract(0.06711056 * xy.x + 0.00583715 * xy.y));
|
||||
}
|
||||
|
||||
// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135)
|
||||
// TODO: Use an array here instead of a bunch of constants, once arrays work properly under DX12.
|
||||
// NOTE: The names have a final underscore to avoid the following error:
|
||||
// `Composable module identifiers must not require substitution according to naga writeback rules`
|
||||
const SPIRAL_OFFSET_0_ = vec2<f32>(-0.7071, 0.7071);
|
||||
const SPIRAL_OFFSET_1_ = vec2<f32>(-0.0000, -0.8750);
|
||||
const SPIRAL_OFFSET_2_ = vec2<f32>( 0.5303, 0.5303);
|
||||
const SPIRAL_OFFSET_3_ = vec2<f32>(-0.6250, -0.0000);
|
||||
const SPIRAL_OFFSET_4_ = vec2<f32>( 0.3536, -0.3536);
|
||||
const SPIRAL_OFFSET_5_ = vec2<f32>(-0.0000, 0.3750);
|
||||
const SPIRAL_OFFSET_6_ = vec2<f32>(-0.1768, -0.1768);
|
||||
const SPIRAL_OFFSET_7_ = vec2<f32>( 0.1250, 0.0000);
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
fn load_noise(pixel_coordinates: vec2<i32>) -> vec2<f32> {
|
||||
var index = textureLoad(hilbert_index_lut, pixel_coordinates % 64, 0).r;
|
||||
|
||||
#ifdef TEMPORAL_NOISE
|
||||
#ifdef TEMPORAL_JITTER
|
||||
index += 288u * (globals.frame_count % 64u);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -560,7 +560,7 @@ impl FromWorld for SsaoPipelines {
|
|||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
struct SsaoPipelineKey {
|
||||
ssao_settings: ScreenSpaceAmbientOcclusionSettings,
|
||||
temporal_noise: bool,
|
||||
temporal_jitter: bool,
|
||||
}
|
||||
|
||||
impl SpecializedComputePipeline for SsaoPipelines {
|
||||
|
@ -577,8 +577,8 @@ impl SpecializedComputePipeline for SsaoPipelines {
|
|||
),
|
||||
];
|
||||
|
||||
if key.temporal_noise {
|
||||
shader_defs.push("TEMPORAL_NOISE".into());
|
||||
if key.temporal_jitter {
|
||||
shader_defs.push("TEMPORAL_JITTER".into());
|
||||
}
|
||||
|
||||
ComputePipelineDescriptor {
|
||||
|
@ -731,7 +731,7 @@ fn prepare_ssao_pipelines(
|
|||
&pipeline,
|
||||
SsaoPipelineKey {
|
||||
ssao_settings: ssao_settings.clone(),
|
||||
temporal_noise: temporal_jitter.is_some(),
|
||||
temporal_jitter: temporal_jitter.is_some(),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -97,7 +97,11 @@ pub fn batch_and_prepare_render_phase<I: CachedRenderPipelinePhaseItem, F: GetBa
|
|||
*item.batch_range_mut() = index..index + 1;
|
||||
*item.dynamic_offset_mut() = buffer_index.dynamic_offset;
|
||||
|
||||
compare_data.map(|compare_data| BatchMeta::new(item, compare_data))
|
||||
if I::AUTOMATIC_BATCHING {
|
||||
compare_data.map(|compare_data| BatchMeta::new(item, compare_data))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
for mut phase in &mut views {
|
||||
|
|
|
@ -139,6 +139,9 @@ pub trait PhaseItem: Sized + Send + Sync + 'static {
|
|||
/// based on the view-space `Z` value of the corresponding view matrix.
|
||||
type SortKey: Ord;
|
||||
|
||||
/// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`)
|
||||
const AUTOMATIC_BATCHING: bool = true;
|
||||
|
||||
/// The corresponding entity that will be drawn.
|
||||
///
|
||||
/// This is used to fetch the render data of the entity, required by the draw function,
|
||||
|
|
|
@ -61,6 +61,7 @@ The default feature set enables most of the expected features of a game engine,
|
|||
|jpeg|JPEG image format support|
|
||||
|minimp3|MP3 audio format support (through minimp3)|
|
||||
|mp3|MP3 audio format support|
|
||||
|pbr_transmission_textures|Enable support for transmission-related textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs|
|
||||
|pnm|PNM image format support, includes pam, pbm, pgm and ppm|
|
||||
|serialize|Enable serialization support through serde|
|
||||
|shader_format_glsl|Enable support for shaders in GLSL|
|
||||
|
|
664
examples/3d/transmission.rs
Normal file
664
examples/3d/transmission.rs
Normal file
|
@ -0,0 +1,664 @@
|
|||
//! This example showcases light transmission
|
||||
//!
|
||||
//! ## Controls
|
||||
//!
|
||||
//! | Key Binding | Action |
|
||||
//! |:-------------------|:-----------------------------------------------------|
|
||||
//! | `J`/`K`/`L`/`;` | Change Screen Space Transmission Quality |
|
||||
//! | `O` / `P` | Decrease / Increase Screen Space Transmission Steps |
|
||||
//! | `1` / `2` | Decrease / Increase Diffuse Transmission |
|
||||
//! | `Q` / `W` | Decrease / Increase Specular Transmission |
|
||||
//! | `A` / `S` | Decrease / Increase Thickness |
|
||||
//! | `Z` / `X` | Decrease / Increase IOR |
|
||||
//! | `E` / `R` | Decrease / Increase Perceptual Roughness |
|
||||
//! | `U` / `I` | Decrease / Increase Reflectance |
|
||||
//! | Arrow Keys | Control Camera |
|
||||
//! | `C` | Randomize Colors |
|
||||
//! | `H` | Toggle HDR + Bloom |
|
||||
//! | `D` | Toggle Depth Prepass |
|
||||
//! | `T` | Toggle TAA |
|
||||
|
||||
// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind
|
||||
// type aliases tends to obfuscate code while offering no improvement in code cleanliness.
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::{
|
||||
bloom::BloomSettings, core_3d::ScreenSpaceTransmissionQuality, prepass::DepthPrepass,
|
||||
tonemapping::Tonemapping,
|
||||
},
|
||||
pbr::{NotShadowCaster, PointLightShadowMap, TransmittedShadowReceiver},
|
||||
prelude::*,
|
||||
render::camera::TemporalJitter,
|
||||
render::view::ColorGrading,
|
||||
};
|
||||
|
||||
#[cfg(not(all(feature = "webgl2", target_arch = "wasm32")))]
|
||||
use bevy::core_pipeline::experimental::taa::{
|
||||
TemporalAntiAliasBundle, TemporalAntiAliasPlugin, TemporalAntiAliasSettings,
|
||||
};
|
||||
|
||||
use rand::random;
|
||||
|
||||
fn main() {
|
||||
let mut app = App::new();
|
||||
|
||||
app.add_plugins(DefaultPlugins)
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.insert_resource(PointLightShadowMap { size: 2048 })
|
||||
.insert_resource(AmbientLight {
|
||||
brightness: 0.0,
|
||||
..default()
|
||||
})
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (example_control_system, flicker_system));
|
||||
|
||||
// *Note:* TAA is not _required_ for specular transmission, but
|
||||
// it _greatly enhances_ the look of the resulting blur effects.
|
||||
// Sadly, it's not available under WebGL.
|
||||
#[cfg(not(all(feature = "webgl2", target_arch = "wasm32")))]
|
||||
app.insert_resource(Msaa::Off)
|
||||
.add_plugins(TemporalAntiAliasPlugin);
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let icosphere_mesh = meshes.add(
|
||||
Mesh::try_from(shape::Icosphere {
|
||||
radius: 0.9,
|
||||
subdivisions: 7,
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let cube_mesh = meshes.add(Mesh::from(shape::Cube { size: 0.7 }));
|
||||
|
||||
let plane_mesh = meshes.add(shape::Plane::from_size(2.0).into());
|
||||
|
||||
let cylinder_mesh = meshes.add(
|
||||
Mesh::try_from(shape::Cylinder {
|
||||
radius: 0.5,
|
||||
height: 2.0,
|
||||
resolution: 50,
|
||||
segments: 1,
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// Cube #1
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: cube_mesh.clone(),
|
||||
material: materials.add(StandardMaterial { ..default() }),
|
||||
transform: Transform::from_xyz(0.25, 0.5, -2.0).with_rotation(Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
1.4,
|
||||
3.7,
|
||||
21.3,
|
||||
)),
|
||||
..default()
|
||||
},
|
||||
ExampleControls {
|
||||
color: true,
|
||||
specular_transmission: false,
|
||||
diffuse_transmission: false,
|
||||
},
|
||||
));
|
||||
|
||||
// Cube #2
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: cube_mesh,
|
||||
material: materials.add(StandardMaterial { ..default() }),
|
||||
transform: Transform::from_xyz(-0.75, 0.7, -2.0).with_rotation(Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
0.4,
|
||||
2.3,
|
||||
4.7,
|
||||
)),
|
||||
..default()
|
||||
},
|
||||
ExampleControls {
|
||||
color: true,
|
||||
specular_transmission: false,
|
||||
diffuse_transmission: false,
|
||||
},
|
||||
));
|
||||
|
||||
// Candle
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: cylinder_mesh,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::rgba(0.9, 0.2, 0.3, 1.0),
|
||||
diffuse_transmission: 0.7,
|
||||
perceptual_roughness: 0.32,
|
||||
thickness: 0.2,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(-1.0, 0.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
ExampleControls {
|
||||
color: true,
|
||||
specular_transmission: false,
|
||||
diffuse_transmission: true,
|
||||
},
|
||||
));
|
||||
|
||||
// Candle Flame
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: icosphere_mesh.clone(),
|
||||
material: materials.add(StandardMaterial {
|
||||
emissive: Color::ANTIQUE_WHITE * 20.0 + Color::ORANGE_RED * 4.0,
|
||||
diffuse_transmission: 1.0,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(-1.0, 1.15, 0.0).with_scale(Vec3::new(0.1, 0.2, 0.1)),
|
||||
..default()
|
||||
},
|
||||
Flicker,
|
||||
NotShadowCaster,
|
||||
));
|
||||
|
||||
// Glass Sphere
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: icosphere_mesh.clone(),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
specular_transmission: 0.9,
|
||||
diffuse_transmission: 1.0,
|
||||
thickness: 1.8,
|
||||
ior: 1.5,
|
||||
perceptual_roughness: 0.12,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(1.0, 0.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
ExampleControls {
|
||||
color: true,
|
||||
specular_transmission: true,
|
||||
diffuse_transmission: false,
|
||||
},
|
||||
));
|
||||
|
||||
// R Sphere
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: icosphere_mesh.clone(),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::RED,
|
||||
specular_transmission: 0.9,
|
||||
diffuse_transmission: 1.0,
|
||||
thickness: 1.8,
|
||||
ior: 1.5,
|
||||
perceptual_roughness: 0.12,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(1.0, -0.5, 2.0).with_scale(Vec3::splat(0.5)),
|
||||
..default()
|
||||
},
|
||||
ExampleControls {
|
||||
color: true,
|
||||
specular_transmission: true,
|
||||
diffuse_transmission: false,
|
||||
},
|
||||
));
|
||||
|
||||
// G Sphere
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: icosphere_mesh.clone(),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::GREEN,
|
||||
specular_transmission: 0.9,
|
||||
diffuse_transmission: 1.0,
|
||||
thickness: 1.8,
|
||||
ior: 1.5,
|
||||
perceptual_roughness: 0.12,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(0.0, -0.5, 2.0).with_scale(Vec3::splat(0.5)),
|
||||
..default()
|
||||
},
|
||||
ExampleControls {
|
||||
color: true,
|
||||
specular_transmission: true,
|
||||
diffuse_transmission: false,
|
||||
},
|
||||
));
|
||||
|
||||
// B Sphere
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: icosphere_mesh,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
specular_transmission: 0.9,
|
||||
diffuse_transmission: 1.0,
|
||||
thickness: 1.8,
|
||||
ior: 1.5,
|
||||
perceptual_roughness: 0.12,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(-1.0, -0.5, 2.0).with_scale(Vec3::splat(0.5)),
|
||||
..default()
|
||||
},
|
||||
ExampleControls {
|
||||
color: true,
|
||||
specular_transmission: true,
|
||||
diffuse_transmission: false,
|
||||
},
|
||||
));
|
||||
|
||||
// Chessboard Plane
|
||||
let black_material = materials.add(StandardMaterial {
|
||||
base_color: Color::BLACK,
|
||||
reflectance: 0.3,
|
||||
perceptual_roughness: 0.8,
|
||||
..default()
|
||||
});
|
||||
|
||||
let white_material = materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
reflectance: 0.3,
|
||||
perceptual_roughness: 0.8,
|
||||
..default()
|
||||
});
|
||||
|
||||
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,
|
||||
specular_transmission: false,
|
||||
diffuse_transmission: false,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Paper
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: plane_mesh,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
diffuse_transmission: 0.6,
|
||||
perceptual_roughness: 0.8,
|
||||
reflectance: 1.0,
|
||||
double_sided: true,
|
||||
cull_mode: None,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(0.0, 0.5, -3.0)
|
||||
.with_scale(Vec3::new(2.0, 1.0, 1.0))
|
||||
.with_rotation(Quat::from_euler(EulerRot::XYZ, PI / 2.0, 0.0, 0.0)),
|
||||
..default()
|
||||
},
|
||||
TransmittedShadowReceiver,
|
||||
ExampleControls {
|
||||
specular_transmission: false,
|
||||
color: false,
|
||||
diffuse_transmission: true,
|
||||
},
|
||||
));
|
||||
|
||||
// Candle Light
|
||||
commands.spawn((
|
||||
PointLightBundle {
|
||||
transform: Transform::from_xyz(-1.0, 1.7, 0.0),
|
||||
point_light: PointLight {
|
||||
color: Color::ANTIQUE_WHITE * 0.8 + Color::ORANGE_RED * 0.2,
|
||||
intensity: 1600.0,
|
||||
radius: 0.2,
|
||||
range: 5.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
Flicker,
|
||||
));
|
||||
|
||||
// Camera
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
camera: Camera {
|
||||
hdr: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(1.0, 1.8, 7.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
color_grading: ColorGrading {
|
||||
exposure: -2.0,
|
||||
post_saturation: 1.2,
|
||||
..default()
|
||||
},
|
||||
tonemapping: Tonemapping::TonyMcMapface,
|
||||
..default()
|
||||
},
|
||||
#[cfg(not(all(feature = "webgl2", target_arch = "wasm32")))]
|
||||
TemporalAntiAliasBundle::default(),
|
||||
EnvironmentMapLight {
|
||||
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
|
||||
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
|
||||
},
|
||||
BloomSettings::default(),
|
||||
));
|
||||
|
||||
// Controls Text
|
||||
let text_style = TextStyle {
|
||||
font_size: 18.0,
|
||||
color: Color::WHITE,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
TextBundle::from_section("", text_style).with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
..default()
|
||||
}),
|
||||
ExampleDisplay,
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Flicker;
|
||||
|
||||
#[derive(Component)]
|
||||
struct ExampleControls {
|
||||
diffuse_transmission: bool,
|
||||
specular_transmission: bool,
|
||||
color: bool,
|
||||
}
|
||||
|
||||
struct ExampleState {
|
||||
diffuse_transmission: f32,
|
||||
specular_transmission: f32,
|
||||
thickness: f32,
|
||||
ior: f32,
|
||||
perceptual_roughness: f32,
|
||||
reflectance: f32,
|
||||
auto_camera: bool,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ExampleDisplay;
|
||||
|
||||
impl Default for ExampleState {
|
||||
fn default() -> Self {
|
||||
ExampleState {
|
||||
diffuse_transmission: 0.5,
|
||||
specular_transmission: 0.9,
|
||||
thickness: 1.8,
|
||||
ior: 1.5,
|
||||
perceptual_roughness: 0.12,
|
||||
reflectance: 0.5,
|
||||
auto_camera: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn example_control_system(
|
||||
mut commands: Commands,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
controllable: Query<(&Handle<StandardMaterial>, &ExampleControls)>,
|
||||
mut camera: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut Camera,
|
||||
&mut Camera3d,
|
||||
&mut Transform,
|
||||
Option<&DepthPrepass>,
|
||||
Option<&TemporalJitter>,
|
||||
),
|
||||
With<Camera3d>,
|
||||
>,
|
||||
mut display: Query<&mut Text, With<ExampleDisplay>>,
|
||||
mut state: Local<ExampleState>,
|
||||
time: Res<Time>,
|
||||
input: Res<Input<KeyCode>>,
|
||||
) {
|
||||
if input.pressed(KeyCode::Key2) {
|
||||
state.diffuse_transmission = (state.diffuse_transmission + time.delta_seconds()).min(1.0);
|
||||
} else if input.pressed(KeyCode::Key1) {
|
||||
state.diffuse_transmission = (state.diffuse_transmission - time.delta_seconds()).max(0.0);
|
||||
}
|
||||
|
||||
if input.pressed(KeyCode::W) {
|
||||
state.specular_transmission = (state.specular_transmission + time.delta_seconds()).min(1.0);
|
||||
} else if input.pressed(KeyCode::Q) {
|
||||
state.specular_transmission = (state.specular_transmission - time.delta_seconds()).max(0.0);
|
||||
}
|
||||
|
||||
if input.pressed(KeyCode::S) {
|
||||
state.thickness = (state.thickness + time.delta_seconds()).min(5.0);
|
||||
} else if input.pressed(KeyCode::A) {
|
||||
state.thickness = (state.thickness - time.delta_seconds()).max(0.0);
|
||||
}
|
||||
|
||||
if input.pressed(KeyCode::X) {
|
||||
state.ior = (state.ior + time.delta_seconds()).min(3.0);
|
||||
} else if input.pressed(KeyCode::Z) {
|
||||
state.ior = (state.ior - time.delta_seconds()).max(1.0);
|
||||
}
|
||||
|
||||
if input.pressed(KeyCode::I) {
|
||||
state.reflectance = (state.reflectance + time.delta_seconds()).min(1.0);
|
||||
} else if input.pressed(KeyCode::U) {
|
||||
state.reflectance = (state.reflectance - time.delta_seconds()).max(0.0);
|
||||
}
|
||||
|
||||
if input.pressed(KeyCode::R) {
|
||||
state.perceptual_roughness = (state.perceptual_roughness + time.delta_seconds()).min(1.0);
|
||||
} else if input.pressed(KeyCode::E) {
|
||||
state.perceptual_roughness = (state.perceptual_roughness - time.delta_seconds()).max(0.0);
|
||||
}
|
||||
|
||||
let randomize_colors = input.just_pressed(KeyCode::C);
|
||||
|
||||
for (material_handle, controls) in &controllable {
|
||||
let material = materials.get_mut(material_handle).unwrap();
|
||||
if controls.specular_transmission {
|
||||
material.specular_transmission = state.specular_transmission;
|
||||
material.thickness = state.thickness;
|
||||
material.ior = state.ior;
|
||||
material.perceptual_roughness = state.perceptual_roughness;
|
||||
material.reflectance = state.reflectance;
|
||||
}
|
||||
|
||||
if controls.diffuse_transmission {
|
||||
material.diffuse_transmission = state.diffuse_transmission;
|
||||
}
|
||||
|
||||
if controls.color && randomize_colors {
|
||||
material.base_color.set_r(random());
|
||||
material.base_color.set_g(random());
|
||||
material.base_color.set_b(random());
|
||||
}
|
||||
}
|
||||
|
||||
let (
|
||||
camera_entity,
|
||||
mut camera,
|
||||
mut camera_3d,
|
||||
mut camera_transform,
|
||||
depth_prepass,
|
||||
temporal_jitter,
|
||||
) = camera.single_mut();
|
||||
|
||||
if input.just_pressed(KeyCode::H) {
|
||||
camera.hdr = !camera.hdr;
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "webgl2", target_arch = "wasm32")))]
|
||||
if input.just_pressed(KeyCode::D) {
|
||||
if depth_prepass.is_none() {
|
||||
commands.entity(camera_entity).insert(DepthPrepass);
|
||||
} else {
|
||||
commands.entity(camera_entity).remove::<DepthPrepass>();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "webgl2", target_arch = "wasm32")))]
|
||||
if input.just_pressed(KeyCode::T) {
|
||||
if temporal_jitter.is_none() {
|
||||
commands.entity(camera_entity).insert((
|
||||
TemporalJitter::default(),
|
||||
TemporalAntiAliasSettings::default(),
|
||||
));
|
||||
} else {
|
||||
commands
|
||||
.entity(camera_entity)
|
||||
.remove::<(TemporalJitter, TemporalAntiAliasSettings)>();
|
||||
}
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::O) && camera_3d.screen_space_specular_transmission_steps > 0 {
|
||||
camera_3d.screen_space_specular_transmission_steps -= 1;
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::P) && camera_3d.screen_space_specular_transmission_steps < 4 {
|
||||
camera_3d.screen_space_specular_transmission_steps += 1;
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::J) {
|
||||
camera_3d.screen_space_specular_transmission_quality = ScreenSpaceTransmissionQuality::Low;
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::K) {
|
||||
camera_3d.screen_space_specular_transmission_quality =
|
||||
ScreenSpaceTransmissionQuality::Medium;
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::L) {
|
||||
camera_3d.screen_space_specular_transmission_quality = ScreenSpaceTransmissionQuality::High;
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::Semicolon) {
|
||||
camera_3d.screen_space_specular_transmission_quality =
|
||||
ScreenSpaceTransmissionQuality::Ultra;
|
||||
}
|
||||
|
||||
let rotation = if input.pressed(KeyCode::Right) {
|
||||
state.auto_camera = false;
|
||||
time.delta_seconds()
|
||||
} else if input.pressed(KeyCode::Left) {
|
||||
state.auto_camera = false;
|
||||
-time.delta_seconds()
|
||||
} else if state.auto_camera {
|
||||
time.delta_seconds() * 0.25
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let distance_change =
|
||||
if input.pressed(KeyCode::Down) && camera_transform.translation.length() < 25.0 {
|
||||
time.delta_seconds()
|
||||
} else if input.pressed(KeyCode::Up) && camera_transform.translation.length() > 2.0 {
|
||||
-time.delta_seconds()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
camera_transform.translation *= distance_change.exp();
|
||||
|
||||
camera_transform.rotate_around(
|
||||
Vec3::ZERO,
|
||||
Quat::from_euler(EulerRot::XYZ, 0.0, rotation, 0.0),
|
||||
);
|
||||
|
||||
let mut display = display.single_mut();
|
||||
display.sections[0].value = format!(
|
||||
concat!(
|
||||
" J / K / L / ; Screen Space Specular Transmissive Quality: {:?}\n",
|
||||
" O / P Screen Space Specular Transmissive Steps: {}\n",
|
||||
" 1 / 2 Diffuse Transmission: {:.2}\n",
|
||||
" Q / W Specular Transmission: {:.2}\n",
|
||||
" A / S Thickness: {:.2}\n",
|
||||
" Z / X IOR: {:.2}\n",
|
||||
" E / R Perceptual Roughness: {:.2}\n",
|
||||
" U / I Reflectance: {:.2}\n",
|
||||
" Arrow Keys Control Camera\n",
|
||||
" C Randomize Colors\n",
|
||||
" H HDR + Bloom: {}\n",
|
||||
" D Depth Prepass: {}\n",
|
||||
" T TAA: {}\n",
|
||||
),
|
||||
camera_3d.screen_space_specular_transmission_quality,
|
||||
camera_3d.screen_space_specular_transmission_steps,
|
||||
state.diffuse_transmission,
|
||||
state.specular_transmission,
|
||||
state.thickness,
|
||||
state.ior,
|
||||
state.perceptual_roughness,
|
||||
state.reflectance,
|
||||
if camera.hdr { "ON " } else { "OFF" },
|
||||
if cfg!(any(not(feature = "webgl2"), not(target_arch = "wasm32"))) {
|
||||
if depth_prepass.is_some() {
|
||||
"ON "
|
||||
} else {
|
||||
"OFF"
|
||||
}
|
||||
} else {
|
||||
"N/A (WebGL)"
|
||||
},
|
||||
if cfg!(any(not(feature = "webgl2"), not(target_arch = "wasm32"))) {
|
||||
if temporal_jitter.is_some() {
|
||||
if depth_prepass.is_some() {
|
||||
"ON "
|
||||
} else {
|
||||
"N/A (Needs Depth Prepass)"
|
||||
}
|
||||
} else {
|
||||
"OFF"
|
||||
}
|
||||
} else {
|
||||
"N/A (WebGL)"
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn flicker_system(
|
||||
mut flame: Query<&mut Transform, (With<Flicker>, With<Handle<Mesh>>)>,
|
||||
mut light: Query<(&mut PointLight, &mut Transform), (With<Flicker>, Without<Handle<Mesh>>)>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let s = time.elapsed_seconds();
|
||||
let a = (s * 6.0).cos() * 0.0125 + (s * 4.0).cos() * 0.025;
|
||||
let b = (s * 5.0).cos() * 0.0125 + (s * 3.0).cos() * 0.025;
|
||||
let c = (s * 7.0).cos() * 0.0125 + (s * 2.0).cos() * 0.025;
|
||||
let (mut light, mut light_transform) = light.single_mut();
|
||||
let mut flame_transform = flame.single_mut();
|
||||
light.intensity = 1600.0 + 3000.0 * (a + b + c);
|
||||
flame_transform.translation = Vec3::new(-1.0, 1.23, 0.0);
|
||||
flame_transform.look_at(Vec3::new(-1.0 - c, 1.7 - b, 0.0 - a), Vec3::X);
|
||||
flame_transform.rotate(Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, PI / 2.0));
|
||||
light_transform.translation = Vec3::new(-1.0 - c, 1.7, 0.0 - a);
|
||||
flame_transform.translation = Vec3::new(-1.0 - c, 1.23, 0.0 - a);
|
||||
}
|
|
@ -142,6 +142,7 @@ Example | Description
|
|||
[Spotlight](../examples/3d/spotlight.rs) | Illustrates spot lights
|
||||
[Texture](../examples/3d/texture.rs) | Shows configuration of texture materials
|
||||
[Tonemapping](../examples/3d/tonemapping.rs) | Compares tonemapping options
|
||||
[Transmission](../examples/3d/transmission.rs) | Showcases light transmission in the PBR material
|
||||
[Transparency in 3D](../examples/3d/transparency_3d.rs) | Demonstrates transparency in 3d
|
||||
[Two Passes](../examples/3d/two_passes.rs) | Renders two 3d passes to the same window from different perspectives
|
||||
[Update glTF Scene](../examples/3d/update_gltf_scene.rs) | Update a scene from a glTF file, either by spawning the scene as a child of another entity, or by accessing the entities of the scene
|
||||
|
|
Loading…
Reference in a new issue