1use bevy::{
7 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
8 core_pipeline::{
9 core_3d::CORE_3D_DEPTH_FORMAT,
10 deferred::{
11 copy_lighting_id::copy_deferred_lighting_id, node::late_deferred_prepass,
12 DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT,
13 },
14 prepass::{DeferredPrepass, DepthPrepass, ViewPrepassTextures},
15 Core3d, Core3dSystems, FullscreenShader,
16 },
17 pbr::{
18 per_view_shadow_pass, shared_shadow_pass, DefaultOpaqueRendererMethod, ShadowView,
19 ViewLightEntities, LATE_SHADOW_PASS,
20 },
21 prelude::*,
22 render::{
23 globals::{GlobalsBuffer, GlobalsUniform},
24 render_resource::{binding_types::uniform_buffer, *},
25 renderer::{RenderContext, ViewQuery},
26 view::{ViewDepthStencilTexture, ViewUniform, ViewUniformOffset, ViewUniforms},
27 RenderApp, RenderStartup,
28 },
29};
30
31fn main() {
32 App::new()
33 .insert_resource(DefaultOpaqueRendererMethod::deferred())
35 .add_plugins((DefaultPlugins, DeferredRaymarchPlugin, FreeCameraPlugin))
36 .add_systems(Startup, setup)
37 .run();
38}
39
40const SHADER_ASSET_PATH: &str = "shaders/deferred_raymarch.wesl";
41
42fn setup(
43 mut commands: Commands,
44 mut meshes: ResMut<Assets<Mesh>>,
45 mut materials: ResMut<Assets<StandardMaterial>>,
46) {
47 commands.spawn((
48 Camera3d::default(),
49 Transform::from_xyz(4.0, 3.0, 6.0).looking_at(Vec3::new(0.0, 0.2, 0.0), Vec3::Y),
50 Msaa::Off,
52 DepthPrepass,
53 DeferredPrepass,
54 AmbientLight {
55 brightness: 200.0,
56 ..default()
57 },
58 FreeCamera::default(),
59 ));
60
61 commands.spawn((
63 Mesh3d(meshes.add(Plane3d::default().mesh().size(20.0, 20.0))),
64 MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
65 Transform::from_xyz(0.0, -1.5, 0.0),
66 ));
67
68 commands.spawn((
70 Mesh3d(meshes.add(Cuboid::from_length(1.2))),
71 MeshMaterial3d(materials.add(StandardMaterial {
72 base_color: Color::srgb(0.2, 0.4, 0.9),
73 perceptual_roughness: 0.4,
74 ..default()
75 })),
76 Transform::from_xyz(2.2, -0.9, 0.5),
77 ));
78
79 commands.spawn((
80 DirectionalLight {
81 illuminance: 8_000.0,
82 shadow_maps_enabled: true,
83 ..default()
84 },
85 Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
86 ));
87}
88
89struct DeferredRaymarchPlugin;
90
91impl Plugin for DeferredRaymarchPlugin {
92 fn build(&self, app: &mut App) {
93 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
94 return;
95 };
96
97 render_app
98 .add_systems(RenderStartup, init_raymarch_pipelines)
99 .add_systems(
100 Core3d,
101 (
102 raymarch_gbuffer_pass
107 .in_set(Core3dSystems::Prepass)
108 .after(late_deferred_prepass)
109 .before(copy_deferred_lighting_id),
110 raymarch_directional_shadow_pass
113 .after(per_view_shadow_pass::<LATE_SHADOW_PASS>)
114 .before(Core3dSystems::MainPass),
115 raymarch_shared_shadow_pass
116 .after(shared_shadow_pass::<LATE_SHADOW_PASS>)
117 .before(Core3dSystems::MainPass),
118 ),
119 );
120 }
121}
122
123#[derive(Resource)]
124struct RaymarchGBufferPipeline {
125 layout: BindGroupLayoutDescriptor,
126 pipeline_id: CachedRenderPipelineId,
127}
128
129#[derive(Resource)]
130struct RaymarchShadowPipeline {
131 layout: BindGroupLayoutDescriptor,
132 pipeline_id: CachedRenderPipelineId,
133}
134
135fn init_raymarch_pipelines(
136 mut commands: Commands,
137 asset_server: Res<AssetServer>,
138 fullscreen_shader: Res<FullscreenShader>,
139 pipeline_cache: Res<PipelineCache>,
140) {
141 let layout = BindGroupLayoutDescriptor::new(
142 "raymarch_bind_group_layout",
143 &BindGroupLayoutEntries::sequential(
144 ShaderStages::FRAGMENT,
145 (
146 uniform_buffer::<ViewUniform>(true),
147 uniform_buffer::<GlobalsUniform>(false),
148 ),
149 ),
150 );
151
152 let shader = asset_server.load::<Shader>(SHADER_ASSET_PATH);
153 let vertex_state = fullscreen_shader.to_vertex_state();
154
155 let depth_stencil = DepthStencilState {
157 format: CORE_3D_DEPTH_FORMAT,
158 depth_write_enabled: Some(true),
159 depth_compare: Some(CompareFunction::GreaterEqual),
160 stencil: StencilState::default(),
161 bias: DepthBiasState::default(),
162 };
163
164 let gbuffer_pipeline_id = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
165 label: Some("raymarch_gbuffer_pipeline".into()),
166 layout: vec![layout.clone()],
167 vertex: vertex_state.clone(),
168 fragment: Some(FragmentState {
169 shader: shader.clone(),
170 entry_point: Some("fragment".into()),
171 targets: vec![
172 Some(ColorTargetState {
173 format: DEFERRED_PREPASS_FORMAT,
174 blend: None,
175 write_mask: ColorWrites::ALL,
176 }),
177 Some(ColorTargetState {
178 format: DEFERRED_LIGHTING_PASS_ID_FORMAT,
179 blend: None,
180 write_mask: ColorWrites::ALL,
181 }),
182 ],
183 ..default()
184 }),
185 depth_stencil: Some(depth_stencil.clone()),
186 ..default()
187 });
188
189 let shadow_pipeline_id = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
190 label: Some("raymarch_shadow_pipeline".into()),
191 layout: vec![layout.clone()],
192 vertex: vertex_state,
193 fragment: Some(FragmentState {
194 shader,
195 entry_point: Some("fragment_shadow".into()),
196 targets: vec![],
197 ..default()
198 }),
199 depth_stencil: Some(depth_stencil),
200 ..default()
201 });
202
203 commands.insert_resource(RaymarchGBufferPipeline {
204 layout: layout.clone(),
205 pipeline_id: gbuffer_pipeline_id,
206 });
207 commands.insert_resource(RaymarchShadowPipeline {
208 layout,
209 pipeline_id: shadow_pipeline_id,
210 });
211}
212
213fn raymarch_bind_group(
214 ctx: &RenderContext,
215 pipeline_cache: &PipelineCache,
216 layout: &BindGroupLayoutDescriptor,
217 view_uniforms: &ViewUniforms,
218 globals: &GlobalsBuffer,
219) -> Option<BindGroup> {
220 let view_binding = view_uniforms.uniforms.binding()?;
221 let globals_binding = globals.buffer.binding()?;
222 Some(ctx.render_device().create_bind_group(
223 "raymarch_bind_group",
224 &pipeline_cache.get_bind_group_layout(layout),
225 &BindGroupEntries::sequential((view_binding, globals_binding)),
226 ))
227}
228
229fn raymarch_gbuffer_pass(
230 view: ViewQuery<(
231 &ViewUniformOffset,
232 &ViewDepthStencilTexture,
233 &ViewPrepassTextures,
234 )>,
235 pipeline: Option<Res<RaymarchGBufferPipeline>>,
236 pipeline_cache: Res<PipelineCache>,
237 view_uniforms: Res<ViewUniforms>,
238 globals: Res<GlobalsBuffer>,
239 mut ctx: RenderContext,
240) {
241 let Some(pipeline) = pipeline else {
242 return;
243 };
244 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(pipeline.pipeline_id) else {
245 return;
246 };
247
248 let (view_uniform_offset, view_depth, view_prepass_textures) = view.into_inner();
249
250 let (Some(deferred), Some(lighting_pass_id)) = (
251 &view_prepass_textures.deferred,
252 &view_prepass_textures.deferred_lighting_pass_id,
253 ) else {
254 return;
255 };
256
257 let Some(bind_group) = raymarch_bind_group(
258 &ctx,
259 &pipeline_cache,
260 &pipeline.layout,
261 &view_uniforms,
262 &globals,
263 ) else {
264 return;
265 };
266
267 {
268 let mut pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
271 label: Some("raymarch_gbuffer_pass"),
272 color_attachments: &[
273 Some(deferred.get_attachment()),
274 Some(lighting_pass_id.get_attachment()),
275 ],
276 depth_stencil_attachment: Some(view_depth.get_attachment(StoreOp::Store)),
277 timestamp_writes: None,
278 occlusion_query_set: None,
279 multiview_mask: None,
280 });
281
282 pass.set_render_pipeline(render_pipeline);
283 pass.set_bind_group(0, &bind_group, &[view_uniform_offset.offset]);
284 pass.draw(0..3, 0..1);
285 }
286
287 if let Some(prepass_depth) = &view_prepass_textures.depth {
290 ctx.command_encoder().copy_texture_to_texture(
291 view_depth.texture().as_image_copy(),
292 prepass_depth.texture.texture.as_image_copy(),
293 view_prepass_textures.size,
294 );
295 }
296}
297
298fn raymarch_directional_shadow_pass(
299 view: ViewQuery<&ViewLightEntities>,
300 shadow_views: Query<(&ShadowView, &ViewUniformOffset)>,
301 pipeline: Option<Res<RaymarchShadowPipeline>>,
302 pipeline_cache: Res<PipelineCache>,
303 view_uniforms: Res<ViewUniforms>,
304 globals: Res<GlobalsBuffer>,
305 mut ctx: RenderContext,
306) {
307 let Some(pipeline) = pipeline else {
308 return;
309 };
310 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(pipeline.pipeline_id) else {
311 return;
312 };
313
314 let view_lights = view.into_inner();
315 for light_entity in view_lights.lights.iter().copied() {
316 let Ok((shadow_view, view_uniform_offset)) = shadow_views.get(light_entity) else {
317 continue;
318 };
319 draw_raymarch_shadow(
320 &mut ctx,
321 &pipeline_cache,
322 &pipeline.layout,
323 &view_uniforms,
324 &globals,
325 render_pipeline,
326 shadow_view,
327 view_uniform_offset,
328 );
329 }
330}
331
332fn raymarch_shared_shadow_pass(
333 view: ViewQuery<(&ShadowView, &ViewUniformOffset)>,
334 pipeline: Option<Res<RaymarchShadowPipeline>>,
335 pipeline_cache: Res<PipelineCache>,
336 view_uniforms: Res<ViewUniforms>,
337 globals: Res<GlobalsBuffer>,
338 mut ctx: RenderContext,
339) {
340 let Some(pipeline) = pipeline else {
341 return;
342 };
343 let Some(render_pipeline) = pipeline_cache.get_render_pipeline(pipeline.pipeline_id) else {
344 return;
345 };
346
347 let (shadow_view, view_uniform_offset) = view.into_inner();
348 draw_raymarch_shadow(
349 &mut ctx,
350 &pipeline_cache,
351 &pipeline.layout,
352 &view_uniforms,
353 &globals,
354 render_pipeline,
355 shadow_view,
356 view_uniform_offset,
357 );
358}
359
360fn draw_raymarch_shadow(
361 ctx: &mut RenderContext,
362 pipeline_cache: &PipelineCache,
363 layout: &BindGroupLayoutDescriptor,
364 view_uniforms: &ViewUniforms,
365 globals: &GlobalsBuffer,
366 render_pipeline: &RenderPipeline,
367 shadow_view: &ShadowView,
368 view_uniform_offset: &ViewUniformOffset,
369) {
370 let Some(bind_group) = raymarch_bind_group(ctx, pipeline_cache, layout, view_uniforms, globals)
371 else {
372 return;
373 };
374
375 let mut pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
376 label: Some("raymarch_shadow_pass"),
377 color_attachments: &[],
378 depth_stencil_attachment: Some(shadow_view.depth_attachment.get_attachment(StoreOp::Store)),
379 timestamp_writes: None,
380 occlusion_query_set: None,
381 multiview_mask: None,
382 });
383
384 pass.set_render_pipeline(render_pipeline);
385 pass.set_bind_group(0, &bind_group, &[view_uniform_offset.offset]);
386 pass.draw(0..3, 0..1);
387}