Skip to content

Commit c99351f

Browse files
robtfmsuperdump
andauthored
allow extensions to StandardMaterial (#7820)
# Objective allow extending `Material`s (including the built in `StandardMaterial`) with custom vertex/fragment shaders and additional data, to easily get pbr lighting with custom modifications, or otherwise extend a base material. # Solution - added `ExtendedMaterial<B: Material, E: MaterialExtension>` which contains a base material and a user-defined extension. - added example `extended_material` showing how to use it - modified AsBindGroup to have "unprepared" functions that return raw resources / layout entries so that the extended material can combine them note: doesn't currently work with array resources, as i can't figure out how to make the OwnedBindingResource::get_binding() work, as wgpu requires a `&'a[&'a TextureView]` and i have a `Vec<TextureView>`. # Migration Guide manual implementations of `AsBindGroup` will need to be adjusted, the changes are pretty straightforward and can be seen in the diff for e.g. the `texture_binding_array` example. --------- Co-authored-by: Robert Swain <[email protected]>
1 parent de8a600 commit c99351f

File tree

16 files changed

+856
-355
lines changed

16 files changed

+856
-355
lines changed

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,16 @@ description = "A shader and a material that uses it"
17201720
category = "Shaders"
17211721
wasm = true
17221722

1723+
[[example]]
1724+
name = "extended_material"
1725+
path = "examples/shader/extended_material.rs"
1726+
1727+
[package.metadata.example.extended_material]
1728+
name = "Extended Material"
1729+
description = "A custom shader that builds on the standard material"
1730+
category = "Shaders"
1731+
wasm = true
1732+
17231733
[[example]]
17241734
name = "shader_prepass"
17251735
path = "examples/shader/shader_prepass.rs"

assets/shaders/extended_material.wgsl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#import bevy_pbr::pbr_fragment pbr_input_from_standard_material
2+
#import bevy_pbr::pbr_functions alpha_discard
3+
4+
#ifdef PREPASS_PIPELINE
5+
#import bevy_pbr::prepass_io VertexOutput, FragmentOutput
6+
#import bevy_pbr::pbr_deferred_functions deferred_output
7+
#else
8+
#import bevy_pbr::forward_io VertexOutput, FragmentOutput
9+
#import bevy_pbr::pbr_functions apply_pbr_lighting, main_pass_post_lighting_processing
10+
#endif
11+
12+
struct MyExtendedMaterial {
13+
quantize_steps: u32,
14+
}
15+
16+
@group(1) @binding(100)
17+
var<uniform> my_extended_material: MyExtendedMaterial;
18+
19+
@fragment
20+
fn fragment(
21+
in: VertexOutput,
22+
@builtin(front_facing) is_front: bool,
23+
) -> FragmentOutput {
24+
// generate a PbrInput struct from the StandardMaterial bindings
25+
var pbr_input = pbr_input_from_standard_material(in, is_front);
26+
27+
// we can optionally modify the input before lighting and alpha_discard is applied
28+
pbr_input.material.base_color.b = pbr_input.material.base_color.r;
29+
30+
// alpha discard
31+
pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
32+
33+
#ifdef PREPASS_PIPELINE
34+
// in deferred mode we can't modify anything after that, as lighting is run in a separate fullscreen shader.
35+
let out = deferred_output(in, pbr_input);
36+
#else
37+
var out: FragmentOutput;
38+
// apply lighting
39+
out.color = apply_pbr_lighting(pbr_input);
40+
41+
// we can optionally modify the lit color before post-processing is applied
42+
out.color = vec4<f32>(vec4<u32>(out.color * f32(my_extended_material.quantize_steps))) / f32(my_extended_material.quantize_steps);
43+
44+
// apply in-shader post processing (fog, alpha-premultiply, and also tonemapping, debanding if the camera is non-hdr)
45+
// note this does not include fullscreen postprocessing effects like bloom.
46+
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
47+
48+
// we can optionally modify the final result here
49+
out.color = out.color * 2.0;
50+
#endif
51+
52+
return out;
53+
}

crates/bevy_pbr/src/deferred/deferred_lighting.wgsl

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,28 +67,12 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
6767
pbr_input.occlusion = min(pbr_input.occlusion, ssao_multibounce);
6868
#endif // SCREEN_SPACE_AMBIENT_OCCLUSION
6969

70-
output_color = pbr_functions::pbr(pbr_input);
70+
output_color = pbr_functions::apply_pbr_lighting(pbr_input);
7171
} else {
7272
output_color = pbr_input.material.base_color;
7373
}
7474

75-
// fog
76-
if (fog.mode != FOG_MODE_OFF && (pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) {
77-
output_color = pbr_functions::apply_fog(fog, output_color, pbr_input.world_position.xyz, view.world_position.xyz);
78-
}
79-
80-
#ifdef TONEMAP_IN_SHADER
81-
output_color = tone_mapping(output_color, view.color_grading);
82-
#ifdef DEBAND_DITHER
83-
var output_rgb = output_color.rgb;
84-
output_rgb = powsafe(output_rgb, 1.0 / 2.2);
85-
output_rgb = output_rgb + screen_space_dither(frag_coord.xy);
86-
// This conversion back to linear space is required because our output texture format is
87-
// SRGB; the GPU will assume our output is linear and will apply an SRGB conversion.
88-
output_rgb = powsafe(output_rgb, 2.2);
89-
output_color = vec4(output_rgb, output_color.a);
90-
#endif
91-
#endif
75+
output_color = pbr_functions::main_pass_post_lighting_processing(pbr_input, output_color);
9276

9377
return output_color;
9478
}

crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
#define_import_path bevy_pbr::pbr_deferred_functions
2+
23
#import bevy_pbr::pbr_types PbrInput, standard_material_new, STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT, STANDARD_MATERIAL_FLAGS_UNLIT_BIT
34
#import bevy_pbr::pbr_deferred_types as deferred_types
45
#import bevy_pbr::pbr_functions as pbr_functions
56
#import bevy_pbr::rgb9e5 as rgb9e5
67
#import bevy_pbr::mesh_view_bindings as view_bindings
78
#import bevy_pbr::mesh_view_bindings view
89
#import bevy_pbr::utils octahedral_encode, octahedral_decode
10+
#import bevy_pbr::prepass_io VertexOutput, FragmentOutput
11+
12+
#ifdef MOTION_VECTOR_PREPASS
13+
#import bevy_pbr::pbr_prepass_functions calculate_motion_vector
14+
#endif
915

1016
// ---------------------------
1117
// from https://github.com/DGriffin91/bevy_coordinate_systems/blob/main/src/transformations.wgsl
@@ -126,4 +132,23 @@ fn pbr_input_from_deferred_gbuffer(frag_coord: vec4<f32>, gbuffer: vec4<u32>) ->
126132
return pbr;
127133
}
128134

135+
#ifdef PREPASS_PIPELINE
136+
fn deferred_output(in: VertexOutput, pbr_input: PbrInput) -> FragmentOutput {
137+
var out: FragmentOutput;
129138

139+
// gbuffer
140+
out.deferred = deferred_gbuffer_from_pbr_input(pbr_input);
141+
// lighting pass id (used to determine which lighting shader to run for the fragment)
142+
out.deferred_lighting_pass_id = pbr_input.material.deferred_lighting_pass_id;
143+
// normal if required
144+
#ifdef NORMAL_PREPASS
145+
out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0);
146+
#endif
147+
// motion vectors if required
148+
#ifdef MOTION_VECTOR_PREPASS
149+
out.motion_vector = calculate_motion_vector(in.world_position, in.previous_world_position);
150+
#endif
151+
152+
return out;
153+
}
154+
#endif

0 commit comments

Comments
 (0)