44#import bevy_pbr :: mesh_view_bindings as bindings
55#import bevy_pbr :: mesh_view_bindings :: light_probes
66#import bevy_pbr :: mesh_view_bindings :: environment_map_uniform
7+ #import bevy_pbr :: mesh_view_types :: {
8+ LIGHT_PROBE_FLAG_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE , LIGHT_PROBE_FLAG_PARALLAX_CORRECT
9+ }
710#import bevy_pbr :: lighting :: {F_Schlick_vec , LightingInput , LayerLightingInput , LAYER_BASE , LAYER_CLEARCOAT }
811#import bevy_pbr :: clustered_forward :: ClusterableObjectIndexRanges
912
13+ // The maximum representable value in a 32-bit floating point number.
14+ const FLOAT_MAX : f32 = 3 .40282347e+38 ;
15+
1016struct EnvironmentMapLight {
1117 diffuse : vec3 <f32 >,
1218 specular : vec3 <f32 >,
@@ -17,6 +23,56 @@ struct EnvironmentMapRadiances {
1723 radiance : vec3 <f32 >,
1824}
1925
26+ // Computes the direction at which to sample the reflection probe.
27+ fn compute_cubemap_sample_dir (
28+ world_ray_origin : vec3 <f32 >,
29+ world_ray_direction : vec3 <f32 >,
30+ light_from_world : mat4x4 <f32 >,
31+ parallax_correct : bool
32+ ) -> vec3 <f32 > {
33+ var sample_dir : vec3 <f32 >;
34+
35+ // If we're supposed to parallax correct, then intersect with the light cube.
36+ if (parallax_correct ) {
37+ // Compute the direction of the ray bouncing off the surface, in light
38+ // probe space.
39+ // Recall that light probe space is a 1×1×1 cube centered at the origin.
40+ let ray_origin = (light_from_world * vec4 (world_ray_origin , 1 .0 )). xyz ;
41+ let ray_direction = (light_from_world * vec4 (world_ray_direction , 0 .0 )). xyz ;
42+
43+ // Solve for the intersection of that ray with each side of the cube.
44+ // Since our light probe is a 1×1×1 cube centered at the origin in light
45+ // probe space, the faces of the cube are at X = ±0.5, Y = ±0.5, and Z =
46+ // ±0.5.
47+ var t0 = (vec3 (- 0 .5 ) - ray_origin ) / ray_direction ;
48+ var t1 = (vec3 (0 .5 ) - ray_origin ) / ray_direction ;
49+
50+ // We're shooting the rays forward, so we need to rule out negative time
51+ // values. So, if t is negative, make it a large value so that we won't
52+ // choose it below.
53+ // We would use infinity here but WGSL forbids it:
54+ // https://github.com/gfx-rs/wgpu/issues/5515
55+ t0 = select (vec3 (FLOAT_MAX ), t0 , t0 >= vec3 (0 .0 ));
56+ t1 = select (vec3 (FLOAT_MAX ), t1 , t1 >= vec3 (0 .0 ));
57+
58+ // Choose the minimum valid time value to find the intersection of the
59+ // first cube face.
60+ let t_min = min (t0 , t1 );
61+ let t = min (min (t_min . x , t_min . y ), t_min . z );
62+
63+ // Compute the sample direction. (It doesn't have to be normalized.)
64+ sample_dir = ray_origin + ray_direction * t ;
65+ } else {
66+ // We treat the reflection as infinitely far away in the non-parallax
67+ // case, so the ray origin is irrelevant.
68+ sample_dir = (light_from_world * vec4 (world_ray_direction , 0 .0 )). xyz ;
69+ }
70+
71+ // Cubemaps are left-handed, so we negate the Z coordinate.
72+ sample_dir . z = - sample_dir . z ;
73+ return sample_dir ;
74+ }
75+
2076// Define two versions of this function, one for the case in which there are
2177// multiple light probes and one for the case in which only the view light probe
2278// is present.
@@ -48,8 +104,11 @@ fn compute_radiances(
48104 if (query_result . texture_index < 0 ) {
49105 query_result . texture_index = light_probes . view_cubemap_index ;
50106 query_result . intensity = light_probes . intensity_for_view ;
51- query_result . affects_lightmapped_mesh_diffuse =
52- light_probes . view_environment_map_affects_lightmapped_mesh_diffuse != 0u ;
107+ if light_probes . view_environment_map_affects_lightmapped_mesh_diffuse != 0u {
108+ query_result . flags = LIGHT_PROBE_FLAG_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE ;
109+ } else {
110+ query_result . flags = 0u ;
111+ }
53112 }
54113
55114 // If there's no cubemap, bail out.
@@ -67,16 +126,19 @@ fn compute_radiances(
67126 // environment map, note that.
68127 var enable_diffuse = ! found_diffuse_indirect ;
69128#ifdef LIGHTMAP
70- enable_diffuse = enable_diffuse && query_result . affects_lightmapped_mesh_diffuse ;
129+ enable_diffuse = enable_diffuse &&
130+ (query_result . flags & LIGHT_PROBE_FLAG_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE ) != 0u ;
71131#endif // LIGHTMAP
72132
133+ let parallax_correct = (query_result . flags & LIGHT_PROBE_FLAG_PARALLAX_CORRECT ) != 0u ;
134+
73135 if (enable_diffuse ) {
74- var irradiance_sample_dir = N ;
75- // Rotating the world space ray direction by the environment light map transform matrix, it is
76- // equivalent to rotating the diffuse environment cubemap itself.
77- irradiance_sample_dir = ( environment_map_uniform . transform * vec4 ( irradiance_sample_dir , 1 .0 )) . xyz ;
78- // Cube maps are left-handed so we negate the z coordinate.
79- irradiance_sample_dir . z = - irradiance_sample_dir . z ;
136+ let irradiance_sample_dir = compute_cubemap_sample_dir (
137+ world_position ,
138+ N ,
139+ query_result . light_from_world ,
140+ parallax_correct
141+ ) ;
80142 radiances . irradiance = textureSampleLevel (
81143 bindings :: diffuse_environment_maps [query_result . texture_index ],
82144 bindings :: environment_map_sampler ,
@@ -85,11 +147,13 @@ fn compute_radiances(
85147 }
86148
87149 var radiance_sample_dir = radiance_sample_direction (N , R , roughness );
88- // Rotating the world space ray direction by the environment light map transform matrix, it is
89- // equivalent to rotating the specular environment cubemap itself.
90- radiance_sample_dir = (environment_map_uniform . transform * vec4 (radiance_sample_dir , 1 .0 )). xyz ;
91- // Cube maps are left-handed so we negate the z coordinate.
92- radiance_sample_dir . z = - radiance_sample_dir . z ;
150+ radiance_sample_dir = compute_cubemap_sample_dir (
151+ world_position ,
152+ radiance_sample_dir ,
153+ query_result . light_from_world ,
154+ parallax_correct
155+ );
156+
93157 radiances . radiance = textureSampleLevel (
94158 bindings :: specular_environment_maps [query_result . texture_index ],
95159 bindings :: environment_map_sampler ,
0 commit comments