
# Objective cleanup some pbr shader code. improve shader stage io consistency and make pbr.wgsl (probably many people's first foray into bevy shader code) a little more human-readable. also fix a couple of small issues with deferred rendering. ## Solution mesh_vertex_output: - rename to forward_io (to align with prepass_io) - rename `MeshVertexOutput` to `VertexOutput` (to align with prepass_io) - move `Vertex` from mesh.wgsl into here (to align with prepass_io) prepass_io: - remove `FragmentInput`, use `VertexOutput` directly (to align with forward_io) - rename `VertexOutput::clip_position` to `position` (to align with forward_io) pbr.wgsl: - restructure so we don't need `#ifdefs` on the actual entrypoint, use VertexOutput and FragmentOutput in all cases and use #ifdefs to import the right struct definitions. - rearrange to make the flow clearer - move alpha_discard up from `pbr_functions::pbr` to avoid needing to call it on some branches and not others - add a bunch of comments deferred_lighting: - move ssao into the `!unlit` block to reflect forward behaviour correctly - fix compile error with deferred + premultiply_alpha ## Migration Guide in custom material shaders: - `pbr_functions::pbr` no longer calls to `pbr_functions::alpha_discard`. if you were using the `pbr` function in a custom shader with alpha mask mode you now also need to call alpha_discard manually - rename imports of `bevy_pbr::mesh_vertex_output` to `bevy_pbr::forward_io` - rename instances of `MeshVertexOutput` to `VertexOutput` in custom material prepass shaders: - rename instances of `VertexOutput::clip_position` to `VertexOutput::position`
151 lines
5.8 KiB
WebGPU Shading Language
151 lines
5.8 KiB
WebGPU Shading Language
#import bevy_pbr::prepass_bindings
|
||
#import bevy_pbr::mesh_functions
|
||
#import bevy_pbr::prepass_io Vertex, VertexOutput, FragmentOutput
|
||
#import bevy_pbr::skinning
|
||
#import bevy_pbr::morph
|
||
#import bevy_pbr::mesh_bindings mesh
|
||
#import bevy_render::instance_index get_instance_index
|
||
#import bevy_pbr::mesh_view_bindings view, previous_view_proj
|
||
|
||
#ifdef DEFERRED_PREPASS
|
||
#import bevy_pbr::rgb9e5
|
||
#endif
|
||
|
||
#ifdef MORPH_TARGETS
|
||
fn morph_vertex(vertex_in: Vertex) -> Vertex {
|
||
var vertex = vertex_in;
|
||
let weight_count = bevy_pbr::morph::layer_count();
|
||
for (var i: u32 = 0u; i < weight_count; i ++) {
|
||
let weight = bevy_pbr::morph::weight_at(i);
|
||
if weight == 0.0 {
|
||
continue;
|
||
}
|
||
vertex.position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i);
|
||
#ifdef VERTEX_NORMALS
|
||
vertex.normal += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::normal_offset, i);
|
||
#endif
|
||
#ifdef VERTEX_TANGENTS
|
||
vertex.tangent += vec4(weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::tangent_offset, i), 0.0);
|
||
#endif
|
||
}
|
||
return vertex;
|
||
}
|
||
#endif
|
||
|
||
@vertex
|
||
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
||
var out: VertexOutput;
|
||
|
||
#ifdef MORPH_TARGETS
|
||
var vertex = morph_vertex(vertex_no_morph);
|
||
#else
|
||
var vertex = vertex_no_morph;
|
||
#endif
|
||
|
||
#ifdef SKINNED
|
||
var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights);
|
||
#else // 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 model = bevy_pbr::mesh_functions::get_model_matrix(vertex_no_morph.instance_index);
|
||
#endif // SKINNED
|
||
|
||
out.position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0));
|
||
#ifdef DEPTH_CLAMP_ORTHO
|
||
out.clip_position_unclamped = out.position;
|
||
out.position.z = min(out.position.z, 1.0);
|
||
#endif // DEPTH_CLAMP_ORTHO
|
||
|
||
#ifdef VERTEX_UVS
|
||
out.uv = vertex.uv;
|
||
#endif // VERTEX_UVS
|
||
|
||
#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||
#ifdef SKINNED
|
||
out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal);
|
||
#else // SKINNED
|
||
out.world_normal = bevy_pbr::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
|
||
get_instance_index(vertex_no_morph.instance_index)
|
||
);
|
||
#endif // SKINNED
|
||
|
||
#ifdef VERTEX_TANGENTS
|
||
out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world(
|
||
model,
|
||
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
|
||
get_instance_index(vertex_no_morph.instance_index)
|
||
);
|
||
#endif // VERTEX_TANGENTS
|
||
#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS
|
||
|
||
#ifdef VERTEX_COLORS
|
||
out.color = vertex.color;
|
||
#endif
|
||
|
||
#ifdef MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS
|
||
out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4<f32>(vertex.position, 1.0));
|
||
#endif // MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS
|
||
|
||
#ifdef MOTION_VECTOR_PREPASS
|
||
// 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.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(
|
||
bevy_pbr::mesh_functions::get_previous_model_matrix(vertex_no_morph.instance_index),
|
||
vec4<f32>(vertex.position, 1.0)
|
||
);
|
||
#endif // MOTION_VECTOR_PREPASS
|
||
|
||
#ifdef 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 = get_instance_index(vertex_no_morph.instance_index);
|
||
#endif
|
||
|
||
return out;
|
||
}
|
||
|
||
#ifdef PREPASS_FRAGMENT
|
||
@fragment
|
||
fn fragment(in: VertexOutput) -> FragmentOutput {
|
||
var out: FragmentOutput;
|
||
|
||
#ifdef NORMAL_PREPASS
|
||
out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0);
|
||
#endif
|
||
|
||
#ifdef DEPTH_CLAMP_ORTHO
|
||
out.frag_depth = in.clip_position_unclamped.z;
|
||
#endif // DEPTH_CLAMP_ORTHO
|
||
|
||
#ifdef MOTION_VECTOR_PREPASS
|
||
let clip_position_t = view.unjittered_view_proj * in.world_position;
|
||
let clip_position = clip_position_t.xy / clip_position_t.w;
|
||
let previous_clip_position_t = bevy_pbr::prepass_bindings::previous_view_proj * 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);
|
||
#endif // MOTION_VECTOR_PREPASS
|
||
|
||
#ifdef 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.
|
||
// The 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, bevy_pbr::rgb9e5::vec3_to_rgb9e5_(vec3(1.0, 0.0, 1.0)), 0u, 0u);
|
||
out.deferred_lighting_pass_id = 1u;
|
||
#endif
|
||
|
||
return out;
|
||
}
|
||
#endif // PREPASS_FRAGMENT
|