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#define_import_path bevy_pbr::environment_map
3#import bevy_pbr::light_probe::query_light_probe
4#import bevy_pbr::mesh_view_bindings as bindings
5#import bevy_pbr::mesh_view_bindings::light_probes
6#import bevy_pbr::mesh_view_bindings::environment_map_uniform
7#import bevy_pbr::lighting::{F_Schlick_vec, LightingInput, LayerLightingInput, LAYER_BASE, LAYER_CLEARCOAT}
8#import bevy_pbr::clustered_forward::ClusterableObjectIndexRanges
10struct EnvironmentMapLight {
11 diffuse: vec3<f32>,
12 specular: vec3<f32>,
13};
15struct EnvironmentMapRadiances {
16 irradiance: vec3<f32>,
17 radiance: vec3<f32>,
20// Define two versions of this function, one for the case in which there are
21// multiple light probes and one for the case in which only the view light probe
22// is present.
24#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY
26fn compute_radiances(
27 input: LayerLightingInput,
28 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
29 world_position: vec3<f32>,
30 found_diffuse_indirect: bool,
31) -> EnvironmentMapRadiances {
32 // Unpack.
33 let N = input.N;
34 let R = input.R;
35 let NdotV = input.NdotV;
36 let perceptual_roughness = input.perceptual_roughness;
37 let roughness = input.roughness;
39 var radiances: EnvironmentMapRadiances;
41 // Search for a reflection probe that contains the fragment.
42 var query_result = query_light_probe(
43 world_position,
44 /*is_irradiance_volume=*/ false,
45 clusterable_object_index_ranges,
46 );
48 // If we didn't find a reflection probe, use the view environment map if applicable.
49 if (query_result.texture_index < 0) {
50 query_result.texture_index = light_probes.view_cubemap_index;
51 query_result.intensity = light_probes.intensity_for_view;
52 query_result.affects_lightmapped_mesh_diffuse =
53 light_probes.view_environment_map_affects_lightmapped_mesh_diffuse != 0u;
54 }
56 // If there's no cubemap, bail out.
57 if (query_result.texture_index < 0) {
58 radiances.irradiance = vec3(0.0);
59 radiances.radiance = vec3(0.0);
60 return radiances;
61 }
63 // Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
64 let radiance_level = perceptual_roughness * f32(textureNumLevels(
65 bindings::specular_environment_maps[query_result.texture_index]) - 1u);
67 // If we're lightmapped, and we shouldn't accumulate diffuse light from the
68 // environment map, note that.
69 var enable_diffuse = !found_diffuse_indirect;
70#ifdef LIGHTMAP
71 enable_diffuse = enable_diffuse && query_result.affects_lightmapped_mesh_diffuse;
72#endif // LIGHTMAP
74 if (enable_diffuse) {
75 var irradiance_sample_dir = N;
76 // Rotating the world space ray direction by the environment light map transform matrix, it is
77 // equivalent to rotating the diffuse environment cubemap itself.
78 irradiance_sample_dir = (environment_map_uniform.transform * vec4(irradiance_sample_dir, 1.0)).xyz;
79 // Cube maps are left-handed so we negate the z coordinate.
80 irradiance_sample_dir.z = -irradiance_sample_dir.z;
81 radiances.irradiance = textureSampleLevel(
82 bindings::diffuse_environment_maps[query_result.texture_index],
83 bindings::environment_map_sampler,
84 irradiance_sample_dir,
85 0.0).rgb * query_result.intensity;
86 }
88 var radiance_sample_dir = radiance_sample_direction(N, R, roughness);
89 // Rotating the world space ray direction by the environment light map transform matrix, it is
90 // equivalent to rotating the specular environment cubemap itself.
91 radiance_sample_dir = (environment_map_uniform.transform * vec4(radiance_sample_dir, 1.0)).xyz;
92 // Cube maps are left-handed so we negate the z coordinate.
93 radiance_sample_dir.z = -radiance_sample_dir.z;
94 radiances.radiance = textureSampleLevel(
95 bindings::specular_environment_maps[query_result.texture_index],
96 bindings::environment_map_sampler,
97 radiance_sample_dir,
98 radiance_level).rgb * query_result.intensity;
100 return radiances;
103#else // MULTIPLE_LIGHT_PROBES_IN_ARRAY
105fn compute_radiances(
106 input: LayerLightingInput,
107 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
108 world_position: vec3<f32>,
109 found_diffuse_indirect: bool,
110) -> EnvironmentMapRadiances {
111 // Unpack.
112 let N = input.N;
113 let R = input.R;
114 let NdotV = input.NdotV;
115 let perceptual_roughness = input.perceptual_roughness;
116 let roughness = input.roughness;
118 var radiances: EnvironmentMapRadiances;
120 if (light_probes.view_cubemap_index < 0) {
121 radiances.irradiance = vec3(0.0);
122 radiances.radiance = vec3(0.0);
123 return radiances;
124 }
126 // Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
127 // Technically we could use textureNumLevels(specular_environment_map) - 1 here, but we use a uniform
128 // because textureNumLevels() does not work on WebGL2
129 let radiance_level = perceptual_roughness * f32(light_probes.smallest_specular_mip_level_for_view);
131 let intensity = light_probes.intensity_for_view;
133 // If we're lightmapped, and we shouldn't accumulate diffuse light from the
134 // environment map, note that.
135 var enable_diffuse = !found_diffuse_indirect;
136#ifdef LIGHTMAP
137 enable_diffuse = enable_diffuse &&
138 light_probes.view_environment_map_affects_lightmapped_mesh_diffuse;
139#endif // LIGHTMAP
141 if (enable_diffuse) {
142 var irradiance_sample_dir = N;
143 // Rotating the world space ray direction by the environment light map transform matrix, it is
144 // equivalent to rotating the diffuse environment cubemap itself.
145 irradiance_sample_dir = (environment_map_uniform.transform * vec4(irradiance_sample_dir, 1.0)).xyz;
146 // Cube maps are left-handed so we negate the z coordinate.
147 irradiance_sample_dir.z = -irradiance_sample_dir.z;
148 radiances.irradiance = textureSampleLevel(
149 bindings::diffuse_environment_map,
150 bindings::environment_map_sampler,
151 irradiance_sample_dir,
152 0.0).rgb * intensity;
153 }
155 var radiance_sample_dir = radiance_sample_direction(N, R, roughness);
156 // Rotating the world space ray direction by the environment light map transform matrix, it is
157 // equivalent to rotating the specular environment cubemap itself.
158 radiance_sample_dir = (environment_map_uniform.transform * vec4(radiance_sample_dir, 1.0)).xyz;
159 // Cube maps are left-handed so we negate the z coordinate.
160 radiance_sample_dir.z = -radiance_sample_dir.z;
161 radiances.radiance = textureSampleLevel(
162 bindings::specular_environment_map,
163 bindings::environment_map_sampler,
164 radiance_sample_dir,
165 radiance_level).rgb * intensity;
167 return radiances;
170#endif // MULTIPLE_LIGHT_PROBES_IN_ARRAY
172#ifdef STANDARD_MATERIAL_CLEARCOAT
174// Adds the environment map light from the clearcoat layer to that of the base
175// layer.
176fn environment_map_light_clearcoat(
177 out: ptr<function, EnvironmentMapLight>,
178 input: ptr<function, LightingInput>,
179 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
180 found_diffuse_indirect: bool,
181) {
182 // Unpack.
183 let world_position = (*input).P;
184 let clearcoat_NdotV = (*input).layers[LAYER_CLEARCOAT].NdotV;
185 let clearcoat_strength = (*input).clearcoat_strength;
187 // Calculate the Fresnel term `Fc` for the clearcoat layer.
188 // 0.04 is a hardcoded value for F0 from the Filament spec.
189 let clearcoat_F0 = vec3<f32>(0.04);
190 let Fc = F_Schlick_vec(clearcoat_F0, 1.0, clearcoat_NdotV) * clearcoat_strength;
191 let inv_Fc = 1.0 - Fc;
193 let clearcoat_radiances = compute_radiances(
194 (*input).layers[LAYER_CLEARCOAT],
195 clusterable_object_index_ranges,
196 world_position,
197 found_diffuse_indirect,
198 );
200 // Composite the clearcoat layer on top of the existing one.
201 // These formulas are from Filament:
202 // <https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/clearcoat>
203 (*out).diffuse *= inv_Fc;
204 (*out).specular = (*out).specular * inv_Fc * inv_Fc + clearcoat_radiances.radiance * Fc;
207#endif // STANDARD_MATERIAL_CLEARCOAT
209fn environment_map_light(
210 input: ptr<function, LightingInput>,
211 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
212 found_diffuse_indirect: bool,
213) -> EnvironmentMapLight {
214 // Unpack.
215 let roughness = (*input).layers[LAYER_BASE].roughness;
216 let diffuse_color = (*input).diffuse_color;
217 let NdotV = (*input).layers[LAYER_BASE].NdotV;
218 let F_ab = (*input).F_ab;
219 let F0 = (*input).F0_;
220 let world_position = (*input).P;
222 var out: EnvironmentMapLight;
224 let radiances = compute_radiances(
225 (*input).layers[LAYER_BASE],
226 clusterable_object_index_ranges,
227 world_position,
228 found_diffuse_indirect,
229 );
231 if (all(radiances.irradiance == vec3(0.0)) && all(radiances.radiance == vec3(0.0))) {
232 out.diffuse = vec3(0.0);
233 out.specular = vec3(0.0);
234 return out;
235 }
237 // No real world material has specular values under 0.02, so we use this range as a
238 // "pre-baked specular occlusion" that extinguishes the fresnel term, for artistic control.
239 // See: https://google.github.io/filament/Filament.html#specularocclusion
240 let specular_occlusion = saturate(dot(F0, vec3(50.0 * 0.33)));
242 // Multiscattering approximation: https://www.jcgt.org/published/0008/01/03/paper.pdf
243 // Useful reference: https://bruop.github.io/ibl
244 let Fr = max(vec3(1.0 - roughness), F0) - F0;
245 let kS = F0 + Fr * pow(1.0 - NdotV, 5.0);
246 let Ess = F_ab.x + F_ab.y;
247 let FssEss = kS * Ess * specular_occlusion;
248 let Ems = 1.0 - Ess;
249 let Favg = F0 + (1.0 - F0) / 21.0;
250 let Fms = FssEss * Favg / (1.0 - Ems * Favg);
251 let FmsEms = Fms * Ems;
252 let Edss = 1.0 - (FssEss + FmsEms);
253 let kD = diffuse_color * Edss;
255 if (!found_diffuse_indirect) {
256 out.diffuse = (FmsEms + kD) * radiances.irradiance;
257 } else {
258 out.diffuse = vec3(0.0);
259 }
261 out.specular = FssEss * radiances.radiance;
263#ifdef STANDARD_MATERIAL_CLEARCOAT
264 environment_map_light_clearcoat(
265 &out,
266 input,
267 clusterable_object_index_ranges,
268 found_diffuse_indirect,
269 );
270#endif // STANDARD_MATERIAL_CLEARCOAT
272 return out;
275// "Moving Frostbite to Physically Based Rendering 3.0", listing 22
276// https://seblagarde.wordpress.com/wp-content/uploads/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf#page=70
277fn radiance_sample_direction(N: vec3<f32>, R: vec3<f32>, roughness: f32) -> vec3<f32> {
278 let smoothness = saturate(1.0 - roughness);
279 let lerp_factor = smoothness * (sqrt(smoothness) + roughness);
280 return mix(N, R, lerp_factor);
Contents