
# Objective Right now, we flip the `world_normal` in response to `double_sided && !is_front`, however when calculating `N` from tangents and the normal map, we don't flip the normal read from the normal map, which produces extremely weird results. ## Solution - Pass `double_sided` and `is_front` flags to the `apply_normal_mapping()` function and use them to conditionally flip `Nt` ## Comparison Note: These are from a custom scene running with the `transmission` branch, (#8015) I noticed lighting got pretty weird for the back side of translucent `double_sided` materials whenever I added a normal map. ### Before <img width="1392" alt="Screenshot 2023-10-31 at 01 26 06" src="https://github.com/bevyengine/bevy/assets/418473/d5f8c9c3-aca1-4c2f-854d-f0d0fd2fb19a"> ### After <img width="1392" alt="Screenshot 2023-10-31 at 01 25 42" src="https://github.com/bevyengine/bevy/assets/418473/fa0e1aa2-19ad-4c27-bb08-37299d97971c"> --- ## Changelog - Fixed a bug where `StandardMaterial::double_sided` would interact incorrectly with normal maps, producing broken results.
168 lines
6.1 KiB
WebGPU Shading Language
168 lines
6.1 KiB
WebGPU Shading Language
#define_import_path bevy_pbr::pbr_fragment
|
|
|
|
#import bevy_pbr::{
|
|
pbr_functions,
|
|
pbr_bindings,
|
|
pbr_types,
|
|
prepass_utils,
|
|
mesh_bindings::mesh,
|
|
mesh_view_bindings::view,
|
|
parallax_mapping::parallaxed_uv,
|
|
}
|
|
|
|
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
|
#import bevy_pbr::mesh_view_bindings::screen_space_ambient_occlusion_texture
|
|
#import bevy_pbr::gtao_utils::gtao_multibounce
|
|
#endif
|
|
|
|
#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();
|
|
|
|
pbr_input.flags = mesh[in.instance_index].flags;
|
|
pbr_input.is_orthographic = view.projection[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 {
|
|
let double_sided = (pbr_bindings::material.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 = pbr_bindings::material.flags;
|
|
pbr_input.material.base_color *= pbr_bindings::material.base_color;
|
|
pbr_input.material.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id;
|
|
|
|
#ifdef VERTEX_UVS
|
|
var uv = in.uv;
|
|
|
|
#ifdef VERTEX_TANGENTS
|
|
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) {
|
|
let V = pbr_input.V;
|
|
let N = in.world_normal;
|
|
let T = in.world_tangent.xyz;
|
|
let B = in.world_tangent.w * cross(N, T);
|
|
// Transform V from fragment to camera in world space to tangent space.
|
|
let Vt = vec3(dot(V, T), dot(V, B), dot(V, N));
|
|
uv = parallaxed_uv(
|
|
pbr_bindings::material.parallax_depth_scale,
|
|
pbr_bindings::material.max_parallax_layer_count,
|
|
pbr_bindings::material.max_relief_mapping_search_steps,
|
|
uv,
|
|
// Flip the direction of Vt to go toward the surface to make the
|
|
// parallax mapping algorithm easier to understand and reason
|
|
// about.
|
|
-Vt,
|
|
);
|
|
}
|
|
#endif // VERTEX_TANGENTS
|
|
|
|
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) {
|
|
pbr_input.material.base_color *= textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, uv, view.mip_bias);
|
|
}
|
|
#endif // VERTEX_UVS
|
|
|
|
pbr_input.material.flags = pbr_bindings::material.flags;
|
|
|
|
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
|
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
|
|
|
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
|
|
pbr_input.material.alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
|
|
|
// emissive
|
|
// TODO use .a for exposure compensation in HDR
|
|
var emissive: vec4<f32> = pbr_bindings::material.emissive;
|
|
#ifdef VERTEX_UVS
|
|
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) {
|
|
emissive = vec4<f32>(emissive.rgb * textureSampleBias(pbr_bindings::emissive_texture, pbr_bindings::emissive_sampler, uv, view.mip_bias).rgb, 1.0);
|
|
}
|
|
#endif
|
|
pbr_input.material.emissive = emissive;
|
|
|
|
// metallic and perceptual roughness
|
|
var metallic: f32 = pbr_bindings::material.metallic;
|
|
var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness;
|
|
#ifdef VERTEX_UVS
|
|
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) {
|
|
let metallic_roughness = textureSampleBias(pbr_bindings::metallic_roughness_texture, pbr_bindings::metallic_roughness_sampler, uv, view.mip_bias);
|
|
// 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;
|
|
|
|
// occlusion
|
|
// TODO: Split into diffuse/specular occlusion?
|
|
var occlusion: vec3<f32> = vec3(1.0);
|
|
#ifdef VERTEX_UVS
|
|
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) {
|
|
occlusion = vec3(textureSampleBias(pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, uv, view.mip_bias).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 = gtao_multibounce(ssao, pbr_input.material.base_color.rgb);
|
|
occlusion = min(occlusion, ssao_multibounce);
|
|
#endif
|
|
pbr_input.occlusion = occlusion;
|
|
|
|
// N (normal vector)
|
|
#ifndef LOAD_PREPASS_NORMALS
|
|
pbr_input.N = pbr_functions::apply_normal_mapping(
|
|
pbr_bindings::material.flags,
|
|
pbr_input.world_normal,
|
|
double_sided,
|
|
is_front,
|
|
#ifdef VERTEX_TANGENTS
|
|
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
|
in.world_tangent,
|
|
#endif
|
|
#endif
|
|
#ifdef VERTEX_UVS
|
|
uv,
|
|
#endif
|
|
view.mip_bias,
|
|
);
|
|
#endif
|
|
}
|
|
|
|
return pbr_input;
|
|
}
|