-
Notifications
You must be signed in to change notification settings - Fork 869
Expand file tree
/
Copy pathLightLoop.hlsl
More file actions
722 lines (621 loc) · 36.7 KB
/
LightLoop.hlsl
File metadata and controls
722 lines (621 loc) · 36.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Macros.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/BuiltinUtilities.hlsl"
#ifndef SCALARIZE_LIGHT_LOOP
// We perform scalarization only for forward rendering as for deferred loads will already be scalar since tiles will match waves and therefore all threads will read from the same tile.
// More info on scalarization: https://flashypixels.wordpress.com/2018/11/10/intro-to-gpu-scalarization-part-2-scalarize-all-the-lights/ .
// Note that it is currently disabled on gamecore platforms for issues with wave intrinsics and the new compiler, it will be soon investigated, but we disable it in the meantime.
#define SCALARIZE_LIGHT_LOOP (defined(PLATFORM_SUPPORTS_WAVE_INTRINSICS) && !defined(LIGHTLOOP_DISABLE_TILE_AND_CLUSTER) && SHADERPASS == SHADERPASS_FORWARD)
#endif
//-----------------------------------------------------------------------------
// LightLoop
// ----------------------------------------------------------------------------
void ApplyDebugToLighting(LightLoopContext context, inout BuiltinData builtinData, inout AggregateLighting aggregateLighting)
{
#ifdef DEBUG_DISPLAY
if (_DebugLightingMode >= DEBUGLIGHTINGMODE_DIFFUSE_LIGHTING && _DebugLightingMode <= DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING)
{
if (_DebugLightingMode == DEBUGLIGHTINGMODE_SPECULAR_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_DIRECT_SPECULAR_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_INDIRECT_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_REFLECTION_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_REFRACTION_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING)
{
aggregateLighting.direct.diffuse = real3(0.0, 0.0, 0.0);
}
if (_DebugLightingMode == DEBUGLIGHTINGMODE_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_DIRECT_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_INDIRECT_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_REFLECTION_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_REFRACTION_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING)
{
aggregateLighting.direct.specular = real3(0.0, 0.0, 0.0);
}
if (_DebugLightingMode == DEBUGLIGHTINGMODE_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_DIRECT_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_DIRECT_SPECULAR_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_INDIRECT_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_REFRACTION_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING)
{
aggregateLighting.indirect.specularReflected = real3(0.0, 0.0, 0.0);
}
// Note: specular transmission is the refraction and as it reflect lighting behind the object it
// must be displayed for both diffuse and specular mode, except if we ask for direct lighting only
if (_DebugLightingMode != DEBUGLIGHTINGMODE_REFRACTION_LIGHTING)
{
aggregateLighting.indirect.specularTransmitted = real3(0.0, 0.0, 0.0);
}
if (_DebugLightingMode == DEBUGLIGHTINGMODE_SPECULAR_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_DIRECT_DIFFUSE_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_DIRECT_SPECULAR_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_REFLECTION_LIGHTING ||
_DebugLightingMode == DEBUGLIGHTINGMODE_REFRACTION_LIGHTING
#if (SHADERPASS != SHADERPASS_DEFERRED_LIGHTING)
|| _DebugLightingMode == DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING // With deferred, Emissive is store in builtinData.bakeDiffuseLighting (See Lit.hlsl EncodeToGbuffer)
#endif
)
{
builtinData.bakeDiffuseLighting = real3(0.0, 0.0, 0.0);
}
if (_DebugLightingMode != DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING)
{
builtinData.emissiveColor = real3(0.0, 0.0, 0.0);
}
}
if (_DebugLightingMode == DEBUGLIGHTINGMODE_PROBE_VOLUME_SAMPLED_SUBDIVISION)
{
aggregateLighting.direct.diffuse = real3(0.0, 0.0, 0.0);
aggregateLighting.direct.specular = real3(0.0, 0.0, 0.0);
aggregateLighting.indirect.specularReflected = real3(0.0, 0.0, 0.0);
aggregateLighting.indirect.specularTransmitted = real3(0.0, 0.0, 0.0);
builtinData.emissiveColor = real3(0.0, 0.0, 0.0);
}
#endif
}
bool UseScreenSpaceShadow(DirectionalLightData light, float3 normalWS)
{
// Two different options are possible here
// - We have a ray trace shadow in which case we have no valid signal for a transmission and we need to fallback on the rasterized shadow
// - We have a screen space shadow and it already contains the transmission shadow and we can use it straight away
bool visibleLight = dot(normalWS, -light.forward) > 0.0;
bool validScreenSpaceShadow = (light.screenSpaceShadowIndex & SCREEN_SPACE_SHADOW_INDEX_MASK) != INVALID_SCREEN_SPACE_SHADOW;
bool rayTracedShadow = (light.screenSpaceShadowIndex & RAY_TRACED_SCREEN_SPACE_SHADOW_FLAG) != 0;
return (validScreenSpaceShadow && ((rayTracedShadow && visibleLight) || !rayTracedShadow));
}
void ApplyDebug(LightLoopContext context, PositionInputs posInput, BSDFData bsdfData, inout LightLoopOutput lightLoopOutput)
{
#ifdef DEBUG_DISPLAY
if (_DebugLightingMode == DEBUGLIGHTINGMODE_LUX_METER)
{
lightLoopOutput.specularLighting = float3(0.0, 0.0, 0.0); // Disable specular lighting
// Take the luminance
lightLoopOutput.diffuseLighting = Luminance(lightLoopOutput.diffuseLighting).xxx;
}
else if (_DebugLightingMode == DEBUGLIGHTINGMODE_VISUALIZE_CASCADE)
{
lightLoopOutput.specularLighting = float3(0.0, 0.0, 0.0);
const float3 s_CascadeColors[] = {
kDebugColorShadowCascade0.rgb,
kDebugColorShadowCascade1.rgb,
kDebugColorShadowCascade2.rgb,
kDebugColorShadowCascade3.rgb,
float3(1.0, 1.0, 1.0)
};
lightLoopOutput.diffuseLighting = Luminance(lightLoopOutput.diffuseLighting);
if (_DirectionalShadowIndex >= 0)
{
real alpha;
int cascadeCount;
int shadowSplitIndex = EvalShadow_GetSplitIndex(context.shadowContext, _DirectionalShadowIndex, posInput.positionWS, alpha, cascadeCount);
if (shadowSplitIndex >= 0)
{
SHADOW_TYPE shadow = 1.0;
if (_DirectionalShadowIndex >= 0)
{
DirectionalLightData light = _DirectionalLightDatas[_DirectionalShadowIndex];
#if defined(SCREEN_SPACE_SHADOWS_ON) && !defined(_SURFACE_TYPE_TRANSPARENT)
if (UseScreenSpaceShadow(light, bsdfData.normalWS))
{
shadow = GetScreenSpaceColorShadow(posInput, light.screenSpaceShadowIndex).SHADOW_TYPE_SWIZZLE;
}
else
#endif
{
float3 L = -light.forward;
shadow = GetDirectionalShadowAttenuation(context.shadowContext,
posInput.positionSS, posInput.positionWS, GetNormalForShadowBias(bsdfData),
light.shadowIndex, L);
}
}
float3 cascadeShadowColor = lerp(s_CascadeColors[shadowSplitIndex], s_CascadeColors[shadowSplitIndex + 1], alpha);
// We can't mix with the lighting as it can be HDR and it is hard to find a good lerp operation for this case that is still compliant with
// exposure. So disable exposure instead and replace color.
lightLoopOutput.diffuseLighting = cascadeShadowColor * Luminance(lightLoopOutput.diffuseLighting) * shadow;
}
}
}
else if (_DebugLightingMode == DEBUGLIGHTINGMODE_MATCAP_VIEW)
{
lightLoopOutput.specularLighting = float3(0.0, 0.0, 0.0);
float3 normalVS = mul((float3x3)UNITY_MATRIX_V, bsdfData.normalWS).xyz;
float3 V = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
float3 R = reflect(V, bsdfData.normalWS);
float2 UV = saturate(normalVS.xy * 0.5f + 0.5f);
float4 defaultColor = GetDiffuseOrDefaultColor(bsdfData, 1.0);
if (defaultColor.a == 1.0)
{
UV = saturate(R.xy * 0.5f + 0.5f);
}
lightLoopOutput.diffuseLighting = SAMPLE_TEXTURE2D_LOD(_DebugMatCapTexture, s_linear_repeat_sampler, UV, 0).rgb * (_MatcapMixAlbedo > 0 ? defaultColor.rgb * _MatcapViewScale : 1.0f);
#ifdef OUTPUT_SPLIT_LIGHTING // Work as matcap view is only call in forward, OUTPUT_SPLIT_LIGHTING isn't define in deferred.compute
if (_EnableSubsurfaceScattering != 0 && ShouldOutputSplitLighting(bsdfData))
{
lightLoopOutput.specularLighting = lightLoopOutput.diffuseLighting;
}
#endif
}
#endif
}
void LightLoop( float3 V, PositionInputs posInput, PreLightData preLightData, BSDFData bsdfData, BuiltinData builtinData, uint featureFlags,
out LightLoopOutput lightLoopOutput)
{
// Init LightLoop output structure
ZERO_INITIALIZE(LightLoopOutput, lightLoopOutput);
LightLoopContext context;
context.shadowContext = InitShadowContext();
context.shadowValue = 1;
context.sampleReflection = 0;
context.splineVisibility = -1;
#ifdef APPLY_FOG_ON_SKY_REFLECTIONS
context.positionWS = posInput.positionWS;
#endif
// With XR single-pass and camera-relative: offset position to do lighting computations from the combined center view (original camera matrix).
// This is required because there is only one list of lights generated on the CPU. Shadows are also generated once and shared between the instanced views.
ApplyCameraRelativeXR(posInput.positionWS);
// Initialize the contactShadow and contactShadowFade fields
InitContactShadow(posInput, context);
// First of all we compute the shadow value of the directional light to reduce the VGPR pressure
if (featureFlags & LIGHTFEATUREFLAGS_DIRECTIONAL)
{
// Evaluate sun shadows.
if (_DirectionalShadowIndex >= 0)
{
DirectionalLightData light = _DirectionalLightDatas[_DirectionalShadowIndex];
#if defined(SCREEN_SPACE_SHADOWS_ON) && !defined(_SURFACE_TYPE_TRANSPARENT)
if (UseScreenSpaceShadow(light, bsdfData.normalWS))
{
context.shadowValue = GetScreenSpaceColorShadow(posInput, light.screenSpaceShadowIndex).SHADOW_TYPE_SWIZZLE;
}
else
#endif
{
// TODO: this will cause us to load from the normal buffer first. Does this cause a performance problem?
float3 L = -light.forward;
// Is it worth sampling the shadow map?
if ((light.lightDimmer > 0) && (light.shadowDimmer > 0) && // Note: Volumetric can have different dimmer, thus why we test it here
IsNonZeroBSDF(V, L, preLightData, bsdfData) &&
!ShouldEvaluateThickObjectTransmission(V, L, preLightData, bsdfData, light.shadowIndex))
{
float3 positionWS = posInput.positionWS;
#ifdef LIGHT_EVALUATION_SPLINE_SHADOW_BIAS
positionWS += L * GetSplineOffsetForShadowBias(bsdfData);
#endif
context.shadowValue = GetDirectionalShadowAttenuation(context.shadowContext,
posInput.positionSS, positionWS, GetNormalForShadowBias(bsdfData),
light.shadowIndex, L);
#ifdef LIGHT_EVALUATION_SPLINE_SHADOW_VISIBILITY_SAMPLE
// Tap the shadow a second time for strand visibility term.
context.splineVisibility = GetDirectionalShadowAttenuation(context.shadowContext,
posInput.positionSS, posInput.positionWS, GetNormalForShadowBias(bsdfData),
light.shadowIndex, L);
#endif
}
}
}
}
// This struct is define in the material. the Lightloop must not access it
// PostEvaluateBSDF call at the end will convert Lighting to diffuse and specular lighting
AggregateLighting aggregateLighting;
ZERO_INITIALIZE(AggregateLighting, aggregateLighting); // LightLoop is in charge of initializing the struct
if (featureFlags & LIGHTFEATUREFLAGS_PUNCTUAL)
{
uint lightCount, lightStart;
#ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER
GetCountAndStart(posInput, LIGHTCATEGORY_PUNCTUAL, lightStart, lightCount);
#else // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER
lightCount = _PunctualLightCount;
lightStart = 0;
#endif
bool fastPath = false;
#if SCALARIZE_LIGHT_LOOP
uint lightStartLane0;
fastPath = IsFastPath(lightStart, lightStartLane0);
if (fastPath)
{
lightStart = lightStartLane0;
}
#endif
// Scalarized loop. All lights that are in a tile/cluster touched by any pixel in the wave are loaded (scalar load), only the one relevant to current thread/pixel are processed.
// For clarity, the following code will follow the convention: variables starting with s_ are meant to be wave uniform (meant for scalar register),
// v_ are variables that might have different value for each thread in the wave (meant for vector registers).
// This will perform more loads than it is supposed to, however, the benefits should offset the downside, especially given that light data accessed should be largely coherent.
// Note that the above is valid only if wave intriniscs are supported.
uint v_lightListOffset = 0;
uint v_lightIdx = lightStart;
#if NEED_TO_CHECK_HELPER_LANE
// On some platform helper lanes don't behave as we'd expect, therefore we prevent them from entering the loop altogether.
// IMPORTANT! This has implications if ddx/ddy is used on results derived from lighting, however given Lightloop is called in compute we should be
// sure it will not happen.
bool isHelperLane = WaveIsHelperLane();
while (!isHelperLane && v_lightListOffset < lightCount)
#else
while (v_lightListOffset < lightCount)
#endif
{
v_lightIdx = FetchIndex(lightStart, v_lightListOffset);
#if SCALARIZE_LIGHT_LOOP
uint s_lightIdx = ScalarizeElementIndex(v_lightIdx, fastPath);
#else
uint s_lightIdx = v_lightIdx;
#endif
if (s_lightIdx == -1)
break;
LightData s_lightData = FetchLight(s_lightIdx);
// If current scalar and vector light index match, we process the light. The v_lightListOffset for current thread is increased.
// Note that the following should really be ==, however, since helper lanes are not considered by WaveActiveMin, such helper lanes could
// end up with a unique v_lightIdx value that is smaller than s_lightIdx hence being stuck in a loop. All the active lanes will not have this problem.
if (s_lightIdx >= v_lightIdx)
{
v_lightListOffset++;
if (IsMatchingLightLayer(s_lightData.lightLayers, builtinData.renderingLayers))
{
DirectLighting lighting = EvaluateBSDF_Punctual(context, V, posInput, preLightData, s_lightData, bsdfData, builtinData);
AccumulateDirectLighting(lighting, aggregateLighting);
}
}
}
}
// Define macro for a better understanding of the loop
// TODO: this code is now much harder to understand...
#define EVALUATE_BSDF_ENV_SKY(envLightData, TYPE, type) \
IndirectLighting lighting = EvaluateBSDF_Env(context, V, posInput, preLightData, envLightData, bsdfData, envLightData.influenceShapeType, MERGE_NAME(GPUIMAGEBASEDLIGHTINGTYPE_, TYPE), MERGE_NAME(type, HierarchyWeight)); \
AccumulateIndirectLighting(lighting, aggregateLighting);
// Environment cubemap test lightlayers, sky don't test it
#define EVALUATE_BSDF_ENV(envLightData, TYPE, type) if (IsMatchingLightLayer(envLightData.lightLayers, builtinData.renderingLayers)) { EVALUATE_BSDF_ENV_SKY(envLightData, TYPE, type) }
// First loop iteration
if (featureFlags & (LIGHTFEATUREFLAGS_ENV | LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_SSREFRACTION | LIGHTFEATUREFLAGS_SSREFLECTION))
{
float reflectionHierarchyWeight = 0.0; // Max: 1.0
float refractionHierarchyWeight = _EnableSSRefraction ? 0.0 : 1.0; // Max: 1.0
uint envLightStart, envLightCount;
// Fetch first env light to provide the scene proxy for screen space computation
#ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER
GetCountAndStart(posInput, LIGHTCATEGORY_ENV, envLightStart, envLightCount);
#else // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER
envLightCount = _EnvLightCount;
envLightStart = 0;
#endif
bool fastPath = false;
#if SCALARIZE_LIGHT_LOOP
uint envStartFirstLane;
fastPath = IsFastPath(envLightStart, envStartFirstLane);
#endif
// Reflection / Refraction hierarchy is
// 1. Screen Space Refraction / Reflection
// 2. Environment Reflection / Refraction
// 3. Sky Reflection / Refraction
// Apply SSR.
#if (defined(_SURFACE_TYPE_TRANSPARENT) && !defined(_DISABLE_SSR_TRANSPARENT)) || (!defined(_SURFACE_TYPE_TRANSPARENT) && !defined(_DISABLE_SSR))
{
IndirectLighting indirect = EvaluateBSDF_ScreenSpaceReflection(posInput, preLightData, bsdfData,
reflectionHierarchyWeight);
AccumulateIndirectLighting(indirect, aggregateLighting);
}
#endif
#if HAS_REFRACTION
uint perPixelEnvStart = envLightStart;
uint perPixelEnvCount = envLightCount;
// For refraction to be stable, we should reuse the same refraction probe for the whole object.
// Otherwise as if the object span different tiles it could produce a different refraction probe picking and thus have visual artifacts.
// For this we need to find the tile that is at the center of the object that is being rendered.
// And then select the first refraction probe of this list.
if ((featureFlags & LIGHTFEATUREFLAGS_SSREFRACTION) && (_EnableSSRefraction > 0))
{
// grab current object position and retrieve in which tile it belongs too
float4x4 modelMat = GetObjectToWorldMatrix();
float3 objPos = modelMat._m03_m13_m23;
float4 posClip = TransformWorldToHClip(objPos);
posClip.xyz = posClip.xyz / posClip.w;
uint2 tileObj = (saturate(posClip.xy * 0.5f + 0.5f) * _ScreenSize.xy) / GetTileSize();
uint envLightStart, envLightCount;
// Fetch first env light to provide the scene proxy for screen space refraction computation
PositionInputs localInput;
ZERO_INITIALIZE(PositionInputs, localInput);
localInput.tileCoord = tileObj.xy;
localInput.linearDepth = posClip.w;
GetCountAndStart(localInput, LIGHTCATEGORY_ENV, envLightStart, envLightCount);
EnvLightData envLightData;
if (envLightCount > 0)
{
envLightData = FetchEnvLight(FetchIndex(envLightStart, 0));
}
else if (perPixelEnvCount > 0) // If no refraction probe at the object center, we either fallback on the per pixel result.
{
envLightData = FetchEnvLight(FetchIndex(perPixelEnvStart, 0));
}
else // .. or the sky
{
envLightData = InitDefaultRefractionEnvLightData(0);
}
IndirectLighting lighting = EvaluateBSDF_ScreenspaceRefraction(context, V, posInput, preLightData, bsdfData, envLightData, refractionHierarchyWeight);
AccumulateIndirectLighting(lighting, aggregateLighting);
}
#endif
//---------------------------------------
// Sum up of operations on indirect diffuse lighting
// Let's define SSGI as SSGI/RTGI/Mixed or APV without lightmaps
// Let's define GI as Lightmaps/Lightprobe/APV with lightmaps
// By default we do those operations in deferred
// GBuffer pass : GI * AO + Emissive -> lightingbuffer
// Lightloop : indirectDiffuse = lightingbuffer; indirectDiffuse * SSAO
// Note that SSAO is apply on emissive in this case and we have double occlusion between AO and SSAO on indirectDiffuse
// By default we do those operation in forward
// Lightloop : indirectDiffuse = GI; indirectDiffuse * min(AO, SSAO) + Emissive
// With any SSGI effect we are performing those operations in deferred
// GBuffer pass : Emissive == 0 ? AmbientOcclusion -> EncodeIn(lightingbuffer) : Emissive -> lightingbuffer
// Lightloop : indirectDiffuse = SSGI; Emissive = lightingbuffer; AmbientOcclusion = Extract(lightingbuffer) or 1.0;
// indirectDiffuse * min(AO, SSAO) + Emissive
// Note that mean that we have the same behavior than forward path if Emissive is 0
// With any SSGI effect we are performing those operations in Forward
// Lightloop : indirectDiffuse = SSGI; indirectDiffuse * min(AO, SSAO) + Emissive
//---------------------------------------
// Explanation about APV and SSGI/RTGI/Mixed effects steps in the rendering pipeline.
// All effects will output only Emissive inside the lighting buffer (gbuffer3) in case of deferred (For APV this is done only if we are not a lightmap).
// The Lightmaps/Lightprobes contribution is 0 for those cases. Code enforce it in SampleBakedGI(). The remaining code of Material pass (and the debug code)
// is exactly the same with or without effects on, including the EncodeToGbuffer.
// builtinData.isLightmap is used by APV to know if we have lightmap or not and is harcoded based on preprocessor in InitBuiltinData()
// AO is also store with a hack in Gbuffer3 if possible. Otherwise it is set to 1.
// In case of regular deferred path (when effects aren't enable) AO is already apply on lightmap and emissive is added on to of it. All is inside bakeDiffuseLighting and emissiveColor is 0. AO must be 1
// When effects are enabled and for APV we don't have lightmaps, bakeDiffuseLighting is 0 and emissiveColor contain emissive (Either in deferred or forward) and AO should be the real AO value.
// Then in the lightloop in below code we will evalaute APV or read the indirectDiffuseTexture to fill bakeDiffuseLighting.
// We will then just do all the regular step we do with bakeDiffuseLighting in PostInitBuiltinData()
// No code change is required to handle AO, it is the same for all path.
// Note: Decals Emissive and Transparent Emissve aren't taken into account by RTGI/Mixed.
// Forward opaque emissive work in all cases. The current code flow with Emissive store in GBuffer3 is only to manage the case of Opaque Lit Material with Emissive in case of deferred
// Only APV can handle backFace lighting, all other effects are front face only.
// If we use SSGI/RTGI/Mixed effect, we are fully replacing the value of builtinData.bakeDiffuseLighting which is 0 at this step.
// If we are APV we only replace the non lightmaps part.
bool replaceBakeDiffuseLighting = false;
#if defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2)
float3 lightInReflDir = float3(-1, -1, -1); // This variable is used with APV for reflection probe normalization - see code for LIGHTFEATUREFLAGS_ENV
#endif
#if !defined(_SURFACE_TYPE_TRANSPARENT) // No SSGI/RTGI/Mixed effect on transparent
if (_IndirectDiffuseMode != INDIRECTDIFFUSEMODE_OFF)
replaceBakeDiffuseLighting = true;
#endif
#if defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2)
if (!builtinData.isLightmap)
replaceBakeDiffuseLighting = true;
#endif
if (replaceBakeDiffuseLighting)
{
BuiltinData tempBuiltinData;
ZERO_INITIALIZE(BuiltinData, tempBuiltinData);
#if !defined(_SURFACE_TYPE_TRANSPARENT) && !defined(SCREEN_SPACE_INDIRECT_DIFFUSE_DISABLED)
if (_IndirectDiffuseMode != INDIRECTDIFFUSEMODE_OFF)
{
tempBuiltinData.bakeDiffuseLighting = LOAD_TEXTURE2D_X(_IndirectDiffuseTexture, posInput.positionSS).xyz * GetInverseCurrentExposureMultiplier();
}
else
#endif
{
#if defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2)
if (_EnableProbeVolumes)
{
// Reflect normal to get lighting for reflection probe tinting
float3 R = reflect(-V, bsdfData.normalWS);
EvaluateAdaptiveProbeVolume(GetAbsolutePositionWS(posInput.positionWS),
bsdfData.normalWS,
-bsdfData.normalWS,
R,
V,
posInput.positionSS,
tempBuiltinData.bakeDiffuseLighting,
tempBuiltinData.backBakeDiffuseLighting,
lightInReflDir);
}
else // If probe volume is disabled we fallback on the ambient probes
{
tempBuiltinData.bakeDiffuseLighting = EvaluateAmbientProbe(bsdfData.normalWS);
tempBuiltinData.backBakeDiffuseLighting = EvaluateAmbientProbe(-bsdfData.normalWS);
}
#endif
}
#ifdef MODIFY_BAKED_DIFFUSE_LIGHTING
#ifdef DEBUG_DISPLAY
// When the lux meter is enabled, we don't want the albedo of the material to modify the diffuse baked lighting
if (_DebugLightingMode != DEBUGLIGHTINGMODE_LUX_METER)
#endif
ModifyBakedDiffuseLighting(V, posInput, preLightData, bsdfData, tempBuiltinData);
#endif
// This is applied only on bakeDiffuseLighting as ModifyBakedDiffuseLighting combine both bakeDiffuseLighting and backBakeDiffuseLighting
tempBuiltinData.bakeDiffuseLighting *= GetIndirectDiffuseMultiplier(builtinData.renderingLayers);
ApplyDebugToBuiltinData(tempBuiltinData); // This will not affect emissive as we don't use it
#if defined(DEBUG_DISPLAY) && (SHADERPASS == SHADERPASS_DEFERRED_LIGHTING)
// We need to handle the specific case of deferred for debug lighting mode here
if (_DebugLightingMode == DEBUGLIGHTINGMODE_EMISSIVE_LIGHTING)
{
tempBuiltinData.bakeDiffuseLighting = real3(0.0, 0.0, 0.0);
}
#endif
// Replace original data
builtinData.bakeDiffuseLighting = tempBuiltinData.bakeDiffuseLighting;
} // if (replaceBakeDiffuseLighting)
// Reflection probes are sorted by volume (in the increasing order).
if (featureFlags & LIGHTFEATUREFLAGS_ENV)
{
context.sampleReflection = SINGLE_PASS_CONTEXT_SAMPLE_REFLECTION_PROBES;
#if SCALARIZE_LIGHT_LOOP
if (fastPath)
{
envLightStart = envStartFirstLane;
}
#endif
// Scalarized loop, same rationale of the punctual light version
uint v_envLightListOffset = 0;
uint v_envLightIdx = envLightStart;
#if NEED_TO_CHECK_HELPER_LANE
// On some platform helper lanes don't behave as we'd expect, therefore we prevent them from entering the loop altogether.
// IMPORTANT! This has implications if ddx/ddy is used on results derived from lighting, however given Lightloop is called in compute we should be
// sure it will not happen.
bool isHelperLane = WaveIsHelperLane();
while (!isHelperLane && v_envLightListOffset < envLightCount)
#else
while (v_envLightListOffset < envLightCount)
#endif
{
v_envLightIdx = FetchIndex(envLightStart, v_envLightListOffset);
#if SCALARIZE_LIGHT_LOOP
uint s_envLightIdx = ScalarizeElementIndex(v_envLightIdx, fastPath);
#else
uint s_envLightIdx = v_envLightIdx;
#endif
if (s_envLightIdx == -1)
break;
EnvLightData s_envLightData = FetchEnvLight(s_envLightIdx); // Scalar load.
// If current scalar and vector light index match, we process the light. The v_envLightListOffset for current thread is increased.
// Note that the following should really be ==, however, since helper lanes are not considered by WaveActiveMin, such helper lanes could
// end up with a unique v_envLightIdx value that is smaller than s_envLightIdx hence being stuck in a loop. All the active lanes will not have this problem.
if (s_envLightIdx >= v_envLightIdx)
{
v_envLightListOffset++;
if (reflectionHierarchyWeight < 1.0)
{
if (IsMatchingLightLayer(s_envLightData.lightLayers, builtinData.renderingLayers))
{
IndirectLighting lighting = EvaluateBSDF_Env(context, V, posInput, preLightData, s_envLightData, bsdfData, s_envLightData.influenceShapeType, GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION, reflectionHierarchyWeight);
#if defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2)
if (s_envLightData.normalizeWithAPV > 0 && all(lightInReflDir >= 0))
{
float factor = GetReflectionProbeNormalizationFactor(lightInReflDir, bsdfData.normalWS, s_envLightData.L0L1, s_envLightData.L2_1, s_envLightData.L2_2);
lighting.specularReflected *= factor;
}
#endif
AccumulateIndirectLighting(lighting, aggregateLighting);
}
}
// Refraction probe and reflection probe will process exactly the same weight. It will be good for performance to be able to share this computation
// However it is hard to deal with the fact that reflectionHierarchyWeight and refractionHierarchyWeight have not the same values, they are independent
// The refraction probe is rarely used and happen only with sphere shape and high IOR. So we accept the slow path that use more simple code and
// doesn't affect the performance of the reflection which is more important.
// We reuse LIGHTFEATUREFLAGS_SSREFRACTION flag as refraction is mainly base on the screen. Would be a waste to not use screen and only cubemap.
if ((featureFlags & LIGHTFEATUREFLAGS_SSREFRACTION) && (refractionHierarchyWeight < 1.0))
{
EVALUATE_BSDF_ENV(s_envLightData, REFRACTION, refraction);
}
}
}
}
// Only apply the sky IBL if the sky texture is available
if ((featureFlags & LIGHTFEATUREFLAGS_SKY) && _EnvLightSkyEnabled)
{
// The sky is a single cubemap texture separate from the reflection probe texture array (different resolution and compression)
context.sampleReflection = SINGLE_PASS_CONTEXT_SAMPLE_SKY;
// The sky data are generated on the fly so the compiler can optimize the code
EnvLightData envLightSky = InitSkyEnvLightData(0);
// Only apply the sky if we haven't yet accumulated enough IBL lighting.
if (reflectionHierarchyWeight < 1.0)
{
EVALUATE_BSDF_ENV_SKY(envLightSky, REFLECTION, reflection);
}
if ((featureFlags & LIGHTFEATUREFLAGS_SSREFRACTION) && (refractionHierarchyWeight < 1.0))
{
EVALUATE_BSDF_ENV_SKY(envLightSky, REFRACTION, refraction);
}
}
}
#undef EVALUATE_BSDF_ENV
#undef EVALUATE_BSDF_ENV_SKY
uint i = 0; // Declare once to avoid the D3D11 compiler warning.
if (featureFlags & LIGHTFEATUREFLAGS_DIRECTIONAL)
{
for (i = 0; i < _DirectionalLightCount; ++i)
{
if (IsMatchingLightLayer(_DirectionalLightDatas[i].lightLayers, builtinData.renderingLayers))
{
DirectLighting lighting = EvaluateBSDF_Directional(context, V, posInput, preLightData, _DirectionalLightDatas[i], bsdfData, builtinData);
AccumulateDirectLighting(lighting, aggregateLighting);
}
}
}
#if SHADEROPTIONS_AREA_LIGHTS
if (featureFlags & LIGHTFEATUREFLAGS_AREA)
{
uint lightCount, lightStart; // Start is the offset specific to the tile (or cluster)
#ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER
GetCountAndStart(posInput, LIGHTCATEGORY_AREA, lightStart, lightCount);
#else
lightCount = _AreaLightCount;
lightStart = _PunctualLightCount;
#endif
bool fastPath = false;
#if SCALARIZE_LIGHT_LOOP
uint lightStartLane0;
fastPath = IsFastPath(lightStart, lightStartLane0); // True if all pixels belong to the same tile (or cluster)
if (fastPath)
{
lightStart = lightStartLane0;
}
#endif
// Scalarized loop. All lights that are in a tile/cluster touched by any pixel in the wave are loaded (scalar load), only the one relevant to current thread/pixel are processed.
// For clarity, the following code will follow the convention: variables starting with s_ are meant to be wave uniform (meant for scalar register),
// v_ are variables that might have different value for each thread in the wave (meant for vector registers).
// This will perform more loads than it is supposed to, however, the benefits should offset the downside, especially given that light data accessed should be largely coherent.
// Note that the above is valid only if wave intriniscs are supported.
uint v_lightListOffset = 0;
uint v_lightIdx = lightStart;
#if NEED_TO_CHECK_HELPER_LANE
// On some platform helper lanes don't behave as we'd expect, therefore we prevent them from entering the loop altogether.
// IMPORTANT! This has implications if ddx/ddy is used on results derived from lighting, however given Lightloop is called in compute we should be
// sure it will not happen.
bool isHelperLane = WaveIsHelperLane();
while (!isHelperLane && v_lightListOffset < lightCount)
#else
while (v_lightListOffset < lightCount)
#endif
{
v_lightIdx = FetchIndex(lightStart, v_lightListOffset);
#if SCALARIZE_LIGHT_LOOP
uint s_lightIdx = ScalarizeElementIndex(v_lightIdx, fastPath);
#else
uint s_lightIdx = v_lightIdx;
#endif
if (s_lightIdx == -1)
break;
LightData s_lightData = FetchLight(s_lightIdx);
// If current scalar and vector light index match, we process the light. The v_lightListOffset for current thread is increased.
// Note that the following should really be ==, however, since helper lanes are not considered by WaveActiveMin, such helper lanes could
// end up with a unique v_lightIdx value that is smaller than s_lightIdx hence being stuck in a loop. All the active lanes will not have this problem.
if (s_lightIdx >= v_lightIdx)
{
v_lightListOffset++;
if (IsMatchingLightLayer(s_lightData.lightLayers, builtinData.renderingLayers))
{
DirectLighting lighting = EvaluateBSDF_Area(context, V, posInput, preLightData, s_lightData, bsdfData, builtinData);
AccumulateDirectLighting(lighting, aggregateLighting);
}
}
}
}
#endif
ApplyDebugToLighting(context, builtinData, aggregateLighting);
// Note: We can't apply the IndirectDiffuseMultiplier here as with GBuffer, Emissive is part of the bakeDiffuseLighting.
// so IndirectDiffuseMultiplier is apply in PostInitBuiltinData or related location (like for probe volume)
aggregateLighting.indirect.specularReflected *= GetIndirectSpecularMultiplier(builtinData.renderingLayers);
// Also Apply indiret diffuse (GI)
// PostEvaluateBSDF will perform any operation wanted by the material and sum everything into diffuseLighting and specularLighting
PostEvaluateBSDF(context, V, posInput, preLightData, bsdfData, builtinData, aggregateLighting, lightLoopOutput);
ApplyDebug(context, posInput, bsdfData, lightLoopOutput);
}