bevy/crates/bevy_pbr/src/render/mesh_functions.wgsl
JMS55 fd30e0c67d
Fix meshlet vertex attribute interpolation (#13775)
# Objective

- Mikktspace requires that we normalize world normals/tangents _before_
interpolation across vertices, and then do _not_ normalize after. I had
it backwards.
- We do not (am not supposed to?) need a second set of barycentrics for
motion vectors. If you think about the typical raster pipeline, in the
vertex shader we calculate previous_world_position, and then it gets
interpolated using the current triangle's barycentrics.

## Solution

- Fix normal/tangent processing 
- Reuse barycentrics for motion vector calculations
- Not implementing this for 0.14, but long term I aim to remove explicit
vertex tangents and calculate them in the shader on the fly.

## Testing

- I tested out some of the normal maps we have in repo. Didn't seem to
make a difference, but mikktspace is all about correctness across
various baking tools. I probably just didn't have any of the ones that
would cause it to break.
- Didn't test motion vectors as there's a known bug with the depth
buffer and meshlets that I'm waiting on the render graph rewrite to fix.
2024-06-10 20:18:43 +00:00

123 lines
5.5 KiB
WebGPU Shading Language

#define_import_path bevy_pbr::mesh_functions
#import bevy_pbr::{
mesh_view_bindings::{
view,
visibility_ranges,
VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE
},
mesh_bindings::mesh,
mesh_types::MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT,
view_transformations::position_world_to_clip,
}
#import bevy_render::maths::{affine3_to_square, mat2x4_f32_to_mat3x3_unpack}
fn get_world_from_local(instance_index: u32) -> mat4x4<f32> {
return affine3_to_square(mesh[instance_index].world_from_local);
}
fn get_previous_world_from_local(instance_index: u32) -> mat4x4<f32> {
return affine3_to_square(mesh[instance_index].previous_world_from_local);
}
fn mesh_position_local_to_world(world_from_local: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
return world_from_local * vertex_position;
}
// NOTE: The intermediate world_position assignment is important
// for precision purposes when using the 'equals' depth comparison
// function.
fn mesh_position_local_to_clip(world_from_local: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
let world_position = mesh_position_local_to_world(world_from_local, vertex_position);
return position_world_to_clip(world_position.xyz);
}
fn mesh_normal_local_to_world(vertex_normal: vec3<f32>, instance_index: u32) -> vec3<f32> {
// NOTE: The mikktspace method of normal mapping requires that the world normal is
// re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents
// and normal maps so that the exact inverse process is applied when shading. Blender, Unity,
// Unreal Engine, Godot, and more all use the mikktspace method.
// We only skip normalization for invalid normals so that they don't become NaN.
// Do not change this code unless you really know what you are doing.
// http://www.mikktspace.com/
if any(vertex_normal != vec3<f32>(0.0)) {
return normalize(
mat2x4_f32_to_mat3x3_unpack(
mesh[instance_index].local_from_world_transpose_a,
mesh[instance_index].local_from_world_transpose_b,
) * vertex_normal
);
} else {
return vertex_normal;
}
}
// Calculates the sign of the determinant of the 3x3 model matrix based on a
// mesh flag
fn sign_determinant_model_3x3m(mesh_flags: u32) -> f32 {
// bool(u32) is false if 0u else true
// f32(bool) is 1.0 if true else 0.0
// * 2.0 - 1.0 remaps 0.0 or 1.0 to -1.0 or 1.0 respectively
return f32(bool(mesh_flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0;
}
fn mesh_tangent_local_to_world(world_from_local: mat4x4<f32>, vertex_tangent: vec4<f32>, instance_index: u32) -> vec4<f32> {
// NOTE: The mikktspace method of normal mapping requires that the world tangent is
// re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents
// and normal maps so that the exact inverse process is applied when shading. Blender, Unity,
// Unreal Engine, Godot, and more all use the mikktspace method.
// We only skip normalization for invalid tangents so that they don't become NaN.
// Do not change this code unless you really know what you are doing.
// http://www.mikktspace.com/
if any(vertex_tangent != vec4<f32>(0.0)) {
return vec4<f32>(
normalize(
mat3x3<f32>(
world_from_local[0].xyz,
world_from_local[1].xyz,
world_from_local[2].xyz,
) * vertex_tangent.xyz
),
// NOTE: Multiplying by the sign of the determinant of the 3x3 model matrix accounts for
// situations such as negative scaling.
vertex_tangent.w * sign_determinant_model_3x3m(mesh[instance_index].flags)
);
} else {
return vertex_tangent;
}
}
// Returns an appropriate dither level for the current mesh instance.
//
// This looks up the LOD range in the `visibility_ranges` table and compares the
// camera distance to determine the dithering level.
#ifdef VISIBILITY_RANGE_DITHER
fn get_visibility_range_dither_level(instance_index: u32, world_position: vec4<f32>) -> i32 {
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
// If we're using a storage buffer, then the length is variable.
let visibility_buffer_array_len = arrayLength(&visibility_ranges);
#else // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
// If we're using a uniform buffer, then the length is constant
let visibility_buffer_array_len = VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE;
#endif // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
let visibility_buffer_index = mesh[instance_index].flags & 0xffffu;
if (visibility_buffer_index > visibility_buffer_array_len) {
return -16;
}
let lod_range = visibility_ranges[visibility_buffer_index];
let camera_distance = length(view.world_position.xyz - world_position.xyz);
// This encodes the following mapping:
//
// `lod_range.` x y z w camera distance
// ←───────┼────────┼────────┼────────┼────────→
// LOD level -16 -16 0 0 16 16 LOD level
let offset = select(-16, 0, camera_distance >= lod_range.z);
let bounds = select(lod_range.xy, lod_range.zw, camera_distance >= lod_range.z);
let level = i32(round((camera_distance - bounds.x) / (bounds.y - bounds.x) * 16.0));
return offset + clamp(level, 0, 16);
}
#endif