import package::{
prepass::{
bindings as prepass_bindings,
io::{Vertex, UncompressedVertex, VertexOutput, FragmentOutput, decompress_vertex},
},
render::{
mesh_bindings::mesh,
mesh_functions,
skinning,
morph,
morph::{morph_position, morph_normal, morph_tangent},
mesh_view_bindings::view,
view_transformations::position_world_to_clip,
},
};
@if(DEFERRED_PREPASS)
import package::render::rgb9e5;
@if(MORPH_TARGETS)
// The instance_index parameter must match vertex_in.instance_index. This is a work around for a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
fn morph_vertex(vertex_in: UncompressedVertex, instance_index: u32) -> UncompressedVertex {
var vertex = vertex_in;
let first_vertex = mesh[instance_index].first_vertex_index;
let vertex_index = vertex.index - first_vertex;
let morph_descriptor_index = mesh[instance_index].morph_descriptor_index;
let weight_count = morph::layer_count(morph_descriptor_index);
for (var i: u32 = 0u; i < weight_count; i ++) {
let weight = morph::weight_at(i, morph_descriptor_index);
if weight == 0.0 {
continue;
}
vertex.position += weight * morph_position(vertex_index, i, morph_descriptor_index);
@if(VERTEX_NORMALS)
vertex.normal += weight * morph_normal(vertex_index, i, morph_descriptor_index);
@if(VERTEX_TANGENTS)
vertex.tangent += vec4(weight * morph_tangent(vertex_index, i, morph_descriptor_index), 0.0);
}
return vertex;
}
@if(MORPH_TARGETS)
/// Returns the morphed position of the given vertex from the previous frame.
///
/// This function is used for motion vector calculation, and, as such, it doesn't
/// bother morphing the normals and tangents.
///
/// The instance_index parameter must match vertex_in.instance_index. This is a work around for a wgpu dx12 bug.
/// See https://github.com/gfx-rs/naga/issues/2416
fn morph_prev_vertex(vertex_in: UncompressedVertex, instance_index: u32) -> UncompressedVertex {
var vertex = vertex_in;
let first_vertex = mesh[instance_index].first_vertex_index;
let vertex_index = vertex.index - first_vertex;
let morph_descriptor_index = mesh[instance_index].morph_descriptor_index;
let weight_count = morph::layer_count(morph_descriptor_index);
for (var i: u32 = 0u; i < weight_count; i ++) {
let weight = morph::prev_weight_at(i, morph_descriptor_index);
if weight == 0.0 {
continue;
}
vertex.position += weight * morph_position(vertex_index, i, morph_descriptor_index);
// Don't bother morphing normals and tangents; we don't need them for
// motion vector calculation.
}
return vertex;
}
@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
var out: VertexOutput;
let uncompressed_vertex_no_morph = decompress_vertex(vertex_no_morph, vertex_no_morph.instance_index);
@if(MORPH_TARGETS)
var vertex = morph_vertex(uncompressed_vertex_no_morph, vertex_no_morph.instance_index);
@else
var vertex = uncompressed_vertex_no_morph;
let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index);
@if(SKINNED)
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
var world_from_local = skinning::skin_model(
vertex.joint_indices,
vertex.joint_weights,
vertex_no_morph.instance_index
);
@else // SKINNED
var world_from_local = mesh_world_from_local;
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(vertex.position, 1.0));
out.position = position_world_to_clip(out.world_position.xyz);
@if(UNCLIPPED_DEPTH_ORTHO_EMULATION) {
out.unclipped_depth = out.position.z;
out.position.z = min(out.position.z, 1.0); // Clamp depth to avoid clipping
}
@if(VERTEX_UVS_A)
out.uv = vertex.uv;
@if(VERTEX_UVS_B)
out.uv_b = vertex.uv_b;
@if(NORMAL_PREPASS_OR_DEFERRED_PREPASS) {
@if(VERTEX_NORMALS && SKINNED)
out.world_normal = skinning::skin_normals(world_from_local, vertex.normal);
@elif(VERTEX_NORMALS)
out.world_normal = mesh_functions::mesh_normal_local_to_world(
vertex.normal,
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
vertex_no_morph.instance_index
);
@if(VERTEX_TANGENTS)
out.world_tangent = mesh_functions::mesh_tangent_local_to_world(
world_from_local,
vertex.tangent,
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
vertex_no_morph.instance_index
);
}
@if(VERTEX_COLORS)
out.color = vertex.color;
// Compute the motion vector for TAA among other purposes. For this we need
// to know where the vertex was last frame.
@if(MOTION_VECTOR_PREPASS) {
// Take morph targets into account.
@if(MORPH_TARGETS && HAS_PREVIOUS_MORPH)
let prev_vertex = morph_prev_vertex(uncompressed_vertex_no_morph, vertex_no_morph.instance_index);
@elif(MORPH_TARGETS)
let prev_vertex = uncompressed_vertex_no_morph;
@else // MORPH_TARGETS
let prev_vertex = uncompressed_vertex_no_morph;
// Take skinning into account.
@if(SKINNED && HAS_PREVIOUS_SKIN)
let prev_model = skinning::skin_prev_model(
prev_vertex.joint_indices,
prev_vertex.joint_weights,
vertex_no_morph.instance_index
);
@elif(SKINNED)
// Use vertex_no_morph.instance_index instead of prev_vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
let prev_model = mesh_functions::get_previous_world_from_local(vertex_no_morph.instance_index);
@else // SKINNED
// Use vertex_no_morph.instance_index instead of prev_vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
let prev_model = mesh_functions::get_previous_world_from_local(vertex_no_morph.instance_index);
out.previous_world_position = mesh_functions::mesh_position_local_to_world(
prev_model,
vec4<f32>(prev_vertex.position, 1.0)
);
}
@if(VERTEX_OUTPUT_INSTANCE_INDEX)
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
// See https://github.com/gfx-rs/naga/issues/2416
out.instance_index = vertex_no_morph.instance_index;
@if(VISIBILITY_RANGE_DITHER)
out.visibility_range_dither = mesh_functions::get_visibility_range_dither_level(
vertex_no_morph.instance_index, mesh_world_from_local[3]);
return out;
}
@if(PREPASS_FRAGMENT)
@fragment
fn fragment(in: VertexOutput) -> FragmentOutput {
var out: FragmentOutput;
@if(NORMAL_PREPASS)
out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0);
@if(UNCLIPPED_DEPTH_ORTHO_EMULATION)
out.frag_depth = in.unclipped_depth;
@if(MOTION_VECTOR_PREPASS) {
let clip_position_t = view.unjittered_clip_from_world * in.world_position;
let clip_position = clip_position_t.xy / clip_position_t.w;
let previous_clip_position_t = prepass_bindings::previous_view_uniforms.unjittered_clip_from_world * in.previous_world_position;
let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w;
// These motion vectors are used as offsets to UV positions and are stored
// in the range -1,1 to allow offsetting from the one corner to the
// diagonally-opposite corner in UV coordinates, in either direction.
// A difference between diagonally-opposite corners of clip space is in the
// range -2,2, so this needs to be scaled by 0.5. And the V direction goes
// down where clip space y goes up, so y needs to be flipped.
out.motion_vector = (clip_position - previous_clip_position) * vec2(0.5, -0.5);
}
@if(DEFERRED_PREPASS) {
// There isn't any material info available for this default prepass shader so we are just writing
// emissive magenta out to the deferred gbuffer to be rendered by the first deferred lighting pass layer.
// This is here so if the default prepass fragment is used for deferred magenta will be rendered, and also
// as an example to show that a user could write to the deferred gbuffer if they were to start from this shader.
out.deferred = vec4(0u, package::render::rgb9e5::vec3_to_rgb9e5_(vec3(1.0, 0.0, 1.0)), 0u, 0u);
out.deferred_lighting_pass_id = 1u;
}
return out;
}