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
|
||||
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.
|
||||
webgl2 = ["bevy_internal/webgl"]
|
||||
|
||||
@ -4020,6 +4023,18 @@ description = "Demonstrates how to make materials that use bindless textures"
|
||||
category = "Shaders"
|
||||
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]
|
||||
inherits = "release"
|
||||
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",
|
||||
]
|
||||
pbr_anisotropy_texture = ["bevy_pbr/pbr_anisotropy_texture"]
|
||||
pbr_specular_textures = []
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
|
@ -51,6 +51,11 @@ use gltf::{
|
||||
Document, Material, Node, Primitive, Semantic,
|
||||
};
|
||||
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 std::{
|
||||
io::Error,
|
||||
@ -1235,6 +1240,10 @@ fn load_material(
|
||||
let anisotropy =
|
||||
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
|
||||
let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]);
|
||||
let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0);
|
||||
@ -1303,6 +1312,21 @@ fn load_material(
|
||||
anisotropy_channel: anisotropy.anisotropy_channel,
|
||||
#[cfg(feature = "pbr_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()
|
||||
}
|
||||
})
|
||||
@ -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.
|
||||
#[cfg(any(
|
||||
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(
|
||||
load_context: &mut LoadContext,
|
||||
@ -2122,40 +2147,35 @@ impl ClearcoatExtension {
|
||||
.as_object()?;
|
||||
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
let (clearcoat_channel, clearcoat_texture) = extension
|
||||
.get("clearcoatTexture")
|
||||
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
|
||||
.map(|json_info| {
|
||||
(
|
||||
get_uv_channel(material, "clearcoat", json_info.tex_coord),
|
||||
texture_handle_from_info(load_context, document, &json_info),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let (clearcoat_channel, clearcoat_texture) = parse_material_extension_texture(
|
||||
load_context,
|
||||
document,
|
||||
material,
|
||||
extension,
|
||||
"clearcoatTexture",
|
||||
"clearcoat",
|
||||
);
|
||||
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
let (clearcoat_roughness_channel, clearcoat_roughness_texture) = extension
|
||||
.get("clearcoatRoughnessTexture")
|
||||
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
|
||||
.map(|json_info| {
|
||||
(
|
||||
get_uv_channel(material, "clearcoat roughness", json_info.tex_coord),
|
||||
texture_handle_from_info(load_context, document, &json_info),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let (clearcoat_roughness_channel, clearcoat_roughness_texture) =
|
||||
parse_material_extension_texture(
|
||||
load_context,
|
||||
document,
|
||||
material,
|
||||
extension,
|
||||
"clearcoatRoughnessTexture",
|
||||
"clearcoat roughness",
|
||||
);
|
||||
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
let (clearcoat_normal_channel, clearcoat_normal_texture) = extension
|
||||
.get("clearcoatNormalTexture")
|
||||
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
|
||||
.map(|json_info| {
|
||||
(
|
||||
get_uv_channel(material, "clearcoat normal", json_info.tex_coord),
|
||||
texture_handle_from_info(load_context, document, &json_info),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let (clearcoat_normal_channel, clearcoat_normal_texture) = parse_material_extension_texture(
|
||||
load_context,
|
||||
document,
|
||||
material,
|
||||
extension,
|
||||
"clearcoatNormalTexture",
|
||||
"clearcoat normal",
|
||||
);
|
||||
|
||||
Some(ClearcoatExtension {
|
||||
clearcoat_factor: extension.get("clearcoatFactor").and_then(Value::as_f64),
|
||||
@ -2163,15 +2183,15 @@ impl ClearcoatExtension {
|
||||
.get("clearcoatRoughnessFactor")
|
||||
.and_then(Value::as_f64),
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_channel: clearcoat_channel.unwrap_or_default(),
|
||||
clearcoat_channel,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_texture,
|
||||
#[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")]
|
||||
clearcoat_roughness_texture,
|
||||
#[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")]
|
||||
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
|
||||
/// given field name in the data for the material extension with the given name,
|
||||
/// if there is one.
|
||||
|
@ -136,6 +136,12 @@ pbr_anisotropy_texture = [
|
||||
# Percentage-closer soft shadows
|
||||
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
|
||||
webgl = [
|
||||
"bevy_core_pipeline?/webgl",
|
||||
|
@ -15,6 +15,7 @@ pbr_transmission_textures = []
|
||||
pbr_multi_layer_material_textures = []
|
||||
pbr_anisotropy_texture = []
|
||||
experimental_pbr_pcss = []
|
||||
pbr_specular_textures = []
|
||||
shader_format_glsl = ["bevy_render/shader_format_glsl"]
|
||||
trace = ["bevy_render/trace"]
|
||||
ios_simulator = ["bevy_render/ios_simulator"]
|
||||
|
@ -23,21 +23,24 @@
|
||||
|
||||
// Creates the deferred gbuffer from a PbrInput.
|
||||
fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
|
||||
// Only monochrome occlusion supported. May not be worth including at all.
|
||||
// Some models have baked occlusion, GLTF only supports monochrome.
|
||||
// Real time occlusion is applied in the deferred lighting pass.
|
||||
// Deriving luminance via Rec. 709. coefficients
|
||||
// https://en.wikipedia.org/wiki/Rec._709
|
||||
let diffuse_occlusion = dot(in.diffuse_occlusion, vec3<f32>(0.2126, 0.7152, 0.0722));
|
||||
// Only monochrome occlusion supported. May not be worth including at all.
|
||||
// Some models have baked occlusion, GLTF only supports monochrome.
|
||||
// Real time occlusion is applied in the deferred lighting pass.
|
||||
// Deriving luminance via Rec. 709. coefficients
|
||||
// https://en.wikipedia.org/wiki/Rec._709
|
||||
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.
|
||||
var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4(
|
||||
in.material.reflectance,
|
||||
reflectance,
|
||||
in.material.metallic,
|
||||
diffuse_occlusion,
|
||||
in.frag_coord.z));
|
||||
#else
|
||||
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
|
||||
diffuse_occlusion, // is this worth including?
|
||||
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.
|
||||
let props = deferred_types::unpack_unorm3x4_plus_unorm_20_(gbuffer.b);
|
||||
// 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
|
||||
let props = deferred_types::unpack_unorm4x8_(gbuffer.b);
|
||||
pbr.material.reflectance = props.r;
|
||||
pbr.material.reflectance = vec3(props.r);
|
||||
#endif // WEBGL2
|
||||
pbr.material.metallic = props.g;
|
||||
pbr.diffuse_occlusion = vec3(props.b);
|
||||
|
@ -183,6 +183,19 @@ pub struct StandardMaterial {
|
||||
#[doc(alias = "specular_intensity")]
|
||||
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”).
|
||||
///
|
||||
/// Implemented as a second, flipped [Lambertian diffuse](https://en.wikipedia.org/wiki/Lambertian_reflectance) lobe,
|
||||
@ -401,6 +414,54 @@ pub struct StandardMaterial {
|
||||
#[dependency]
|
||||
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
|
||||
/// typically used for painted surfaces.
|
||||
///
|
||||
@ -801,6 +862,15 @@ impl Default for StandardMaterial {
|
||||
occlusion_texture: None,
|
||||
normal_map_channel: UvChannel::Uv0,
|
||||
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_perceptual_roughness: 0.5,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
@ -887,6 +957,8 @@ bitflags::bitflags! {
|
||||
const CLEARCOAT_ROUGHNESS_TEXTURE = 1 << 15;
|
||||
const CLEARCOAT_NORMAL_TEXTURE = 1 << 16;
|
||||
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_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.
|
||||
@ -918,14 +990,14 @@ pub struct StandardMaterialUniform {
|
||||
pub attenuation_color: Vec4,
|
||||
/// The transform applied to the UVs corresponding to `ATTRIBUTE_UV_0` on the mesh before sampling. Default is identity.
|
||||
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
|
||||
/// Defaults to minimum of 0.089
|
||||
pub roughness: f32,
|
||||
/// From [0.0, 1.0], dielectric to pure metallic
|
||||
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
|
||||
pub diffuse_transmission: f32,
|
||||
/// 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")]
|
||||
{
|
||||
if self.clearcoat_texture.is_some() {
|
||||
@ -1075,7 +1157,7 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
||||
emissive,
|
||||
roughness: self.perceptual_roughness,
|
||||
metallic: self.metallic,
|
||||
reflectance: self.reflectance,
|
||||
reflectance: LinearRgba::from(self.specular_tint).to_vec3() * self.reflectance,
|
||||
clearcoat: self.clearcoat,
|
||||
clearcoat_perceptual_roughness: self.clearcoat_perceptual_roughness,
|
||||
anisotropy_strength: self.anisotropy_strength,
|
||||
@ -1125,6 +1207,8 @@ bitflags! {
|
||||
const CLEARCOAT_UV = 0x040000;
|
||||
const CLEARCOAT_ROUGHNESS_UV = 0x080000;
|
||||
const CLEARCOAT_NORMAL_UV = 0x100000;
|
||||
const SPECULAR_UV = 0x200000;
|
||||
const SPECULAR_TINT_UV = 0x400000;
|
||||
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")]
|
||||
{
|
||||
key.set(
|
||||
@ -1392,7 +1488,15 @@ impl Material for StandardMaterial {
|
||||
),
|
||||
(
|
||||
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) {
|
||||
|
@ -2054,6 +2054,9 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||
if cfg!(feature = "pbr_anisotropy_texture") {
|
||||
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()];
|
||||
|
||||
|
@ -77,3 +77,17 @@
|
||||
@group(2) @binding(26) var clearcoat_normal_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
#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
|
||||
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
||||
#ifdef BINDLESS
|
||||
pbr_input.material.reflectance = pbr_bindings::material[slot].reflectance;
|
||||
pbr_input.material.ior = pbr_bindings::material[slot].ior;
|
||||
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.alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff;
|
||||
#else // BINDLESS
|
||||
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
|
||||
pbr_input.material.ior = pbr_bindings::material.ior;
|
||||
pbr_input.material.attenuation_color = pbr_bindings::material.attenuation_color;
|
||||
pbr_input.material.attenuation_distance = pbr_bindings::material.attenuation_distance;
|
||||
pbr_input.material.alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
||||
#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
|
||||
#ifdef BINDLESS
|
||||
var emissive: vec4<f32> = pbr_bindings::material[slot].emissive;
|
||||
|
@ -273,7 +273,7 @@ fn calculate_diffuse_color(
|
||||
|
||||
// Remapping [0,1] reflectance to F0
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,9 @@ struct StandardMaterial {
|
||||
emissive: vec4<f32>,
|
||||
attenuation_color: vec4<f32>,
|
||||
uv_transform: mat3x3<f32>,
|
||||
reflectance: vec3<f32>,
|
||||
perceptual_roughness: f32,
|
||||
metallic: f32,
|
||||
reflectance: f32,
|
||||
diffuse_transmission: f32,
|
||||
specular_transmission: 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_NORMAL_TEXTURE_BIT: u32 = 65536u;
|
||||
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_OPAQUE: u32 = 0u; // (0u32 << 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.perceptual_roughness = 0.5;
|
||||
material.metallic = 0.00;
|
||||
material.reflectance = 0.5;
|
||||
material.reflectance = vec3<f32>(0.5);
|
||||
material.diffuse_transmission = 0.0;
|
||||
material.specular_transmission = 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|
|
||||
|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_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|
|
||||
|pnm|PNM image format support, includes pam, pbm, pgm and ppm|
|
||||
|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 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.
|
||||
[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
|
||||
[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
|
||||
|
Loading…
Reference in New Issue
Block a user