1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #ifndef SHADERS__COMMON__3D_DM_LIGHTING_COMMON_H
17 #define SHADERS__COMMON__3D_DM_LIGHTING_COMMON_H
18 
19 #include "3d/shaders/common/3d_dm_brdf_common.h"
20 #include "3d/shaders/common/3d_dm_indirect_lighting_common.h"
21 #include "3d/shaders/common/3d_dm_shadowing_common.h"
22 #include "render/shaders/common/render_compatibility_common.h"
23 
24 /*
25  * NOTE: Inplace data uCameras from descriptor sets
26  * Needs to be included after the descriptor sets are bound.
27  */
28 
29 #define CORE3D_PBR_LIGHTING_EPSILON 0.0001
30 #define CORE_GEOMETRIC_SPECULAR_AA_ROUGHNESS 1
31 
32 struct InputBrdfData {
33     vec4 f0;
34     vec3 diffuseColor;
35     float roughness;
36     float alpha2;
37 };
38 
39 struct ShadingData {
40     vec3 pos;
41     vec3 N;
42     float NoV;
43     vec3 V;
44     float alpha2;
45     vec4 f0; // f0 = f0.xyz, f90 = f0.w
46     vec3 diffuseColor;
47 };
48 
49 struct ShadingDataInplace {
50     vec3 pos;
51     vec3 N;
52     float NoV;
53     vec3 V;
54     float alpha2;
55     vec4 f0; // f0 = f0.xyz, f90 = f0.w
56     vec3 diffuseColor;
57     uint materialFlags;
58     uvec2 layers;
59 };
60 
61 struct ClearcoatShadingVariables {
62     vec3 ccNormal;
63     float cc;
64     CORE_RELAXEDP float ccRoughness;
65     float ccAlpha2;
66 };
67 
68 struct SheenShadingVariables {
69     CORE_RELAXEDP vec3 sheenColor;
70     CORE_RELAXEDP float sheenRoughness;
71     CORE_RELAXEDP float sheenColorMax;
72     CORE_RELAXEDP float sheenBRDFApprox;
73 };
74 
75 struct AnisotropicShadingVariables {
76     float roughness;
77     float alpha;
78     float anisotropy;
79     float ToV;
80     float BoV;
81     vec3 anisotropicT;
82     vec3 anisotropicB;
83 };
84 
85 struct SubsurfaceScatterShadingVariables {
86     vec3 scatterColor;
87     float scatterDistance;
88     float thickness;
89 };
90 
91 #define CORE3D_GEOMETRIC_SPECULAR_AA_ROUGHNESS 1
92 
GetFinalCorrectedRoughness(CORE_RELAXEDP in vec3 polygonNormal,CORE_RELAXEDP inout float roughness)93 void GetFinalCorrectedRoughness(CORE_RELAXEDP in vec3 polygonNormal, CORE_RELAXEDP inout float roughness)
94 {
95 #if (CORE_GEOMETRIC_SPECULAR_AA_ROUGHNESS == 1)
96     // reduce shading aliasing by increasing roughness based on the curvature of the geometry
97     const CORE_RELAXEDP vec3 normalDFdx = dFdx(polygonNormal);
98     const CORE_RELAXEDP vec3 normalDdFdy = dFdy(polygonNormal);
99     const CORE_RELAXEDP float geometricRoughness =
100         pow(clamp(max(dot(normalDFdx, normalDFdx), dot(normalDdFdy, normalDdFdy)), 0.0, 1.0), 0.333);
101     roughness = max(roughness, geometricRoughness);
102 #endif
103     roughness = clamp(roughness, CORE_BRDF_MIN_ROUGHNESS, 1.0);
104 }
105 
AppendIndirectSheen(in SheenShadingVariables ssv,in CORE_RELAXEDP vec3 radianceSample,in CORE_RELAXEDP float alpha,inout CORE_RELAXEDP vec3 irradiance,inout CORE_RELAXEDP vec3 radiance)106 void AppendIndirectSheen(in SheenShadingVariables ssv, in CORE_RELAXEDP vec3 radianceSample,
107     in CORE_RELAXEDP float alpha, inout CORE_RELAXEDP vec3 irradiance, inout CORE_RELAXEDP vec3 radiance)
108 {
109     const vec3 sheenF = ssv.sheenColor * ssv.sheenBRDFApprox;
110     const float sheenAttenuation = 1.0 - (ssv.sheenColorMax * ssv.sheenBRDFApprox);
111     // energy compensation
112     irradiance *= sheenAttenuation;
113     radiance *= sheenAttenuation;
114 
115     radiance += sheenF * radianceSample;
116 }
117 
AppendIndirectClearcoat(in ClearcoatShadingVariables ccsv,in CORE_RELAXEDP vec3 radianceSample,in CORE_RELAXEDP float alpha,in vec3 V,inout CORE_RELAXEDP vec3 irradiance,inout CORE_RELAXEDP vec3 radiance)118 void AppendIndirectClearcoat(in ClearcoatShadingVariables ccsv, in CORE_RELAXEDP vec3 radianceSample,
119     in CORE_RELAXEDP float alpha, in vec3 V, inout CORE_RELAXEDP vec3 irradiance, inout CORE_RELAXEDP vec3 radiance)
120 {
121     const float ccNoV = clamp(dot(ccsv.ccNormal, V), CORE3D_PBR_LIGHTING_EPSILON, 1.0);
122 
123     GetFinalCorrectedRoughness(ccsv.ccNormal, ccsv.ccAlpha2);
124 
125     float ccRadianceFactor = EnvBRDFApproxNonmetal(ccsv.ccAlpha2, ccNoV);
126     float ccAttenuation = 1.0 - ccRadianceFactor;
127 
128     // energy compensation
129     irradiance *= ccAttenuation;
130     radiance *= ccAttenuation;
131 
132     // add clear coat radiance
133     radiance += radianceSample * ccRadianceFactor;
134 }
135 
AppendIndirectTransmission(in CORE_RELAXEDP vec3 radianceSample,in CORE_RELAXEDP vec3 baseColor,in CORE_RELAXEDP float transmission,inout CORE_RELAXEDP vec3 irradiance)136 void AppendIndirectTransmission(in CORE_RELAXEDP vec3 radianceSample, in CORE_RELAXEDP vec3 baseColor,
137     in CORE_RELAXEDP float transmission, inout CORE_RELAXEDP vec3 irradiance)
138 {
139     // Approximate double refraction by assuming a solid sphere beneath the point.
140     const CORE_RELAXEDP vec3 Ft = radianceSample * baseColor.rgb;
141     irradiance *= (1.0 - transmission);
142     irradiance = mix(irradiance, Ft, transmission);
143 }
144 
GetFinalSampledBrdfGeometricCorrection(in vec3 polygonNormal,inout InputBrdfData ibd)145 void GetFinalSampledBrdfGeometricCorrection(in vec3 polygonNormal, inout InputBrdfData ibd)
146 {
147     // NOTE: diffuse color is already premultiplied
148     CORE_RELAXEDP float roughness = ibd.roughness;
149     GetFinalCorrectedRoughness(polygonNormal, roughness);
150 
151     const CORE_RELAXEDP float alpha = roughness * roughness;
152     ibd.alpha2 = alpha * alpha;
153     ibd.roughness = roughness;
154 }
155 
GetFinalSampledBrdfGeometricCorrection(inout InputBrdfData ibd)156 void GetFinalSampledBrdfGeometricCorrection(inout InputBrdfData ibd)
157 {
158     const CORE_RELAXEDP float roughness = ibd.roughness;
159     const CORE_RELAXEDP float alpha = roughness * roughness;
160     ibd.alpha2 = alpha * alpha;
161 }
162 
CalcBRDFMetallicRoughness(CORE_RELAXEDP vec4 baseColor,vec3 polygonNormal,CORE_RELAXEDP vec4 material)163 InputBrdfData CalcBRDFMetallicRoughness(CORE_RELAXEDP vec4 baseColor, vec3 polygonNormal, CORE_RELAXEDP vec4 material)
164 {
165     InputBrdfData bd;
166     {
167         const CORE_RELAXEDP float metallic = clamp(material.b, 0.0, 1.0);
168         bd.f0.xyz = mix(vec3(material.a), baseColor.rgb, metallic); // f0 reflectance
169         bd.f0.w = 1.0;                                              // f90
170         // spec and glTF-Sample-Viewer don't use f0 here, but reflectance:
171         bd.diffuseColor = mix(baseColor.rgb * (1.0 - bd.f0.xyz), vec3(0.0), vec3(metallic));
172         bd.roughness = material.g;
173     }
174     // NOTE: diffuse color is already premultiplied
175     GetFinalSampledBrdfGeometricCorrection(polygonNormal, bd);
176 
177     return bd;
178 }
179 
CalcBRDFMetallicRoughness(CORE_RELAXEDP vec4 baseColor,CORE_RELAXEDP vec4 material)180 InputBrdfData CalcBRDFMetallicRoughness(CORE_RELAXEDP vec4 baseColor, CORE_RELAXEDP vec4 material)
181 {
182     InputBrdfData bd;
183     {
184         const CORE_RELAXEDP float metallic = clamp(material.b, 0.0, 1.0);
185         bd.f0.xyz = mix(vec3(material.a), baseColor.rgb, metallic); // f0 reflectance
186         bd.f0.w = 1.0;                                              // f90
187         // spec and glTF-Sample-Viewer don't use f0 here, but reflectance:
188         bd.diffuseColor = mix(baseColor.rgb * (1.0 - bd.f0.xyz), vec3(0.0), vec3(metallic));
189         bd.roughness = material.g;
190     }
191     // NOTE: diffuse color is already premultiplied
192     GetFinalSampledBrdfGeometricCorrection(bd);
193 
194     return bd;
195 }
196 
CalcBRDFSpecularGlossiness(CORE_RELAXEDP vec4 baseColor,vec3 polygonNormal,CORE_RELAXEDP vec4 material)197 InputBrdfData CalcBRDFSpecularGlossiness(CORE_RELAXEDP vec4 baseColor, vec3 polygonNormal, CORE_RELAXEDP vec4 material)
198 {
199     InputBrdfData bd;
200     {
201         bd.f0.xyz = material.xyz; // f0 reflectance
202         bd.f0.w = 1.0;            // f90
203         bd.diffuseColor = baseColor.rgb * (1.0 - max(bd.f0.x, max(bd.f0.y, bd.f0.z)));
204         bd.roughness = 1.0 - clamp(material.a, 0.0, 1.0);
205     }
206     // NOTE: diffuse color is already premultiplied
207     GetFinalSampledBrdfGeometricCorrection(polygonNormal, bd);
208 
209     return bd;
210 }
211 
CalcBRDFSpecular(CORE_RELAXEDP vec4 baseColor,vec3 polygonNormal,CORE_RELAXEDP vec4 material,CORE_RELAXEDP vec4 specular)212 InputBrdfData CalcBRDFSpecular(
213     CORE_RELAXEDP vec4 baseColor, vec3 polygonNormal, CORE_RELAXEDP vec4 material, CORE_RELAXEDP vec4 specular)
214 {
215     InputBrdfData bd;
216     {
217         const CORE_RELAXEDP float metallic = clamp(material.b, 0.0, 1.0);
218         // start with metal-rough dielectricSpecularF0:
219         bd.f0.xyz = mix(vec3(material.a), baseColor.rgb, metallic); // f0 reflectance
220 
221         // tint by specular color and multiply by strength:
222         bd.f0.xyz = min(bd.f0.xyz * specular.rgb, vec3(1.0)) * specular.a;
223 
224         // spec: f0, glTF-Sample-Viewer: dielectricSpecularF0. later would be closer to the metal-rough case
225         bd.diffuseColor = mix(baseColor.rgb * (1.0 - max(bd.f0.x, max(bd.f0.y, bd.f0.z))), vec3(0.0), vec3(metallic));
226 
227         // final f0
228         bd.f0.xyz = mix(bd.f0.xyz, baseColor.rgb, metallic);
229         bd.f0.w = mix(specular.a, 1.0, metallic); // f90
230         bd.roughness = material.g;
231     }
232     // NOTE: diffuse color is already premultiplied
233     GetFinalSampledBrdfGeometricCorrection(polygonNormal, bd);
234 
235     return bd;
236 }
237 
CalcTbnMatrix(in vec3 polygonNormal,in vec4 tangentW)238 mat3 CalcTbnMatrix(in vec3 polygonNormal, in vec4 tangentW)
239 {
240     const vec3 tangent = normalize(tangentW.xyz);
241     const vec3 bitangent = cross(polygonNormal, tangent.xyz) * tangentW.w;
242     return mat3(tangent.xyz, bitangent.xyz, polygonNormal);
243 }
244 
CalcFinalNormal(in mat3 tbn,in vec3 normal,in float normalScale)245 vec3 CalcFinalNormal(in mat3 tbn, in vec3 normal, in float normalScale)
246 {
247     vec3 n = normalize((2.0 * normal - 1.0) * vec3(normalScale, normalScale, 1.0f));
248     return normalize(tbn * n);
249 }
250 
GetAnistropicReflectionVector(const vec3 V,const vec3 N,AnisotropicShadingVariables asv)251 vec3 GetAnistropicReflectionVector(const vec3 V, const vec3 N, AnisotropicShadingVariables asv)
252 {
253     // bent towards aniso direction should be re-evaluated with reference (based on filament)
254     const vec3 anisoDir = asv.anisotropy >= 0.0 ? asv.anisotropicB : asv.anisotropicT;
255     const vec3 anisoTangent = cross(anisoDir, V);
256     const vec3 anisoNormal = cross(anisoTangent, anisoDir);
257     // creates low bend factors for smooth surfaces
258     const float bendFactor = abs(asv.anisotropy) * clamp(asv.roughness * 5.0, 0.0, 1.0);
259     const vec3 bentNormal = normalize(mix(N, anisoNormal, bendFactor));
260 
261     return reflect(-V, bentNormal);
262 }
263 
GetShadowMatrix(const uint shadowCamIdx)264 mat4 GetShadowMatrix(const uint shadowCamIdx)
265 {
266     // NOTE: uCameras needs to be visible in the main shader which includes this file
267     return uCameras[shadowCamIdx].shadowViewProj;
268 }
269 
CalculateLight(uint currLightIdx,vec3 materialDiffuseBRDF,vec3 L,float NoL,ShadingData sd,const uint materialFlags)270 vec3 CalculateLight(
271     uint currLightIdx, vec3 materialDiffuseBRDF, vec3 L, float NoL, ShadingData sd, const uint materialFlags)
272 {
273     const vec3 H = normalize(L + sd.V);
274     const float VoH = clamp(dot(sd.V, H), 0.0, 1.0);
275     const float NoH = clamp(dot(sd.N, H), 0.0, 1.0);
276 
277     float extAttenuation = 1.0;
278     vec3 calculatedColor = vec3(0.0);
279     const float D = dGGX(sd.alpha2, NoH);
280     const float G = vGGXWithCombinedDenominator(sd.alpha2, sd.NoV, NoL);
281     const vec3 F = fSchlick(sd.f0, VoH);
282     const vec3 specContrib = F * (D * G);
283 
284     // KHR_materials_specular: "In the diffuse component we have to account for the fact that F is now an RGB value.",
285     // but it already was? const vec3 diffuseContrib = (1.0 - max(F.x, (max(F.y, F.z)))) * materialDiffuseBRDF;
286     const vec3 diffuseContrib = (1.0 - F.xyz) * materialDiffuseBRDF;
287     calculatedColor += (diffuseContrib + specContrib * extAttenuation) * extAttenuation * NoL;
288     calculatedColor *= uLightData.lights[currLightIdx].color.xyz;
289     return calculatedColor;
290 }
291 
CalculateLight(uint currLightIdx,vec3 materialDiffuseBRDF,vec3 L,float NoL,ShadingData sd,ClearcoatShadingVariables ccsv,SheenShadingVariables ssv,const uint materialFlags)292 vec3 CalculateLight(uint currLightIdx, vec3 materialDiffuseBRDF, vec3 L, float NoL, ShadingData sd,
293     ClearcoatShadingVariables ccsv, SheenShadingVariables ssv, const uint materialFlags)
294 {
295     const vec3 H = normalize(L + sd.V);
296     const float VoH = clamp(dot(sd.V, H), 0.0, 1.0);
297     const float NoH = clamp(dot(sd.N, H), 0.0, 1.0);
298 
299     float extAttenuation = 1.0;
300     vec3 calculatedColor = vec3(0.0);
301     if ((materialFlags & CORE_MATERIAL_SHEEN_BIT) == CORE_MATERIAL_SHEEN_BIT) {
302         const float sheenD = dCharlie(ssv.sheenRoughness, NoH);
303         const float sheenV = vAshikhmin(sd.NoV, NoL);
304         const vec3 sheenSpec = ssv.sheenColor * (sheenD * sheenV); // F = 1.0
305 
306         extAttenuation *= (1.0 - (ssv.sheenColorMax * ssv.sheenBRDFApprox));
307         calculatedColor += (sheenSpec * NoL);
308     }
309     if ((materialFlags & CORE_MATERIAL_CLEARCOAT_BIT) == CORE_MATERIAL_CLEARCOAT_BIT) {
310         const float ccNoL = clamp(dot(ccsv.ccNormal, L), CORE3D_PBR_LIGHTING_EPSILON, 1.0);
311         const float ccNoH = clamp(dot(ccsv.ccNormal, H), CORE3D_PBR_LIGHTING_EPSILON, 1.0);
312         const float ccLoH = clamp(dot(L, H), CORE3D_PBR_LIGHTING_EPSILON, 1.0);
313         const float ccNoV = clamp(dot(ccsv.ccNormal, sd.V), CORE3D_PBR_LIGHTING_EPSILON, 1.0);
314         const float ccf0 = 0.04;
315 
316         const float ccD = dGGX(ccsv.ccAlpha2, ccNoH);
317         const float ccG = vKelemen(ccLoH);
318         const float ccF = fSchlickSingle(ccf0, ccNoV) * ccsv.cc; // NOTE: cc in ccF
319         const float ccSpec = ccF * ccD * ccG;
320 
321         extAttenuation *= (1.0 - ccF);
322         calculatedColor += vec3(ccSpec * ccNoL);
323     }
324     const float D = dGGX(sd.alpha2, NoH);
325     const float G = vGGXWithCombinedDenominator(sd.alpha2, sd.NoV, NoL);
326     const vec3 F = fSchlick(sd.f0, VoH);
327     const vec3 specContrib = F * (D * G);
328 
329     // KHR_materials_specular: "In the diffuse component we have to account for the fact that F is now an RGB value.",
330     // but it already was? const vec3 diffuseContrib = (1.0 - max(F.x, (max(F.y, F.z)))) * materialDiffuseBRDF;
331     const vec3 diffuseContrib = (1.0 - F.xyz) * materialDiffuseBRDF;
332     calculatedColor += (diffuseContrib + specContrib * extAttenuation) * extAttenuation * NoL;
333     calculatedColor *= uLightData.lights[currLightIdx].color.xyz;
334     return calculatedColor;
335 }
336 
CalculateLighting(ShadingData sd,const uint materialFlags)337 vec3 CalculateLighting(ShadingData sd, const uint materialFlags)
338 {
339     const vec3 materialDiffuseBRDF = sd.diffuseColor * diffuseCoeff();
340     vec3 color = vec3(0.0);
341     const uint directionalLightCount = uLightData.directionalLightCount;
342     const uint directionalLightBeginIndex = uLightData.directionalLightBeginIndex;
343     const vec4 atlasSizeInvSize = uLightData.atlasSizeInvSize;
344     for (uint lightIdx = 0; lightIdx < directionalLightCount; ++lightIdx) {
345         const uint currLightIdx = directionalLightBeginIndex + lightIdx;
346         const vec3 L = -uLightData.lights[currLightIdx].dir.xyz; // normalization already done in c-code
347         const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
348         // NOTE: could check for NoL > 0.0 and NoV > 0.0
349         CORE_RELAXEDP float shadowCoeff = 1.0;
350         if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
351             const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
352             if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
353                 const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
354                 const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
355                 if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) == CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
356                     shadowCoeff = CalcVsmShadow(
357                         uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
358                 } else {
359                     shadowCoeff = CalcPcfShadow(
360                         uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
361                 }
362             }
363         }
364         color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, materialFlags) * shadowCoeff;
365     }
366 
367     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SPOT_ENABLED_BIT) == CORE_LIGHTING_SPOT_ENABLED_BIT) {
368         const uint spotLightCount = uLightData.spotLightCount;
369         const uint spotLightLightBeginIndex = uLightData.spotLightBeginIndex;
370         for (uint spotIdx = 0; spotIdx < spotLightCount; ++spotIdx) {
371             const uint currLightIdx = spotLightLightBeginIndex + spotIdx;
372 
373             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
374             const float dist = length(pointToLight);
375             const vec3 L = pointToLight / dist;
376             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
377             // NOTE: could check for NoL > 0.0 and NoV > 0.0
378             CORE_RELAXEDP float shadowCoeff = 1.0;
379             if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
380                 const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
381                 if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
382                     const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
383                     const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
384                     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) ==
385                         CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
386                         shadowCoeff = CalcVsmShadow(
387                             uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
388                     } else {
389                         shadowCoeff = CalcPcfShadow(
390                             uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
391                     }
392                 }
393             }
394 
395             const float lightAngleScale = uLightData.lights[currLightIdx].spotLightParams.x;
396             const float lightAngleOffset = uLightData.lights[currLightIdx].spotLightParams.y;
397             // See: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
398             const float cd = dot(uLightData.lights[currLightIdx].dir.xyz, -L);
399             const float angularAttenuation = clamp(cd * lightAngleScale + lightAngleOffset, 0.0, 1.0);
400 
401             const float range = uLightData.lights[currLightIdx].dir.w;
402             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
403             color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, materialFlags) *
404                      (angularAttenuation * angularAttenuation * attenuation) * shadowCoeff;
405         }
406     }
407 
408     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_POINT_ENABLED_BIT) == CORE_LIGHTING_POINT_ENABLED_BIT) {
409         const uint pointLightCount = uLightData.pointLightCount;
410         const uint pointLightBeginIndex = uLightData.pointLightBeginIndex;
411         for (uint pointIdx = 0; pointIdx < pointLightCount; ++pointIdx) {
412             const uint currLightIdx = pointLightBeginIndex + pointIdx;
413 
414             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
415             const float dist = length(pointToLight);
416             const vec3 L = pointToLight / dist;
417             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
418             const float range = uLightData.lights[currLightIdx].dir.w;
419             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
420             // NOTE: could check for NoL > 0.0 and NoV > 0.0
421             color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, materialFlags) * attenuation;
422         }
423     }
424 
425     return color;
426 }
427 
CalculateLighting(ShadingData sd,ClearcoatShadingVariables ccsv,SheenShadingVariables ssv,const uint materialFlags)428 vec3 CalculateLighting(
429     ShadingData sd, ClearcoatShadingVariables ccsv, SheenShadingVariables ssv, const uint materialFlags)
430 {
431     const vec3 materialDiffuseBRDF = sd.diffuseColor * diffuseCoeff();
432     vec3 color = vec3(0.0);
433     const uint directionalLightCount = uLightData.directionalLightCount;
434     const uint directionalLightBeginIndex = uLightData.directionalLightBeginIndex;
435     const vec4 atlasSizeInvSize = uLightData.atlasSizeInvSize;
436     for (uint lightIdx = 0; lightIdx < directionalLightCount; ++lightIdx) {
437         const uint currLightIdx = directionalLightBeginIndex + lightIdx;
438         const vec3 L = -uLightData.lights[currLightIdx].dir.xyz; // normalization already done in c-code
439         const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
440         // NOTE: could check for NoL > 0.0 and NoV > 0.0
441         CORE_RELAXEDP float shadowCoeff = 1.0;
442         if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
443             const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
444             if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
445                 const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
446                 const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
447                 if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) == CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
448                     shadowCoeff = CalcVsmShadow(
449                         uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
450                 } else {
451                     shadowCoeff = CalcPcfShadow(
452                         uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
453                 }
454             }
455         }
456         color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, ccsv, ssv, materialFlags) * shadowCoeff;
457     }
458 
459     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SPOT_ENABLED_BIT) == CORE_LIGHTING_SPOT_ENABLED_BIT) {
460         const uint spotLightCount = uLightData.spotLightCount;
461         const uint spotLightLightBeginIndex = uLightData.spotLightBeginIndex;
462         for (uint spotIdx = 0; spotIdx < spotLightCount; ++spotIdx) {
463             const uint currLightIdx = spotLightLightBeginIndex + spotIdx;
464 
465             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
466             const float dist = length(pointToLight);
467             const vec3 L = pointToLight / dist;
468             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
469             // NOTE: could check for NoL > 0.0 and NoV > 0.0
470             CORE_RELAXEDP float shadowCoeff = 1.0;
471             if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
472                 const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
473                 if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
474                     const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
475                     const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
476                     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) ==
477                         CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
478                         shadowCoeff = CalcVsmShadow(
479                             uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
480                     } else {
481                         shadowCoeff = CalcPcfShadow(
482                             uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
483                     }
484                 }
485             }
486 
487             const float lightAngleScale = uLightData.lights[currLightIdx].spotLightParams.x;
488             const float lightAngleOffset = uLightData.lights[currLightIdx].spotLightParams.y;
489             // See: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
490             const float cd = dot(uLightData.lights[currLightIdx].dir.xyz, -L);
491             const float angularAttenuation = clamp(cd * lightAngleScale + lightAngleOffset, 0.0, 1.0);
492 
493             const float range = uLightData.lights[currLightIdx].dir.w;
494             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
495             color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, ccsv, ssv, materialFlags) *
496                      (angularAttenuation * angularAttenuation * attenuation) * shadowCoeff;
497         }
498     }
499     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_POINT_ENABLED_BIT) == CORE_LIGHTING_POINT_ENABLED_BIT) {
500         const uint pointLightCount = uLightData.pointLightCount;
501         const uint pointLightBeginIndex = uLightData.pointLightBeginIndex;
502         for (uint pointIdx = 0; pointIdx < pointLightCount; ++pointIdx) {
503             const uint currLightIdx = pointLightBeginIndex + pointIdx;
504 
505             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
506             const float dist = length(pointToLight);
507             const vec3 L = pointToLight / dist;
508             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
509             const float range = uLightData.lights[currLightIdx].dir.w;
510             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
511             // NOTE: could check for NoL > 0.0 and NoV > 0.0
512             color +=
513                 CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, ccsv, ssv, materialFlags) * attenuation;
514         }
515     }
516 
517     return color;
518 }
519 
CalculateLight(uint currLightIdx,vec3 materialDiffuseBRDF,vec3 L,float NoL,ShadingData sd,AnisotropicShadingVariables asv,ClearcoatShadingVariables ccsv,SheenShadingVariables ssv,const uint materialFlags)520 vec3 CalculateLight(uint currLightIdx, vec3 materialDiffuseBRDF, vec3 L, float NoL, ShadingData sd,
521     AnisotropicShadingVariables asv, ClearcoatShadingVariables ccsv, SheenShadingVariables ssv,
522     const uint materialFlags)
523 {
524     const vec3 H = normalize(L + sd.V);
525     const float VoH = dot(sd.V, H);
526     const float NoH = dot(sd.N, H);
527 
528     vec3 calculatedColor = vec3(0.0);
529     float extAttenuation = 1.0;
530     if ((materialFlags & CORE_MATERIAL_SHEEN_BIT) == CORE_MATERIAL_SHEEN_BIT) {
531         const float sheenD = dCharlie(ssv.sheenRoughness, NoH);
532         const float sheenV = vAshikhmin(sd.NoV, NoL);
533         const vec3 sheenSpec = ssv.sheenColor * (sheenD * sheenV); // F = 1.0
534 
535         extAttenuation *= (1.0 - (ssv.sheenColorMax * ssv.sheenBRDFApprox));
536         calculatedColor += (sheenSpec * NoL);
537     }
538     if ((materialFlags & CORE_MATERIAL_CLEARCOAT_BIT) == CORE_MATERIAL_CLEARCOAT_BIT) {
539         const float ccNoL = clamp(dot(ccsv.ccNormal, L), CORE3D_PBR_LIGHTING_EPSILON, 1.0);
540         const float ccNoH = clamp(dot(ccsv.ccNormal, H), 0.0, 1.0);
541         const float ccf0 = 0.04;
542 
543         const float ccD = dGGX(ccsv.ccAlpha2, ccNoH);
544         const float ccG = vKelemen(ccNoH);
545         const float ccF = fSchlickSingle(ccf0, VoH) * ccsv.cc;
546         const float ccSpec = ccF * ccD * ccG;
547 
548         extAttenuation *= (1.0 - ccF);
549         calculatedColor += vec3(ccSpec * ccNoL);
550     }
551 
552     const float ToL = dot(asv.anisotropicT, L);
553     const float ToH = dot(asv.anisotropicT, H);
554     const float BoL = dot(asv.anisotropicB, L);
555     const float BoH = dot(asv.anisotropicB, H);
556     const float at = max(asv.alpha * (1.0 + asv.anisotropy), CORE3D_PBR_LIGHTING_EPSILON);
557     const float ab = max(asv.alpha * (1.0 - asv.anisotropy), CORE3D_PBR_LIGHTING_EPSILON);
558 
559     const float D = dGGXAnisotropic(at, ab, NoH, ToH, BoH, asv.anisotropy);
560     const float G = vGGXAnisotropic(at, ab, NoL, sd.NoV, ToL, asv.ToV, BoL, asv.BoV, asv.anisotropy);
561     const vec3 F = fSchlick(sd.f0, VoH);
562     const vec3 specContrib = F * (D * G);
563 
564     // KHR_materials_specular: "In the diffuse component we have to account for the fact that F is now an RGB value.",
565     // but it already was? const vec3 diffuseContrib = (1.0 - max(F.x, (max(F.y, F.z)))) * materialDiffuseBRDF;
566     const vec3 diffuseContrib = (1.0 - F.xyz) * materialDiffuseBRDF;
567     calculatedColor += (diffuseContrib + specContrib * extAttenuation) * extAttenuation * NoL;
568     calculatedColor *= uLightData.lights[currLightIdx].color.xyz;
569     return calculatedColor;
570 }
571 
CalculateLighting(ShadingData sd,AnisotropicShadingVariables asv,ClearcoatShadingVariables ccsv,SheenShadingVariables ssv,const uint materialFlags)572 vec3 CalculateLighting(ShadingData sd, AnisotropicShadingVariables asv, ClearcoatShadingVariables ccsv,
573     SheenShadingVariables ssv, const uint materialFlags)
574 {
575     const vec3 materialDiffuseBRDF = sd.diffuseColor * diffuseCoeff();
576     vec3 color = vec3(0.0);
577     const uint directionalLightCount = uLightData.directionalLightCount;
578     const uint directionalLightBeginIndex = uLightData.directionalLightBeginIndex;
579     const vec4 atlasSizeInvSize = uLightData.atlasSizeInvSize;
580     for (uint lightIdx = 0; lightIdx < directionalLightCount; ++lightIdx) {
581         const uint currLightIdx = directionalLightBeginIndex + lightIdx;
582         const vec3 L = -uLightData.lights[currLightIdx].dir.xyz; // normalization already done in c-code
583         const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
584         // NOTE: could check for NoL > 0.0 and NoV > 0.0
585         CORE_RELAXEDP float shadowCoeff = 1.0;
586         if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
587             const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
588             if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
589                 const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
590                 const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
591                 if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) == CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
592                     shadowCoeff = CalcVsmShadow(
593                         uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
594                 } else {
595                     shadowCoeff = CalcPcfShadow(
596                         uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
597                 }
598             }
599         }
600         color +=
601             CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, asv, ccsv, ssv, materialFlags) * shadowCoeff;
602     }
603 
604     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SPOT_ENABLED_BIT) == CORE_LIGHTING_SPOT_ENABLED_BIT) {
605         const uint spotLightCount = uLightData.spotLightCount;
606         const uint spotLightLightBeginIndex = uLightData.spotLightBeginIndex;
607         for (uint spotIdx = 0; spotIdx < spotLightCount; ++spotIdx) {
608             const uint currLightIdx = spotLightLightBeginIndex + spotIdx;
609 
610             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
611             const float dist = length(pointToLight);
612             const vec3 L = pointToLight / dist;
613             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
614             // NOTE: could check for NoL > 0.0 and NoV > 0.0
615             CORE_RELAXEDP float shadowCoeff = 1.0;
616             if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
617                 const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
618                 if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
619                     const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
620                     const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
621                     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) ==
622                         CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
623                         shadowCoeff = CalcVsmShadow(
624                             uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
625                     } else {
626                         shadowCoeff = CalcPcfShadow(
627                             uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
628                     }
629                 }
630             }
631 
632             const float lightAngleScale = uLightData.lights[currLightIdx].spotLightParams.x;
633             const float lightAngleOffset = uLightData.lights[currLightIdx].spotLightParams.y;
634             // See: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
635             const float cd = dot(uLightData.lights[currLightIdx].dir.xyz, -L);
636             const float angularAttenuation = clamp(cd * lightAngleScale + lightAngleOffset, 0.0, 1.0);
637 
638             const float range = uLightData.lights[currLightIdx].dir.w;
639             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
640             color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, asv, ccsv, ssv, materialFlags) *
641                      (angularAttenuation * angularAttenuation * attenuation) * shadowCoeff;
642         }
643     }
644     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_POINT_ENABLED_BIT) == CORE_LIGHTING_POINT_ENABLED_BIT) {
645         const uint pointLightCount = uLightData.pointLightCount;
646         const uint pointLightBeginIndex = uLightData.pointLightBeginIndex;
647         for (uint pointIdx = 0; pointIdx < pointLightCount; ++pointIdx) {
648             const uint currLightIdx = pointLightBeginIndex + pointIdx;
649 
650             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
651             const float dist = length(pointToLight);
652             const vec3 L = pointToLight / dist;
653             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
654             const float range = uLightData.lights[currLightIdx].dir.w;
655             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
656             // NOTE: could check for NoL > 0.0 and NoV > 0.0
657             color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, asv, ccsv, ssv, materialFlags) *
658                      attenuation;
659         }
660     }
661 
662     return color;
663 }
664 
CalculateLight(uint currLightIdx,vec3 materialDiffuseBRDF,vec3 L,float NoL,ShadingData sd,SubsurfaceScatterShadingVariables ssssv,const uint materialFlags)665 vec3 CalculateLight(uint currLightIdx, vec3 materialDiffuseBRDF, vec3 L, float NoL, ShadingData sd,
666     SubsurfaceScatterShadingVariables ssssv, const uint materialFlags)
667 {
668     const vec3 H = normalize(L + sd.V);
669     const float VoH = clamp(dot(sd.V, H), 0.0, 1.0);
670     const float NoH = clamp(dot(sd.N, H), 0.0, 1.0);
671 
672     float extAttenuation = 1.0;
673     vec3 calculatedColor = vec3(0.0);
674     const float D = dGGX(sd.alpha2, NoH);
675     const float G = vGGXWithCombinedDenominator(sd.alpha2, sd.NoV, NoL);
676     const vec3 F = fSchlick(sd.f0, VoH);
677     const vec3 specContrib = F * (D * G);
678 
679     const vec3 diffuseContrib = (1.0 - F.xyz) * materialDiffuseBRDF;
680     calculatedColor += (diffuseContrib + specContrib * extAttenuation) * extAttenuation * NoL;
681 
682     // subsurface scattering
683     // Use a spherical gaussian approximation of pow() for forwardScattering
684     // We could include distortion by adding shading_normal * distortion to light.l
685     float scatterVoH = clamp(dot(sd.V, -L), 0.0, 1.0);
686     // map distance to gaussian sharpness, the lower the distance, the higher the sharpness
687     float sharpness = 10000.0 - 10000.0 * ssssv.scatterDistance;
688     float forwardScatter = exp2(scatterVoH * sharpness - sharpness);
689     float backScatter = clamp(NoL * ssssv.thickness + (1.0 - ssssv.thickness), 0.0, 1.0) * 0.5;
690     float subsurface = mix(backScatter, 1.0, forwardScatter) * (1.0 - ssssv.thickness);
691     calculatedColor += ssssv.scatterColor * (subsurface * dLambert());
692 
693     // TODO: apply attenuation to the transmitted light, i.e. uLightData.lights[currLightIdx].attenuation
694     return (calculatedColor * uLightData.lights[currLightIdx].color.rgb);
695 }
696 
CalculateLighting(ShadingData sd,SubsurfaceScatterShadingVariables sssv,const uint materialFlags)697 vec3 CalculateLighting(ShadingData sd, SubsurfaceScatterShadingVariables sssv, const uint materialFlags)
698 {
699     const vec3 materialDiffuseBRDF = sd.diffuseColor * diffuseCoeff();
700     vec3 color = vec3(0.0);
701     const uint directionalLightCount = uLightData.directionalLightCount;
702     const uint directionalLightBeginIndex = uLightData.directionalLightBeginIndex;
703     const vec4 atlasSizeInvSize = uLightData.atlasSizeInvSize;
704     for (uint lightIdx = 0; lightIdx < directionalLightCount; ++lightIdx) {
705         const uint currLightIdx = directionalLightBeginIndex + lightIdx;
706         const vec3 L = -uLightData.lights[currLightIdx].dir.xyz; // normalization already done in c-code
707         const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
708         // NOTE: could check for NoL > 0.0 and NoV > 0.0
709         CORE_RELAXEDP float shadowCoeff = 1.0;
710         if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
711             const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
712             if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
713                 const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
714                 const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
715                 if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) == CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
716                     shadowCoeff = CalcVsmShadow(
717                         uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
718                 } else {
719                     shadowCoeff = CalcPcfShadow(
720                         uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
721                 }
722             }
723         }
724         color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, sssv, materialFlags) * shadowCoeff;
725     }
726 
727     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SPOT_ENABLED_BIT) == CORE_LIGHTING_SPOT_ENABLED_BIT) {
728         const uint spotLightCount = uLightData.spotLightCount;
729         const uint spotLightLightBeginIndex = uLightData.spotLightBeginIndex;
730         for (uint spotIdx = 0; spotIdx < spotLightCount; ++spotIdx) {
731             const uint currLightIdx = spotLightLightBeginIndex + spotIdx;
732 
733             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
734             const float dist = length(pointToLight);
735             const vec3 L = pointToLight / dist;
736             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
737             // NOTE: could check for NoL > 0.0 and NoV > 0.0
738             CORE_RELAXEDP float shadowCoeff = 1.0;
739             if ((materialFlags & CORE_MATERIAL_SHADOW_RECEIVER_BIT) == CORE_MATERIAL_SHADOW_RECEIVER_BIT) {
740                 const uvec4 lightFlags = uLightData.lights[currLightIdx].flags;
741                 if ((lightFlags.x & CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) == CORE_LIGHT_USAGE_SHADOW_LIGHT_BIT) {
742                     const vec4 shadowCoord = GetShadowMatrix(lightFlags.y) * vec4(sd.pos.xyz, 1.0);
743                     const vec4 shadowFactors = uLightData.lights[currLightIdx].shadowFactors;
744                     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) ==
745                         CORE_LIGHTING_SHADOW_TYPE_VSM_BIT) {
746                         shadowCoeff = CalcVsmShadow(
747                             uSampColorShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
748                     } else {
749                         shadowCoeff = CalcPcfShadow(
750                             uSampDepthShadow, shadowCoord, NoL, shadowFactors, atlasSizeInvSize, lightFlags.zw);
751                     }
752                 }
753             }
754 
755             const float lightAngleScale = uLightData.lights[currLightIdx].spotLightParams.x;
756             const float lightAngleOffset = uLightData.lights[currLightIdx].spotLightParams.y;
757             // See: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
758             const float cd = dot(uLightData.lights[currLightIdx].dir.xyz, -L);
759             const float angularAttenuation = clamp(cd * lightAngleScale + lightAngleOffset, 0.0, 1.0);
760 
761             const float range = uLightData.lights[currLightIdx].dir.w;
762             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
763             color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, sssv, materialFlags) *
764                      (angularAttenuation * angularAttenuation * attenuation) * shadowCoeff;
765         }
766     }
767     if ((CORE_LIGHTING_FLAGS & CORE_LIGHTING_POINT_ENABLED_BIT) == CORE_LIGHTING_POINT_ENABLED_BIT) {
768         const uint pointLightCount = uLightData.pointLightCount;
769         const uint pointLightBeginIndex = uLightData.pointLightBeginIndex;
770         for (uint pointIdx = 0; pointIdx < pointLightCount; ++pointIdx) {
771             const uint currLightIdx = pointLightBeginIndex + pointIdx;
772 
773             const vec3 pointToLight = uLightData.lights[currLightIdx].pos.xyz - sd.pos.xyz;
774             const float dist = length(pointToLight);
775             const vec3 L = pointToLight / dist;
776             const float NoL = clamp(dot(sd.N, L), 0.0, 1.0);
777             const float range = uLightData.lights[currLightIdx].dir.w;
778             const float attenuation = max(min(1.0 - pow(dist / range, 4.0), 1.0), 0.0) / (dist * dist);
779             // NOTE: could check for NoL > 0.0 and NoV > 0.0
780             color += CalculateLight(currLightIdx, materialDiffuseBRDF, L, NoL, sd, sssv, materialFlags) * attenuation;
781         }
782     }
783 
784     return color;
785 }
786 
787 #endif // SHADERS__COMMON__3D_DM_LIGHTING_COMMON_H
788