Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/render/pbr_fragment.wgsl
6600 views
#define_import_path bevy_pbr::pbr_fragment

#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}

#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

#ifdef BINDLESS
#import bevy_pbr::pbr_bindings::material_indices
#endif  // BINDLESS

// 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 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
#ifdef BINDLESS
    let flags = pbr_bindings::material_array[material_indices[slot].material].flags;
    let base_color = pbr_bindings::material_array[material_indices[slot].material].base_color;
    let deferred_lighting_pass_id =
        pbr_bindings::material_array[material_indices[slot].material].deferred_lighting_pass_id;
#else   // BINDLESS
    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_array[material_indices[slot].material].uv_transform;
#else   // BINDLESS
    let uv_transform = pbr_bindings::material.uv_transform;
#endif  // BINDLESS

pbr_input.material.uv_transform = uv_transform;

#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_array[material_indices[slot].material].parallax_depth_scale,
            pbr_bindings::material_array[material_indices[slot].material].max_parallax_layer_count,
            pbr_bindings::material_array[material_indices[slot].material].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_array[material_indices[slot].material].parallax_depth_scale,
            pbr_bindings::material_array[material_indices[slot].material].max_parallax_layer_count,
            pbr_bindings::material_array[material_indices[slot].material].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
                bindless_textures_2d[material_indices[slot].base_color_texture],
                bindless_samplers_filtering[material_indices[slot].base_color_sampler],
#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_array[material_indices[slot].material].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_array[material_indices[slot].material].ior;
        pbr_input.material.attenuation_color =
                pbr_bindings::material_array[material_indices[slot].material].attenuation_color;
        pbr_input.material.attenuation_distance =
                pbr_bindings::material_array[material_indices[slot].material].attenuation_distance;
        pbr_input.material.alpha_cutoff =
                pbr_bindings::material_array[material_indices[slot].material].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_array[material_indices[slot].material].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
                bindless_textures_2d[material_indices[slot].specular_texture],
                bindless_samplers_filtering[material_indices[slot].specular_sampler],
#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
                bindless_textures_2d[material_indices[slot].specular_tint_texture],
                bindless_samplers_filtering[material_indices[slot].specular_tint_sampler],
#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_array[material_indices[slot].material].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
                    bindless_textures_2d[material_indices[slot].emissive_texture],
                    bindless_samplers_filtering[material_indices[slot].emissive_sampler],
#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_array[material_indices[slot].material].metallic;
        var perceptual_roughness: f32 = pbr_bindings::material_array[material_indices[slot].material].perceptual_roughness;
#else   // BINDLESS
        var metallic: f32 = pbr_bindings::material.metallic;
        var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness;
#endif  // BINDLESS

#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
                    bindless_textures_2d[material_indices[slot].metallic_roughness_texture],
                    bindless_samplers_filtering[material_indices[slot].metallic_roughness_sampler],
#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_array[material_indices[slot].material].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
                    bindless_textures_2d[material_indices[slot].clearcoat_texture],
                    bindless_samplers_filtering[material_indices[slot].clearcoat_sampler],
#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_array[material_indices[slot].material].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
                    bindless_textures_2d[material_indices[slot].clearcoat_roughness_texture],
                    bindless_samplers_filtering[material_indices[slot].clearcoat_roughness_sampler],
#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_array[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
                    bindless_textures_2d[
                        material_indices[slot].specular_transmission_texture
                    ],
                    bindless_samplers_filtering[
                        material_indices[slot].specular_transmission_sampler
                    ],
#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_array[material_indices[slot].material].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
                    bindless_textures_2d[material_indices[slot].thickness_texture],
                    bindless_samplers_filtering[material_indices[slot].thickness_sampler],
#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_array[material_indices[slot].material].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
                    bindless_textures_2d[material_indices[slot].diffuse_transmission_texture],
                    bindless_samplers_filtering[material_indices[slot].diffuse_transmission_sampler],
#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
                    bindless_textures_2d[material_indices[slot].occlusion_texture],
                    bindless_samplers_filtering[material_indices[slot].occlusion_sampler],
#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"
        let roughness = lighting::perceptualRoughnessToRoughness(pbr_input.material.perceptual_roughness);
        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
                bindless_textures_2d[material_indices[slot].normal_map_texture],
                bindless_samplers_filtering[material_indices[slot].normal_map_sampler],
#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
                bindless_textures_2d[material_indices[slot].clearcoat_normal_texture],
                bindless_samplers_filtering[material_indices[slot].clearcoat_normal_sampler],
#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_array[material_indices[slot].material].anisotropy_strength;
        var anisotropy_direction =
                pbr_bindings::material_array[material_indices[slot].material].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
                    bindless_textures_2d[material_indices[slot].anisotropy_texture],
                    bindless_samplers_filtering[material_indices[slot].anisotropy_sampler],
#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_array[material_indices[slot].material].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;
}