A game about forced loneliness, made by TACStudios
1#ifndef __PROBEVOLUME_HLSL__
2#define __PROBEVOLUME_HLSL__
3
4#if defined(SHADER_API_MOBILE) || defined(SHADER_API_SWITCH)
5//#define USE_APV_TEXTURE_HALF
6#endif // SHADER_API_MOBILE || SHADER_API_SWITCH
7
8#include "Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ShaderVariablesProbeVolumes.cs.hlsl"
9#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SphericalHarmonics.hlsl"
10#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
11
12// Unpack variables
13#define _APVWorldOffset _Offset_LayerCount.xyz
14#define _APVIndirectionEntryDim _MinLoadedCellInEntries_IndirectionEntryDim.w
15#define _APVRcpIndirectionEntryDim _MaxLoadedCellInEntries_RcpIndirectionEntryDim.w
16#define _APVMinBrickSize _PoolDim_MinBrickSize.w
17#define _APVPoolDim _PoolDim_MinBrickSize.xyz
18#define _APVRcpPoolDim _RcpPoolDim_XY.xyz
19#define _APVRcpPoolDimXY _RcpPoolDim_XY.w
20#define _APVMinEntryPosition _MinEntryPos_Noise.xyz
21#define _APVSamplingNoise _MinEntryPos_Noise.w
22#define _APVEntryCount _EntryCount_X_XY_LeakReduction.xy
23#define _APVLeakReductionMode _EntryCount_X_XY_LeakReduction.z
24#define _APVNormalBias _Biases_NormalizationClamp.x
25#define _APVViewBias _Biases_NormalizationClamp.y
26#define _APVMinLoadedCellInEntries _MinLoadedCellInEntries_IndirectionEntryDim.xyz
27#define _APVMaxLoadedCellInEntries _MaxLoadedCellInEntries_RcpIndirectionEntryDim.xyz
28#define _APVLayerCount (uint)(_Offset_LayerCount.w)
29#define _APVMinReflProbeNormalizationFactor _Biases_NormalizationClamp.z
30#define _APVMaxReflProbeNormalizationFactor _Biases_NormalizationClamp.w
31#define _APVFrameIndex _FrameIndex_Weights.x
32#define _APVWeight _FrameIndex_Weights.y
33#define _APVSkyOcclusionWeight _FrameIndex_Weights.z
34#define _APVSkyDirectionWeight _FrameIndex_Weights.w
35
36#ifndef DECODE_SH
37#include "Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/DecodeSH.hlsl"
38#endif
39
40#ifndef __AMBIENTPROBE_HLSL__
41float3 EvaluateAmbientProbe(float3 normalWS)
42{
43 return float3(0, 0, 0);
44}
45#endif
46
47#ifndef UNITY_SHADER_VARIABLES_INCLUDED
48SAMPLER(s_linear_clamp_sampler);
49SAMPLER(s_point_clamp_sampler);
50#endif
51
52#ifdef USE_APV_TEXTURE_HALF
53#define TEXTURE3D_APV TEXTURE3D_HALF
54#else
55#define TEXTURE3D_APV TEXTURE3D_FLOAT
56#endif
57
58struct APVResources
59{
60 StructuredBuffer<int> index;
61 StructuredBuffer<float3> SkyPrecomputedDirections;
62
63 TEXTURE3D_APV(L0_L1Rx);
64 TEXTURE3D_APV(L1G_L1Ry);
65 TEXTURE3D_APV(L1B_L1Rz);
66 TEXTURE3D_APV(L2_0);
67 TEXTURE3D_APV(L2_1);
68 TEXTURE3D_APV(L2_2);
69 TEXTURE3D_APV(L2_3);
70 TEXTURE3D_FLOAT(Validity); // Validity stores indices and requires full float precision to be decoded properly.
71
72 TEXTURE3D_APV(ProbeOcclusion);
73
74 TEXTURE3D_APV(SkyOcclusionL0L1);
75 TEXTURE3D_FLOAT(SkyShadingDirectionIndices);
76};
77
78struct APVResourcesRW
79{
80 RWTexture3D<float4> L0_L1Rx;
81 RWTexture3D<float4> L1G_L1Ry;
82 RWTexture3D<float4> L1B_L1Rz;
83 RWTexture3D<float4> L2_0;
84 RWTexture3D<float4> L2_1;
85 RWTexture3D<float4> L2_2;
86 RWTexture3D<float4> L2_3;
87 RWTexture3D<float4> ProbeOcclusion;
88};
89
90#ifndef USE_APV_PROBE_OCCLUSION
91// If we are rendering a probe lit renderer, and we have APV enabled, and we are using subtractive or shadowmask mode, we sample occlusion from APV.
92#if !defined(LIGHTMAP_ON) && (defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2)) && (defined(LIGHTMAP_SHADOW_MIXING) || defined(SHADOWS_SHADOWMASK))
93#define USE_APV_PROBE_OCCLUSION 1
94#endif
95#endif
96
97#define LOAD_APV_RES_L1(res, target) \
98 res.L0_L1Rx = CALL_MERGE_NAME(target, _L0_L1Rx); \
99 res.L1G_L1Ry = CALL_MERGE_NAME(target, _L1G_L1Ry); \
100 res.L1B_L1Rz = CALL_MERGE_NAME(target, _L1B_L1Rz);
101#define LOAD_APV_RES_L2(res, target) \
102 res.L2_0 = CALL_MERGE_NAME(target, _L2_0); \
103 res.L2_1 = CALL_MERGE_NAME(target, _L2_1); \
104 res.L2_2 = CALL_MERGE_NAME(target, _L2_2); \
105 res.L2_3 = CALL_MERGE_NAME(target, _L2_3);
106#define LOAD_APV_RES_OCCLUSION(res, target) \
107 res.ProbeOcclusion = CALL_MERGE_NAME(target, _ProbeOcclusion);
108
109#ifndef PROBE_VOLUMES_L2
110 #ifndef USE_APV_PROBE_OCCLUSION
111 # define LOAD_APV_RES(res, target) LOAD_APV_RES_L1(res, target)
112 #else
113 # define LOAD_APV_RES(res, target) LOAD_APV_RES_L1(res, target) \
114 LOAD_APV_RES_OCCLUSION(res, target)
115 #endif
116#else
117 #ifndef USE_APV_PROBE_OCCLUSION
118 # define LOAD_APV_RES(res, target) \
119 LOAD_APV_RES_L1(res, target) \
120 LOAD_APV_RES_L2(res, target)
121 #else
122 # define LOAD_APV_RES(res, target) \
123 LOAD_APV_RES_L1(res, target) \
124 LOAD_APV_RES_L2(res, target) \
125 LOAD_APV_RES_OCCLUSION(res, target)
126 #endif
127#endif
128
129struct APVSample
130{
131 half3 L0;
132 half3 L1_R;
133 half3 L1_G;
134 half3 L1_B;
135#ifdef PROBE_VOLUMES_L2
136 half4 L2_R;
137 half4 L2_G;
138 half4 L2_B;
139 half3 L2_C;
140#endif // PROBE_VOLUMES_L2
141
142 float4 skyOcclusionL0L1;
143 float3 skyShadingDirection;
144
145#ifdef USE_APV_PROBE_OCCLUSION
146 float4 probeOcclusion;
147#endif
148
149#define APV_SAMPLE_STATUS_INVALID -1
150#define APV_SAMPLE_STATUS_ENCODED 0
151#define APV_SAMPLE_STATUS_DECODED 1
152
153 int status;
154
155 // Note: at the moment this is called at the moment the struct is built, but it is kept as a separate step
156 // as ideally should be called as far as possible from sample to allow for latency hiding.
157 void Decode()
158 {
159 if (status == APV_SAMPLE_STATUS_ENCODED)
160 {
161 L1_R = DecodeSH(L0.r, L1_R);
162 L1_G = DecodeSH(L0.g, L1_G);
163 L1_B = DecodeSH(L0.b, L1_B);
164#ifdef PROBE_VOLUMES_L2
165 DecodeSH_L2(L0, L2_R, L2_G, L2_B, L2_C);
166#endif // PROBE_VOLUMES_L2
167
168 status = APV_SAMPLE_STATUS_DECODED;
169 }
170 }
171
172 void Encode()
173 {
174 if (status == APV_SAMPLE_STATUS_DECODED)
175 {
176 L1_R = EncodeSH(L0.r, L1_R);
177 L1_G = EncodeSH(L0.g, L1_G);
178 L1_B = EncodeSH(L0.b, L1_B);
179#ifdef PROBE_VOLUMES_L2
180 EncodeSH_L2(L0, L2_R, L2_G, L2_B, L2_C);
181#endif // PROBE_VOLUMES_L2
182
183 status = APV_SAMPLE_STATUS_ENCODED;
184 }
185 }
186};
187
188// Resources required for APV
189StructuredBuffer<int> _APVResIndex;
190StructuredBuffer<uint3> _APVResCellIndices;
191StructuredBuffer<float3> _SkyPrecomputedDirections;
192StructuredBuffer<uint> _AntiLeakData;
193
194TEXTURE3D_APV(_APVResL0_L1Rx);
195
196TEXTURE3D_APV(_APVResL1G_L1Ry);
197TEXTURE3D_APV(_APVResL1B_L1Rz);
198TEXTURE3D_APV(_APVResL2_0);
199TEXTURE3D_APV(_APVResL2_1);
200TEXTURE3D_APV(_APVResL2_2);
201TEXTURE3D_APV(_APVResL2_3);
202
203TEXTURE3D_APV(_APVProbeOcclusion);
204
205TEXTURE3D_APV(_APVResValidity);
206
207TEXTURE3D_APV(_SkyOcclusionTexL0L1);
208TEXTURE3D(_SkyShadingDirectionIndicesTex);
209
210
211// -------------------------------------------------------------
212// Various weighting functions for occlusion or helper functions.
213// -------------------------------------------------------------
214float3 AddNoiseToSamplingPosition(float3 posWS, float2 positionSS, float3 direction)
215{
216#ifdef UNITY_SPACE_TRANSFORMS_INCLUDED
217 float3 right = mul((float3x3)GetViewToWorldMatrix(), float3(1.0, 0.0, 0.0));
218 float3 top = mul((float3x3)GetViewToWorldMatrix(), float3(0.0, 1.0, 0.0));
219 float noise01 = InterleavedGradientNoise(positionSS, _APVFrameIndex);
220 float noise02 = frac(noise01 * 100.0);
221 float noise03 = frac(noise01 * 1000.0);
222 direction += top * (noise02 - 0.5) + right * (noise03 - 0.5);
223 return _APVSamplingNoise > 0 ? posWS + noise01 * _APVSamplingNoise * direction : posWS;
224#else
225 return posWS;
226#endif
227}
228
229uint3 GetSampleOffset(uint i)
230{
231 return uint3(i, i >> 1, i >> 2) & 1;
232}
233
234// The validity mask is sampled once and contains a binary info on whether a probe neighbour (relevant for trilinear) is to be used
235// or not. The entry in the mask uses the same mapping that GetSampleOffset above uses.
236half GetValidityWeight(uint offset, uint validityMask)
237{
238 uint mask = 1U << offset;
239 return (validityMask & mask) > 0 ? 1 : 0;
240}
241
242float ProbeDistance(uint subdiv)
243{
244 return pow(3, subdiv) * _APVMinBrickSize / 3.0f;
245}
246
247half ProbeDistanceHalf(uint subdiv)
248{
249 return pow(half(3), half(subdiv)) * half(_APVMinBrickSize) / 3.0;
250}
251
252float3 GetSnappedProbePosition(float3 posWS, uint subdiv)
253{
254 float3 distBetweenProbes = ProbeDistance(subdiv);
255 float3 dividedPos = posWS / distBetweenProbes;
256 return (dividedPos - frac(dividedPos)) * distBetweenProbes;
257}
258
259// -------------------------------------------------------------
260// Indexing functions
261// -------------------------------------------------------------
262
263bool LoadCellIndexMetaData(uint cellFlatIdx, out uint chunkIndex, out int stepSize, out int3 minRelativeIdx, out uint3 sizeOfValid)
264{
265 uint3 metaData = _APVResCellIndices[cellFlatIdx];
266
267 // See ProbeIndexOfIndices.cs for packing
268 chunkIndex = metaData.x & 0x1FFFFFFF;
269 stepSize = round(pow(3, (metaData.x >> 29) & 0x7));
270
271 minRelativeIdx.x = metaData.y & 0x3FF;
272 minRelativeIdx.y = (metaData.y >> 10) & 0x3FF;
273 minRelativeIdx.z = (metaData.y >> 20) & 0x3FF;
274
275 sizeOfValid.x = metaData.z & 0x3FF;
276 sizeOfValid.y = (metaData.z >> 10) & 0x3FF;
277 sizeOfValid.z = (metaData.z >> 20) & 0x3FF;
278
279 return metaData.x != 0xFFFFFFFF;
280}
281
282uint GetIndexData(APVResources apvRes, float3 posWS)
283{
284 float3 entryPos = floor(posWS * _APVRcpIndirectionEntryDim);
285 float3 topLeftEntryWS = entryPos * _APVIndirectionEntryDim;
286
287 bool isALoadedCell = all(entryPos >= _APVMinLoadedCellInEntries) && all(entryPos <= _APVMaxLoadedCellInEntries);
288
289 // Make sure we start from 0
290 uint3 entryPosInt = (uint3)(entryPos - _APVMinEntryPosition);
291 uint flatIdx = dot(entryPosInt, uint3(1, _APVEntryCount.x, _APVEntryCount.y));
292
293 // Dynamic branch must be enforced to avoid out-of-bounds memory access in LoadCellIndexMetaData
294 uint result = 0xffffffff;
295 UNITY_BRANCH if (isALoadedCell)
296 {
297 int stepSize;
298 int3 minRelativeIdx;
299 uint3 sizeOfValid;
300 uint chunkIdx;
301 if (LoadCellIndexMetaData(flatIdx, chunkIdx, stepSize, minRelativeIdx, sizeOfValid))
302 {
303 float3 residualPosWS = posWS - topLeftEntryWS;
304 uint3 localBrickIndex = floor(residualPosWS / (_APVMinBrickSize * stepSize));
305 localBrickIndex = min(localBrickIndex, (uint3)(3 * 3 * 3 - 1)); // due to floating point issue, we may query an invalid brick
306 localBrickIndex -= minRelativeIdx; // Relative to valid region
307
308 UNITY_BRANCH
309 if (all(localBrickIndex < sizeOfValid))
310 {
311 uint flattenedLocationInCell = dot(localBrickIndex, uint3(sizeOfValid.y, 1, sizeOfValid.x * sizeOfValid.y));
312 uint locationInPhysicalBuffer = chunkIdx * (uint)PROBE_INDEX_CHUNK_SIZE + flattenedLocationInCell;
313 result = apvRes.index[locationInPhysicalBuffer];
314 }
315 }
316 }
317
318 return result;
319}
320
321// -------------------------------------------------------------
322// Loading functions
323// -------------------------------------------------------------
324APVResources FillAPVResources()
325{
326 APVResources apvRes;
327 apvRes.index = _APVResIndex;
328
329 apvRes.L0_L1Rx = _APVResL0_L1Rx;
330
331 apvRes.L1G_L1Ry = _APVResL1G_L1Ry;
332 apvRes.L1B_L1Rz = _APVResL1B_L1Rz;
333
334 apvRes.L2_0 = _APVResL2_0;
335 apvRes.L2_1 = _APVResL2_1;
336 apvRes.L2_2 = _APVResL2_2;
337 apvRes.L2_3 = _APVResL2_3;
338
339 apvRes.Validity = _APVResValidity;
340 apvRes.SkyOcclusionL0L1 = _SkyOcclusionTexL0L1;
341 apvRes.SkyShadingDirectionIndices = _SkyShadingDirectionIndicesTex;
342 apvRes.SkyPrecomputedDirections = _SkyPrecomputedDirections;
343
344 apvRes.ProbeOcclusion = _APVProbeOcclusion;
345
346 return apvRes;
347}
348
349
350bool TryToGetPoolUVWAndSubdiv(APVResources apvRes, float3 posWSForSample, out float3 uvw, out uint subdiv)
351{
352 // resolve the index
353 uint packed_pool_idx = GetIndexData(apvRes, posWSForSample.xyz);
354
355 // unpack pool idx
356 // size is encoded in the upper 4 bits
357 subdiv = (packed_pool_idx >> 28) & 15;
358
359 float flattened_pool_idx = packed_pool_idx & ((1 << 28) - 1);
360 float3 pool_idx;
361 pool_idx.z = floor(flattened_pool_idx * _APVRcpPoolDimXY);
362 flattened_pool_idx -= (pool_idx.z * (_APVPoolDim.x * _APVPoolDim.y));
363 pool_idx.y = floor(flattened_pool_idx * _APVRcpPoolDim.x);
364 pool_idx.x = floor(flattened_pool_idx - (pool_idx.y * _APVPoolDim.x));
365
366 // calculate uv offset and scale
367 float brickSizeWS = pow(3.0, subdiv) * _APVMinBrickSize;
368 float3 offset = frac(posWSForSample.xyz / brickSizeWS); // [0;1] in brick space
369 //offset = clamp( offset, 0.25, 0.75 ); // [0.25;0.75] in brick space (is this actually necessary?)
370
371 uvw = (pool_idx + 0.5 + (3.0 * offset)) * _APVRcpPoolDim; // add offset with brick footprint converted to text footprint in pool texel space
372
373 // no valid brick loaded for this index, fallback to ambient probe
374 // Note: we could instead early return when we know we'll have invalid UVs, but some bade code gen on Vulkan generates shader warnings if we do.
375 return packed_pool_idx != 0xffffffffu;
376}
377
378bool TryToGetPoolUVWAndSubdiv(APVResources apvRes, float3 posWS, float3 normalWS, float3 viewDirWS, out float3 uvw, out uint subdiv, out float3 biasedPosWS)
379{
380 biasedPosWS = (posWS + normalWS * _APVNormalBias) + viewDirWS * _APVViewBias;
381 return TryToGetPoolUVWAndSubdiv(apvRes, biasedPosWS, uvw, subdiv);
382}
383
384bool TryToGetPoolUVW(APVResources apvRes, float3 posWS, float3 normalWS, float3 viewDir, out float3 uvw)
385{
386 uint unusedSubdiv;
387 float3 unusedPos;
388 return TryToGetPoolUVWAndSubdiv(apvRes, posWS, normalWS, viewDir, uvw, unusedSubdiv, unusedPos);
389}
390
391
392APVSample SampleAPV(APVResources apvRes, float3 uvw)
393{
394 APVSample apvSample;
395 half4 L0_L1Rx = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L0_L1Rx, s_linear_clamp_sampler, uvw, 0).rgba);
396 half4 L1G_L1Ry = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L1G_L1Ry, s_linear_clamp_sampler, uvw, 0).rgba);
397 half4 L1B_L1Rz = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L1B_L1Rz, s_linear_clamp_sampler, uvw, 0).rgba);
398
399 apvSample.L0 = L0_L1Rx.xyz;
400 apvSample.L1_R = half3(L0_L1Rx.w, L1G_L1Ry.w, L1B_L1Rz.w);
401 apvSample.L1_G = L1G_L1Ry.xyz;
402 apvSample.L1_B = L1B_L1Rz.xyz;
403
404#ifdef PROBE_VOLUMES_L2
405 apvSample.L2_R = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L2_0, s_linear_clamp_sampler, uvw, 0).rgba);
406 apvSample.L2_G = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L2_1, s_linear_clamp_sampler, uvw, 0).rgba);
407 apvSample.L2_B = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L2_2, s_linear_clamp_sampler, uvw, 0).rgba);
408 apvSample.L2_C = half3(SAMPLE_TEXTURE3D_LOD(apvRes.L2_3, s_linear_clamp_sampler, uvw, 0).rgb);
409#endif // PROBE_VOLUMES_L2
410
411 if (_APVSkyOcclusionWeight > 0)
412 apvSample.skyOcclusionL0L1 = SAMPLE_TEXTURE3D_LOD(apvRes.SkyOcclusionL0L1, s_linear_clamp_sampler, uvw, 0).rgba;
413 else
414 apvSample.skyOcclusionL0L1 = float4(0, 0, 0, 0);
415
416 if (_APVSkyDirectionWeight > 0)
417 {
418 // No interpolation for sky shading indices
419 int3 texCoord = uvw * _APVPoolDim - 0.5f;
420 uint index = LOAD_TEXTURE3D(apvRes.SkyShadingDirectionIndices, texCoord).x * 255.0;
421
422 if (index == 255)
423 apvSample.skyShadingDirection = float3(0, 0, 0);
424 else
425 apvSample.skyShadingDirection = apvRes.SkyPrecomputedDirections[index].rgb;
426 }
427 else
428 apvSample.skyShadingDirection = float3(0, 0, 0);
429
430#ifdef USE_APV_PROBE_OCCLUSION
431 apvSample.probeOcclusion = SAMPLE_TEXTURE3D_LOD(apvRes.ProbeOcclusion, s_linear_clamp_sampler, uvw, 0).rgba;
432#endif
433
434 apvSample.status = APV_SAMPLE_STATUS_ENCODED;
435
436 return apvSample;
437}
438
439APVSample LoadAndDecodeAPV(APVResources apvRes, int3 loc)
440{
441 APVSample apvSample;
442
443 half4 L0_L1Rx = half4(LOAD_TEXTURE3D(apvRes.L0_L1Rx, loc).rgba);
444 half4 L1G_L1Ry = half4(LOAD_TEXTURE3D(apvRes.L1G_L1Ry, loc).rgba);
445 half4 L1B_L1Rz = half4(LOAD_TEXTURE3D(apvRes.L1B_L1Rz, loc).rgba);
446
447 apvSample.L0 = L0_L1Rx.xyz;
448 apvSample.L1_R = half3(L0_L1Rx.w, L1G_L1Ry.w, L1B_L1Rz.w);
449 apvSample.L1_G = L1G_L1Ry.xyz;
450 apvSample.L1_B = L1B_L1Rz.xyz;
451
452#ifdef PROBE_VOLUMES_L2
453 apvSample.L2_R = half4(LOAD_TEXTURE3D(apvRes.L2_0, loc).rgba);
454 apvSample.L2_G = half4(LOAD_TEXTURE3D(apvRes.L2_1, loc).rgba);
455 apvSample.L2_B = half4(LOAD_TEXTURE3D(apvRes.L2_2, loc).rgba);
456 apvSample.L2_C = half3(LOAD_TEXTURE3D(apvRes.L2_3, loc).rgb);
457#endif // PROBE_VOLUMES_L2
458
459#ifdef USE_APV_PROBE_OCCLUSION
460 apvSample.probeOcclusion = LOAD_TEXTURE3D(apvRes.ProbeOcclusion, loc).rgba;
461#endif
462
463 apvSample.status = APV_SAMPLE_STATUS_ENCODED;
464 apvSample.Decode();
465
466 return apvSample;
467}
468
469void WeightSample(inout APVSample apvSample, half weight)
470{
471 apvSample.L0 *= weight;
472 apvSample.L1_R *= weight;
473 apvSample.L1_G *= weight;
474 apvSample.L1_B *= weight;
475
476#ifdef PROBE_VOLUMES_L2
477 apvSample.L2_R *= weight;
478 apvSample.L2_G *= weight;
479 apvSample.L2_B *= weight;
480 apvSample.L2_C *= weight;
481#endif // PROBE_VOLUMES_L2
482
483#ifdef USE_APV_PROBE_OCCLUSION
484 apvSample.probeOcclusion *= weight;
485#endif
486
487 apvSample.skyOcclusionL0L1 *= weight;
488}
489
490void AccumulateSamples(inout APVSample dst, APVSample other, half weight)
491{
492 WeightSample(other, weight);
493 dst.L0 += other.L0;
494 dst.L1_R += other.L1_R;
495 dst.L1_G += other.L1_G;
496 dst.L1_B += other.L1_B;
497
498#ifdef PROBE_VOLUMES_L2
499 dst.L2_R += other.L2_R;
500 dst.L2_G += other.L2_G;
501 dst.L2_B += other.L2_B;
502 dst.L2_C += other.L2_C;
503#endif // PROBE_VOLUMES_L2
504
505#ifdef USE_APV_PROBE_OCCLUSION
506 dst.probeOcclusion += other.probeOcclusion;
507#endif
508
509 dst.skyOcclusionL0L1 += other.skyOcclusionL0L1;
510}
511
512uint LoadValidityMask(APVResources apvRes, uint renderingLayer, int3 coord)
513{
514 float rawValidity = LOAD_TEXTURE3D(apvRes.Validity, coord).x;
515
516 uint validityMask;
517 if (_APVLayerCount == 1)
518 {
519 validityMask = rawValidity * 255.0;
520 }
521 else
522 {
523 // If the object is on none of the masks, enable all layers to still sample validity correctly
524 uint globalLayer = _ProbeVolumeLayerMask[0] | _ProbeVolumeLayerMask[1] | _ProbeVolumeLayerMask[2] | _ProbeVolumeLayerMask[3];
525 if ((renderingLayer & globalLayer) == 0) renderingLayer = 0xFFFFFFFF;
526
527 validityMask = 0;
528 if ((renderingLayer & _ProbeVolumeLayerMask[0]) != 0)
529 validityMask = asuint(rawValidity);
530 if ((renderingLayer & _ProbeVolumeLayerMask[1]) != 0)
531 validityMask |= asuint(rawValidity) >> 8;
532 if ((renderingLayer & _ProbeVolumeLayerMask[2]) != 0)
533 validityMask |= asuint(rawValidity) >> 16;
534 if ((renderingLayer & _ProbeVolumeLayerMask[3]) != 0)
535 validityMask |= asuint(rawValidity) >> 24;
536 validityMask = validityMask & 0xFF;
537 }
538
539 return validityMask;
540}
541
542float UnpackSamplingWeight(uint mask, float3 texFrac)
543{
544 int3 dir = 0;
545 dir.x = (int)(mask >> 1) & 3;
546 dir.y = (int)(mask >> 4) & 3;
547 dir.z = (int)(mask >> 7) & 3;
548
549 float3 weights;
550 weights.x = saturate(mask & 1) + (dir.x - 1) * texFrac.x;
551 weights.y = saturate(mask & 8) + (dir.y - 1) * texFrac.y;
552 weights.z = saturate(mask & 64) + (dir.z - 1) * texFrac.z;
553
554 return weights.x * weights.y * weights.z;
555}
556
557void UnpackSamplingOffset(uint mask, float3 texFrac, out float3 uvwOffset)
558{
559 uvwOffset.x = saturate(mask & 4) + (saturate(mask & 2) * texFrac.x - texFrac.x);
560 uvwOffset.y = saturate(mask & 32) + (saturate(mask & 16) * texFrac.y - texFrac.y);
561 uvwOffset.z = saturate(mask & 256) + (saturate(mask & 128) * texFrac.z - texFrac.z);
562}
563
564APVSample QualityLeakReduction(APVResources apvRes, uint renderingLayer, inout float3 uvw)
565{
566 float3 texCoord = uvw * _APVPoolDim - .5f;
567 float3 texFrac = frac(texCoord);
568
569 half3 offset;
570
571 uint validityMask = LoadValidityMask(apvRes, renderingLayer, texCoord);
572 uint antileak = _AntiLeakData[validityMask];
573
574 UnpackSamplingOffset(antileak >> 0, texFrac, offset);
575 APVSample apvSample = SampleAPV(apvRes, uvw + offset * _APVRcpPoolDim);
576
577 // Optional additional samples for quality leak reduction
578 if (validityMask != 0xFF)
579 {
580 float3 weights;
581 weights.x = UnpackSamplingWeight(antileak >> 0, texFrac);
582 weights.y = UnpackSamplingWeight(antileak >> 9, texFrac);
583 weights.z = UnpackSamplingWeight(antileak >> 18, texFrac);
584
585 weights *= rcp(max(0.0001, weights.x+weights.y+weights.z));
586 WeightSample(apvSample, weights.x);
587
588 UNITY_BRANCH if (weights.y != 0)
589 {
590 UnpackSamplingOffset(antileak >> 9, texFrac, offset);
591 APVSample partialSample = SampleAPV(apvRes, uvw + offset * _APVRcpPoolDim);
592 AccumulateSamples(apvSample, partialSample, weights.y);
593 }
594
595 UNITY_BRANCH if (weights.z != 0)
596 {
597 UnpackSamplingOffset(antileak >> 18, texFrac, offset);
598 APVSample partialSample = SampleAPV(apvRes, uvw + offset * _APVRcpPoolDim);
599 AccumulateSamples(apvSample, partialSample, weights.z);
600 }
601 }
602
603 return apvSample;
604}
605
606void WarpUVWLeakReduction(APVResources apvRes, uint renderingLayer, inout float3 uvw)
607{
608 float3 texCoord = uvw * _APVPoolDim - 0.5f;
609 half3 texFrac = half3(frac(texCoord));
610
611 uint validityMask = LoadValidityMask(apvRes, renderingLayer, texCoord);
612 if (validityMask != 0xFF)
613 {
614 half weights[8];
615 half totalW = 0.0;
616 half3 offset = 0;
617 uint i;
618
619 UNITY_UNROLL
620 for (i = 0; i < 8; ++i)
621 {
622 uint3 probeOffset = GetSampleOffset(i);
623 half validityWeight =
624 ((probeOffset.x == 1) ? texFrac.x : 1.0f - texFrac.x) *
625 ((probeOffset.y == 1) ? texFrac.y : 1.0f - texFrac.y) *
626 ((probeOffset.z == 1) ? texFrac.z : 1.0f - texFrac.z);
627
628 validityWeight *= GetValidityWeight(i, validityMask);
629 half weight = saturate(validityWeight);
630 weights[i] = weight;
631 totalW += weight;
632 }
633
634 offset = -texFrac;
635 half rcpTotalW = rcp(max(0.0001, totalW));
636
637 UNITY_UNROLL
638 for (i = 0; i < 8; ++i)
639 {
640 uint3 probeOffset = GetSampleOffset(i);
641 offset += (half3)probeOffset * (weights[i] * rcpTotalW);
642 }
643
644 uvw += (float3)offset * _APVRcpPoolDim;
645 }
646}
647
648APVSample SampleAPV(APVResources apvRes, float3 posWS, float3 biasNormalWS, uint renderingLayer, float3 viewDir)
649{
650 APVSample outSample;
651
652 posWS -= _APVWorldOffset;
653
654 uint subdiv;
655 float3 pool_uvw;
656 float3 biasedPosWS;
657 if (TryToGetPoolUVWAndSubdiv(apvRes, posWS, biasNormalWS, viewDir, pool_uvw, subdiv, biasedPosWS))
658 {
659 UNITY_BRANCH if (_APVLeakReductionMode == APVLEAKREDUCTIONMODE_QUALITY)
660 {
661 outSample = QualityLeakReduction(apvRes, renderingLayer, pool_uvw);
662 }
663 else
664 {
665 if (_APVLeakReductionMode == APVLEAKREDUCTIONMODE_PERFORMANCE)
666 WarpUVWLeakReduction(apvRes, renderingLayer, pool_uvw);
667 outSample = SampleAPV(apvRes, pool_uvw);
668 }
669 }
670 else
671 {
672 ZERO_INITIALIZE(APVSample, outSample);
673
674 #ifdef USE_APV_PROBE_OCCLUSION
675 // The "neutral" value for probe occlusion is 1.0. We write it here to prevent objects that are outside
676 // the bounds of the probe volume from being entirely shadowed.
677 outSample.probeOcclusion = 1.0f;
678 #endif
679
680 outSample.status = APV_SAMPLE_STATUS_INVALID;
681 }
682
683 return outSample;
684}
685
686
687APVSample SampleAPV(float3 posWS, float3 biasNormalWS, uint renderingLayer, float3 viewDir)
688{
689 APVResources apvRes = FillAPVResources();
690 return SampleAPV(apvRes, posWS, biasNormalWS, renderingLayer, viewDir);
691}
692
693// -------------------------------------------------------------
694// Dynamic Sky Handling
695// -------------------------------------------------------------
696
697// Expects Layout DC, x, y, z
698// See on baking side in DynamicGISkyOcclusion.hlsl
699float EvalSHSkyOcclusion(float3 dir, APVSample apvSample)
700{
701 // L0 L1
702 float4 temp = float4(kSHBasis0, kSHBasis1 * dir.x, kSHBasis1 * dir.y, kSHBasis1 * dir.z);
703 return _APVSkyOcclusionWeight * dot(temp, apvSample.skyOcclusionL0L1);
704}
705
706float3 EvaluateOccludedSky(APVSample apvSample, float3 N)
707{
708 float occValue = EvalSHSkyOcclusion(N, apvSample);
709 float3 shadingNormal = N;
710
711 if (_APVSkyDirectionWeight > 0)
712 {
713 shadingNormal = apvSample.skyShadingDirection;
714 float normSquared = dot(shadingNormal, shadingNormal);
715 if (normSquared < 0.2f)
716 shadingNormal = N;
717 else
718 {
719 shadingNormal = shadingNormal * rsqrt(normSquared);
720 }
721 }
722 return occValue * EvaluateAmbientProbe(shadingNormal);
723}
724
725// -------------------------------------------------------------
726// Internal Evaluation functions (avoid usage in caller code outside this file)
727// -------------------------------------------------------------
728float3 EvaluateAPVL0(APVSample apvSample)
729{
730 return apvSample.L0;
731}
732
733void EvaluateAPVL1(APVSample apvSample, float3 N, out float3 diffuseLighting)
734{
735 diffuseLighting = SHEvalLinearL1(N, apvSample.L1_R, apvSample.L1_G, apvSample.L1_B);
736}
737
738#ifdef PROBE_VOLUMES_L2
739void EvaluateAPVL1L2(APVSample apvSample, float3 N, out float3 diffuseLighting)
740{
741 EvaluateAPVL1(apvSample, N, diffuseLighting);
742 diffuseLighting += SHEvalLinearL2(N, apvSample.L2_R, apvSample.L2_G, apvSample.L2_B, float4(apvSample.L2_C, 0.0f));
743}
744#endif
745
746
747// -------------------------------------------------------------
748// "Public" Evaluation functions, the one that callers outside this file should use
749// -------------------------------------------------------------
750void EvaluateAdaptiveProbeVolume(APVSample apvSample, float3 normalWS, out float3 bakeDiffuseLighting)
751{
752 if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
753 {
754 apvSample.Decode();
755
756#if defined(PROBE_VOLUMES_L1)
757 EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
758#elif defined(PROBE_VOLUMES_L2)
759 EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
760#endif
761
762 bakeDiffuseLighting += apvSample.L0;
763 if (_APVSkyOcclusionWeight > 0)
764 bakeDiffuseLighting += EvaluateOccludedSky(apvSample, normalWS);
765
766 //if (_APVWeight < 1.f)
767 {
768 bakeDiffuseLighting = bakeDiffuseLighting * _APVWeight;
769 }
770 }
771 else
772 {
773 // no valid brick, fallback to ambient probe
774 bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
775 }
776}
777
778void EvaluateAdaptiveProbeVolume(APVSample apvSample, float3 normalWS, float3 backNormalWS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
779{
780 if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
781 {
782 apvSample.Decode();
783
784#ifdef PROBE_VOLUMES_L1
785 EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
786 EvaluateAPVL1(apvSample, backNormalWS, backBakeDiffuseLighting);
787#elif defined(PROBE_VOLUMES_L2)
788 EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
789 EvaluateAPVL1L2(apvSample, backNormalWS, backBakeDiffuseLighting);
790#endif
791
792 bakeDiffuseLighting += apvSample.L0;
793 backBakeDiffuseLighting += apvSample.L0;
794 if (_APVSkyOcclusionWeight > 0)
795 {
796 bakeDiffuseLighting += EvaluateOccludedSky(apvSample, normalWS);
797 backBakeDiffuseLighting += EvaluateOccludedSky(apvSample, backNormalWS);
798 }
799
800 //if (_APVWeight < 1.f)
801 {
802 bakeDiffuseLighting = bakeDiffuseLighting * _APVWeight;
803 backBakeDiffuseLighting = backBakeDiffuseLighting * _APVWeight;
804 }
805 }
806 else
807 {
808 // no valid brick, fallback to ambient probe
809 bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
810 backBakeDiffuseLighting = EvaluateAmbientProbe(backNormalWS);
811 }
812}
813
814void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 reflDir, in float3 viewDir,
815 in float2 positionSS, in uint renderingLayer, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting, out float3 lightingInReflDir)
816{
817 APVResources apvRes = FillAPVResources();
818
819 posWS = AddNoiseToSamplingPosition(posWS, positionSS, viewDir);
820
821 APVSample apvSample = SampleAPV(posWS, normalWS, renderingLayer, viewDir);
822
823 if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
824 {
825 apvSample.Decode();
826
827#ifdef PROBE_VOLUMES_L1
828 EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
829 EvaluateAPVL1(apvSample, backNormalWS, backBakeDiffuseLighting);
830 EvaluateAPVL1(apvSample, reflDir, lightingInReflDir);
831#elif defined(PROBE_VOLUMES_L2)
832 EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
833 EvaluateAPVL1L2(apvSample, backNormalWS, backBakeDiffuseLighting);
834 EvaluateAPVL1L2(apvSample, reflDir, lightingInReflDir);
835#endif
836
837 bakeDiffuseLighting += apvSample.L0;
838 backBakeDiffuseLighting += apvSample.L0;
839 lightingInReflDir += apvSample.L0;
840 if (_APVSkyOcclusionWeight > 0)
841 {
842 bakeDiffuseLighting += EvaluateOccludedSky(apvSample, normalWS);
843 backBakeDiffuseLighting += EvaluateOccludedSky(apvSample, backNormalWS);
844 lightingInReflDir += EvaluateOccludedSky(apvSample, reflDir);
845 }
846
847 //if (_APVWeight < 1.f)
848 {
849 bakeDiffuseLighting = bakeDiffuseLighting * _APVWeight;
850 backBakeDiffuseLighting = backBakeDiffuseLighting * _APVWeight;
851 }
852 }
853 else
854 {
855 bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
856 backBakeDiffuseLighting = EvaluateAmbientProbe(backNormalWS);
857 lightingInReflDir = -1;
858 }
859}
860
861void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 viewDir,
862 in float2 positionSS, in uint renderingLayer, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting, out float4 probeOcclusion)
863{
864 bakeDiffuseLighting = float3(0.0, 0.0, 0.0);
865 backBakeDiffuseLighting = float3(0.0, 0.0, 0.0);
866
867 posWS = AddNoiseToSamplingPosition(posWS, positionSS, viewDir);
868
869 APVSample apvSample = SampleAPV(posWS, normalWS, renderingLayer, viewDir);
870#ifdef USE_APV_PROBE_OCCLUSION
871 probeOcclusion = apvSample.probeOcclusion;
872#else
873 probeOcclusion = 1;
874#endif
875
876 EvaluateAdaptiveProbeVolume(apvSample, normalWS, backNormalWS, bakeDiffuseLighting, backBakeDiffuseLighting);
877}
878
879void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 viewDir,
880 in float2 positionSS, in uint renderingLayer, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
881{
882 float4 unusedProbeOcclusion = 0;
883 EvaluateAdaptiveProbeVolume(posWS, normalWS, backNormalWS, viewDir, positionSS, renderingLayer, bakeDiffuseLighting, backBakeDiffuseLighting, unusedProbeOcclusion);
884}
885
886void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 viewDir, in float2 positionSS, in uint renderingLayer,
887 out float3 bakeDiffuseLighting, out float4 probeOcclusion)
888{
889 bakeDiffuseLighting = float3(0.0, 0.0, 0.0);
890
891 posWS = AddNoiseToSamplingPosition(posWS, positionSS, viewDir);
892
893 APVSample apvSample = SampleAPV(posWS, normalWS, renderingLayer, viewDir);
894#ifdef USE_APV_PROBE_OCCLUSION
895 probeOcclusion = apvSample.probeOcclusion;
896#else
897 probeOcclusion = 1;
898#endif
899
900 EvaluateAdaptiveProbeVolume(apvSample, normalWS, bakeDiffuseLighting);
901}
902
903void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 viewDir, in float2 positionSS, in uint renderingLayer,
904 out float3 bakeDiffuseLighting)
905{
906 float4 unusedProbeOcclusion = 0;
907 EvaluateAdaptiveProbeVolume(posWS, normalWS, viewDir, positionSS, renderingLayer, bakeDiffuseLighting, unusedProbeOcclusion);
908}
909
910void EvaluateAdaptiveProbeVolume(in float3 posWS, in float2 positionSS, out float3 bakeDiffuseLighting)
911{
912 APVResources apvRes = FillAPVResources();
913
914 posWS = AddNoiseToSamplingPosition(posWS, positionSS, 1);
915 posWS -= _APVWorldOffset;
916
917 float3 uvw;
918 if (TryToGetPoolUVW(apvRes, posWS, 0, 0, uvw))
919 {
920 bakeDiffuseLighting = SAMPLE_TEXTURE3D_LOD(apvRes.L0_L1Rx, s_linear_clamp_sampler, uvw, 0).rgb;
921 }
922 else
923 {
924 bakeDiffuseLighting = EvaluateAmbientProbe(0);
925 }
926}
927
928// public APIs for backward compatibility
929// to be removed after Unity 6
930void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 reflDir, in float3 viewDir, in float2 positionSS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting, out float3 lightingInReflDir)
931{ EvaluateAdaptiveProbeVolume(posWS, normalWS, backNormalWS, reflDir, viewDir, positionSS, 0xFFFFFFFF, bakeDiffuseLighting, backBakeDiffuseLighting, lightingInReflDir); }
932void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 viewDir, in float2 positionSS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
933{ EvaluateAdaptiveProbeVolume(posWS, normalWS, backNormalWS, viewDir, positionSS, 0xFFFFFFFF, bakeDiffuseLighting, backBakeDiffuseLighting); }
934void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 viewDir, in float2 positionSS, out float3 bakeDiffuseLighting)
935{ EvaluateAdaptiveProbeVolume(posWS, normalWS, viewDir, positionSS, 0xFFFFFFFF, bakeDiffuseLighting); }
936
937// -------------------------------------------------------------
938// Reflection Probe Normalization functions
939// -------------------------------------------------------------
940// Same idea as in Rendering of COD:IW [Drobot 2017]
941
942float EvaluateReflectionProbeSH(float3 sampleDir, float4 reflProbeSHL0L1, float4 reflProbeSHL2_1, float reflProbeSHL2_2)
943{
944 float outFactor = 0;
945 float L0 = reflProbeSHL0L1.x;
946 float L1 = dot(reflProbeSHL0L1.yzw, sampleDir);
947
948 outFactor = L0 + L1;
949
950#ifdef PROBE_VOLUMES_L2
951
952 // IMPORTANT: The encoding is unravelled C# side before being sent
953
954 float4 vB = sampleDir.xyzz * sampleDir.yzzx;
955 // First 4 coeff.
956 float L2 = dot(reflProbeSHL2_1, vB);
957 float vC = sampleDir.x * sampleDir.x - sampleDir.y * sampleDir.y;
958 L2 += reflProbeSHL2_2 * vC;
959
960 outFactor += L2;
961#endif // PROBE_VOLUMES_L2
962
963 return outFactor;
964}
965
966float GetReflectionProbeNormalizationFactor(float3 lightingInReflDir, float3 sampleDir, float4 reflProbeSHL0L1, float4 reflProbeSHL2_1, float reflProbeSHL2_2)
967{
968 float refProbeNormalization = EvaluateReflectionProbeSH(sampleDir, reflProbeSHL0L1, reflProbeSHL2_1, reflProbeSHL2_2);
969
970 float localNormalization = Luminance(real3(lightingInReflDir));
971 return lerp(1.f, clamp(SafeDiv(localNormalization, refProbeNormalization), _APVMinReflProbeNormalizationFactor, _APVMaxReflProbeNormalizationFactor), _APVWeight);
972
973}
974
975#endif // __PROBEVOLUME_HLSL__