Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1//! Raymarches a signed distance field and packs it into the deferred gbuffer exactly as
2//! a `StandardMaterial` would (via `deferred_gbuffer_from_pbr_input`), so Bevy's deferred
3//! PBR lighting shades it like any other geometry. This example assumes familiarity with
4//! raymarching.
6import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput;
7import bevy_pbr::render::mesh_view_bindings::view;
8import bevy_pbr::render::view_transformations::{
9 uv_to_ndc,
10 position_ndc_to_view,
11 position_ndc_to_world,
12 direction_view_to_world,
13 position_world_to_view,
14 view_z_to_depth_ndc,
15};
16import bevy_pbr::deferred::functions::deferred_gbuffer_from_pbr_input;
17import bevy_pbr::render::pbr_types::{pbr_input_new, STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE};
18import bevy_pbr::render::mesh_types::MESH_FLAGS_SHADOW_RECEIVER_BIT;
19import bevy_render::globals::Globals;
21@group(0) @binding(1) var<uniform> globals: Globals;
23const FAR: f32 = 100.0;
24const SURFACE_EPSILON: f32 = 0.001;
25const MAX_STEPS: u32 = 160u;
26const STEP_SCALE: f32 = 0.5;
28// Swap this for any SDF you like, the gbuffer integration below is independent of the
29// shape.
30const GYROID_SCALE: f32 = 5.0;
32fn gyroid_field(p: vec3<f32>) -> f32 {
33 let q = p * GYROID_SCALE + globals.time * 0.6;
34 return dot(sin(q), cos(q.yzx));
37fn map(p: vec3<f32>) -> f32 {
38 let shell = (abs(gyroid_field(p)) - 0.14) / GYROID_SCALE;
39 let ball = length(p) - 1.3;
40 return max(ball, shell);
43fn sdf_normal(p: vec3<f32>) -> vec3<f32> {
44 let e = vec2<f32>(1.0, -1.0) * 0.0005;
45 return normalize(
46 e.xyy * map(p + e.xyy) +
47 e.yyx * map(p + e.yyx) +
48 e.yxy * map(p + e.yxy) +
49 e.xxx * map(p + e.xxx)
50 );
53fn raymarch(ray_origin: vec3<f32>, ray_dir: vec3<f32>) -> f32 {
54 var t = 0.0;
55 for (var i = 0u; i < MAX_STEPS; i++) {
56 let d = map(ray_origin + ray_dir * t);
57 if d < SURFACE_EPSILON {
58 return t;
59 }
60 t += d * STEP_SCALE;
61 if t > FAR {
62 break;
63 }
64 }
65 return FAR;
68struct Ray {
69 origin: vec3<f32>,
70 dir: vec3<f32>,
73fn ray_for_uv(uv: vec2<f32>) -> Ray {
74 let ndc = uv_to_ndc(uv);
75 var ray: Ray;
76 // Check ortho projection
77 if view.clip_from_view[3].w == 1.0 {
78 ray.origin = position_ndc_to_world(vec3<f32>(ndc, 1.0));
79 ray.dir = normalize(direction_view_to_world(vec3<f32>(0.0, 0.0, -1.0)));
80 } else {
81 ray.origin = view.world_position;
82 ray.dir = normalize(direction_view_to_world(position_ndc_to_view(vec3<f32>(ndc, 1.0))));
83 }
84 return ray;
87fn depth_for_world_pos(world_pos: vec3<f32>) -> f32 {
88 return view_z_to_depth_ndc(position_world_to_view(world_pos).z);
91/// Our render pass has two color targets, matching the deferred prepass:
92/// location 0: the packed gbuffer (Rgba32Uint)
93/// location 1: the deferred lighting pass id (R8Uint)
94struct GBufferOutput {
95 @location(0) deferred: vec4<u32>,
96 @location(1) deferred_lighting_pass_id: u32,
97 @builtin(frag_depth) depth: f32,
100@fragment
101fn fragment(in: FullscreenVertexOutput) -> GBufferOutput {
102 let ray = ray_for_uv(in.uv);
103 let t = raymarch(ray.origin, ray.dir);
105 // discard so we leave the mesh gbuffer intact when we miss
106 if t >= FAR {
107 discard;
108 }
110 let world_pos = ray.origin + ray.dir * t;
111 let normal = sdf_normal(world_pos);
113 // Fill in a PbrInput as a StandardMaterial fragment would
114 var pbr_input = pbr_input_new();
115 pbr_input.frag_coord = vec4<f32>(in.position.xy, depth_for_world_pos(world_pos), 1.0);
116 pbr_input.world_position = vec4<f32>(world_pos, 1.0);
117 pbr_input.world_normal = normal;
118 pbr_input.N = normal;
119 pbr_input.V = normalize(view.world_position - world_pos);
121 // Per-pixel base color
122 let field = gyroid_field(world_pos);
123 let color = 0.5 + 0.5 * cos(
124 6.2831 * (field * 0.4 + globals.time * 0.05) + vec3<f32>(0.0, 0.8, 1.6)
125 );
126 pbr_input.material.base_color = vec4<f32>(color, 1.0);
127 pbr_input.material.metallic = 0.7;
128 pbr_input.material.perceptual_roughness = 0.25;
129 pbr_input.material.flags = STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE;
130 // Let the surface receive shadows cast by other geometry.
131 pbr_input.flags = MESH_FLAGS_SHADOW_RECEIVER_BIT;
133 var out: GBufferOutput;
134 out.deferred = deferred_gbuffer_from_pbr_input(pbr_input);
135 // The lighting pass reads this per-pixel id to choose which lighting shader runs.
136 // 1 is Bevy's built-in PBR deferred shader.
137 out.deferred_lighting_pass_id = 1u;
138 out.depth = pbr_input.frag_coord.z;
139 return out;
142// When rendering into a light's shadow map we only need depth
143@fragment
144fn fragment_shadow(in: FullscreenVertexOutput) -> @builtin(frag_depth) f32 {
145 let ray = ray_for_uv(in.uv);
146 let t = raymarch(ray.origin, ray.dir);
147 if t >= FAR {
148 discard;
149 }
150 return depth_for_world_pos(ray.origin + ray.dir * t);
Contents