Add support for specular tints and maps per the KHR_materials_specular
glTF extension. (#14069)
This commit allows specular highlights to be tinted with a color and for the reflectance and color tint values to vary across a model via a pair of maps. The implementation follows the [`KHR_materials_specular`] glTF extension. In order to reduce the number of samplers and textures in the default `StandardMaterial` configuration, the maps are gated behind the `pbr_specular_textures` Cargo feature. Specular tinting is currently unsupported in the deferred renderer, because I didn't want to bloat the deferred G-buffers. A possible fix for this in the future would be to make the G-buffer layout more configurable, so that specular tints could be supported on an opt-in basis. As an alternative, Bevy could force meshes with specular tints to render in forward mode. Both of these solutions require some more design, so I consider them out of scope for now. Note that the map is a *specular* map, not a *reflectance* map. In Bevy and Filament terms, the reflectance values in the specular map range from [0.0, 0.5], rather than [0.0, 1.0]. This is an unfortunate [`KHR_materials_specular`] specification requirement that stems from the fact that glTF is specified in terms of a specular strength model, not the reflectance model that Filament and Bevy use. A workaround, which is noted in the `StandardMaterial` documentation, is to set the `reflectance` value to 2.0, which spreads the specular map range from [0.0, 1.0] as normal. The glTF loader has been updated to parse the [`KHR_materials_specular`] extension. Note that, unless the non-default `pbr_specular_textures` is supplied, the maps are ignored. The `specularFactor` value is applied as usual. Note that, as with the specular map, the glTF `specularFactor` is twice Bevy's `reflectance` value. This PR adds a new example, `specular_tint`, which demonstrates the specular tint and map features. Note that this example requires the [`KHR_materials_specular`] Cargo feature. [`KHR_materials_specular`]: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular ## Changelog ### Added * Specular highlights can now be tinted with the `specular_tint` field in `StandardMaterial`. * Specular maps are now available in `StandardMaterial`, gated behind the `pbr_specular_textures` Cargo feature. * The `KHR_materials_specular` glTF extension is now supported, allowing for customization of specular reflectance and specular maps. Note that the latter are gated behind the `pbr_specular_textures` Cargo feature.
This commit is contained in:
parent
fc831c390d
commit
1c765c9ae7
15
Cargo.toml
15
Cargo.toml
@ -430,6 +430,9 @@ pbr_anisotropy_texture = ["bevy_internal/pbr_anisotropy_texture"]
|
|||||||
# Enable support for PCSS, at the risk of blowing past the global, per-shader sampler limit on older/lower-end GPUs
|
# Enable support for PCSS, at the risk of blowing past the global, per-shader sampler limit on older/lower-end GPUs
|
||||||
experimental_pbr_pcss = ["bevy_internal/experimental_pbr_pcss"]
|
experimental_pbr_pcss = ["bevy_internal/experimental_pbr_pcss"]
|
||||||
|
|
||||||
|
# Enable support for specular textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs
|
||||||
|
pbr_specular_textures = ["bevy_internal/pbr_specular_textures"]
|
||||||
|
|
||||||
# Enable some limitations to be able to use WebGL2. 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.
|
# Enable some limitations to be able to use WebGL2. 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"]
|
webgl2 = ["bevy_internal/webgl"]
|
||||||
|
|
||||||
@ -4020,6 +4023,18 @@ description = "Demonstrates how to make materials that use bindless textures"
|
|||||||
category = "Shaders"
|
category = "Shaders"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "specular_tint"
|
||||||
|
path = "examples/3d/specular_tint.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
required-features = ["pbr_specular_textures"]
|
||||||
|
|
||||||
|
[package.metadata.example.specular_tint]
|
||||||
|
name = "Specular Tint"
|
||||||
|
description = "Demonstrates specular tints and maps"
|
||||||
|
category = "3D Rendering"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
[profile.wasm-release]
|
[profile.wasm-release]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
BIN
assets/textures/AlphaNoise.png
Normal file
BIN
assets/textures/AlphaNoise.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 180 KiB |
@ -15,6 +15,7 @@ pbr_multi_layer_material_textures = [
|
|||||||
"bevy_pbr/pbr_multi_layer_material_textures",
|
"bevy_pbr/pbr_multi_layer_material_textures",
|
||||||
]
|
]
|
||||||
pbr_anisotropy_texture = ["bevy_pbr/pbr_anisotropy_texture"]
|
pbr_anisotropy_texture = ["bevy_pbr/pbr_anisotropy_texture"]
|
||||||
|
pbr_specular_textures = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
|
@ -51,6 +51,11 @@ use gltf::{
|
|||||||
Document, Material, Node, Primitive, Semantic,
|
Document, Material, Node, Primitive, Semantic,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "pbr_specular_textures",
|
||||||
|
feature = "pbr_multi_layer_material_textures"
|
||||||
|
))]
|
||||||
|
use serde_json::Map;
|
||||||
use serde_json::{value, Value};
|
use serde_json::{value, Value};
|
||||||
use std::{
|
use std::{
|
||||||
io::Error,
|
io::Error,
|
||||||
@ -1235,6 +1240,10 @@ fn load_material(
|
|||||||
let anisotropy =
|
let anisotropy =
|
||||||
AnisotropyExtension::parse(load_context, document, material).unwrap_or_default();
|
AnisotropyExtension::parse(load_context, document, material).unwrap_or_default();
|
||||||
|
|
||||||
|
// Parse the `KHR_materials_specular` extension data if necessary.
|
||||||
|
let specular =
|
||||||
|
SpecularExtension::parse(load_context, document, material).unwrap_or_default();
|
||||||
|
|
||||||
// We need to operate in the Linear color space and be willing to exceed 1.0 in our channels
|
// We need to operate in the Linear color space and be willing to exceed 1.0 in our channels
|
||||||
let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]);
|
let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]);
|
||||||
let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0);
|
let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0);
|
||||||
@ -1303,6 +1312,21 @@ fn load_material(
|
|||||||
anisotropy_channel: anisotropy.anisotropy_channel,
|
anisotropy_channel: anisotropy.anisotropy_channel,
|
||||||
#[cfg(feature = "pbr_anisotropy_texture")]
|
#[cfg(feature = "pbr_anisotropy_texture")]
|
||||||
anisotropy_texture: anisotropy.anisotropy_texture,
|
anisotropy_texture: anisotropy.anisotropy_texture,
|
||||||
|
// From the `KHR_materials_specular` spec:
|
||||||
|
// <https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular#materials-with-reflectance-parameter>
|
||||||
|
reflectance: specular.specular_factor.unwrap_or(1.0) as f32 * 0.5,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_channel: specular.specular_channel,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_texture: specular.specular_texture,
|
||||||
|
specular_tint: match specular.specular_color_factor {
|
||||||
|
Some(color) => Color::linear_rgb(color[0] as f32, color[1] as f32, color[2] as f32),
|
||||||
|
None => Color::WHITE,
|
||||||
|
},
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_tint_channel: specular.specular_color_channel,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_tint_texture: specular.specular_color_texture,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1731,7 +1755,8 @@ fn texture_handle(load_context: &mut LoadContext, texture: &gltf::Texture) -> Ha
|
|||||||
/// for an extension, forcing us to parse its texture references manually.
|
/// for an extension, forcing us to parse its texture references manually.
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "pbr_anisotropy_texture",
|
feature = "pbr_anisotropy_texture",
|
||||||
feature = "pbr_multi_layer_material_textures"
|
feature = "pbr_multi_layer_material_textures",
|
||||||
|
feature = "pbr_specular_textures"
|
||||||
))]
|
))]
|
||||||
fn texture_handle_from_info(
|
fn texture_handle_from_info(
|
||||||
load_context: &mut LoadContext,
|
load_context: &mut LoadContext,
|
||||||
@ -2122,40 +2147,35 @@ impl ClearcoatExtension {
|
|||||||
.as_object()?;
|
.as_object()?;
|
||||||
|
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
let (clearcoat_channel, clearcoat_texture) = extension
|
let (clearcoat_channel, clearcoat_texture) = parse_material_extension_texture(
|
||||||
.get("clearcoatTexture")
|
load_context,
|
||||||
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
|
document,
|
||||||
.map(|json_info| {
|
material,
|
||||||
(
|
extension,
|
||||||
get_uv_channel(material, "clearcoat", json_info.tex_coord),
|
"clearcoatTexture",
|
||||||
texture_handle_from_info(load_context, document, &json_info),
|
"clearcoat",
|
||||||
)
|
);
|
||||||
})
|
|
||||||
.unzip();
|
|
||||||
|
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
let (clearcoat_roughness_channel, clearcoat_roughness_texture) = extension
|
let (clearcoat_roughness_channel, clearcoat_roughness_texture) =
|
||||||
.get("clearcoatRoughnessTexture")
|
parse_material_extension_texture(
|
||||||
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
|
load_context,
|
||||||
.map(|json_info| {
|
document,
|
||||||
(
|
material,
|
||||||
get_uv_channel(material, "clearcoat roughness", json_info.tex_coord),
|
extension,
|
||||||
texture_handle_from_info(load_context, document, &json_info),
|
"clearcoatRoughnessTexture",
|
||||||
)
|
"clearcoat roughness",
|
||||||
})
|
);
|
||||||
.unzip();
|
|
||||||
|
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
let (clearcoat_normal_channel, clearcoat_normal_texture) = extension
|
let (clearcoat_normal_channel, clearcoat_normal_texture) = parse_material_extension_texture(
|
||||||
.get("clearcoatNormalTexture")
|
load_context,
|
||||||
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
|
document,
|
||||||
.map(|json_info| {
|
material,
|
||||||
(
|
extension,
|
||||||
get_uv_channel(material, "clearcoat normal", json_info.tex_coord),
|
"clearcoatNormalTexture",
|
||||||
texture_handle_from_info(load_context, document, &json_info),
|
"clearcoat normal",
|
||||||
)
|
);
|
||||||
})
|
|
||||||
.unzip();
|
|
||||||
|
|
||||||
Some(ClearcoatExtension {
|
Some(ClearcoatExtension {
|
||||||
clearcoat_factor: extension.get("clearcoatFactor").and_then(Value::as_f64),
|
clearcoat_factor: extension.get("clearcoatFactor").and_then(Value::as_f64),
|
||||||
@ -2163,15 +2183,15 @@ impl ClearcoatExtension {
|
|||||||
.get("clearcoatRoughnessFactor")
|
.get("clearcoatRoughnessFactor")
|
||||||
.and_then(Value::as_f64),
|
.and_then(Value::as_f64),
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
clearcoat_channel: clearcoat_channel.unwrap_or_default(),
|
clearcoat_channel,
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
clearcoat_texture,
|
clearcoat_texture,
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
clearcoat_roughness_channel: clearcoat_roughness_channel.unwrap_or_default(),
|
clearcoat_roughness_channel,
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
clearcoat_roughness_texture,
|
clearcoat_roughness_texture,
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
clearcoat_normal_channel: clearcoat_normal_channel.unwrap_or_default(),
|
clearcoat_normal_channel,
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
clearcoat_normal_texture,
|
clearcoat_normal_texture,
|
||||||
})
|
})
|
||||||
@ -2234,6 +2254,121 @@ impl AnisotropyExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parsed data from the `KHR_materials_specular` extension.
|
||||||
|
///
|
||||||
|
/// We currently don't parse `specularFactor` and `specularTexture`, since
|
||||||
|
/// they're incompatible with Filament.
|
||||||
|
///
|
||||||
|
/// Note that the map is a *specular map*, not a *reflectance map*. In Bevy and
|
||||||
|
/// Filament terms, the reflectance values in the specular map range from [0.0,
|
||||||
|
/// 0.5], rather than [0.0, 1.0]. This is an unfortunate
|
||||||
|
/// `KHR_materials_specular` specification requirement that stems from the fact
|
||||||
|
/// that glTF is specified in terms of a specular strength model, not the
|
||||||
|
/// reflectance model that Filament and Bevy use. A workaround, which is noted
|
||||||
|
/// in the [`StandardMaterial`] documentation, is to set the reflectance value
|
||||||
|
/// to 2.0, which spreads the specular map range from [0.0, 1.0] as normal.
|
||||||
|
///
|
||||||
|
/// See the specification:
|
||||||
|
/// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md>
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SpecularExtension {
|
||||||
|
specular_factor: Option<f64>,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_channel: UvChannel,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_texture: Option<Handle<Image>>,
|
||||||
|
specular_color_factor: Option<[f64; 3]>,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_color_channel: UvChannel,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_color_texture: Option<Handle<Image>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecularExtension {
|
||||||
|
fn parse(
|
||||||
|
_load_context: &mut LoadContext,
|
||||||
|
_document: &Document,
|
||||||
|
material: &Material,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let extension = material
|
||||||
|
.extensions()?
|
||||||
|
.get("KHR_materials_specular")?
|
||||||
|
.as_object()?;
|
||||||
|
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
let (_specular_channel, _specular_texture) = parse_material_extension_texture(
|
||||||
|
_load_context,
|
||||||
|
_document,
|
||||||
|
material,
|
||||||
|
extension,
|
||||||
|
"specularTexture",
|
||||||
|
"specular",
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
let (_specular_color_channel, _specular_color_texture) = parse_material_extension_texture(
|
||||||
|
_load_context,
|
||||||
|
_document,
|
||||||
|
material,
|
||||||
|
extension,
|
||||||
|
"specularColorTexture",
|
||||||
|
"specular color",
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(SpecularExtension {
|
||||||
|
specular_factor: extension.get("specularFactor").and_then(Value::as_f64),
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_channel: _specular_channel,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_texture: _specular_texture,
|
||||||
|
specular_color_factor: extension
|
||||||
|
.get("specularColorFactor")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.and_then(|json_array| {
|
||||||
|
if json_array.len() < 3 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some([
|
||||||
|
json_array[0].as_f64()?,
|
||||||
|
json_array[1].as_f64()?,
|
||||||
|
json_array[2].as_f64()?,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_color_channel: _specular_color_channel,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_color_texture: _specular_color_texture,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a texture that's part of a material extension block and returns its
|
||||||
|
/// UV channel and image reference.
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "pbr_specular_textures",
|
||||||
|
feature = "pbr_multi_layer_material_textures"
|
||||||
|
))]
|
||||||
|
fn parse_material_extension_texture(
|
||||||
|
load_context: &mut LoadContext,
|
||||||
|
document: &Document,
|
||||||
|
material: &Material,
|
||||||
|
extension: &Map<String, Value>,
|
||||||
|
texture_name: &str,
|
||||||
|
texture_kind: &str,
|
||||||
|
) -> (UvChannel, Option<Handle<Image>>) {
|
||||||
|
match extension
|
||||||
|
.get(texture_name)
|
||||||
|
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
|
||||||
|
{
|
||||||
|
Some(json_info) => (
|
||||||
|
get_uv_channel(material, texture_kind, json_info.tex_coord),
|
||||||
|
Some(texture_handle_from_info(load_context, document, &json_info)),
|
||||||
|
),
|
||||||
|
None => (UvChannel::default(), None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the index (within the `textures` array) of the texture with the
|
/// Returns the index (within the `textures` array) of the texture with the
|
||||||
/// given field name in the data for the material extension with the given name,
|
/// given field name in the data for the material extension with the given name,
|
||||||
/// if there is one.
|
/// if there is one.
|
||||||
|
@ -136,6 +136,12 @@ pbr_anisotropy_texture = [
|
|||||||
# Percentage-closer soft shadows
|
# Percentage-closer soft shadows
|
||||||
experimental_pbr_pcss = ["bevy_pbr?/experimental_pbr_pcss"]
|
experimental_pbr_pcss = ["bevy_pbr?/experimental_pbr_pcss"]
|
||||||
|
|
||||||
|
# Specular textures in `StandardMaterial`:
|
||||||
|
pbr_specular_textures = [
|
||||||
|
"bevy_pbr?/pbr_specular_textures",
|
||||||
|
"bevy_gltf?/pbr_specular_textures",
|
||||||
|
]
|
||||||
|
|
||||||
# Optimise for WebGL2
|
# Optimise for WebGL2
|
||||||
webgl = [
|
webgl = [
|
||||||
"bevy_core_pipeline?/webgl",
|
"bevy_core_pipeline?/webgl",
|
||||||
|
@ -15,6 +15,7 @@ pbr_transmission_textures = []
|
|||||||
pbr_multi_layer_material_textures = []
|
pbr_multi_layer_material_textures = []
|
||||||
pbr_anisotropy_texture = []
|
pbr_anisotropy_texture = []
|
||||||
experimental_pbr_pcss = []
|
experimental_pbr_pcss = []
|
||||||
|
pbr_specular_textures = []
|
||||||
shader_format_glsl = ["bevy_render/shader_format_glsl"]
|
shader_format_glsl = ["bevy_render/shader_format_glsl"]
|
||||||
trace = ["bevy_render/trace"]
|
trace = ["bevy_render/trace"]
|
||||||
ios_simulator = ["bevy_render/ios_simulator"]
|
ios_simulator = ["bevy_render/ios_simulator"]
|
||||||
|
@ -28,16 +28,19 @@ fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
|
|||||||
// Real time occlusion is applied in the deferred lighting pass.
|
// Real time occlusion is applied in the deferred lighting pass.
|
||||||
// Deriving luminance via Rec. 709. coefficients
|
// Deriving luminance via Rec. 709. coefficients
|
||||||
// https://en.wikipedia.org/wiki/Rec._709
|
// https://en.wikipedia.org/wiki/Rec._709
|
||||||
let diffuse_occlusion = dot(in.diffuse_occlusion, vec3<f32>(0.2126, 0.7152, 0.0722));
|
let rec_709_coeffs = vec3<f32>(0.2126, 0.7152, 0.0722);
|
||||||
|
let diffuse_occlusion = dot(in.diffuse_occlusion, rec_709_coeffs);
|
||||||
|
// Only monochrome specular supported.
|
||||||
|
let reflectance = dot(in.material.reflectance, rec_709_coeffs);
|
||||||
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
|
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
|
||||||
var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
|
var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
|
||||||
in.material.reflectance,
|
reflectance,
|
||||||
in.material.metallic,
|
in.material.metallic,
|
||||||
diffuse_occlusion,
|
diffuse_occlusion,
|
||||||
in.frag_coord.z));
|
in.frag_coord.z));
|
||||||
#else
|
#else
|
||||||
var props = deferred_types::pack_unorm4x8_(vec4(
|
var props = deferred_types::pack_unorm4x8_(vec4(
|
||||||
in.material.reflectance, // could be fewer bits
|
reflectance, // could be fewer bits
|
||||||
in.material.metallic, // could be fewer bits
|
in.material.metallic, // could be fewer bits
|
||||||
diffuse_occlusion, // is this worth including?
|
diffuse_occlusion, // is this worth including?
|
||||||
0.0)); // spare
|
0.0)); // spare
|
||||||
@ -100,10 +103,10 @@ fn pbr_input_from_deferred_gbuffer(frag_coord: vec4<f32>, gbuffer: vec4<u32>) ->
|
|||||||
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
|
#ifdef WEBGL2 // More crunched for webgl so we can also fit depth.
|
||||||
let props = deferred_types::unpack_unorm3x4_plus_unorm_20_(gbuffer.b);
|
let props = deferred_types::unpack_unorm3x4_plus_unorm_20_(gbuffer.b);
|
||||||
// Bias to 0.5 since that's the value for almost all materials.
|
// Bias to 0.5 since that's the value for almost all materials.
|
||||||
pbr.material.reflectance = saturate(props.r - 0.03333333333);
|
pbr.material.reflectance = vec3(saturate(props.r - 0.03333333333));
|
||||||
#else
|
#else
|
||||||
let props = deferred_types::unpack_unorm4x8_(gbuffer.b);
|
let props = deferred_types::unpack_unorm4x8_(gbuffer.b);
|
||||||
pbr.material.reflectance = props.r;
|
pbr.material.reflectance = vec3(props.r);
|
||||||
#endif // WEBGL2
|
#endif // WEBGL2
|
||||||
pbr.material.metallic = props.g;
|
pbr.material.metallic = props.g;
|
||||||
pbr.diffuse_occlusion = vec3(props.b);
|
pbr.diffuse_occlusion = vec3(props.b);
|
||||||
|
@ -183,6 +183,19 @@ pub struct StandardMaterial {
|
|||||||
#[doc(alias = "specular_intensity")]
|
#[doc(alias = "specular_intensity")]
|
||||||
pub reflectance: f32,
|
pub reflectance: f32,
|
||||||
|
|
||||||
|
/// A color with which to modulate the [`StandardMaterial::reflectance`] for
|
||||||
|
/// non-metals.
|
||||||
|
///
|
||||||
|
/// The specular highlights and reflection are tinted with this color. Note
|
||||||
|
/// that it has no effect for non-metals.
|
||||||
|
///
|
||||||
|
/// This feature is currently unsupported in the deferred rendering path, in
|
||||||
|
/// order to reduce the size of the geometry buffers.
|
||||||
|
///
|
||||||
|
/// Defaults to [`Color::WHITE`].
|
||||||
|
#[doc(alias = "specular_color")]
|
||||||
|
pub specular_tint: Color,
|
||||||
|
|
||||||
/// The amount of light transmitted _diffusely_ through the material (i.e. “translucency”).
|
/// 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,
|
/// Implemented as a second, flipped [Lambertian diffuse](https://en.wikipedia.org/wiki/Lambertian_reflectance) lobe,
|
||||||
@ -401,6 +414,54 @@ pub struct StandardMaterial {
|
|||||||
#[dependency]
|
#[dependency]
|
||||||
pub occlusion_texture: Option<Handle<Image>>,
|
pub occlusion_texture: Option<Handle<Image>>,
|
||||||
|
|
||||||
|
/// The UV channel to use for the [`StandardMaterial::specular_texture`].
|
||||||
|
///
|
||||||
|
/// Defaults to [`UvChannel::Uv0`].
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
pub specular_channel: UvChannel,
|
||||||
|
|
||||||
|
/// A map that specifies reflectance for non-metallic materials.
|
||||||
|
///
|
||||||
|
/// Alpha values from [0.0, 1.0] in this texture are linearly mapped to
|
||||||
|
/// reflectance values of [0.0, 0.5] and multiplied by the constant
|
||||||
|
/// [`StandardMaterial::reflectance`] value. This follows the
|
||||||
|
/// `KHR_materials_specular` specification. The map will have no effect if
|
||||||
|
/// the material is fully metallic.
|
||||||
|
///
|
||||||
|
/// When using this map, you may wish to set the
|
||||||
|
/// [`StandardMaterial::reflectance`] value to 2.0 so that this map can
|
||||||
|
/// express the full [0.0, 1.0] range of values.
|
||||||
|
///
|
||||||
|
/// Note that, because the reflectance is stored in the alpha channel, and
|
||||||
|
/// the [`StandardMaterial::specular_tint_texture`] has no alpha value, it
|
||||||
|
/// may be desirable to pack the values together and supply the same
|
||||||
|
/// texture to both fields.
|
||||||
|
#[texture(27)]
|
||||||
|
#[sampler(28)]
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
pub specular_texture: Option<Handle<Image>>,
|
||||||
|
|
||||||
|
/// The UV channel to use for the
|
||||||
|
/// [`StandardMaterial::specular_tint_texture`].
|
||||||
|
///
|
||||||
|
/// Defaults to [`UvChannel::Uv0`].
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
pub specular_tint_channel: UvChannel,
|
||||||
|
|
||||||
|
/// A map that specifies color adjustment to be applied to the specular
|
||||||
|
/// reflection for non-metallic materials.
|
||||||
|
///
|
||||||
|
/// The RGB values of this texture modulate the
|
||||||
|
/// [`StandardMaterial::specular_tint`] value. See the documentation for
|
||||||
|
/// that field for more information.
|
||||||
|
///
|
||||||
|
/// Like the fixed specular tint value, this texture map isn't supported in
|
||||||
|
/// the deferred renderer.
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
#[texture(29)]
|
||||||
|
#[sampler(30)]
|
||||||
|
pub specular_tint_texture: Option<Handle<Image>>,
|
||||||
|
|
||||||
/// An extra thin translucent layer on top of the main PBR layer. This is
|
/// An extra thin translucent layer on top of the main PBR layer. This is
|
||||||
/// typically used for painted surfaces.
|
/// typically used for painted surfaces.
|
||||||
///
|
///
|
||||||
@ -801,6 +862,15 @@ impl Default for StandardMaterial {
|
|||||||
occlusion_texture: None,
|
occlusion_texture: None,
|
||||||
normal_map_channel: UvChannel::Uv0,
|
normal_map_channel: UvChannel::Uv0,
|
||||||
normal_map_texture: None,
|
normal_map_texture: None,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_channel: UvChannel::Uv0,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_texture: None,
|
||||||
|
specular_tint: Color::WHITE,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_tint_channel: UvChannel::Uv0,
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
specular_tint_texture: None,
|
||||||
clearcoat: 0.0,
|
clearcoat: 0.0,
|
||||||
clearcoat_perceptual_roughness: 0.5,
|
clearcoat_perceptual_roughness: 0.5,
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
@ -887,6 +957,8 @@ bitflags::bitflags! {
|
|||||||
const CLEARCOAT_ROUGHNESS_TEXTURE = 1 << 15;
|
const CLEARCOAT_ROUGHNESS_TEXTURE = 1 << 15;
|
||||||
const CLEARCOAT_NORMAL_TEXTURE = 1 << 16;
|
const CLEARCOAT_NORMAL_TEXTURE = 1 << 16;
|
||||||
const ANISOTROPY_TEXTURE = 1 << 17;
|
const ANISOTROPY_TEXTURE = 1 << 17;
|
||||||
|
const SPECULAR_TEXTURE = 1 << 18;
|
||||||
|
const SPECULAR_TINT_TEXTURE = 1 << 19;
|
||||||
const ALPHA_MODE_RESERVED_BITS = Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS; // ← Bitmask reserving bits for the `AlphaMode`
|
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_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.
|
const ALPHA_MODE_MASK = 1 << Self::ALPHA_MODE_SHIFT_BITS; // the bitmask, and can range from 0 to 7.
|
||||||
@ -918,14 +990,14 @@ pub struct StandardMaterialUniform {
|
|||||||
pub attenuation_color: Vec4,
|
pub attenuation_color: Vec4,
|
||||||
/// The transform applied to the UVs corresponding to `ATTRIBUTE_UV_0` on the mesh before sampling. Default is identity.
|
/// The transform applied to the UVs corresponding to `ATTRIBUTE_UV_0` on the mesh before sampling. Default is identity.
|
||||||
pub uv_transform: Mat3,
|
pub uv_transform: Mat3,
|
||||||
|
/// 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: Vec3,
|
||||||
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
|
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
|
||||||
/// Defaults to minimum of 0.089
|
/// Defaults to minimum of 0.089
|
||||||
pub roughness: f32,
|
pub roughness: f32,
|
||||||
/// From [0.0, 1.0], dielectric to pure metallic
|
/// From [0.0, 1.0], dielectric to pure metallic
|
||||||
pub metallic: f32,
|
pub metallic: f32,
|
||||||
/// 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
|
/// Amount of diffuse light transmitted through the material
|
||||||
pub diffuse_transmission: f32,
|
pub diffuse_transmission: f32,
|
||||||
/// Amount of specular light transmitted through the material
|
/// Amount of specular light transmitted through the material
|
||||||
@ -1011,6 +1083,16 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
{
|
||||||
|
if self.specular_texture.is_some() {
|
||||||
|
flags |= StandardMaterialFlags::SPECULAR_TEXTURE;
|
||||||
|
}
|
||||||
|
if self.specular_tint_texture.is_some() {
|
||||||
|
flags |= StandardMaterialFlags::SPECULAR_TINT_TEXTURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
{
|
{
|
||||||
if self.clearcoat_texture.is_some() {
|
if self.clearcoat_texture.is_some() {
|
||||||
@ -1075,7 +1157,7 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||||||
emissive,
|
emissive,
|
||||||
roughness: self.perceptual_roughness,
|
roughness: self.perceptual_roughness,
|
||||||
metallic: self.metallic,
|
metallic: self.metallic,
|
||||||
reflectance: self.reflectance,
|
reflectance: LinearRgba::from(self.specular_tint).to_vec3() * self.reflectance,
|
||||||
clearcoat: self.clearcoat,
|
clearcoat: self.clearcoat,
|
||||||
clearcoat_perceptual_roughness: self.clearcoat_perceptual_roughness,
|
clearcoat_perceptual_roughness: self.clearcoat_perceptual_roughness,
|
||||||
anisotropy_strength: self.anisotropy_strength,
|
anisotropy_strength: self.anisotropy_strength,
|
||||||
@ -1125,6 +1207,8 @@ bitflags! {
|
|||||||
const CLEARCOAT_UV = 0x040000;
|
const CLEARCOAT_UV = 0x040000;
|
||||||
const CLEARCOAT_ROUGHNESS_UV = 0x080000;
|
const CLEARCOAT_ROUGHNESS_UV = 0x080000;
|
||||||
const CLEARCOAT_NORMAL_UV = 0x100000;
|
const CLEARCOAT_NORMAL_UV = 0x100000;
|
||||||
|
const SPECULAR_UV = 0x200000;
|
||||||
|
const SPECULAR_TINT_UV = 0x400000;
|
||||||
const DEPTH_BIAS = 0xffffffff_00000000;
|
const DEPTH_BIAS = 0xffffffff_00000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1221,6 +1305,18 @@ impl From<&StandardMaterial> for StandardMaterialKey {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "pbr_specular_textures")]
|
||||||
|
{
|
||||||
|
key.set(
|
||||||
|
StandardMaterialKey::SPECULAR_UV,
|
||||||
|
material.specular_channel != UvChannel::Uv0,
|
||||||
|
);
|
||||||
|
key.set(
|
||||||
|
StandardMaterialKey::SPECULAR_TINT_UV,
|
||||||
|
material.specular_tint_channel != UvChannel::Uv0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||||
{
|
{
|
||||||
key.set(
|
key.set(
|
||||||
@ -1392,7 +1488,15 @@ impl Material for StandardMaterial {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
StandardMaterialKey::ANISOTROPY_UV,
|
StandardMaterialKey::ANISOTROPY_UV,
|
||||||
"STANDARD_MATERIAL_ANISOTROPY_UV",
|
"STANDARD_MATERIAL_ANISOTROPY_UV_B",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
StandardMaterialKey::SPECULAR_UV,
|
||||||
|
"STANDARD_MATERIAL_SPECULAR_UV_B",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
StandardMaterialKey::SPECULAR_TINT_UV,
|
||||||
|
"STANDARD_MATERIAL_SPECULAR_TINT_UV_B",
|
||||||
),
|
),
|
||||||
] {
|
] {
|
||||||
if key.bind_group_data.intersects(flags) {
|
if key.bind_group_data.intersects(flags) {
|
||||||
|
@ -2054,6 +2054,9 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||||||
if cfg!(feature = "pbr_anisotropy_texture") {
|
if cfg!(feature = "pbr_anisotropy_texture") {
|
||||||
shader_defs.push("PBR_ANISOTROPY_TEXTURE_SUPPORTED".into());
|
shader_defs.push("PBR_ANISOTROPY_TEXTURE_SUPPORTED".into());
|
||||||
}
|
}
|
||||||
|
if cfg!(feature = "pbr_specular_textures") {
|
||||||
|
shader_defs.push("PBR_SPECULAR_TEXTURES_SUPPORTED".into());
|
||||||
|
}
|
||||||
|
|
||||||
let mut bind_group_layout = vec![self.get_view_layout(key.into()).clone()];
|
let mut bind_group_layout = vec![self.get_view_layout(key.into()).clone()];
|
||||||
|
|
||||||
|
@ -77,3 +77,17 @@
|
|||||||
@group(2) @binding(26) var clearcoat_normal_sampler: sampler;
|
@group(2) @binding(26) var clearcoat_normal_sampler: sampler;
|
||||||
#endif // BINDLESS
|
#endif // BINDLESS
|
||||||
#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
||||||
|
|
||||||
|
#ifdef PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||||
|
#ifdef BINDLESS
|
||||||
|
@group(2) @binding(27) var specular_texture: binding_array<texture_2d<f32>, 16>;
|
||||||
|
@group(2) @binding(28) var specular_sampler: binding_array<sampler, 16>;
|
||||||
|
@group(2) @binding(29) var specular_tint_texture: binding_array<texture_2d<f32>, 16>;
|
||||||
|
@group(2) @binding(30) var specular_tint_sampler: binding_array<sampler, 16>;
|
||||||
|
#else
|
||||||
|
@group(2) @binding(27) var specular_texture: texture_2d<f32>;
|
||||||
|
@group(2) @binding(28) var specular_sampler: sampler;
|
||||||
|
@group(2) @binding(29) var specular_tint_texture: texture_2d<f32>;
|
||||||
|
@group(2) @binding(30) var specular_tint_sampler: sampler;
|
||||||
|
#endif // BINDLESS
|
||||||
|
#endif // PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||||
|
@ -233,19 +233,92 @@ fn pbr_input_from_standard_material(
|
|||||||
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
||||||
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
||||||
#ifdef BINDLESS
|
#ifdef BINDLESS
|
||||||
pbr_input.material.reflectance = pbr_bindings::material[slot].reflectance;
|
|
||||||
pbr_input.material.ior = pbr_bindings::material[slot].ior;
|
pbr_input.material.ior = pbr_bindings::material[slot].ior;
|
||||||
pbr_input.material.attenuation_color = pbr_bindings::material[slot].attenuation_color;
|
pbr_input.material.attenuation_color = pbr_bindings::material[slot].attenuation_color;
|
||||||
pbr_input.material.attenuation_distance = pbr_bindings::material[slot].attenuation_distance;
|
pbr_input.material.attenuation_distance = pbr_bindings::material[slot].attenuation_distance;
|
||||||
pbr_input.material.alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff;
|
pbr_input.material.alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff;
|
||||||
#else // BINDLESS
|
#else // BINDLESS
|
||||||
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
|
|
||||||
pbr_input.material.ior = pbr_bindings::material.ior;
|
pbr_input.material.ior = pbr_bindings::material.ior;
|
||||||
pbr_input.material.attenuation_color = pbr_bindings::material.attenuation_color;
|
pbr_input.material.attenuation_color = pbr_bindings::material.attenuation_color;
|
||||||
pbr_input.material.attenuation_distance = pbr_bindings::material.attenuation_distance;
|
pbr_input.material.attenuation_distance = pbr_bindings::material.attenuation_distance;
|
||||||
pbr_input.material.alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
pbr_input.material.alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
||||||
#endif // BINDLESS
|
#endif // BINDLESS
|
||||||
|
|
||||||
|
// reflectance
|
||||||
|
#ifdef BINDLESS
|
||||||
|
pbr_input.material.reflectance = pbr_bindings::material[slot].reflectance;
|
||||||
|
#else // BINDLESS
|
||||||
|
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
|
||||||
|
#endif // BINDLESS
|
||||||
|
|
||||||
|
#ifdef PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||||
|
#ifdef VERTEX_UVS
|
||||||
|
|
||||||
|
// Specular texture
|
||||||
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_SPECULAR_TEXTURE_BIT) != 0u) {
|
||||||
|
let specular =
|
||||||
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||||
|
textureSampleGrad(
|
||||||
|
#else // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
textureSampleBias(
|
||||||
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
#ifdef BINDLESS
|
||||||
|
pbr_bindings::specular_texture[slot],
|
||||||
|
pbr_bindings::specular_sampler[slot],
|
||||||
|
#else // BINDLESS
|
||||||
|
pbr_bindings::specular_texture,
|
||||||
|
pbr_bindings::specular_sampler,
|
||||||
|
#endif // BINDLESS
|
||||||
|
#ifdef STANDARD_MATERIAL_SPECULAR_UV_B
|
||||||
|
uv_b,
|
||||||
|
#else // STANDARD_MATERIAL_SPECULAR_UV_B
|
||||||
|
uv,
|
||||||
|
#endif // STANDARD_MATERIAL_SPECULAR_UV_B
|
||||||
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||||
|
bias.ddx_uv,
|
||||||
|
bias.ddy_uv,
|
||||||
|
#else // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
bias.mip_bias,
|
||||||
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
).a;
|
||||||
|
// This 0.5 factor is from the `KHR_materials_specular` specification:
|
||||||
|
// <https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular#materials-with-reflectance-parameter>
|
||||||
|
pbr_input.material.reflectance *= specular * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specular tint texture
|
||||||
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_SPECULAR_TINT_TEXTURE_BIT) != 0u) {
|
||||||
|
let specular_tint =
|
||||||
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||||
|
textureSampleGrad(
|
||||||
|
#else // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
textureSampleBias(
|
||||||
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
#ifdef BINDLESS
|
||||||
|
pbr_bindings::specular_tint_texture[slot],
|
||||||
|
pbr_bindings::specular_tint_sampler[slot],
|
||||||
|
#else // BINDLESS
|
||||||
|
pbr_bindings::specular_tint_texture,
|
||||||
|
pbr_bindings::specular_tint_sampler,
|
||||||
|
#endif // BINDLESS
|
||||||
|
#ifdef STANDARD_MATERIAL_SPECULAR_TINT_UV_B
|
||||||
|
uv_b,
|
||||||
|
#else // STANDARD_MATERIAL_SPECULAR_TINT_UV_B
|
||||||
|
uv,
|
||||||
|
#endif // STANDARD_MATERIAL_SPECULAR_TINT_UV_B
|
||||||
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||||
|
bias.ddx_uv,
|
||||||
|
bias.ddy_uv,
|
||||||
|
#else // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
bias.mip_bias,
|
||||||
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||||
|
).rgb;
|
||||||
|
pbr_input.material.reflectance *= specular_tint;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // VERTEX_UVS
|
||||||
|
#endif // PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||||
|
|
||||||
// emissive
|
// emissive
|
||||||
#ifdef BINDLESS
|
#ifdef BINDLESS
|
||||||
var emissive: vec4<f32> = pbr_bindings::material[slot].emissive;
|
var emissive: vec4<f32> = pbr_bindings::material[slot].emissive;
|
||||||
|
@ -273,7 +273,7 @@ fn calculate_diffuse_color(
|
|||||||
|
|
||||||
// Remapping [0,1] reflectance to F0
|
// Remapping [0,1] reflectance to F0
|
||||||
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
|
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
|
||||||
fn calculate_F0(base_color: vec3<f32>, metallic: f32, reflectance: f32) -> vec3<f32> {
|
fn calculate_F0(base_color: vec3<f32>, metallic: f32, reflectance: vec3<f32>) -> vec3<f32> {
|
||||||
return 0.16 * reflectance * reflectance * (1.0 - metallic) + base_color * metallic;
|
return 0.16 * reflectance * reflectance * (1.0 - metallic) + base_color * metallic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@ struct StandardMaterial {
|
|||||||
emissive: vec4<f32>,
|
emissive: vec4<f32>,
|
||||||
attenuation_color: vec4<f32>,
|
attenuation_color: vec4<f32>,
|
||||||
uv_transform: mat3x3<f32>,
|
uv_transform: mat3x3<f32>,
|
||||||
|
reflectance: vec3<f32>,
|
||||||
perceptual_roughness: f32,
|
perceptual_roughness: f32,
|
||||||
metallic: f32,
|
metallic: f32,
|
||||||
reflectance: f32,
|
|
||||||
diffuse_transmission: f32,
|
diffuse_transmission: f32,
|
||||||
specular_transmission: f32,
|
specular_transmission: f32,
|
||||||
thickness: f32,
|
thickness: f32,
|
||||||
@ -52,6 +52,8 @@ const STANDARD_MATERIAL_FLAGS_CLEARCOAT_TEXTURE_BIT: u32 = 16384u;
|
|||||||
const STANDARD_MATERIAL_FLAGS_CLEARCOAT_ROUGHNESS_TEXTURE_BIT: u32 = 32768u;
|
const STANDARD_MATERIAL_FLAGS_CLEARCOAT_ROUGHNESS_TEXTURE_BIT: u32 = 32768u;
|
||||||
const STANDARD_MATERIAL_FLAGS_CLEARCOAT_NORMAL_TEXTURE_BIT: u32 = 65536u;
|
const STANDARD_MATERIAL_FLAGS_CLEARCOAT_NORMAL_TEXTURE_BIT: u32 = 65536u;
|
||||||
const STANDARD_MATERIAL_FLAGS_ANISOTROPY_TEXTURE_BIT: u32 = 131072u;
|
const STANDARD_MATERIAL_FLAGS_ANISOTROPY_TEXTURE_BIT: u32 = 131072u;
|
||||||
|
const STANDARD_MATERIAL_FLAGS_SPECULAR_TEXTURE_BIT: u32 = 262144u;
|
||||||
|
const STANDARD_MATERIAL_FLAGS_SPECULAR_TINT_TEXTURE_BIT: u32 = 524288u;
|
||||||
const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS: u32 = 3758096384u; // (0b111u32 << 29)
|
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_OPAQUE: u32 = 0u; // (0u32 << 29)
|
||||||
const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 536870912u; // (1u32 << 29)
|
const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 536870912u; // (1u32 << 29)
|
||||||
@ -73,7 +75,7 @@ fn standard_material_new() -> StandardMaterial {
|
|||||||
material.emissive = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
material.emissive = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
material.perceptual_roughness = 0.5;
|
material.perceptual_roughness = 0.5;
|
||||||
material.metallic = 0.00;
|
material.metallic = 0.00;
|
||||||
material.reflectance = 0.5;
|
material.reflectance = vec3<f32>(0.5);
|
||||||
material.diffuse_transmission = 0.0;
|
material.diffuse_transmission = 0.0;
|
||||||
material.specular_transmission = 0.0;
|
material.specular_transmission = 0.0;
|
||||||
material.thickness = 0.0;
|
material.thickness = 0.0;
|
||||||
|
@ -87,6 +87,7 @@ The default feature set enables most of the expected features of a game engine,
|
|||||||
|mp3|MP3 audio format support|
|
|mp3|MP3 audio format support|
|
||||||
|pbr_anisotropy_texture|Enable support for anisotropy texture in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs|
|
|pbr_anisotropy_texture|Enable support for anisotropy texture in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs|
|
||||||
|pbr_multi_layer_material_textures|Enable support for multi-layer material textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs|
|
|pbr_multi_layer_material_textures|Enable support for multi-layer material textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs|
|
||||||
|
|pbr_specular_textures|Enable support for specular textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs|
|
||||||
|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|
|
|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|
|
|pnm|PNM image format support, includes pam, pbm, pgm and ppm|
|
||||||
|qoi|QOI image format support|
|
|qoi|QOI image format support|
|
||||||
|
227
examples/3d/specular_tint.rs
Normal file
227
examples/3d/specular_tint.rs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
//! Demonstrates specular tints and maps.
|
||||||
|
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use bevy::{color::palettes::css::WHITE, core_pipeline::Skybox, prelude::*};
|
||||||
|
|
||||||
|
/// The camera rotation speed in radians per frame.
|
||||||
|
const ROTATION_SPEED: f32 = 0.005;
|
||||||
|
/// The rate at which the specular tint hue changes in degrees per frame.
|
||||||
|
const HUE_SHIFT_SPEED: f32 = 0.2;
|
||||||
|
|
||||||
|
static SWITCH_TO_MAP_HELP_TEXT: &str = "Press Space to switch to a specular map";
|
||||||
|
static SWITCH_TO_SOLID_TINT_HELP_TEXT: &str = "Press Space to switch to a solid specular tint";
|
||||||
|
|
||||||
|
/// The current settings the user has chosen.
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct AppStatus {
|
||||||
|
/// The type of tint (solid or texture map).
|
||||||
|
tint_type: TintType,
|
||||||
|
/// The hue of the solid tint in radians.
|
||||||
|
hue: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assets needed by the demo.
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct AppAssets {
|
||||||
|
/// A color tileable 3D noise texture.
|
||||||
|
noise_texture: Handle<Image>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for AppAssets {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
let asset_server = world.resource::<AssetServer>();
|
||||||
|
Self {
|
||||||
|
noise_texture: asset_server.load("textures/AlphaNoise.png"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of specular tint that the user has selected.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Default)]
|
||||||
|
enum TintType {
|
||||||
|
/// A solid color.
|
||||||
|
#[default]
|
||||||
|
Solid,
|
||||||
|
/// A Perlin noise texture.
|
||||||
|
Map,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The entry point.
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
|
primary_window: Some(Window {
|
||||||
|
title: "Bevy Specular Tint Example".into(),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
}))
|
||||||
|
.init_resource::<AppAssets>()
|
||||||
|
.init_resource::<AppStatus>()
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
color: Color::BLACK,
|
||||||
|
brightness: 0.0,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, rotate_camera)
|
||||||
|
.add_systems(Update, (toggle_specular_map, update_text).chain())
|
||||||
|
.add_systems(Update, shift_hue.after(toggle_specular_map))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the scene.
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
app_status: Res<AppStatus>,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut standard_materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// Spawns a camera.
|
||||||
|
commands.spawn((
|
||||||
|
Transform::from_xyz(-2.0, 0.0, 3.5).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
Camera {
|
||||||
|
hdr: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Camera3d::default(),
|
||||||
|
Skybox {
|
||||||
|
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
|
||||||
|
brightness: 3000.0,
|
||||||
|
..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"),
|
||||||
|
// We want relatively high intensity here in order for the specular
|
||||||
|
// tint to show up well.
|
||||||
|
intensity: 25000.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
// Spawn the sphere.
|
||||||
|
commands.spawn((
|
||||||
|
Transform::from_rotation(Quat::from_rotation_x(PI * 0.5)),
|
||||||
|
Mesh3d(meshes.add(Sphere::default().mesh().uv(32, 18))),
|
||||||
|
MeshMaterial3d(standard_materials.add(StandardMaterial {
|
||||||
|
// We want only reflected specular light here, so we set the base
|
||||||
|
// color as black.
|
||||||
|
base_color: Color::BLACK,
|
||||||
|
reflectance: 1.0,
|
||||||
|
specular_tint: Color::hsva(app_status.hue, 1.0, 1.0, 1.0),
|
||||||
|
// The object must not be metallic, or else the reflectance is
|
||||||
|
// ignored per the Filament spec:
|
||||||
|
//
|
||||||
|
// <https://google.github.io/filament/Filament.html#listing_fnormal>
|
||||||
|
metallic: 0.0,
|
||||||
|
perceptual_roughness: 0.0,
|
||||||
|
..default()
|
||||||
|
})),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Spawn the help text.
|
||||||
|
commands.spawn((
|
||||||
|
Node {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
bottom: Val::Px(12.0),
|
||||||
|
left: Val::Px(12.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
app_status.create_text(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rotates the camera a bit every frame.
|
||||||
|
fn rotate_camera(mut cameras: Query<&mut Transform, With<Camera3d>>) {
|
||||||
|
for mut camera_transform in cameras.iter_mut() {
|
||||||
|
camera_transform.translation =
|
||||||
|
Quat::from_rotation_y(ROTATION_SPEED) * camera_transform.translation;
|
||||||
|
camera_transform.look_at(Vec3::ZERO, Vec3::Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alters the hue of the solid color a bit every frame.
|
||||||
|
fn shift_hue(
|
||||||
|
mut app_status: ResMut<AppStatus>,
|
||||||
|
objects_with_materials: Query<&MeshMaterial3d<StandardMaterial>>,
|
||||||
|
mut standard_materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
if app_status.tint_type != TintType::Solid {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app_status.hue += HUE_SHIFT_SPEED;
|
||||||
|
|
||||||
|
for material_handle in objects_with_materials.iter() {
|
||||||
|
let Some(material) = standard_materials.get_mut(material_handle) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
material.specular_tint = Color::hsva(app_status.hue, 1.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppStatus {
|
||||||
|
/// Returns appropriate help text that reflects the current app status.
|
||||||
|
fn create_text(&self) -> Text {
|
||||||
|
let tint_map_help_text = match self.tint_type {
|
||||||
|
TintType::Solid => SWITCH_TO_MAP_HELP_TEXT,
|
||||||
|
TintType::Map => SWITCH_TO_SOLID_TINT_HELP_TEXT,
|
||||||
|
};
|
||||||
|
|
||||||
|
Text::new(tint_map_help_text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the specular tint to a solid color or map when the user presses
|
||||||
|
/// Space.
|
||||||
|
fn toggle_specular_map(
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut app_status: ResMut<AppStatus>,
|
||||||
|
app_assets: Res<AppAssets>,
|
||||||
|
objects_with_materials: Query<&MeshMaterial3d<StandardMaterial>>,
|
||||||
|
mut standard_materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
if !keyboard.just_pressed(KeyCode::Space) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap tint type.
|
||||||
|
app_status.tint_type = match app_status.tint_type {
|
||||||
|
TintType::Solid => TintType::Map,
|
||||||
|
TintType::Map => TintType::Solid,
|
||||||
|
};
|
||||||
|
|
||||||
|
for material_handle in objects_with_materials.iter() {
|
||||||
|
let Some(material) = standard_materials.get_mut(material_handle) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adjust the tint type.
|
||||||
|
match app_status.tint_type {
|
||||||
|
TintType::Solid => {
|
||||||
|
material.reflectance = 1.0;
|
||||||
|
material.specular_tint_texture = None;
|
||||||
|
}
|
||||||
|
TintType::Map => {
|
||||||
|
// Set reflectance to 2.0 to spread out the map's reflectance
|
||||||
|
// range from the default [0.0, 0.5] to [0.0, 1.0].
|
||||||
|
material.reflectance = 2.0;
|
||||||
|
// As the tint map is multiplied by the tint color, we set the
|
||||||
|
// latter to white so that only the map has an effect.
|
||||||
|
material.specular_tint = WHITE.into();
|
||||||
|
material.specular_tint_texture = Some(app_assets.noise_texture.clone());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the help text at the bottom of the screen to reflect the current app
|
||||||
|
/// status.
|
||||||
|
fn update_text(mut text_query: Query<&mut Text>, app_status: Res<AppStatus>) {
|
||||||
|
for mut text in text_query.iter_mut() {
|
||||||
|
*text = app_status.create_text();
|
||||||
|
}
|
||||||
|
}
|
@ -180,6 +180,7 @@ Example | Description
|
|||||||
[Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
[Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
||||||
[Shadow Caster and Receiver](../examples/3d/shadow_caster_receiver.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
|
[Shadow Caster and Receiver](../examples/3d/shadow_caster_receiver.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
|
||||||
[Skybox](../examples/3d/skybox.rs) | Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats.
|
[Skybox](../examples/3d/skybox.rs) | Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats.
|
||||||
|
[Specular Tint](../examples/3d/specular_tint.rs) | Demonstrates specular tints and maps
|
||||||
[Spherical Area Lights](../examples/3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior
|
[Spherical Area Lights](../examples/3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior
|
||||||
[Split Screen](../examples/3d/split_screen.rs) | Demonstrates how to render two cameras to the same window to accomplish "split screen"
|
[Split Screen](../examples/3d/split_screen.rs) | Demonstrates how to render two cameras to the same window to accomplish "split screen"
|
||||||
[Spotlight](../examples/3d/spotlight.rs) | Illustrates spot lights
|
[Spotlight](../examples/3d/spotlight.rs) | Illustrates spot lights
|
||||||
|
Loading…
Reference in New Issue
Block a user