@fragment
fn fragment(in: VertexOutput, @builtin(front_facing) is_front: bool) -> FragmentOutput {
    var pbr_input = pbr_input_from_standard_material(in, is_front);
#ifdef VERTEX_UVS_A
    let uv0 = in.uv;
#else
    let uv0 = vec2<f32>(0.0);
#endif
    let input = SurfaceInput(uv0, pbr_input.world_position.xyz, pbr_input.N);
    let params = SurfaceParams(surface_uniform.params);
    let surface = evaluate(input, params, surface_samples(uv0));
    pbr_input.material.base_color = surface.base_color;
    pbr_input.material.emissive = vec4<f32>(surface.emissive, 1.0);
    pbr_input.material.metallic = clamp(surface.metallic, 0.0, 1.0);
    pbr_input.material.perceptual_roughness = clamp(surface.perceptual_roughness, 0.001, 1.0);
    pbr_input.material.reflectance = vec3<f32>(clamp(surface.reflectance, 0.0, 1.0));
    pbr_input.diffuse_occlusion = vec3<f32>(clamp(surface.occlusion, 0.0, 1.0));
#ifdef VERTEX_TANGENTS
    let surface_tbn = calculate_tbn_mikktspace(pbr_input.world_normal, in.world_tangent);
    let surface_nt = normalize(surface.normal_ts);
    pbr_input.N = normalize(
        surface_nt.x * surface_tbn[0]
        + surface_nt.y * surface_tbn[1]
        + surface_nt.z * surface_tbn[2]
    );
#endif
    if surface.model == BLACKSITE_SURFACE_UNLIT {
        pbr_input.material.flags |= STANDARD_MATERIAL_FLAGS_UNLIT_BIT;
    } else {
        pbr_input.material.flags &= ~STANDARD_MATERIAL_FLAGS_UNLIT_BIT;
    }
    pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
#ifdef PREPASS_PIPELINE
    return deferred_output(in, pbr_input);
#else
    var out: FragmentOutput;
    out.color = apply_pbr_lighting(pbr_input);
    out.color = main_pass_post_lighting_processing(pbr_input, out.color);
    return out;
#endif
}
