2023-10-17 21:28:08 +00:00
|
|
|
use bevy_asset::{Asset, Handle};
|
|
|
|
use bevy_reflect::TypePath;
|
|
|
|
use bevy_render::{
|
|
|
|
mesh::MeshVertexBufferLayout,
|
|
|
|
render_asset::RenderAssets,
|
|
|
|
render_resource::{
|
|
|
|
AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader,
|
|
|
|
ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup,
|
|
|
|
},
|
|
|
|
renderer::RenderDevice,
|
|
|
|
texture::{FallbackImage, Image},
|
|
|
|
};
|
|
|
|
|
|
|
|
use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey};
|
|
|
|
|
|
|
|
pub struct MaterialExtensionPipeline {
|
|
|
|
pub mesh_pipeline: MeshPipeline,
|
|
|
|
pub material_layout: BindGroupLayout,
|
|
|
|
pub vertex_shader: Option<Handle<Shader>>,
|
|
|
|
pub fragment_shader: Option<Handle<Shader>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct MaterialExtensionKey<E: MaterialExtension> {
|
|
|
|
pub mesh_key: MeshPipelineKey,
|
|
|
|
pub bind_group_data: E::Data,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A subset of the `Material` trait for defining extensions to a base `Material`, such as the builtin `StandardMaterial`.
|
|
|
|
/// A user type implementing the trait should be used as the `E` generic param in an `ExtendedMaterial` struct.
|
|
|
|
pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {
|
|
|
|
/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the base material mesh vertex shader
|
|
|
|
/// will be used.
|
|
|
|
fn vertex_shader() -> ShaderRef {
|
|
|
|
ShaderRef::Default
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the base material mesh fragment shader
|
|
|
|
/// will be used.
|
|
|
|
#[allow(unused_variables)]
|
|
|
|
fn fragment_shader() -> ShaderRef {
|
|
|
|
ShaderRef::Default
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader
|
|
|
|
/// will be used.
|
|
|
|
fn prepass_vertex_shader() -> ShaderRef {
|
|
|
|
ShaderRef::Default
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material prepass fragment shader
|
|
|
|
/// will be used.
|
|
|
|
#[allow(unused_variables)]
|
|
|
|
fn prepass_fragment_shader() -> ShaderRef {
|
|
|
|
ShaderRef::Default
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the base material deferred vertex shader
|
|
|
|
/// will be used.
|
|
|
|
fn deferred_vertex_shader() -> ShaderRef {
|
|
|
|
ShaderRef::Default
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material deferred fragment shader
|
|
|
|
/// will be used.
|
|
|
|
#[allow(unused_variables)]
|
|
|
|
fn deferred_fragment_shader() -> ShaderRef {
|
|
|
|
ShaderRef::Default
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
|
|
|
|
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input.
|
|
|
|
/// Specialization for the base material is applied before this function is called.
|
|
|
|
#[allow(unused_variables)]
|
|
|
|
#[inline]
|
|
|
|
fn specialize(
|
|
|
|
pipeline: &MaterialExtensionPipeline,
|
|
|
|
descriptor: &mut RenderPipelineDescriptor,
|
|
|
|
layout: &MeshVertexBufferLayout,
|
|
|
|
key: MaterialExtensionKey<Self>,
|
|
|
|
) -> Result<(), SpecializedMeshPipelineError> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A material that extends a base [`Material`] with additional shaders and data.
|
|
|
|
///
|
|
|
|
/// The data from both materials will be combined and made available to the shader
|
|
|
|
/// so that shader functions built for the base material (and referencing the base material
|
|
|
|
/// bindings) will work as expected, and custom alterations based on custom data can also be used.
|
|
|
|
///
|
|
|
|
/// If the extension `E` returns a non-default result from `vertex_shader()` it will be used in place of the base
|
|
|
|
/// material's vertex shader.
|
|
|
|
///
|
|
|
|
/// If the extension `E` returns a non-default result from `fragment_shader()` it will be used in place of the base
|
|
|
|
/// fragment shader.
|
|
|
|
///
|
|
|
|
/// When used with `StandardMaterial` as the base, all the standard material fields are
|
|
|
|
/// present, so the `pbr_fragment` shader functions can be called from the extension shader (see
|
|
|
|
/// the `extended_material` example).
|
|
|
|
#[derive(Asset, Clone, TypePath)]
|
|
|
|
pub struct ExtendedMaterial<B: Material, E: MaterialExtension> {
|
|
|
|
pub base: B,
|
|
|
|
pub extension: E,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
|
|
|
|
type Data = (<B as AsBindGroup>::Data, <E as AsBindGroup>::Data);
|
|
|
|
|
|
|
|
fn unprepared_bind_group(
|
|
|
|
&self,
|
|
|
|
layout: &BindGroupLayout,
|
|
|
|
render_device: &RenderDevice,
|
|
|
|
images: &RenderAssets<Image>,
|
|
|
|
fallback_image: &FallbackImage,
|
|
|
|
) -> Result<bevy_render::render_resource::UnpreparedBindGroup<Self::Data>, AsBindGroupError>
|
|
|
|
{
|
|
|
|
// add together the bindings of the base material and the user material
|
|
|
|
let UnpreparedBindGroup {
|
|
|
|
mut bindings,
|
|
|
|
data: base_data,
|
|
|
|
} = B::unprepared_bind_group(&self.base, layout, render_device, images, fallback_image)?;
|
|
|
|
let extended_bindgroup = E::unprepared_bind_group(
|
|
|
|
&self.extension,
|
|
|
|
layout,
|
|
|
|
render_device,
|
|
|
|
images,
|
|
|
|
fallback_image,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
bindings.extend(extended_bindgroup.bindings);
|
|
|
|
|
|
|
|
Ok(UnpreparedBindGroup {
|
|
|
|
bindings,
|
|
|
|
data: (base_data, extended_bindgroup.data),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn bind_group_layout_entries(
|
|
|
|
render_device: &RenderDevice,
|
|
|
|
) -> Vec<bevy_render::render_resource::BindGroupLayoutEntry>
|
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
|
|
|
// add together the bindings of the standard material and the user material
|
|
|
|
let mut entries = B::bind_group_layout_entries(render_device);
|
|
|
|
entries.extend(E::bind_group_layout_entries(render_device));
|
|
|
|
entries
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
|
|
|
|
fn vertex_shader() -> bevy_render::render_resource::ShaderRef {
|
|
|
|
match E::vertex_shader() {
|
|
|
|
ShaderRef::Default => B::vertex_shader(),
|
|
|
|
specified => specified,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fragment_shader() -> bevy_render::render_resource::ShaderRef {
|
|
|
|
match E::fragment_shader() {
|
|
|
|
ShaderRef::Default => B::fragment_shader(),
|
|
|
|
specified => specified,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn prepass_vertex_shader() -> bevy_render::render_resource::ShaderRef {
|
|
|
|
match E::prepass_vertex_shader() {
|
|
|
|
ShaderRef::Default => B::prepass_vertex_shader(),
|
|
|
|
specified => specified,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn prepass_fragment_shader() -> bevy_render::render_resource::ShaderRef {
|
|
|
|
match E::prepass_fragment_shader() {
|
|
|
|
ShaderRef::Default => B::prepass_fragment_shader(),
|
|
|
|
specified => specified,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn deferred_vertex_shader() -> bevy_render::render_resource::ShaderRef {
|
|
|
|
match E::deferred_vertex_shader() {
|
|
|
|
ShaderRef::Default => B::deferred_vertex_shader(),
|
|
|
|
specified => specified,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn deferred_fragment_shader() -> bevy_render::render_resource::ShaderRef {
|
|
|
|
match E::deferred_fragment_shader() {
|
|
|
|
ShaderRef::Default => B::deferred_fragment_shader(),
|
|
|
|
specified => specified,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alpha_mode(&self) -> crate::AlphaMode {
|
|
|
|
B::alpha_mode(&self.base)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn depth_bias(&self) -> f32 {
|
|
|
|
B::depth_bias(&self.base)
|
|
|
|
}
|
|
|
|
|
`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`
2023-10-31 20:59:02 +00:00
|
|
|
fn reads_view_transmission_texture(&self) -> bool {
|
|
|
|
B::reads_view_transmission_texture(&self.base)
|
|
|
|
}
|
|
|
|
|
2023-10-17 21:28:08 +00:00
|
|
|
fn opaque_render_method(&self) -> crate::OpaqueRendererMethod {
|
|
|
|
B::opaque_render_method(&self.base)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn specialize(
|
|
|
|
pipeline: &MaterialPipeline<Self>,
|
|
|
|
descriptor: &mut RenderPipelineDescriptor,
|
|
|
|
layout: &MeshVertexBufferLayout,
|
|
|
|
key: MaterialPipelineKey<Self>,
|
|
|
|
) -> Result<(), SpecializedMeshPipelineError> {
|
|
|
|
// Call the base material's specialize function
|
|
|
|
let MaterialPipeline::<Self> {
|
|
|
|
mesh_pipeline,
|
|
|
|
material_layout,
|
|
|
|
vertex_shader,
|
|
|
|
fragment_shader,
|
|
|
|
..
|
|
|
|
} = pipeline.clone();
|
|
|
|
let base_pipeline = MaterialPipeline::<B> {
|
|
|
|
mesh_pipeline,
|
|
|
|
material_layout,
|
|
|
|
vertex_shader,
|
|
|
|
fragment_shader,
|
|
|
|
marker: Default::default(),
|
|
|
|
};
|
|
|
|
let base_key = MaterialPipelineKey::<B> {
|
|
|
|
mesh_key: key.mesh_key,
|
|
|
|
bind_group_data: key.bind_group_data.0,
|
|
|
|
};
|
|
|
|
B::specialize(&base_pipeline, descriptor, layout, base_key)?;
|
|
|
|
|
|
|
|
// Call the extended material's specialize function afterwards
|
|
|
|
let MaterialPipeline::<Self> {
|
|
|
|
mesh_pipeline,
|
|
|
|
material_layout,
|
|
|
|
vertex_shader,
|
|
|
|
fragment_shader,
|
|
|
|
..
|
|
|
|
} = pipeline.clone();
|
|
|
|
|
|
|
|
E::specialize(
|
|
|
|
&MaterialExtensionPipeline {
|
|
|
|
mesh_pipeline,
|
|
|
|
material_layout,
|
|
|
|
vertex_shader,
|
|
|
|
fragment_shader,
|
|
|
|
},
|
|
|
|
descriptor,
|
|
|
|
layout,
|
|
|
|
MaterialExtensionKey {
|
|
|
|
mesh_key: key.mesh_key,
|
|
|
|
bind_group_data: key.bind_group_data.1,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|