Skip to content

Commit 1bd7e5a

Browse files
View Transformations (#9726)
# Objective - Add functions for common view transformations. --------- Co-authored-by: Robert Swain <robert.swain@gmail.com>
1 parent fb55884 commit 1bd7e5a

File tree

8 files changed

+247
-37
lines changed

8 files changed

+247
-37
lines changed

crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,13 @@
88
mesh_view_bindings::view,
99
utils::{octahedral_encode, octahedral_decode},
1010
prepass_io::{VertexOutput, FragmentOutput},
11+
view_transformations::{position_ndc_to_world, frag_coord_to_ndc},
1112
}
1213

1314
#ifdef MOTION_VECTOR_PREPASS
1415
#import bevy_pbr::pbr_prepass_functions::calculate_motion_vector
1516
#endif
1617

17-
// ---------------------------
18-
// from https://github.com/DGriffin91/bevy_coordinate_systems/blob/main/src/transformations.wgsl
19-
// ---------------------------
20-
21-
/// Convert a ndc space position to world space
22-
fn position_ndc_to_world(ndc_pos: vec3<f32>) -> vec3<f32> {
23-
let world_pos = view.inverse_view_proj * vec4(ndc_pos, 1.0);
24-
return world_pos.xyz / world_pos.w;
25-
}
26-
27-
/// Convert ndc space xy coordinate [-1.0 .. 1.0] to uv [0.0 .. 1.0]
28-
fn ndc_to_uv(ndc: vec2<f32>) -> vec2<f32> {
29-
return ndc * vec2(0.5, -0.5) + vec2(0.5);
30-
}
31-
32-
/// Convert uv [0.0 .. 1.0] coordinate to ndc space xy [-1.0 .. 1.0]
33-
fn uv_to_ndc(uv: vec2<f32>) -> vec2<f32> {
34-
return uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0);
35-
}
36-
37-
/// Returns the (0.0, 0.0) .. (1.0, 1.0) position within the viewport for the current render target.
38-
/// [0 .. render target viewport size] eg. [(0.0, 0.0) .. (1280.0, 720.0)] to [(0.0, 0.0) .. (1.0, 1.0)]
39-
fn frag_coord_to_uv(frag_coord: vec2<f32>) -> vec2<f32> {
40-
return (frag_coord - view.viewport.xy) / view.viewport.zw;
41-
}
42-
43-
/// Convert frag coord to ndc.
44-
fn frag_coord_to_ndc(frag_coord: vec4<f32>) -> vec3<f32> {
45-
return vec3(uv_to_ndc(frag_coord_to_uv(frag_coord.xy)), frag_coord.z);
46-
}
47-
4818
// Creates the deferred gbuffer from a PbrInput.
4919
fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4<u32> {
5020
// Only monochrome occlusion supported. May not be worth including at all.

crates/bevy_pbr/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub const PBR_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(16550102
8282
pub const PBR_AMBIENT_HANDLE: Handle<Shader> = Handle::weak_from_u128(2441520459096337034);
8383
pub const PARALLAX_MAPPING_SHADER_HANDLE: Handle<Shader> =
8484
Handle::weak_from_u128(17035894873630133905);
85+
pub const VIEW_TRANSFORMATIONS_SHADER_HANDLE: Handle<Shader> =
86+
Handle::weak_from_u128(2098345702398750291);
8587
pub const PBR_PREPASS_FUNCTIONS_SHADER_HANDLE: Handle<Shader> =
8688
Handle::weak_from_u128(73204817249182637);
8789
pub const PBR_DEFERRED_TYPES_HANDLE: Handle<Shader> = Handle::weak_from_u128(3221241127431430599);
@@ -200,6 +202,12 @@ impl Plugin for PbrPlugin {
200202
"render/parallax_mapping.wgsl",
201203
Shader::from_wgsl
202204
);
205+
load_internal_asset!(
206+
app,
207+
VIEW_TRANSFORMATIONS_SHADER_HANDLE,
208+
"render/view_transformations.wgsl",
209+
Shader::from_wgsl
210+
);
203211

204212
app.register_asset_reflect::<StandardMaterial>()
205213
.register_type::<AlphaMode>()

crates/bevy_pbr/src/material.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use bevy_ecs::{
1818
};
1919
use bevy_reflect::Reflect;
2020
use bevy_render::{
21+
camera::Projection,
2122
extract_instances::{ExtractInstancesPlugin, ExtractedInstances},
2223
extract_resource::ExtractResource,
2324
mesh::{Mesh, MeshVertexBufferLayout},
@@ -457,6 +458,7 @@ pub fn queue_material_meshes<M: Material>(
457458
Has<DeferredPrepass>,
458459
),
459460
Option<&TemporalAntiAliasSettings>,
461+
Option<&Projection>,
460462
&mut RenderPhase<Opaque3d>,
461463
&mut RenderPhase<AlphaMask3d>,
462464
&mut RenderPhase<Transparent3d>,
@@ -474,6 +476,7 @@ pub fn queue_material_meshes<M: Material>(
474476
ssao,
475477
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
476478
taa_settings,
479+
projection,
477480
mut opaque_phase,
478481
mut alpha_mask_phase,
479482
mut transparent_phase,
@@ -511,6 +514,13 @@ pub fn queue_material_meshes<M: Material>(
511514
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
512515
}
513516

517+
if let Some(projection) = projection {
518+
view_key |= match projection {
519+
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
520+
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
521+
};
522+
}
523+
514524
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
515525
ShadowFilteringMethod::Hardware2x2 => {
516526
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,11 @@ bitflags::bitflags! {
520520
const SHADOW_FILTER_METHOD_HARDWARE_2X2 = 0 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
521521
const SHADOW_FILTER_METHOD_CASTANO_13 = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
522522
const SHADOW_FILTER_METHOD_JIMENEZ_14 = 2 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
523+
const VIEW_PROJECTION_RESERVED_BITS = Self::VIEW_PROJECTION_MASK_BITS << Self::VIEW_PROJECTION_SHIFT_BITS;
524+
const VIEW_PROJECTION_NONSTANDARD = 0 << Self::VIEW_PROJECTION_SHIFT_BITS;
525+
const VIEW_PROJECTION_PERSPECTIVE = 1 << Self::VIEW_PROJECTION_SHIFT_BITS;
526+
const VIEW_PROJECTION_ORTHOGRAPHIC = 2 << Self::VIEW_PROJECTION_SHIFT_BITS;
527+
const VIEW_PROJECTION_RESERVED = 3 << Self::VIEW_PROJECTION_SHIFT_BITS;
523528
}
524529
}
525530

@@ -543,6 +548,10 @@ impl MeshPipelineKey {
543548
const SHADOW_FILTER_METHOD_SHIFT_BITS: u32 =
544549
Self::TONEMAP_METHOD_SHIFT_BITS - Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones();
545550

551+
const VIEW_PROJECTION_MASK_BITS: u32 = 0b11;
552+
const VIEW_PROJECTION_SHIFT_BITS: u32 =
553+
Self::SHADOW_FILTER_METHOD_SHIFT_BITS - Self::VIEW_PROJECTION_MASK_BITS.count_ones();
554+
546555
pub fn from_msaa_samples(msaa_samples: u32) -> Self {
547556
let msaa_bits =
548557
(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
@@ -747,6 +756,15 @@ impl SpecializedMeshPipeline for MeshPipeline {
747756
shader_defs.push("LOAD_PREPASS_NORMALS".into());
748757
}
749758

759+
let view_projection = key.intersection(MeshPipelineKey::VIEW_PROJECTION_RESERVED_BITS);
760+
if view_projection == MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD {
761+
shader_defs.push("VIEW_PROJECTION_NONSTANDARD".into());
762+
} else if view_projection == MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE {
763+
shader_defs.push("VIEW_PROJECTION_PERSPECTIVE".into());
764+
} else if view_projection == MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC {
765+
shader_defs.push("VIEW_PROJECTION_ORTHOGRAPHIC".into());
766+
}
767+
750768
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
751769
shader_defs.push("WEBGL2".into());
752770

crates/bevy_pbr/src/render/mesh.wgsl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
skinning,
44
morph::morph,
55
forward_io::{Vertex, VertexOutput},
6+
view_transformations::position_world_to_clip,
67
}
78
#import bevy_render::instance_index::get_instance_index
89

@@ -60,7 +61,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
6061

6162
#ifdef VERTEX_POSITIONS
6263
out.world_position = mesh_functions::mesh_position_local_to_world(model, vec4<f32>(vertex.position, 1.0));
63-
out.position = mesh_functions::mesh_position_world_to_clip(out.world_position);
64+
out.position = position_world_to_clip(out.world_position.xyz);
6465
#endif
6566

6667
#ifdef VERTEX_UVS

crates/bevy_pbr/src/render/mesh_functions.wgsl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
mesh_view_bindings::view,
55
mesh_bindings::mesh,
66
mesh_types::MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT,
7+
view_transformations::position_world_to_clip,
78
}
89
#import bevy_render::{
910
instance_index::get_instance_index,
@@ -22,16 +23,12 @@ fn mesh_position_local_to_world(model: mat4x4<f32>, vertex_position: vec4<f32>)
2223
return model * vertex_position;
2324
}
2425

25-
fn mesh_position_world_to_clip(world_position: vec4<f32>) -> vec4<f32> {
26-
return view.view_proj * world_position;
27-
}
28-
2926
// NOTE: The intermediate world_position assignment is important
3027
// for precision purposes when using the 'equals' depth comparison
3128
// function.
3229
fn mesh_position_local_to_clip(model: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
3330
let world_position = mesh_position_local_to_world(model, vertex_position);
34-
return mesh_position_world_to_clip(world_position);
31+
return position_world_to_clip(world_position.xyz);
3532
}
3633

3734
fn mesh_normal_local_to_world(vertex_normal: vec3<f32>, instance_index: u32) -> vec3<f32> {
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#define_import_path bevy_pbr::view_transformations
2+
3+
#import bevy_pbr::mesh_view_bindings as view_bindings
4+
5+
/// World space:
6+
/// +y is up
7+
8+
/// View space:
9+
/// -z is forward, +x is right, +y is up
10+
/// Forward is from the camera position into the scene.
11+
/// (0.0, 0.0, -1.0) is linear distance of 1.0 in front of the camera's view relative to the camera's rotation
12+
/// (0.0, 1.0, 0.0) is linear distance of 1.0 above the camera's view relative to the camera's rotation
13+
14+
/// NDC (normalized device coordinate):
15+
/// https://www.w3.org/TR/webgpu/#coordinate-systems
16+
/// (-1.0, -1.0) in NDC is located at the bottom-left corner of NDC
17+
/// (1.0, 1.0) in NDC is located at the top-right corner of NDC
18+
/// Z is depth where:
19+
/// 1.0 is near clipping plane
20+
/// Perspective projection: 0.0 is inf far away
21+
/// Orthographic projection: 0.0 is far clipping plane
22+
23+
/// UV space:
24+
/// 0.0, 0.0 is the top left
25+
/// 1.0, 1.0 is the bottom right
26+
27+
28+
// -----------------
29+
// TO WORLD --------
30+
// -----------------
31+
32+
/// Convert a view space position to world space
33+
fn position_view_to_world(view_pos: vec3<f32>) -> vec3<f32> {
34+
let world_pos = view_bindings::view.view * vec4(view_pos, 1.0);
35+
return world_pos.xyz;
36+
}
37+
38+
/// Convert a clip space position to world space
39+
fn position_clip_to_world(clip_pos: vec4<f32>) -> vec3<f32> {
40+
let world_pos = view_bindings::view.inverse_view_proj * clip_pos;
41+
return world_pos.xyz;
42+
}
43+
44+
/// Convert a ndc space position to world space
45+
fn position_ndc_to_world(ndc_pos: vec3<f32>) -> vec3<f32> {
46+
let world_pos = view_bindings::view.inverse_view_proj * vec4(ndc_pos, 1.0);
47+
return world_pos.xyz / world_pos.w;
48+
}
49+
50+
/// Convert a view space direction to world space
51+
fn direction_view_to_world(view_dir: vec3<f32>) -> vec3<f32> {
52+
let world_dir = view_bindings::view.view * vec4(view_dir, 0.0);
53+
return world_dir.xyz;
54+
}
55+
56+
/// Convert a clip space direction to world space
57+
fn direction_clip_to_world(clip_dir: vec4<f32>) -> vec3<f32> {
58+
let world_dir = view_bindings::view.inverse_view_proj * clip_dir;
59+
return world_dir.xyz;
60+
}
61+
62+
// -----------------
63+
// TO VIEW ---------
64+
// -----------------
65+
66+
/// Convert a world space position to view space
67+
fn position_world_to_view(world_pos: vec3<f32>) -> vec3<f32> {
68+
let view_pos = view_bindings::view.inverse_view * vec4(world_pos, 1.0);
69+
return view_pos.xyz;
70+
}
71+
72+
/// Convert a clip space position to view space
73+
fn position_clip_to_view(clip_pos: vec4<f32>) -> vec3<f32> {
74+
let view_pos = view_bindings::view.inverse_projection * clip_pos;
75+
return view_pos.xyz;
76+
}
77+
78+
/// Convert a ndc space position to view space
79+
fn position_ndc_to_view(ndc_pos: vec3<f32>) -> vec3<f32> {
80+
let view_pos = view_bindings::view.inverse_projection * vec4(ndc_pos, 1.0);
81+
return view_pos.xyz / view_pos.w;
82+
}
83+
84+
/// Convert a world space direction to view space
85+
fn direction_world_to_view(world_dir: vec3<f32>) -> vec3<f32> {
86+
let view_dir = view_bindings::view.inverse_view * vec4(world_dir, 0.0);
87+
return view_dir.xyz;
88+
}
89+
90+
/// Convert a clip space direction to view space
91+
fn direction_clip_to_view(clip_dir: vec4<f32>) -> vec3<f32> {
92+
let view_dir = view_bindings::view.inverse_projection * clip_dir;
93+
return view_dir.xyz;
94+
}
95+
96+
// -----------------
97+
// TO CLIP ---------
98+
// -----------------
99+
100+
/// Convert a world space position to clip space
101+
fn position_world_to_clip(world_pos: vec3<f32>) -> vec4<f32> {
102+
let clip_pos = view_bindings::view.view_proj * vec4(world_pos, 1.0);
103+
return clip_pos;
104+
}
105+
106+
/// Convert a view space position to clip space
107+
fn position_view_to_clip(view_pos: vec3<f32>) -> vec4<f32> {
108+
let clip_pos = view_bindings::view.projection * vec4(view_pos, 1.0);
109+
return clip_pos;
110+
}
111+
112+
/// Convert a world space direction to clip space
113+
fn direction_world_to_clip(world_dir: vec3<f32>) -> vec4<f32> {
114+
let clip_dir = view_bindings::view.view_proj * vec4(world_dir, 0.0);
115+
return clip_dir;
116+
}
117+
118+
/// Convert a view space direction to clip space
119+
fn direction_view_to_clip(view_dir: vec3<f32>) -> vec4<f32> {
120+
let clip_dir = view_bindings::view.projection * vec4(view_dir, 0.0);
121+
return clip_dir;
122+
}
123+
124+
// -----------------
125+
// TO NDC ----------
126+
// -----------------
127+
128+
/// Convert a world space position to ndc space
129+
fn position_world_to_ndc(world_pos: vec3<f32>) -> vec3<f32> {
130+
let ndc_pos = view_bindings::view.view_proj * vec4(world_pos, 1.0);
131+
return ndc_pos.xyz / ndc_pos.w;
132+
}
133+
134+
/// Convert a view space position to ndc space
135+
fn position_view_to_ndc(view_pos: vec3<f32>) -> vec3<f32> {
136+
let ndc_pos = view_bindings::view.projection * vec4(view_pos, 1.0);
137+
return ndc_pos.xyz / ndc_pos.w;
138+
}
139+
140+
// -----------------
141+
// DEPTH -----------
142+
// -----------------
143+
144+
/// Retrieve the perspective camera near clipping plane
145+
fn perspective_camera_near() -> f32 {
146+
return view_bindings::view.projection[3][2];
147+
}
148+
149+
/// Convert ndc depth to linear view z.
150+
/// Note: Depth values in front of the camera will be negative as -z is forward
151+
fn depth_ndc_to_view_z(ndc_depth: f32) -> f32 {
152+
#ifdef VIEW_PROJECTION_PERSPECTIVE
153+
return -perspective_camera_near() / ndc_depth;
154+
#else ifdef VIEW_PROJECTION_ORTHOGRAPHIC
155+
return -(view_bindings::view.projection[3][2] - ndc_depth) / view_bindings::view.projection[2][2];
156+
#else
157+
let view_pos = view_bindings::view.inverse_projection * vec4(0.0, 0.0, ndc_depth, 1.0);
158+
return view_pos.z / view_pos.w;
159+
#endif
160+
}
161+
162+
/// Convert linear view z to ndc depth.
163+
/// Note: View z input should be negative for values in front of the camera as -z is forward
164+
fn view_z_to_depth_ndc(view_z: f32) -> f32 {
165+
#ifdef VIEW_PROJECTION_PERSPECTIVE
166+
return -perspective_camera_near() / view_z;
167+
#else ifdef VIEW_PROJECTION_ORTHOGRAPHIC
168+
return view_bindings::view.projection[3][2] + view_z * view_bindings::view.projection[2][2];
169+
#else
170+
let ndc_pos = view_bindings::view.projection * vec4(0.0, 0.0, view_z, 1.0);
171+
return ndc_pos.z / ndc_pos.w;
172+
#endif
173+
}
174+
175+
// -----------------
176+
// UV --------------
177+
// -----------------
178+
179+
/// Convert ndc space xy coordinate [-1.0 .. 1.0] to uv [0.0 .. 1.0]
180+
fn ndc_to_uv(ndc: vec2<f32>) -> vec2<f32> {
181+
return ndc * vec2(0.5, -0.5) + vec2(0.5);
182+
}
183+
184+
/// Convert uv [0.0 .. 1.0] coordinate to ndc space xy [-1.0 .. 1.0]
185+
fn uv_to_ndc(uv: vec2<f32>) -> vec2<f32> {
186+
return uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0);
187+
}
188+
189+
/// returns the (0.0, 0.0) .. (1.0, 1.0) position within the viewport for the current render target
190+
/// [0 .. render target viewport size] eg. [(0.0, 0.0) .. (1280.0, 720.0)] to [(0.0, 0.0) .. (1.0, 1.0)]
191+
fn frag_coord_to_uv(frag_coord: vec2<f32>) -> vec2<f32> {
192+
return (frag_coord - view_bindings::view.viewport.xy) / view_bindings::view.viewport.zw;
193+
}
194+
195+
/// Convert frag coord to ndc
196+
fn frag_coord_to_ndc(frag_coord: vec4<f32>) -> vec3<f32> {
197+
return vec3(uv_to_ndc(frag_coord_to_uv(frag_coord.xy)), frag_coord.z);
198+
}

0 commit comments

Comments
 (0)