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::{
8 F_Schlick_vec, LayerLightingInput, LightingInput, LAYER_BASE, LAYER_CLEARCOAT
9}
11struct EnvironmentMapLight {
12 diffuse: vec3<f32>,
13 specular: vec3<f32>,
14};
16struct EnvironmentMapRadiances {
17 irradiance: vec3<f32>,
18 radiance: vec3<f32>,
21// Define two versions of this function, one for the case in which there are
22// multiple light probes and one for the case in which only the view light probe
23// is present.
25#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY
27fn compute_radiances(
28 input: ptr<function, LightingInput>,
29 layer: u32,
30 world_position: vec3<f32>,
31 found_diffuse_indirect: bool,
32) -> EnvironmentMapRadiances {
33 // Unpack.
34 let perceptual_roughness = (*input).layers[layer].perceptual_roughness;
35 let N = (*input).layers[layer].N;
36 let R = (*input).layers[layer].R;
38 var radiances: EnvironmentMapRadiances;
40 // Search for a reflection probe that contains the fragment.
41 var query_result = query_light_probe(world_position, /*is_irradiance_volume=*/ false);
43 // If we didn't find a reflection probe, use the view environment map if applicable.
44 if (query_result.texture_index < 0) {
45 query_result.texture_index = light_probes.view_cubemap_index;
46 query_result.intensity = light_probes.intensity_for_view;
47 }
49 // If there's no cubemap, bail out.
50 if (query_result.texture_index < 0) {
51 radiances.irradiance = vec3(0.0);
52 radiances.radiance = vec3(0.0);
53 return radiances;
54 }
56 // Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
57 let radiance_level = perceptual_roughness * f32(textureNumLevels(
58 bindings::specular_environment_maps[query_result.texture_index]) - 1u);
60 if (!found_diffuse_indirect) {
61 var irradiance_sample_dir = N;
62 // Rotating the world space ray direction by the environment light map transform matrix, it is
63 // equivalent to rotating the diffuse environment cubemap itself.
64 irradiance_sample_dir = (environment_map_uniform.transform * vec4(irradiance_sample_dir, 1.0)).xyz;
65 // Cube maps are left-handed so we negate the z coordinate.
66 irradiance_sample_dir.z = -irradiance_sample_dir.z;
67 radiances.irradiance = textureSampleLevel(
68 bindings::diffuse_environment_maps[query_result.texture_index],
69 bindings::environment_map_sampler,
70 irradiance_sample_dir,
71 0.0).rgb * query_result.intensity;
72 }
74 var radiance_sample_dir = R;
75 // Rotating the world space ray direction by the environment light map transform matrix, it is
76 // equivalent to rotating the specular environment cubemap itself.
77 radiance_sample_dir = (environment_map_uniform.transform * vec4(radiance_sample_dir, 1.0)).xyz;
78 // Cube maps are left-handed so we negate the z coordinate.
79 radiance_sample_dir.z = -radiance_sample_dir.z;
80 radiances.radiance = textureSampleLevel(
81 bindings::specular_environment_maps[query_result.texture_index],
82 bindings::environment_map_sampler,
83 radiance_sample_dir,
84 radiance_level).rgb * query_result.intensity;
86 return radiances;
89#else // MULTIPLE_LIGHT_PROBES_IN_ARRAY
91fn compute_radiances(
92 input: ptr<function, LightingInput>,
93 layer: u32,
94 world_position: vec3<f32>,
95 found_diffuse_indirect: bool,
96) -> EnvironmentMapRadiances {
97 // Unpack.
98 let perceptual_roughness = (*input).layers[layer].perceptual_roughness;
99 let N = (*input).layers[layer].N;
100 let R = (*input).layers[layer].R;
102 var radiances: EnvironmentMapRadiances;
104 if (light_probes.view_cubemap_index < 0) {
105 radiances.irradiance = vec3(0.0);
106 radiances.radiance = vec3(0.0);
107 return radiances;
108 }
110 // Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
111 // Technically we could use textureNumLevels(specular_environment_map) - 1 here, but we use a uniform
112 // because textureNumLevels() does not work on WebGL2
113 let radiance_level = perceptual_roughness * f32(light_probes.smallest_specular_mip_level_for_view);
115 let intensity = light_probes.intensity_for_view;
117 if (!found_diffuse_indirect) {
118 var irradiance_sample_dir = N;
119 // Rotating the world space ray direction by the environment light map transform matrix, it is
120 // equivalent to rotating the diffuse environment cubemap itself.
121 irradiance_sample_dir = (environment_map_uniform.transform * vec4(irradiance_sample_dir, 1.0)).xyz;
122 // Cube maps are left-handed so we negate the z coordinate.
123 irradiance_sample_dir.z = -irradiance_sample_dir.z;
124 radiances.irradiance = textureSampleLevel(
125 bindings::diffuse_environment_map,
126 bindings::environment_map_sampler,
127 irradiance_sample_dir,
128 0.0).rgb * intensity;
129 }
131 var radiance_sample_dir = R;
132 // Rotating the world space ray direction by the environment light map transform matrix, it is
133 // equivalent to rotating the specular environment cubemap itself.
134 radiance_sample_dir = (environment_map_uniform.transform * vec4(radiance_sample_dir, 1.0)).xyz;
135 // Cube maps are left-handed so we negate the z coordinate.
136 radiance_sample_dir.z = -radiance_sample_dir.z;
137 radiances.radiance = textureSampleLevel(
138 bindings::specular_environment_map,
139 bindings::environment_map_sampler,
140 radiance_sample_dir,
141 radiance_level).rgb * intensity;
143 return radiances;
146#endif // MULTIPLE_LIGHT_PROBES_IN_ARRAY
148#ifdef STANDARD_MATERIAL_CLEARCOAT
150// Adds the environment map light from the clearcoat layer to that of the base
151// layer.
152fn environment_map_light_clearcoat(
153 out: ptr<function, EnvironmentMapLight>,
154 input: ptr<function, LightingInput>,
155 found_diffuse_indirect: bool,
156) {
157 // Unpack.
158 let world_position = (*input).P;
159 let clearcoat_NdotV = (*input).layers[LAYER_CLEARCOAT].NdotV;
160 let clearcoat_strength = (*input).clearcoat_strength;
162 // Calculate the Fresnel term `Fc` for the clearcoat layer.
163 // 0.04 is a hardcoded value for F0 from the Filament spec.
164 let clearcoat_F0 = vec3<f32>(0.04);
165 let Fc = F_Schlick_vec(clearcoat_F0, 1.0, clearcoat_NdotV) * clearcoat_strength;
166 let inv_Fc = 1.0 - Fc;
168 let clearcoat_radiances = compute_radiances(
169 input, LAYER_CLEARCOAT, world_position, found_diffuse_indirect);
171 // Composite the clearcoat layer on top of the existing one.
172 // These formulas are from Filament:
173 // <https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/clearcoat>
174 (*out).diffuse *= inv_Fc;
175 (*out).specular = (*out).specular * inv_Fc * inv_Fc + clearcoat_radiances.radiance * Fc;
178#endif // STANDARD_MATERIAL_CLEARCOAT
180fn environment_map_light(
181 input: ptr<function, LightingInput>,
182 found_diffuse_indirect: bool,
183) -> EnvironmentMapLight {
184 // Unpack.
185 let roughness = (*input).layers[LAYER_BASE].roughness;
186 let diffuse_color = (*input).diffuse_color;
187 let NdotV = (*input).layers[LAYER_BASE].NdotV;
188 let F_ab = (*input).F_ab;
189 let F0 = (*input).F0_;
190 let world_position = (*input).P;
192 var out: EnvironmentMapLight;
194 let radiances = compute_radiances(input, LAYER_BASE, world_position, found_diffuse_indirect);
195 if (all(radiances.irradiance == vec3(0.0)) && all(radiances.radiance == vec3(0.0))) {
196 out.diffuse = vec3(0.0);
197 out.specular = vec3(0.0);
198 return out;
199 }
201 // No real world material has specular values under 0.02, so we use this range as a
202 // "pre-baked specular occlusion" that extinguishes the fresnel term, for artistic control.
203 // See: https://google.github.io/filament/Filament.html#specularocclusion
204 let specular_occlusion = saturate(dot(F0, vec3(50.0 * 0.33)));
206 // Multiscattering approximation: https://www.jcgt.org/published/0008/01/03/paper.pdf
207 // Useful reference: https://bruop.github.io/ibl
208 let Fr = max(vec3(1.0 - roughness), F0) - F0;
209 let kS = F0 + Fr * pow(1.0 - NdotV, 5.0);
210 let Ess = F_ab.x + F_ab.y;
211 let FssEss = kS * Ess * specular_occlusion;
212 let Ems = 1.0 - Ess;
213 let Favg = F0 + (1.0 - F0) / 21.0;
214 let Fms = FssEss * Favg / (1.0 - Ems * Favg);
215 let FmsEms = Fms * Ems;
216 let Edss = 1.0 - (FssEss + FmsEms);
217 let kD = diffuse_color * Edss;
219 if (!found_diffuse_indirect) {
220 out.diffuse = (FmsEms + kD) * radiances.irradiance;
221 } else {
222 out.diffuse = vec3(0.0);
223 }
225 out.specular = FssEss * radiances.radiance;
227#ifdef STANDARD_MATERIAL_CLEARCOAT
228 environment_map_light_clearcoat(&out, input, found_diffuse_indirect);
229#endif // STANDARD_MATERIAL_CLEARCOAT
231 return out;
Contents