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:
Patrick Walton 2025-01-26 12:38:46 -08:00 committed by GitHub
parent fc831c390d
commit 1c765c9ae7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 640 additions and 54 deletions

View File

@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@ -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

View File

@ -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.

View File

@ -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",

View File

@ -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"]

View File

@ -23,21 +23,24 @@
// Creates the deferred gbuffer from a PbrInput. // Creates the deferred gbuffer from a PbrInput.
fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> { fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
// Only monochrome occlusion supported. May not be worth including at all. // Only monochrome occlusion supported. May not be worth including at all.
// Some models have baked occlusion, GLTF only supports monochrome. // Some models have baked occlusion, GLTF only supports monochrome.
// 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);

View File

@ -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) {

View File

@ -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()];

View File

@ -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

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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|

View 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();
}
}

View File

@ -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