Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions crates/bevy_gltf/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1500,9 +1500,7 @@ 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,
specular: specular.specular_factor.unwrap_or(1.0) as f32,
#[cfg(feature = "pbr_specular_textures")]
specular_channel: specular.specular_channel,
#[cfg(feature = "pbr_specular_textures")]
Expand Down
13 changes: 5 additions & 8 deletions crates/bevy_gltf/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ pub struct GltfMaterial {
/// Metallic and roughness maps, stored as a single texture.
pub metallic_roughness_texture: Option<Handle<Image>>,

/// Specular intensity for non-metals on a linear scale of `[0.0, 1.0]`.
pub reflectance: f32,
/// Specular strength for non-metals on a linear scale of `[0.0, 1.0]`.
pub specular: f32,

/// The UV channel to use for the [`GltfMaterial::specular_texture`].
#[cfg(feature = "pbr_specular_textures")]
Expand All @@ -54,8 +54,8 @@ pub struct GltfMaterial {
#[cfg(feature = "pbr_specular_textures")]
pub specular_texture: Option<Handle<Image>>,

/// A color with which to modulate the [`GltfMaterial::reflectance`] for
/// non-metals.
/// A color with which to modulate the default reflectance at normal incidence (which is computed from [`GltfMaterial::ior`]).
/// Only affects non-metals.
pub specular_tint: Color,

/// The UV channel to use for the
Expand Down Expand Up @@ -206,10 +206,6 @@ impl Default for GltfMaterial {
metallic: 0.0,
metallic_roughness_channel: UvChannel::Uv0,
metallic_roughness_texture: None,
// Minimum real-world reflectance is 2%, most materials between 2-5%
// Expressed in a linear scale and equivalent to 4% reflectance see
// <https://google.github.io/filament/Material%20Properties.pdf>
reflectance: 0.5,
specular_transmission: 0.0,
#[cfg(feature = "pbr_transmission_textures")]
specular_transmission_channel: UvChannel::Uv0,
Expand All @@ -227,6 +223,7 @@ impl Default for GltfMaterial {
occlusion_texture: None,
normal_map_channel: UvChannel::Uv0,
normal_map_texture: None,
specular: 1.0,
#[cfg(feature = "pbr_specular_textures")]
specular_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_specular_textures")]
Expand Down
21 changes: 13 additions & 8 deletions crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,23 @@ fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
// 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(
reflectance,
let ior_specular = deferred_types::pack_4bit_ior_specular(in.material.ior, in.material.specular_weight);
let ior_specular_props = f32(ior_specular) / 15.0;
var props = deferred_types::pack_unorm3x4_unorm_20_(vec4(
ior_specular_props,
in.material.metallic,
diffuse_occlusion,
in.frag_coord.z));
#else
let ior_specular = deferred_types::pack_8bit_ior_specular(in.material.ior, in.material.specular_weight);
let ior_specular_props = f32(ior_specular) / 255.0;
let clearcoat = u32(round(saturate(in.material.clearcoat) * 15.0));
let clearcoat_perceptual_roughness =
u32(round(clamp(in.material.clearcoat_perceptual_roughness, 0.0, 0.5) * 30.0));
let clearcoat_props = f32(clearcoat | (clearcoat_perceptual_roughness << 4u)) / 255.0;
var props = deferred_types::pack_unorm4x8_(vec4(
reflectance, // could be fewer bits
ior_specular_props,
in.material.metallic, // could be fewer bits
diffuse_occlusion, // is this worth including?
clearcoat_props));
Expand Down Expand Up @@ -106,15 +108,18 @@ 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 = vec3(saturate(props.r - 0.03333333333));
let ior_specular_props = u32(rounds(props.g * 15.0));
let ior_specular = deferred_types::unpack_4bit_ior_specular(ior_specular_props);
#else
let props = deferred_types::unpack_unorm4x8_(gbuffer.b);
let ior_specular_props = u32(round(props.r * 255.0));
let ior_specular = deferred_types::unpack_8bit_ior_specular(ior_specular_props);
let clearcoat_props = u32(round(props.a * 255.0));
pbr.material.reflectance = vec3(props.r);
pbr.material.clearcoat = f32(clearcoat_props & 0x0fu) / 15.0;
pbr.material.clearcoat_perceptual_roughness = f32(clearcoat_props >> 4u) / 30.0;
#endif // WEBGL2
pbr.material.ior = ior_specular.x;
pbr.material.specular_weight = ior_specular.y;
pbr.material.metallic = props.g;
pbr.diffuse_occlusion = vec3(props.b);
let octahedral_normal = deferred_types::unpack_24bit_normal(gbuffer.a);
Expand Down
39 changes: 39 additions & 0 deletions crates/bevy_pbr/src/deferred/pbr_deferred_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,45 @@ fn unpack_flags(packed: u32) -> u32 {
return (packed >> 24u) & 0xFFu;
}


#ifdef WEBGL2
// Pack ior and specular strength into 4 bits.
// With so little space, specular is made binary, and all values except specular = 0.0 are mapped to 1
// IOR values outside [1.33, 2.33] are clamped.
fn pack_4bit_ior_specular(ior: f32, specular: f32) -> u32 {
var ior_remapped = saturate(ior - 1.33);
// give more range to lower values
ior_remapped = sqrt(ior_remapped);
let ior3 = u32(ior_remapped * 7.0 + 0.5);
return (u32(specular > 0.0) << 3u) | ior3;
}

fn unpack_4bit_ior_specular(v: u32) -> vec2<f32> {
let specular = f32(v >> 3u);
var ior = f32(v & 0x7u) / 7.0;
ior = ior * ior + 1.33;
return vec2<f32>(ior, specular);
}
#endif // WEBGL2

// Pack ior and specular strength into 8 bits. Allocation is IOR: 5 bits / specular: 3 bits
// IOR values outside [1.0. 3.0] are clamped
fn pack_8bit_ior_specular(ior: f32, specular: f32) -> u32 {
var ior_remapped = saturate((ior - 1.0) / 2.0);
// give more range to lower values
ior_remapped = sqrt(ior_remapped);
let ior5 = u32(ior_remapped * 31.0 + 0.5);
let specular3 = u32(saturate(specular) * 7.0 + 0.5);
return (specular3 << 5u) | ior5;
}

fn unpack_8bit_ior_specular(v: u32) -> vec2<f32> {
let specular = f32(v >> 5u) / 7.0;
var ior = f32(v & 0x1Fu) / 31.0;
ior = ior * ior * 2.0 + 1.0;
return vec2<f32>(ior, specular);
}

// The builtin one didn't work in webgl.
// "'unpackUnorm4x8' : no matching overloaded function found"
// https://github.com/gfx-rs/naga/issues/2006
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn standard_material_from_gltf_material(material: &GltfMaterial) -> Standard
metallic: material.metallic,
metallic_roughness_channel: material.metallic_roughness_channel.clone(),
metallic_roughness_texture: material.metallic_roughness_texture.clone(),
reflectance: material.reflectance,
specular: material.specular,
specular_tint: material.specular_tint,
specular_transmission: material.specular_transmission,
#[cfg(feature = "pbr_transmission_textures")]
Expand Down
14 changes: 5 additions & 9 deletions crates/bevy_pbr/src/light_probe/environment_map.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,8 @@ fn compute_multiscatter(
F0: vec3<f32>,
F_ab: vec2<f32>,
Ems: f32,
specular_occlusion: f32,
) -> MultiscatterResult {
let FssEss = (F0 * F_ab.x + F_ab.y) * specular_occlusion;
let FssEss = (F0 * F_ab.x + F_ab.y);
let Favg = F0 + (1.0 - F0) / 21.0;
let FmsEms = FssEss * Favg / (1.0 - Ems * Favg) * Ems;
let Edss = 1.0 - (FssEss + FmsEms);
Expand All @@ -352,6 +351,7 @@ fn environment_map_light(
let F_ab = (*input).F_ab;
let F0_dielectric = (*input).F0_dielectric;
let F0_metallic = (*input).F0_metallic;
let specular_weight = (*input).specular_weight;
let world_position = (*input).P;

var out: EnvironmentMapLight;
Expand All @@ -369,18 +369,14 @@ fn environment_map_light(
return out;
}

// No real world material has specular values under 0.02, so we use this range as a
// "pre-baked specular occlusion" that extinguishes the fresnel term, for artistic control.
// See: https://google.github.io/filament/Filament.md.html#specularocclusion
let F0_surface = mix(F0_dielectric, F0_metallic, metallic);
let specular_occlusion = saturate(dot(F0_surface, vec3(50.0 * 0.33)));

// Compute per-material (dielectric and metallic separately) then mix the results.
// We can't use F0 directly as the multiscattering term is nonlinear.
let Ems = 1.0 - (F_ab.x + F_ab.y);

let ms_dielectric = compute_multiscatter(F0_dielectric, F_ab, Ems, specular_occlusion);
let ms_metallic = compute_multiscatter(F0_metallic, F_ab, Ems, specular_occlusion);
let ms_dielectric = compute_multiscatter(F0_dielectric, F_ab, Ems);
let ms_metallic = compute_multiscatter(F0_metallic, F_ab, Ems);

let FssEss = mix(ms_dielectric.FssEss, ms_metallic.FssEss, metallic);
let FmsEms = mix(ms_dielectric.FmsEms, ms_metallic.FmsEms, metallic);
Expand All @@ -392,7 +388,7 @@ fn environment_map_light(
out.diffuse = vec3(0.0);
}

out.specular = FssEss * radiances.radiance;
out.specular = specular_weight * FssEss * radiances.radiance;

#ifdef STANDARD_MATERIAL_CLEARCOAT
environment_map_light_clearcoat(
Expand Down
53 changes: 23 additions & 30 deletions crates/bevy_pbr/src/pbr_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,24 +169,22 @@ pub struct StandardMaterial {
#[dependency]
pub metallic_roughness_texture: Option<Handle<Image>>,

/// Specular intensity for non-metals on a linear scale of `[0.0, 1.0]`.
/// Specular strength for non-metals on a linear scale of `[0.0, 1.0]`.
///
/// Use the value as a way to control the intensity of the
/// specular highlight of the material, i.e. how reflective is the material,
/// rather than the physical property "reflectance."
/// For non metals the specular reflectance at normal incidence is governed by the [`StandardMaterial::ior`] parameter.
///
/// Set to `0.0`, no specular highlight is visible, the highlight is strongest
/// when `reflectance` is set to `1.0`.
/// Use this value as a way to scale down this default reflectance, and thus the intensity of the
/// specular highlight of the material, i.e. how reflective the material ultimately is.
///
/// Defaults to `0.5` which is mapped to 4% reflectance in the shader.
/// Set to `0.0`, no specular highlight is visible. The highlight is strongest when `specular` is set to `1.0`.
///
/// Defaults to `1.0`, which will compute the normal incidence reflectance according to the material's IOR.
#[doc(alias = "specular_intensity")]
pub reflectance: f32,
pub specular: f32,

/// A color with which to modulate the [`StandardMaterial::reflectance`] for
/// non-metals.
/// A color with which to modulate the specular reflectance for non-metals.
///
/// The specular highlights and reflection are tinted with this color. Note
/// that it has no effect for non-metals.
/// The specular highlights and reflection are tinted with this color.
///
/// This feature is currently unsupported in the deferred rendering path, in
/// order to reduce the size of the geometry buffers.
Expand Down Expand Up @@ -444,19 +442,15 @@ pub struct StandardMaterial {
#[cfg(feature = "pbr_specular_textures")]
pub specular_channel: UvChannel,

/// A map that specifies reflectance for non-metallic materials.
/// A map that adjusts the strength of the highlights and reflection 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
/// Alpha values from [0.0, 1.0] in this texture will be multiplied with the constant
/// [`StandardMaterial::specular`] value, to obtain a strength factor that will linearly
/// linearly scale the default specular reflectance of the material.
/// 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
/// Note that, because the specular strength 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.
Expand Down Expand Up @@ -866,10 +860,6 @@ impl Default for StandardMaterial {
metallic: 0.0,
metallic_roughness_channel: UvChannel::Uv0,
metallic_roughness_texture: None,
// Minimum real-world reflectance is 2%, most materials between 2-5%
// Expressed in a linear scale and equivalent to 4% reflectance see
// <https://google.github.io/filament/Material%20Properties.pdf>
reflectance: 0.5,
diffuse_transmission: 0.0,
#[cfg(feature = "pbr_transmission_textures")]
diffuse_transmission_channel: UvChannel::Uv0,
Expand All @@ -892,6 +882,7 @@ impl Default for StandardMaterial {
occlusion_texture: None,
normal_map_channel: UvChannel::Uv0,
normal_map_texture: None,
specular: 1.0,
#[cfg(feature = "pbr_specular_textures")]
specular_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_specular_textures")]
Expand Down Expand Up @@ -1020,9 +1011,10 @@ 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,
/// Specular tint modulating non-metals specular reflectance.
pub specular_tint: Vec3,
/// Specular strength for non-metals on a linear scale of [0.0, 1.0]. Default is 1.0.
pub specular_weight: f32,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
pub roughness: f32,
Expand Down Expand Up @@ -1187,7 +1179,8 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
emissive,
roughness: self.perceptual_roughness,
metallic: self.metallic,
reflectance: LinearRgba::from(self.specular_tint).to_vec3() * self.reflectance,
specular_tint: LinearRgba::from(self.specular_tint).to_vec3(),
specular_weight: self.specular,
clearcoat: self.clearcoat,
clearcoat_perceptual_roughness: self.clearcoat_perceptual_roughness,
anisotropy_strength: self.anisotropy_strength,
Expand Down
17 changes: 9 additions & 8 deletions crates/bevy_pbr/src/render/pbr_fragment.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,15 @@ pbr_input.material.uv_transform = uv_transform;
pbr_input.material.attenuation_distance = pbr_bindings::material.attenuation_distance;
#endif // BINDLESS

// reflectance
// Specular reflectance
#ifdef BINDLESS
pbr_input.material.reflectance =
pbr_bindings::material_array[material_indices[slot].material].reflectance;
pbr_input.material.specular_tint =
pbr_bindings::material_array[material_indices[slot].material].specular_tint;
pbr_input.material.specular_weight =
pbr_bindings::material_array[material_indices[slot].material].specular_weight;
#else // BINDLESS
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
pbr_input.material.specular_tint = pbr_bindings::material.specular_tint;
pbr_input.material.specular_weight = pbr_bindings::material.specular_weight;
#endif // BINDLESS

#ifdef PBR_SPECULAR_TEXTURES_SUPPORTED
Expand Down Expand Up @@ -295,9 +298,7 @@ pbr_input.material.uv_transform = uv_transform;
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;
pbr_input.material.specular_weight *= specular;
}

// Specular tint texture
Expand Down Expand Up @@ -327,7 +328,7 @@ pbr_input.material.uv_transform = uv_transform;
bias.mip_bias,
#endif // MESHLET_MESH_MATERIAL_PASS
).rgb;
pbr_input.material.reflectance *= specular_tint;
pbr_input.material.specular_tint *= specular_tint;
}

#endif // VERTEX_UVS
Expand Down
Loading