
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.
824 lines
28 KiB
WebGPU Shading Language
824 lines
28 KiB
WebGPU Shading Language
#define_import_path bevy_pbr::pbr_fragment
|
|
|
|
#import bevy_pbr::{
|
|
pbr_functions,
|
|
pbr_functions::SampleBias,
|
|
pbr_bindings,
|
|
pbr_types,
|
|
prepass_utils,
|
|
lighting,
|
|
mesh_bindings::mesh,
|
|
mesh_view_bindings::view,
|
|
parallax_mapping::parallaxed_uv,
|
|
lightmap::lightmap,
|
|
}
|
|
|
|
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
|
#import bevy_pbr::mesh_view_bindings::screen_space_ambient_occlusion_texture
|
|
#import bevy_pbr::ssao_utils::ssao_multibounce
|
|
#endif
|
|
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
#import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput
|
|
#else ifdef PREPASS_PIPELINE
|
|
#import bevy_pbr::prepass_io::VertexOutput
|
|
#else
|
|
#import bevy_pbr::forward_io::VertexOutput
|
|
#endif
|
|
|
|
// prepare a basic PbrInput from the vertex stage output, mesh binding and view binding
|
|
fn pbr_input_from_vertex_output(
|
|
in: VertexOutput,
|
|
is_front: bool,
|
|
double_sided: bool,
|
|
) -> pbr_types::PbrInput {
|
|
var pbr_input: pbr_types::PbrInput = pbr_types::pbr_input_new();
|
|
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
pbr_input.flags = in.mesh_flags;
|
|
#else
|
|
pbr_input.flags = mesh[in.instance_index].flags;
|
|
#endif
|
|
|
|
pbr_input.is_orthographic = view.clip_from_view[3].w == 1.0;
|
|
pbr_input.V = pbr_functions::calculate_view(in.world_position, pbr_input.is_orthographic);
|
|
pbr_input.frag_coord = in.position;
|
|
pbr_input.world_position = in.world_position;
|
|
|
|
#ifdef VERTEX_COLORS
|
|
pbr_input.material.base_color = in.color;
|
|
#endif
|
|
|
|
pbr_input.world_normal = pbr_functions::prepare_world_normal(
|
|
in.world_normal,
|
|
double_sided,
|
|
is_front,
|
|
);
|
|
|
|
#ifdef LOAD_PREPASS_NORMALS
|
|
pbr_input.N = prepass_utils::prepass_normal(in.position, 0u);
|
|
#else
|
|
pbr_input.N = normalize(pbr_input.world_normal);
|
|
#endif
|
|
|
|
return pbr_input;
|
|
}
|
|
|
|
// Prepare a full PbrInput by sampling all textures to resolve
|
|
// the material members
|
|
fn pbr_input_from_standard_material(
|
|
in: VertexOutput,
|
|
is_front: bool,
|
|
) -> pbr_types::PbrInput {
|
|
#ifdef BINDLESS
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
let slot = in.material_bind_group_slot;
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
let flags = pbr_bindings::material[slot].flags;
|
|
let base_color = pbr_bindings::material[slot].base_color;
|
|
let deferred_lighting_pass_id = pbr_bindings::material[slot].deferred_lighting_pass_id;
|
|
#else // BINDLESS
|
|
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
|
let flags = pbr_bindings::material.flags;
|
|
let base_color = pbr_bindings::material.base_color;
|
|
let deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id;
|
|
#endif
|
|
|
|
let double_sided = (flags & pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u;
|
|
|
|
var pbr_input: pbr_types::PbrInput = pbr_input_from_vertex_output(in, is_front, double_sided);
|
|
pbr_input.material.flags = flags;
|
|
pbr_input.material.base_color *= base_color;
|
|
pbr_input.material.deferred_lighting_pass_id = deferred_lighting_pass_id;
|
|
|
|
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
|
|
let NdotV = max(dot(pbr_input.N, pbr_input.V), 0.0001);
|
|
|
|
// Fill in the sample bias so we can sample from textures.
|
|
var bias: SampleBias;
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv = in.ddx_uv;
|
|
bias.ddy_uv = in.ddy_uv;
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias = view.mip_bias;
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
|
|
// TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass
|
|
#ifdef VERTEX_UVS
|
|
|
|
#ifdef BINDLESS
|
|
let uv_transform = pbr_bindings::material[slot].uv_transform;
|
|
#else // BINDLESS
|
|
let uv_transform = pbr_bindings::material.uv_transform;
|
|
#endif // BINDLESS
|
|
|
|
#ifdef VERTEX_UVS_A
|
|
var uv = (uv_transform * vec3(in.uv, 1.0)).xy;
|
|
#endif
|
|
|
|
// TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass
|
|
#ifdef VERTEX_UVS_B
|
|
var uv_b = (uv_transform * vec3(in.uv_b, 1.0)).xy;
|
|
#else
|
|
var uv_b = uv;
|
|
#endif
|
|
|
|
#ifdef VERTEX_TANGENTS
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) {
|
|
let V = pbr_input.V;
|
|
let TBN = pbr_functions::calculate_tbn_mikktspace(in.world_normal, in.world_tangent);
|
|
let T = TBN[0];
|
|
let B = TBN[1];
|
|
let N = TBN[2];
|
|
// Transform V from fragment to camera in world space to tangent space.
|
|
let Vt = vec3(dot(V, T), dot(V, B), dot(V, N));
|
|
#ifdef VERTEX_UVS_A
|
|
// TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass
|
|
uv = parallaxed_uv(
|
|
#ifdef BINDLESS
|
|
pbr_bindings::material[slot].parallax_depth_scale,
|
|
pbr_bindings::material[slot].max_parallax_layer_count,
|
|
pbr_bindings::material[slot].max_relief_mapping_search_steps,
|
|
#else // BINDLESS
|
|
pbr_bindings::material.parallax_depth_scale,
|
|
pbr_bindings::material.max_parallax_layer_count,
|
|
pbr_bindings::material.max_relief_mapping_search_steps,
|
|
#endif // BINDLESS
|
|
uv,
|
|
// Flip the direction of Vt to go toward the surface to make the
|
|
// parallax mapping algorithm easier to understand and reason
|
|
// about.
|
|
-Vt,
|
|
slot,
|
|
);
|
|
#endif
|
|
|
|
#ifdef VERTEX_UVS_B
|
|
// TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass
|
|
uv_b = parallaxed_uv(
|
|
#ifdef BINDLESS
|
|
pbr_bindings::material[slot].parallax_depth_scale,
|
|
pbr_bindings::material[slot].max_parallax_layer_count,
|
|
pbr_bindings::material[slot].max_relief_mapping_search_steps,
|
|
#else // BINDLESS
|
|
pbr_bindings::material.parallax_depth_scale,
|
|
pbr_bindings::material.max_parallax_layer_count,
|
|
pbr_bindings::material.max_relief_mapping_search_steps,
|
|
#endif // BINDLESS
|
|
uv_b,
|
|
// Flip the direction of Vt to go toward the surface to make the
|
|
// parallax mapping algorithm easier to understand and reason
|
|
// about.
|
|
-Vt,
|
|
slot,
|
|
);
|
|
#else
|
|
uv_b = uv;
|
|
#endif
|
|
}
|
|
#endif // VERTEX_TANGENTS
|
|
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) {
|
|
pbr_input.material.base_color *=
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::base_color_texture[slot],
|
|
pbr_bindings::base_color_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::base_color_texture,
|
|
pbr_bindings::base_color_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_BASE_COLOR_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv,
|
|
bias.ddy_uv,
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias,
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
);
|
|
|
|
#ifdef ALPHA_TO_COVERAGE
|
|
// Sharpen alpha edges.
|
|
//
|
|
// https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f
|
|
let alpha_mode = flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
|
|
if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ALPHA_TO_COVERAGE {
|
|
|
|
#ifdef BINDLESS
|
|
let alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff;
|
|
#else // BINDLESS
|
|
let alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
|
#endif // BINDLESS
|
|
|
|
pbr_input.material.base_color.a = (pbr_input.material.base_color.a - alpha_cutoff) /
|
|
max(fwidth(pbr_input.material.base_color.a), 0.0001) + 0.5;
|
|
}
|
|
#endif // ALPHA_TO_COVERAGE
|
|
|
|
}
|
|
#endif // VERTEX_UVS
|
|
|
|
pbr_input.material.flags = flags;
|
|
|
|
// 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.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.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;
|
|
#else // BINDLESS
|
|
var emissive: vec4<f32> = pbr_bindings::material.emissive;
|
|
#endif // BINDLESS
|
|
|
|
#ifdef VERTEX_UVS
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) {
|
|
emissive = vec4<f32>(emissive.rgb *
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::emissive_texture[slot],
|
|
pbr_bindings::emissive_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::emissive_texture,
|
|
pbr_bindings::emissive_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_EMISSIVE_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#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,
|
|
emissive.a);
|
|
}
|
|
#endif
|
|
pbr_input.material.emissive = emissive;
|
|
|
|
// metallic and perceptual roughness
|
|
#ifdef BINDLESS
|
|
var metallic: f32 = pbr_bindings::material[slot].metallic;
|
|
var perceptual_roughness: f32 = pbr_bindings::material[slot].perceptual_roughness;
|
|
#else // BINDLESS
|
|
var metallic: f32 = pbr_bindings::material.metallic;
|
|
var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness;
|
|
#endif // BINDLESS
|
|
|
|
let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);
|
|
#ifdef VERTEX_UVS
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) {
|
|
let metallic_roughness =
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::metallic_roughness_texture[slot],
|
|
pbr_bindings::metallic_roughness_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::metallic_roughness_texture,
|
|
pbr_bindings::metallic_roughness_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_METALLIC_ROUGHNESS_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv,
|
|
bias.ddy_uv,
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias,
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
);
|
|
// Sampling from GLTF standard channels for now
|
|
metallic *= metallic_roughness.b;
|
|
perceptual_roughness *= metallic_roughness.g;
|
|
}
|
|
#endif
|
|
pbr_input.material.metallic = metallic;
|
|
pbr_input.material.perceptual_roughness = perceptual_roughness;
|
|
|
|
// Clearcoat factor
|
|
#ifdef BINDLESS
|
|
pbr_input.material.clearcoat = pbr_bindings::material[slot].clearcoat;
|
|
#else // BINDLESS
|
|
pbr_input.material.clearcoat = pbr_bindings::material.clearcoat;
|
|
#endif // BINDLESS
|
|
|
|
#ifdef VERTEX_UVS
|
|
#ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_CLEARCOAT_TEXTURE_BIT) != 0u) {
|
|
pbr_input.material.clearcoat *=
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::clearcoat_texture[slot],
|
|
pbr_bindings::clearcoat_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::clearcoat_texture,
|
|
pbr_bindings::clearcoat_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_CLEARCOAT_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv,
|
|
bias.ddy_uv,
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias,
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
).r;
|
|
}
|
|
#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
|
#endif // VERTEX_UVS
|
|
|
|
// Clearcoat roughness
|
|
#ifdef BINDLESS
|
|
pbr_input.material.clearcoat_perceptual_roughness =
|
|
pbr_bindings::material[slot].clearcoat_perceptual_roughness;
|
|
#else // BINDLESS
|
|
pbr_input.material.clearcoat_perceptual_roughness =
|
|
pbr_bindings::material.clearcoat_perceptual_roughness;
|
|
#endif // BINDLESS
|
|
|
|
#ifdef VERTEX_UVS
|
|
#ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_CLEARCOAT_ROUGHNESS_TEXTURE_BIT) != 0u) {
|
|
pbr_input.material.clearcoat_perceptual_roughness *=
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::clearcoat_roughness_texture[slot],
|
|
pbr_bindings::clearcoat_roughness_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::clearcoat_roughness_texture,
|
|
pbr_bindings::clearcoat_roughness_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_CLEARCOAT_ROUGHNESS_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv,
|
|
bias.ddy_uv,
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias,
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
).g;
|
|
}
|
|
#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
|
#endif // VERTEX_UVS
|
|
|
|
#ifdef BINDLESS
|
|
var specular_transmission: f32 = pbr_bindings::material[slot].specular_transmission;
|
|
#else // BINDLESS
|
|
var specular_transmission: f32 = pbr_bindings::material.specular_transmission;
|
|
#endif // BINDLESS
|
|
|
|
#ifdef VERTEX_UVS
|
|
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_SPECULAR_TRANSMISSION_TEXTURE_BIT) != 0u) {
|
|
specular_transmission *=
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::specular_transmission_texture[slot],
|
|
pbr_bindings::specular_transmission_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::specular_transmission_texture,
|
|
pbr_bindings::specular_transmission_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_SPECULAR_TRANSMISSION_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv,
|
|
bias.ddy_uv,
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias,
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
).r;
|
|
}
|
|
#endif
|
|
#endif
|
|
pbr_input.material.specular_transmission = specular_transmission;
|
|
|
|
#ifdef BINDLESS
|
|
var thickness: f32 = pbr_bindings::material[slot].thickness;
|
|
#else // BINDLESS
|
|
var thickness: f32 = pbr_bindings::material.thickness;
|
|
#endif // BINDLESS
|
|
|
|
#ifdef VERTEX_UVS
|
|
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_THICKNESS_TEXTURE_BIT) != 0u) {
|
|
thickness *=
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::thickness_texture[slot],
|
|
pbr_bindings::thickness_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::thickness_texture,
|
|
pbr_bindings::thickness_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_THICKNESS_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv,
|
|
bias.ddy_uv,
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias,
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
).g;
|
|
}
|
|
#endif
|
|
#endif
|
|
// scale thickness, accounting for non-uniform scaling (e.g. a “squished” mesh)
|
|
// TODO: Meshlet support
|
|
#ifndef MESHLET_MESH_MATERIAL_PASS
|
|
thickness *= length(
|
|
(transpose(mesh[in.instance_index].world_from_local) * vec4(pbr_input.N, 0.0)).xyz
|
|
);
|
|
#endif
|
|
pbr_input.material.thickness = thickness;
|
|
|
|
#ifdef BINDLESS
|
|
var diffuse_transmission = pbr_bindings::material[slot].diffuse_transmission;
|
|
#else // BINDLESS
|
|
var diffuse_transmission = pbr_bindings::material.diffuse_transmission;
|
|
#endif // BINDLESS
|
|
|
|
#ifdef VERTEX_UVS
|
|
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_DIFFUSE_TRANSMISSION_TEXTURE_BIT) != 0u) {
|
|
diffuse_transmission *=
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::diffuse_transmission_texture[slot],
|
|
pbr_bindings::diffuse_transmission_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::diffuse_transmission_texture,
|
|
pbr_bindings::diffuse_transmission_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#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;
|
|
}
|
|
#endif
|
|
#endif
|
|
pbr_input.material.diffuse_transmission = diffuse_transmission;
|
|
|
|
var diffuse_occlusion: vec3<f32> = vec3(1.0);
|
|
var specular_occlusion: f32 = 1.0;
|
|
#ifdef VERTEX_UVS
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) {
|
|
diffuse_occlusion *=
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::occlusion_texture[slot],
|
|
pbr_bindings::occlusion_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::occlusion_texture,
|
|
pbr_bindings::occlusion_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_OCCLUSION_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
bias.ddx_uv,
|
|
bias.ddy_uv,
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
bias.mip_bias,
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
).r;
|
|
}
|
|
#endif
|
|
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
|
let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2<i32>(in.position.xy), 0i).r;
|
|
let ssao_multibounce = ssao_multibounce(ssao, pbr_input.material.base_color.rgb);
|
|
diffuse_occlusion = min(diffuse_occlusion, ssao_multibounce);
|
|
// Use SSAO to estimate the specular occlusion.
|
|
// Lagarde and Rousiers 2014, "Moving Frostbite to Physically Based Rendering"
|
|
specular_occlusion = saturate(pow(NdotV + ssao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ssao);
|
|
#endif
|
|
pbr_input.diffuse_occlusion = diffuse_occlusion;
|
|
pbr_input.specular_occlusion = specular_occlusion;
|
|
|
|
// N (normal vector)
|
|
#ifndef LOAD_PREPASS_NORMALS
|
|
|
|
pbr_input.N = normalize(pbr_input.world_normal);
|
|
pbr_input.clearcoat_N = pbr_input.N;
|
|
|
|
#ifdef VERTEX_UVS
|
|
#ifdef VERTEX_TANGENTS
|
|
|
|
let TBN = pbr_functions::calculate_tbn_mikktspace(pbr_input.world_normal, in.world_tangent);
|
|
|
|
#ifdef STANDARD_MATERIAL_NORMAL_MAP
|
|
|
|
let Nt =
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::normal_map_texture[slot],
|
|
pbr_bindings::normal_map_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::normal_map_texture,
|
|
pbr_bindings::normal_map_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_NORMAL_MAP_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#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.N = pbr_functions::apply_normal_mapping(flags, TBN, double_sided, is_front, Nt);
|
|
|
|
#endif // STANDARD_MATERIAL_NORMAL_MAP
|
|
|
|
#ifdef STANDARD_MATERIAL_CLEARCOAT
|
|
|
|
// Note: `KHR_materials_clearcoat` specifies that, if there's no
|
|
// clearcoat normal map, we must set the normal to the mesh's normal,
|
|
// and not to the main layer's bumped normal.
|
|
|
|
#ifdef STANDARD_MATERIAL_CLEARCOAT_NORMAL_MAP
|
|
|
|
let clearcoat_Nt =
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::clearcoat_normal_texture[slot],
|
|
pbr_bindings::clearcoat_normal_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::clearcoat_normal_texture,
|
|
pbr_bindings::clearcoat_normal_sampler,
|
|
#endif // BINDLESS
|
|
#ifdef STANDARD_MATERIAL_CLEARCOAT_NORMAL_UV_B
|
|
uv_b,
|
|
#else
|
|
uv,
|
|
#endif
|
|
#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.clearcoat_N = pbr_functions::apply_normal_mapping(
|
|
flags,
|
|
TBN,
|
|
double_sided,
|
|
is_front,
|
|
clearcoat_Nt,
|
|
);
|
|
|
|
#endif // STANDARD_MATERIAL_CLEARCOAT_NORMAL_MAP
|
|
|
|
#endif // STANDARD_MATERIAL_CLEARCOAT
|
|
|
|
#endif // VERTEX_TANGENTS
|
|
#endif // VERTEX_UVS
|
|
|
|
// Take anisotropy into account.
|
|
//
|
|
// This code comes from the `KHR_materials_anisotropy` spec:
|
|
// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md#individual-lights>
|
|
#ifdef PBR_ANISOTROPY_TEXTURE_SUPPORTED
|
|
#ifdef VERTEX_TANGENTS
|
|
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
|
|
|
#ifdef BINDLESS
|
|
var anisotropy_strength = pbr_bindings::material[slot].anisotropy_strength;
|
|
var anisotropy_direction = pbr_bindings::material[slot].anisotropy_rotation;
|
|
#else // BINDLESS
|
|
var anisotropy_strength = pbr_bindings::material.anisotropy_strength;
|
|
var anisotropy_direction = pbr_bindings::material.anisotropy_rotation;
|
|
#endif // BINDLESS
|
|
|
|
// Adjust based on the anisotropy map if there is one.
|
|
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_ANISOTROPY_TEXTURE_BIT) != 0u) {
|
|
let anisotropy_texel =
|
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleGrad(
|
|
#else // MESHLET_MESH_MATERIAL_PASS
|
|
textureSampleBias(
|
|
#endif // MESHLET_MESH_MATERIAL_PASS
|
|
#ifdef BINDLESS
|
|
pbr_bindings::anisotropy_texture[slot],
|
|
pbr_bindings::anisotropy_sampler[slot],
|
|
#else // BINDLESS
|
|
pbr_bindings::anisotropy_texture,
|
|
pbr_bindings::anisotropy_sampler,
|
|
#endif
|
|
#ifdef STANDARD_MATERIAL_ANISOTROPY_UV_B
|
|
uv_b,
|
|
#else // STANDARD_MATERIAL_ANISOTROPY_UV_B
|
|
uv,
|
|
#endif // STANDARD_MATERIAL_ANISOTROPY_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;
|
|
|
|
let anisotropy_direction_from_texture = normalize(anisotropy_texel.rg * 2.0 - 1.0);
|
|
// Rotate by the anisotropy direction.
|
|
anisotropy_direction =
|
|
mat2x2(anisotropy_direction.xy, anisotropy_direction.yx * vec2(-1.0, 1.0)) *
|
|
anisotropy_direction_from_texture;
|
|
anisotropy_strength *= anisotropy_texel.b;
|
|
}
|
|
|
|
pbr_input.anisotropy_strength = anisotropy_strength;
|
|
|
|
let anisotropy_T = normalize(TBN * vec3(anisotropy_direction, 0.0));
|
|
let anisotropy_B = normalize(cross(pbr_input.world_normal, anisotropy_T));
|
|
pbr_input.anisotropy_T = anisotropy_T;
|
|
pbr_input.anisotropy_B = anisotropy_B;
|
|
|
|
#endif // STANDARD_MATERIAL_ANISOTROPY
|
|
#endif // VERTEX_TANGENTS
|
|
#endif // PBR_ANISOTROPY_TEXTURE_SUPPORTED
|
|
|
|
#endif // LOAD_PREPASS_NORMALS
|
|
|
|
// TODO: Meshlet support
|
|
#ifdef LIGHTMAP
|
|
|
|
#ifdef BINDLESS
|
|
let lightmap_exposure = pbr_bindings::material[slot].lightmap_exposure;
|
|
#else // BINDLESS
|
|
let lightmap_exposure = pbr_bindings::material.lightmap_exposure;
|
|
#endif // BINDLESS
|
|
|
|
pbr_input.lightmap_light = lightmap(in.uv_b, lightmap_exposure, in.instance_index);
|
|
#endif
|
|
}
|
|
|
|
return pbr_input;
|
|
}
|