You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
945 lines
33 KiB
945 lines
33 KiB
#ifndef __PROBEVOLUME_HLSL__
|
|
#define __PROBEVOLUME_HLSL__
|
|
|
|
#if defined(SHADER_API_MOBILE) || defined(SHADER_API_SWITCH)
|
|
//#define USE_APV_TEXTURE_HALF
|
|
#endif // SHADER_API_MOBILE || SHADER_API_SWITCH
|
|
|
|
#include "Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ShaderVariablesProbeVolumes.cs.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SphericalHarmonics.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
|
|
|
|
// Unpack variables
|
|
#define _WorldOffset _Offset_IndirectionEntryDim.xyz
|
|
#define _GlobalIndirectionEntryDim _Offset_IndirectionEntryDim.w
|
|
#define _MinBrickSize _PoolDim_MinBrickSize.w
|
|
#define _PoolDim _PoolDim_MinBrickSize.xyz
|
|
#define _RcpPoolDim _RcpPoolDim_XY.xyz
|
|
#define _RcpPoolDimXY _RcpPoolDim_XY.w
|
|
#define _MinEntryPosition _MinEntryPos_Noise.xyz
|
|
#define _PVSamplingNoise _MinEntryPos_Noise.w
|
|
#define _GlobalIndirectionDimension _IndicesDim_FrameIndex.xyz
|
|
#define _NoiseFrameIndex _IndicesDim_FrameIndex.w
|
|
#define _NormalBias _Biases_NormalizationClamp.x
|
|
#define _ViewBias _Biases_NormalizationClamp.y
|
|
#define _Weight _Weight_MinLoadedCellInEntries.x
|
|
#define _MinLoadedCellInEntries _Weight_MinLoadedCellInEntries.yzw
|
|
#define _MaxLoadedCellInEntries _MaxLoadedCellInEntries_LayerCount.xyz
|
|
#define _ProbeLayerCount (int)(_MaxLoadedCellInEntries_LayerCount.w)
|
|
#define _MinReflProbeNormalizationFactor _Biases_NormalizationClamp.z
|
|
#define _MaxReflProbeNormalizationFactor _Biases_NormalizationClamp.w
|
|
#define _LeakReductionMode _LeakReduction_SkyOcclusion.x
|
|
#define _MinValidNormalWeight _LeakReduction_SkyOcclusion.y
|
|
#define _SkyOcclusionIntensity _LeakReduction_SkyOcclusion.z
|
|
#define _EnableSkyOcclusionShadingDirection _LeakReduction_SkyOcclusion.w
|
|
|
|
#ifndef DECODE_SH
|
|
#include "Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/DecodeSH.hlsl"
|
|
#endif
|
|
|
|
#ifndef __AMBIENTPROBE_HLSL__
|
|
float3 EvaluateAmbientProbe(float3 normalWS)
|
|
{
|
|
return float3(0, 0, 0);
|
|
}
|
|
#endif
|
|
|
|
#ifndef UNITY_SHADER_VARIABLES_INCLUDED
|
|
SAMPLER(s_linear_clamp_sampler);
|
|
SAMPLER(s_point_clamp_sampler);
|
|
#endif
|
|
|
|
// TODO: Remove define when we are sure about what to do with this.
|
|
#define MANUAL_FILTERING 0
|
|
|
|
#ifdef USE_APV_TEXTURE_HALF
|
|
#define TEXTURE3D_APV TEXTURE3D_HALF
|
|
#else
|
|
#define TEXTURE3D_APV TEXTURE3D
|
|
#endif
|
|
|
|
struct APVResources
|
|
{
|
|
StructuredBuffer<int> index;
|
|
StructuredBuffer<float3> SkyPrecomputedDirections;
|
|
|
|
TEXTURE3D_APV(L0_L1Rx);
|
|
TEXTURE3D_APV(L1G_L1Ry);
|
|
TEXTURE3D_APV(L1B_L1Rz);
|
|
TEXTURE3D_APV(L2_0);
|
|
TEXTURE3D_APV(L2_1);
|
|
TEXTURE3D_APV(L2_2);
|
|
TEXTURE3D_APV(L2_3);
|
|
TEXTURE3D_APV(Validity);
|
|
|
|
TEXTURE3D_APV(SkyOcclusionL0L1);
|
|
TEXTURE3D(SkyShadingDirectionIndices);
|
|
};
|
|
|
|
struct APVResourcesRW
|
|
{
|
|
RWTexture3D<float4> L0_L1Rx;
|
|
RWTexture3D<float4> L1G_L1Ry;
|
|
RWTexture3D<float4> L1B_L1Rz;
|
|
RWTexture3D<float4> L2_0;
|
|
RWTexture3D<float4> L2_1;
|
|
RWTexture3D<float4> L2_2;
|
|
RWTexture3D<float4> L2_3;
|
|
};
|
|
|
|
#define LOAD_APV_RES_L1(res, target) \
|
|
res.L0_L1Rx = CALL_MERGE_NAME(target, _L0_L1Rx); \
|
|
res.L1G_L1Ry = CALL_MERGE_NAME(target, _L1G_L1Ry); \
|
|
res.L1B_L1Rz = CALL_MERGE_NAME(target, _L1B_L1Rz);
|
|
#define LOAD_APV_RES_L2(res, target) \
|
|
res.L2_0 = CALL_MERGE_NAME(target, _L2_0); \
|
|
res.L2_1 = CALL_MERGE_NAME(target, _L2_1); \
|
|
res.L2_2 = CALL_MERGE_NAME(target, _L2_2); \
|
|
res.L2_3 = CALL_MERGE_NAME(target, _L2_3);
|
|
|
|
#ifndef PROBE_VOLUMES_L2
|
|
# define LOAD_APV_RES(res, target) LOAD_APV_RES_L1(res, target)
|
|
#else
|
|
# define LOAD_APV_RES(res, target) \
|
|
LOAD_APV_RES_L1(res, target) \
|
|
LOAD_APV_RES_L2(res, target)
|
|
#endif
|
|
|
|
struct APVSample
|
|
{
|
|
half3 L0;
|
|
half3 L1_R;
|
|
half3 L1_G;
|
|
half3 L1_B;
|
|
#ifdef PROBE_VOLUMES_L2
|
|
half4 L2_R;
|
|
half4 L2_G;
|
|
half4 L2_B;
|
|
half3 L2_C;
|
|
#endif // PROBE_VOLUMES_L2
|
|
|
|
float4 skyOcclusionL0L1;
|
|
float3 skyShadingDirection;
|
|
|
|
#define APV_SAMPLE_STATUS_INVALID -1
|
|
#define APV_SAMPLE_STATUS_ENCODED 0
|
|
#define APV_SAMPLE_STATUS_DECODED 1
|
|
|
|
int status;
|
|
|
|
// Note: at the moment this is called at the moment the struct is built, but it is kept as a separate step
|
|
// as ideally should be called as far as possible from sample to allow for latency hiding.
|
|
void Decode()
|
|
{
|
|
if (status == APV_SAMPLE_STATUS_ENCODED)
|
|
{
|
|
L1_R = DecodeSH(L0.r, L1_R);
|
|
L1_G = DecodeSH(L0.g, L1_G);
|
|
L1_B = DecodeSH(L0.b, L1_B);
|
|
#ifdef PROBE_VOLUMES_L2
|
|
DecodeSH_L2(L0, L2_R, L2_G, L2_B, L2_C);
|
|
#endif // PROBE_VOLUMES_L2
|
|
|
|
status = APV_SAMPLE_STATUS_DECODED;
|
|
}
|
|
}
|
|
|
|
void Encode()
|
|
{
|
|
if (status == APV_SAMPLE_STATUS_DECODED)
|
|
{
|
|
L1_R = EncodeSH(L0.r, L1_R);
|
|
L1_G = EncodeSH(L0.g, L1_G);
|
|
L1_B = EncodeSH(L0.b, L1_B);
|
|
#ifdef PROBE_VOLUMES_L2
|
|
EncodeSH_L2(L0, L2_R, L2_G, L2_B, L2_C);
|
|
#endif // PROBE_VOLUMES_L2
|
|
|
|
status = APV_SAMPLE_STATUS_ENCODED;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Resources required for APV
|
|
StructuredBuffer<int> _APVResIndex;
|
|
StructuredBuffer<uint3> _APVResCellIndices;
|
|
StructuredBuffer<float3> _SkyPrecomputedDirections;
|
|
|
|
TEXTURE3D_APV(_APVResL0_L1Rx);
|
|
|
|
TEXTURE3D_APV(_APVResL1G_L1Ry);
|
|
TEXTURE3D_APV(_APVResL1B_L1Rz);
|
|
TEXTURE3D_APV(_APVResL2_0);
|
|
TEXTURE3D_APV(_APVResL2_1);
|
|
TEXTURE3D_APV(_APVResL2_2);
|
|
TEXTURE3D_APV(_APVResL2_3);
|
|
|
|
TEXTURE3D_APV(_APVResValidity);
|
|
|
|
TEXTURE3D_APV(_SkyOcclusionTexL0L1);
|
|
TEXTURE3D(_SkyShadingDirectionIndicesTex);
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
// Various weighting functions for occlusion or helper functions.
|
|
// -------------------------------------------------------------
|
|
float3 AddNoiseToSamplingPosition(float3 posWS, float2 positionSS, float3 direction)
|
|
{
|
|
#ifdef UNITY_SPACE_TRANSFORMS_INCLUDED
|
|
float3 right = mul((float3x3)GetViewToWorldMatrix(), float3(1.0, 0.0, 0.0));
|
|
float3 top = mul((float3x3)GetViewToWorldMatrix(), float3(0.0, 1.0, 0.0));
|
|
float noise01 = InterleavedGradientNoise(positionSS, _NoiseFrameIndex);
|
|
float noise02 = frac(noise01 * 100.0);
|
|
float noise03 = frac(noise01 * 1000.0);
|
|
direction += top * (noise02 - 0.5) + right * (noise03 - 0.5);
|
|
return _PVSamplingNoise > 0 ? posWS + noise01 * _PVSamplingNoise * direction : posWS;
|
|
#else
|
|
return posWS;
|
|
#endif
|
|
}
|
|
|
|
uint3 GetSampleOffset(uint i)
|
|
{
|
|
return uint3(i, i >> 1, i >> 2) & 1;
|
|
}
|
|
|
|
// The validity mask is sampled once and contains a binary info on whether a probe neighbour (relevant for trilinear) is to be used
|
|
// or not. The entry in the mask uses the same mapping that GetSampleOffset above uses.
|
|
half GetValidityWeight(uint offset, uint validityMask)
|
|
{
|
|
uint mask = 1U << offset;
|
|
return (validityMask & mask) > 0 ? 1 : 0;
|
|
}
|
|
|
|
float ProbeDistance(uint subdiv)
|
|
{
|
|
return pow(3, subdiv) * _MinBrickSize / 3.0f;
|
|
}
|
|
|
|
half ProbeDistanceHalf(uint subdiv)
|
|
{
|
|
return pow(half(3), half(subdiv)) * half(_MinBrickSize) / 3.0;
|
|
}
|
|
|
|
float3 GetSnappedProbePosition(float3 posWS, uint subdiv)
|
|
{
|
|
float3 distBetweenProbes = ProbeDistance(subdiv);
|
|
float3 dividedPos = posWS / distBetweenProbes;
|
|
return (dividedPos - frac(dividedPos)) * distBetweenProbes;
|
|
}
|
|
|
|
float GetNormalWeight(uint3 offset, float3 posWS, float3 sample0Pos, float3 normalWS, uint subdiv)
|
|
{
|
|
// TODO: This can be optimized.
|
|
float3 samplePos = (sample0Pos - posWS) + (float3)offset * ProbeDistance(subdiv);
|
|
float3 vecToProbe = normalize(samplePos);
|
|
float weight = saturate(dot(vecToProbe, normalWS) - _MinValidNormalWeight);
|
|
return weight;
|
|
}
|
|
|
|
half GetNormalWeightHalf(uint3 offset, float3 posWS, float3 sample0Pos, float3 normalWS, uint subdiv)
|
|
{
|
|
// TODO: This can be optimized.
|
|
half3 samplePos = (half3)(sample0Pos - posWS) + (half3)offset * ProbeDistanceHalf(subdiv);
|
|
half3 vecToProbe = normalize(samplePos);
|
|
half weight = saturate(dot(vecToProbe, (half3)normalWS) - (half)_MinValidNormalWeight);
|
|
return weight;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// Indexing functions
|
|
// -------------------------------------------------------------
|
|
|
|
bool LoadCellIndexMetaData(int cellFlatIdx, out int chunkIndex, out int stepSize, out int3 minRelativeIdx, out int3 maxRelativeIdxPlusOne)
|
|
{
|
|
bool cellIsLoaded = false;
|
|
uint3 metaData = _APVResCellIndices[cellFlatIdx];
|
|
|
|
if (metaData.x != 0xFFFFFFFF)
|
|
{
|
|
chunkIndex = metaData.x & 0x1FFFFFFF;
|
|
stepSize = round(pow(3, (metaData.x >> 29) & 0x7));
|
|
|
|
minRelativeIdx.x = metaData.y & 0x3FF;
|
|
minRelativeIdx.y = (metaData.y >> 10) & 0x3FF;
|
|
minRelativeIdx.z = (metaData.y >> 20) & 0x3FF;
|
|
|
|
maxRelativeIdxPlusOne.x = metaData.z & 0x3FF;
|
|
maxRelativeIdxPlusOne.y = (metaData.z >> 10) & 0x3FF;
|
|
maxRelativeIdxPlusOne.z = (metaData.z >> 20) & 0x3FF;
|
|
cellIsLoaded = true;
|
|
}
|
|
else
|
|
{
|
|
chunkIndex = -1;
|
|
stepSize = -1;
|
|
minRelativeIdx = -1;
|
|
maxRelativeIdxPlusOne = -1;
|
|
}
|
|
|
|
return cellIsLoaded;
|
|
}
|
|
|
|
uint GetIndexData(APVResources apvRes, float3 posWS)
|
|
{
|
|
float3 entryPos = floor(posWS / _GlobalIndirectionEntryDim);
|
|
float3 topLeftEntryWS = entryPos * _GlobalIndirectionEntryDim;
|
|
|
|
bool isALoadedCell = all(entryPos >= _MinLoadedCellInEntries) && all(entryPos <= _MaxLoadedCellInEntries);
|
|
|
|
// Make sure we start from 0
|
|
int3 entryPosInt = (int3)(entryPos - _MinEntryPosition);
|
|
|
|
int flatIdx = dot(entryPosInt, int3(1, (int)_GlobalIndirectionDimension.x, ((int)_GlobalIndirectionDimension.x * (int)_GlobalIndirectionDimension.y)));
|
|
|
|
int stepSize = 0;
|
|
int3 minRelativeIdx, maxRelativeIdxPlusOne;
|
|
int chunkIdx = -1;
|
|
bool isValidBrick = false;
|
|
int locationInPhysicalBuffer = 0;
|
|
|
|
// Dynamic branch must be enforced to avoid out-of-bounds memory access in LoadCellIndexMetaData
|
|
UNITY_BRANCH if (isALoadedCell)
|
|
{
|
|
if (LoadCellIndexMetaData(flatIdx, chunkIdx, stepSize, minRelativeIdx, maxRelativeIdxPlusOne))
|
|
{
|
|
float3 residualPosWS = posWS - topLeftEntryWS;
|
|
int3 localBrickIndex = floor(residualPosWS / (_MinBrickSize * stepSize));
|
|
localBrickIndex = min(localBrickIndex, (int3)(3 * 3 * 3 - 1)); // due to floating point issue, we may query an invalid brick
|
|
|
|
// Out of bounds.
|
|
isValidBrick = all(localBrickIndex >= minRelativeIdx) && all(localBrickIndex < maxRelativeIdxPlusOne);
|
|
|
|
int3 sizeOfValid = maxRelativeIdxPlusOne - minRelativeIdx;
|
|
// Relative to valid region
|
|
int3 localRelativeIndexLoc = (localBrickIndex - minRelativeIdx);
|
|
int flattenedLocationInCell = dot(localRelativeIndexLoc, int3(sizeOfValid.y, 1, sizeOfValid.x * sizeOfValid.y));
|
|
|
|
locationInPhysicalBuffer = chunkIdx * (int)PROBE_INDEX_CHUNK_SIZE + flattenedLocationInCell;
|
|
}
|
|
}
|
|
|
|
uint result = 0xffffffff;
|
|
|
|
// Dynamic branch must be enforced to avoid out-of-bounds memory access in the physical APV buffer
|
|
UNITY_BRANCH if (isValidBrick)
|
|
{
|
|
result = apvRes.index[locationInPhysicalBuffer];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// Loading functions
|
|
// -------------------------------------------------------------
|
|
APVResources FillAPVResources()
|
|
{
|
|
APVResources apvRes;
|
|
apvRes.index = _APVResIndex;
|
|
|
|
apvRes.L0_L1Rx = _APVResL0_L1Rx;
|
|
|
|
apvRes.L1G_L1Ry = _APVResL1G_L1Ry;
|
|
apvRes.L1B_L1Rz = _APVResL1B_L1Rz;
|
|
|
|
apvRes.L2_0 = _APVResL2_0;
|
|
apvRes.L2_1 = _APVResL2_1;
|
|
apvRes.L2_2 = _APVResL2_2;
|
|
apvRes.L2_3 = _APVResL2_3;
|
|
|
|
apvRes.Validity = _APVResValidity;
|
|
apvRes.SkyOcclusionL0L1 = _SkyOcclusionTexL0L1;
|
|
apvRes.SkyShadingDirectionIndices = _SkyShadingDirectionIndicesTex;
|
|
apvRes.SkyPrecomputedDirections = _SkyPrecomputedDirections;
|
|
|
|
return apvRes;
|
|
}
|
|
|
|
|
|
bool TryToGetPoolUVWAndSubdiv(APVResources apvRes, float3 posWSForSample, out float3 uvw, out uint subdiv)
|
|
{
|
|
// resolve the index
|
|
uint packed_pool_idx = GetIndexData(apvRes, posWSForSample.xyz);
|
|
|
|
// unpack pool idx
|
|
// size is encoded in the upper 4 bits
|
|
subdiv = (packed_pool_idx >> 28) & 15;
|
|
|
|
float flattened_pool_idx = packed_pool_idx & ((1 << 28) - 1);
|
|
float3 pool_idx;
|
|
pool_idx.z = floor(flattened_pool_idx * _RcpPoolDimXY);
|
|
flattened_pool_idx -= (pool_idx.z * (_PoolDim.x * _PoolDim.y));
|
|
pool_idx.y = floor(flattened_pool_idx * _RcpPoolDim.x);
|
|
pool_idx.x = floor(flattened_pool_idx - (pool_idx.y * _PoolDim.x));
|
|
|
|
// calculate uv offset and scale
|
|
float brickSizeWS = pow(3.0, subdiv) * _MinBrickSize;
|
|
float3 offset = frac(posWSForSample.xyz / brickSizeWS); // [0;1] in brick space
|
|
//offset = clamp( offset, 0.25, 0.75 ); // [0.25;0.75] in brick space (is this actually necessary?)
|
|
|
|
uvw = (pool_idx + 0.5 + (3.0 * offset)) * _RcpPoolDim; // add offset with brick footprint converted to text footprint in pool texel space
|
|
|
|
// no valid brick loaded for this index, fallback to ambient probe
|
|
// 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.
|
|
return packed_pool_idx != 0xffffffffu;
|
|
}
|
|
|
|
bool TryToGetPoolUVWAndSubdiv(APVResources apvRes, float3 posWS, float3 normalWS, float3 viewDirWS, out float3 uvw, out uint subdiv, out float3 biasedPosWS)
|
|
{
|
|
biasedPosWS = (posWS + normalWS * _NormalBias) + viewDirWS * _ViewBias;
|
|
return TryToGetPoolUVWAndSubdiv(apvRes, biasedPosWS, uvw, subdiv);
|
|
}
|
|
|
|
bool TryToGetPoolUVW(APVResources apvRes, float3 posWS, float3 normalWS, float3 viewDir, out float3 uvw)
|
|
{
|
|
uint unusedSubdiv;
|
|
float3 unusedPos;
|
|
return TryToGetPoolUVWAndSubdiv(apvRes, posWS, normalWS, viewDir, uvw, unusedSubdiv, unusedPos);
|
|
}
|
|
|
|
|
|
APVSample SampleAPV(APVResources apvRes, float3 uvw)
|
|
{
|
|
APVSample apvSample;
|
|
half4 L0_L1Rx = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L0_L1Rx, s_linear_clamp_sampler, uvw, 0).rgba);
|
|
half4 L1G_L1Ry = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L1G_L1Ry, s_linear_clamp_sampler, uvw, 0).rgba);
|
|
half4 L1B_L1Rz = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L1B_L1Rz, s_linear_clamp_sampler, uvw, 0).rgba);
|
|
|
|
apvSample.L0 = L0_L1Rx.xyz;
|
|
apvSample.L1_R = half3(L0_L1Rx.w, L1G_L1Ry.w, L1B_L1Rz.w);
|
|
apvSample.L1_G = L1G_L1Ry.xyz;
|
|
apvSample.L1_B = L1B_L1Rz.xyz;
|
|
|
|
#ifdef PROBE_VOLUMES_L2
|
|
apvSample.L2_R = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L2_0, s_linear_clamp_sampler, uvw, 0).rgba);
|
|
apvSample.L2_G = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L2_1, s_linear_clamp_sampler, uvw, 0).rgba);
|
|
apvSample.L2_B = half4(SAMPLE_TEXTURE3D_LOD(apvRes.L2_2, s_linear_clamp_sampler, uvw, 0).rgba);
|
|
apvSample.L2_C = half3(SAMPLE_TEXTURE3D_LOD(apvRes.L2_3, s_linear_clamp_sampler, uvw, 0).rgb);
|
|
#endif // PROBE_VOLUMES_L2
|
|
|
|
if (_SkyOcclusionIntensity > 0)
|
|
apvSample.skyOcclusionL0L1 = SAMPLE_TEXTURE3D_LOD(apvRes.SkyOcclusionL0L1, s_linear_clamp_sampler, uvw, 0).rgba;
|
|
else
|
|
apvSample.skyOcclusionL0L1 = float4(0, 0, 0, 0);
|
|
|
|
if (_EnableSkyOcclusionShadingDirection > 0)
|
|
{
|
|
// No interpolation for sky shading indices
|
|
float3 texCoordFloat = uvw * _PoolDim - 0.5f;
|
|
int3 texCoordInt = texCoordFloat;
|
|
uint index = LOAD_TEXTURE3D(apvRes.SkyShadingDirectionIndices, texCoordInt).x * 255.0;
|
|
|
|
if (index == 255)
|
|
apvSample.skyShadingDirection = float3(0, 0, 0);
|
|
else
|
|
apvSample.skyShadingDirection = apvRes.SkyPrecomputedDirections[index].rgb;
|
|
}
|
|
else
|
|
apvSample.skyShadingDirection = float3(0, 0, 0);
|
|
|
|
apvSample.status = APV_SAMPLE_STATUS_ENCODED;
|
|
|
|
return apvSample;
|
|
}
|
|
|
|
APVSample LoadAndDecodeAPV(APVResources apvRes, int3 loc)
|
|
{
|
|
APVSample apvSample;
|
|
|
|
half4 L0_L1Rx = half4(LOAD_TEXTURE3D(apvRes.L0_L1Rx, loc).rgba);
|
|
half4 L1G_L1Ry = half4(LOAD_TEXTURE3D(apvRes.L1G_L1Ry, loc).rgba);
|
|
half4 L1B_L1Rz = half4(LOAD_TEXTURE3D(apvRes.L1B_L1Rz, loc).rgba);
|
|
|
|
apvSample.L0 = L0_L1Rx.xyz;
|
|
apvSample.L1_R = half3(L0_L1Rx.w, L1G_L1Ry.w, L1B_L1Rz.w);
|
|
apvSample.L1_G = L1G_L1Ry.xyz;
|
|
apvSample.L1_B = L1B_L1Rz.xyz;
|
|
|
|
#ifdef PROBE_VOLUMES_L2
|
|
apvSample.L2_R = half4(LOAD_TEXTURE3D(apvRes.L2_0, loc).rgba);
|
|
apvSample.L2_G = half4(LOAD_TEXTURE3D(apvRes.L2_1, loc).rgba);
|
|
apvSample.L2_B = half4(LOAD_TEXTURE3D(apvRes.L2_2, loc).rgba);
|
|
apvSample.L2_C = half3(LOAD_TEXTURE3D(apvRes.L2_3, loc).rgb);
|
|
#endif // PROBE_VOLUMES_L2
|
|
|
|
apvSample.status = APV_SAMPLE_STATUS_ENCODED;
|
|
apvSample.Decode();
|
|
|
|
return apvSample;
|
|
}
|
|
|
|
void WeightSample(inout APVSample apvSample, half weight)
|
|
{
|
|
apvSample.L0 *= weight;
|
|
apvSample.L1_R *= weight;
|
|
apvSample.L1_G *= weight;
|
|
apvSample.L1_B *= weight;
|
|
|
|
#ifdef PROBE_VOLUMES_L2
|
|
apvSample.L2_R *= weight;
|
|
apvSample.L2_G *= weight;
|
|
apvSample.L2_B *= weight;
|
|
apvSample.L2_C *= weight;
|
|
#endif // PROBE_VOLUMES_L2
|
|
}
|
|
|
|
void AccumulateSamples(inout APVSample dst, APVSample other, half weight)
|
|
{
|
|
WeightSample(other, weight);
|
|
dst.L0 += other.L0;
|
|
dst.L1_R += other.L1_R;
|
|
dst.L1_G += other.L1_G;
|
|
dst.L1_B += other.L1_B;
|
|
|
|
#ifdef PROBE_VOLUMES_L2
|
|
dst.L2_R += other.L2_R;
|
|
dst.L2_G += other.L2_G;
|
|
dst.L2_B += other.L2_B;
|
|
dst.L2_C += other.L2_C;
|
|
#endif // PROBE_VOLUMES_L2
|
|
}
|
|
|
|
uint LoadValidityMask(APVResources apvRes, uint renderingLayer, int3 coord)
|
|
{
|
|
float rawValidity = LOAD_TEXTURE3D(apvRes.Validity, coord).x;
|
|
|
|
uint validityMask;
|
|
if (_ProbeLayerCount == 1)
|
|
{
|
|
validityMask = rawValidity * 255.0;
|
|
}
|
|
else
|
|
{
|
|
// If the object is on none of the masks, enable all layers to still sample validity correctly
|
|
uint globalLayer = _ProbeVolumeLayerMask[0] | _ProbeVolumeLayerMask[1] | _ProbeVolumeLayerMask[2] | _ProbeVolumeLayerMask[3];
|
|
if ((renderingLayer & globalLayer) == 0) renderingLayer = 0xFFFFFFFF;
|
|
|
|
validityMask = 0;
|
|
if ((renderingLayer & _ProbeVolumeLayerMask[0]) != 0)
|
|
validityMask = asuint(rawValidity);
|
|
if ((renderingLayer & _ProbeVolumeLayerMask[1]) != 0)
|
|
validityMask |= asuint(rawValidity) >> 8;
|
|
if ((renderingLayer & _ProbeVolumeLayerMask[2]) != 0)
|
|
validityMask |= asuint(rawValidity) >> 16;
|
|
if ((renderingLayer & _ProbeVolumeLayerMask[3]) != 0)
|
|
validityMask |= asuint(rawValidity) >> 24;
|
|
validityMask = validityMask & 0xFF;
|
|
}
|
|
|
|
return validityMask;
|
|
}
|
|
|
|
APVSample ManuallyFilteredSample(APVResources apvRes, float3 posWS, float3 normalWS, uint renderingLayer, int subdiv, float3 biasedPosWS, float3 uvw)
|
|
{
|
|
float3 texCoordFloat = uvw * _PoolDim - .5f;
|
|
int3 texCoordInt = texCoordFloat;
|
|
float3 texFrac = frac(texCoordFloat);
|
|
float3 oneMinTexFrac = 1.0f - texFrac;
|
|
|
|
bool sampled = false;
|
|
float totalW = 0.0f;
|
|
|
|
APVSample baseSample;
|
|
|
|
float3 positionCentralProbe = GetSnappedProbePosition(biasedPosWS, subdiv);
|
|
|
|
ZERO_INITIALIZE(APVSample, baseSample);
|
|
|
|
uint validityMask = LoadValidityMask(apvRes, renderingLayer, texCoordInt);
|
|
for (uint i = 0; i < 8; ++i)
|
|
{
|
|
uint3 offset = GetSampleOffset(i);
|
|
float trilinearW =
|
|
((offset.x == 1) ? texFrac.x : oneMinTexFrac.x) *
|
|
((offset.y == 1) ? texFrac.y : oneMinTexFrac.y) *
|
|
((offset.z == 1) ? texFrac.z : oneMinTexFrac.z);
|
|
|
|
half validityWeight = GetValidityWeight(i, validityMask);
|
|
|
|
if (validityWeight > 0)
|
|
{
|
|
APVSample apvSample = LoadAndDecodeAPV(apvRes, texCoordInt + offset);
|
|
half geoW = GetNormalWeightHalf(offset, posWS, positionCentralProbe, normalWS, subdiv);
|
|
|
|
half finalW = half(geoW * trilinearW);
|
|
AccumulateSamples(baseSample, apvSample, finalW);
|
|
totalW += finalW;
|
|
}
|
|
}
|
|
|
|
WeightSample(baseSample, half(rcp(totalW)));
|
|
|
|
return baseSample;
|
|
}
|
|
|
|
void WarpUVWLeakReduction(APVResources apvRes, float3 posWS, float3 normalWS, uint renderingLayer, uint subdiv, float3 biasedPosWS, inout float3 uvw, out float3 normalizedOffset, out float validityWeights[8])
|
|
{
|
|
float3 texCoordFloat = uvw * _PoolDim - 0.5f;
|
|
int3 texCoordInt = texCoordFloat;
|
|
half3 texFrac = half3(frac(texCoordFloat));
|
|
uint validityMask = LoadValidityMask(apvRes, renderingLayer, texCoordInt);
|
|
|
|
if (_LeakReductionMode == APVLEAKREDUCTIONMODE_VALIDITY_AND_NORMAL_BASED || validityMask != 0xFF)
|
|
{
|
|
half4 weights[2];
|
|
half totalW = 0.0;
|
|
|
|
float3 positionCentralProbe = GetSnappedProbePosition(biasedPosWS, subdiv);
|
|
half3 oneMinTexFrac = 1.0 - texFrac;
|
|
uint i = 0;
|
|
|
|
UNITY_UNROLL
|
|
for (i = 0; i < 8; ++i)
|
|
{
|
|
uint3 offset = GetSampleOffset(i);
|
|
half validityWeight =
|
|
((offset.x == 1) ? texFrac.x : oneMinTexFrac.x) *
|
|
((offset.y == 1) ? texFrac.y : oneMinTexFrac.y) *
|
|
((offset.z == 1) ? texFrac.z : oneMinTexFrac.z);
|
|
|
|
validityWeight *= GetValidityWeight(i, validityMask);
|
|
|
|
if (_LeakReductionMode == APVLEAKREDUCTIONMODE_VALIDITY_AND_NORMAL_BASED)
|
|
validityWeight *= GetNormalWeightHalf(offset, posWS, positionCentralProbe, normalWS, subdiv);
|
|
|
|
half weight = saturate(validityWeight);
|
|
|
|
weights[i/4][i%4] = weight;
|
|
totalW += weight;
|
|
}
|
|
|
|
half rcpTotalW = rcp(max(0.0001, totalW));
|
|
weights[0] *= rcpTotalW;
|
|
weights[1] *= rcpTotalW;
|
|
|
|
half3 fracOffset = -texFrac;
|
|
|
|
UNITY_UNROLL
|
|
for (i = 0; i < 8; ++i)
|
|
{
|
|
uint3 offset = GetSampleOffset(i);
|
|
fracOffset += (half3)offset * weights[i/4][i%4];
|
|
}
|
|
|
|
uvw = uvw + (float3)fracOffset * _RcpPoolDim;
|
|
}
|
|
|
|
// Output values used for debug only
|
|
UNITY_UNROLL
|
|
for (uint i = 0; i < 8; i++)
|
|
{
|
|
int3 probeCoord = GetSampleOffset(i);
|
|
half validityWeight = GetValidityWeight(i, validityMask);
|
|
validityWeights[i] = validityWeight;
|
|
}
|
|
|
|
normalizedOffset = (float3)(uvw * _PoolDim - (texCoordInt + 0.5));
|
|
}
|
|
|
|
void WarpUVWLeakReduction(APVResources apvRes, float3 posWS, float3 normalWS, uint renderingLayer, uint subdiv, float3 biasedPosWS, inout float3 uvw)
|
|
{
|
|
float3 normalizedOffset;
|
|
float validityWeights[8];
|
|
WarpUVWLeakReduction(apvRes, posWS, normalWS, renderingLayer, subdiv, biasedPosWS, uvw, normalizedOffset, validityWeights);
|
|
}
|
|
|
|
APVSample SampleAPV(APVResources apvRes, float3 posWS, float3 biasNormalWS, uint renderingLayer, float3 viewDir)
|
|
{
|
|
APVSample outSample;
|
|
|
|
posWS -= _WorldOffset;
|
|
|
|
float3 pool_uvw;
|
|
uint subdiv;
|
|
float3 biasedPosWS;
|
|
if (TryToGetPoolUVWAndSubdiv(apvRes, posWS, biasNormalWS, viewDir, pool_uvw, subdiv, biasedPosWS))
|
|
{
|
|
#if MANUAL_FILTERING == 1
|
|
if (_LeakReductionMode != 0)
|
|
outSample = ManuallyFilteredSample(apvRes, posWS, biasNormalWS, renderingLayer, subdiv, biasedPosWS, pool_uvw);
|
|
else
|
|
outSample = SampleAPV(apvRes, pool_uvw);
|
|
#else
|
|
if (_LeakReductionMode != 0)
|
|
{
|
|
WarpUVWLeakReduction(apvRes, posWS, biasNormalWS, renderingLayer, subdiv, biasedPosWS, pool_uvw);
|
|
}
|
|
outSample = SampleAPV(apvRes, pool_uvw);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ZERO_INITIALIZE(APVSample, outSample);
|
|
outSample.status = APV_SAMPLE_STATUS_INVALID;
|
|
}
|
|
|
|
return outSample;
|
|
}
|
|
|
|
|
|
APVSample SampleAPV(float3 posWS, float3 biasNormalWS, uint renderingLayer, float3 viewDir)
|
|
{
|
|
APVResources apvRes = FillAPVResources();
|
|
return SampleAPV(apvRes, posWS, biasNormalWS, renderingLayer, viewDir);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// Dynamic Sky Handling
|
|
// -------------------------------------------------------------
|
|
|
|
// Expects Layout DC, x, y, z
|
|
// See on baking side in DynamicGISkyOcclusion.hlsl
|
|
float EvalSHSkyOcclusion(float3 dir, APVSample apvSample)
|
|
{
|
|
// L0 L1
|
|
float4 temp = float4(kSHBasis0, kSHBasis1 * dir.x, kSHBasis1 * dir.y, kSHBasis1 * dir.z);
|
|
return _SkyOcclusionIntensity * dot(temp, apvSample.skyOcclusionL0L1);
|
|
}
|
|
|
|
float3 EvaluateOccludedSky(APVSample apvSample, float3 N)
|
|
{
|
|
float occValue = EvalSHSkyOcclusion(N, apvSample);
|
|
float3 shadingNormal = N;
|
|
|
|
if (_EnableSkyOcclusionShadingDirection > 0)
|
|
{
|
|
shadingNormal = apvSample.skyShadingDirection;
|
|
float normSquared = dot(shadingNormal, shadingNormal);
|
|
if (normSquared < 0.2f)
|
|
shadingNormal = N;
|
|
else
|
|
{
|
|
shadingNormal = shadingNormal * rsqrt(normSquared);
|
|
}
|
|
}
|
|
return occValue * EvaluateAmbientProbe(shadingNormal);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// Internal Evaluation functions (avoid usage in caller code outside this file)
|
|
// -------------------------------------------------------------
|
|
float3 EvaluateAPVL0(APVSample apvSample)
|
|
{
|
|
return apvSample.L0;
|
|
}
|
|
|
|
void EvaluateAPVL1(APVSample apvSample, float3 N, out float3 diffuseLighting)
|
|
{
|
|
diffuseLighting = SHEvalLinearL1(N, apvSample.L1_R, apvSample.L1_G, apvSample.L1_B);
|
|
}
|
|
|
|
#ifdef PROBE_VOLUMES_L2
|
|
void EvaluateAPVL1L2(APVSample apvSample, float3 N, out float3 diffuseLighting)
|
|
{
|
|
EvaluateAPVL1(apvSample, N, diffuseLighting);
|
|
diffuseLighting += SHEvalLinearL2(N, apvSample.L2_R, apvSample.L2_G, apvSample.L2_B, float4(apvSample.L2_C, 0.0f));
|
|
}
|
|
#endif
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
// "Public" Evaluation functions, the one that callers outside this file should use
|
|
// -------------------------------------------------------------
|
|
void EvaluateAdaptiveProbeVolume(APVSample apvSample, float3 normalWS, out float3 bakeDiffuseLighting)
|
|
{
|
|
if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
|
|
{
|
|
apvSample.Decode();
|
|
|
|
#if defined(PROBE_VOLUMES_L1)
|
|
EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
|
|
#elif defined(PROBE_VOLUMES_L2)
|
|
EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
|
|
#endif
|
|
|
|
bakeDiffuseLighting += apvSample.L0;
|
|
if (_SkyOcclusionIntensity > 0)
|
|
bakeDiffuseLighting += EvaluateOccludedSky(apvSample, normalWS);
|
|
|
|
//if (_Weight < 1.f)
|
|
{
|
|
bakeDiffuseLighting = bakeDiffuseLighting * _Weight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no valid brick, fallback to ambient probe
|
|
bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
|
|
}
|
|
}
|
|
|
|
void EvaluateAdaptiveProbeVolume(APVSample apvSample, float3 normalWS, float3 backNormalWS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
|
|
{
|
|
if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
|
|
{
|
|
apvSample.Decode();
|
|
|
|
#ifdef PROBE_VOLUMES_L1
|
|
EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
|
|
EvaluateAPVL1(apvSample, backNormalWS, backBakeDiffuseLighting);
|
|
#elif defined(PROBE_VOLUMES_L2)
|
|
EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
|
|
EvaluateAPVL1L2(apvSample, backNormalWS, backBakeDiffuseLighting);
|
|
#endif
|
|
|
|
bakeDiffuseLighting += apvSample.L0;
|
|
backBakeDiffuseLighting += apvSample.L0;
|
|
if (_SkyOcclusionIntensity > 0)
|
|
{
|
|
bakeDiffuseLighting += EvaluateOccludedSky(apvSample, normalWS);
|
|
backBakeDiffuseLighting += EvaluateOccludedSky(apvSample, backNormalWS);
|
|
}
|
|
|
|
//if (_Weight < 1.f)
|
|
{
|
|
bakeDiffuseLighting = bakeDiffuseLighting * _Weight;
|
|
backBakeDiffuseLighting = backBakeDiffuseLighting * _Weight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no valid brick, fallback to ambient probe
|
|
bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
|
|
backBakeDiffuseLighting = EvaluateAmbientProbe(backNormalWS);
|
|
}
|
|
}
|
|
|
|
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 reflDir, in float3 viewDir,
|
|
in float2 positionSS, in uint renderingLayer, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting, out float3 lightingInReflDir)
|
|
{
|
|
APVResources apvRes = FillAPVResources();
|
|
|
|
posWS = AddNoiseToSamplingPosition(posWS, positionSS, viewDir);
|
|
|
|
APVSample apvSample = SampleAPV(posWS, normalWS, renderingLayer, viewDir);
|
|
|
|
if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
|
|
{
|
|
#if MANUAL_FILTERING == 0
|
|
apvSample.Decode();
|
|
#endif
|
|
|
|
#ifdef PROBE_VOLUMES_L1
|
|
EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
|
|
EvaluateAPVL1(apvSample, backNormalWS, backBakeDiffuseLighting);
|
|
EvaluateAPVL1(apvSample, reflDir, lightingInReflDir);
|
|
#elif defined(PROBE_VOLUMES_L2)
|
|
EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
|
|
EvaluateAPVL1L2(apvSample, backNormalWS, backBakeDiffuseLighting);
|
|
EvaluateAPVL1L2(apvSample, reflDir, lightingInReflDir);
|
|
#endif
|
|
|
|
bakeDiffuseLighting += apvSample.L0;
|
|
backBakeDiffuseLighting += apvSample.L0;
|
|
lightingInReflDir += apvSample.L0;
|
|
if (_SkyOcclusionIntensity > 0)
|
|
{
|
|
bakeDiffuseLighting += EvaluateOccludedSky(apvSample, normalWS);
|
|
backBakeDiffuseLighting += EvaluateOccludedSky(apvSample, backNormalWS);
|
|
lightingInReflDir += EvaluateOccludedSky(apvSample, reflDir);
|
|
}
|
|
|
|
//if (_Weight < 1.f)
|
|
{
|
|
bakeDiffuseLighting = bakeDiffuseLighting * _Weight;
|
|
backBakeDiffuseLighting = backBakeDiffuseLighting * _Weight;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
|
|
backBakeDiffuseLighting = EvaluateAmbientProbe(backNormalWS);
|
|
lightingInReflDir = -1;
|
|
}
|
|
}
|
|
|
|
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 viewDir,
|
|
in float2 positionSS, in uint renderingLayer, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
|
|
{
|
|
bakeDiffuseLighting = float3(0.0, 0.0, 0.0);
|
|
backBakeDiffuseLighting = float3(0.0, 0.0, 0.0);
|
|
|
|
posWS = AddNoiseToSamplingPosition(posWS, positionSS, viewDir);
|
|
|
|
APVSample apvSample = SampleAPV(posWS, normalWS, renderingLayer, viewDir);
|
|
EvaluateAdaptiveProbeVolume(apvSample, normalWS, backNormalWS, bakeDiffuseLighting, backBakeDiffuseLighting);
|
|
}
|
|
|
|
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 viewDir, in float2 positionSS, in uint renderingLayer,
|
|
out float3 bakeDiffuseLighting)
|
|
{
|
|
bakeDiffuseLighting = float3(0.0, 0.0, 0.0);
|
|
|
|
posWS = AddNoiseToSamplingPosition(posWS, positionSS, viewDir);
|
|
|
|
APVSample apvSample = SampleAPV(posWS, normalWS, renderingLayer, viewDir);
|
|
EvaluateAdaptiveProbeVolume(apvSample, normalWS, bakeDiffuseLighting);
|
|
}
|
|
|
|
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float2 positionSS, out float3 bakeDiffuseLighting)
|
|
{
|
|
APVResources apvRes = FillAPVResources();
|
|
|
|
posWS = AddNoiseToSamplingPosition(posWS, positionSS, 1);
|
|
posWS -= _WorldOffset;
|
|
|
|
float3 uvw;
|
|
if (TryToGetPoolUVW(apvRes, posWS, 0, 0, uvw))
|
|
{
|
|
bakeDiffuseLighting = SAMPLE_TEXTURE3D_LOD(apvRes.L0_L1Rx, s_linear_clamp_sampler, uvw, 0).rgb;
|
|
}
|
|
else
|
|
{
|
|
bakeDiffuseLighting = EvaluateAmbientProbe(0);
|
|
}
|
|
}
|
|
|
|
// public APIs for backward compatibility
|
|
// to be removed after Unity 6
|
|
void 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)
|
|
{ EvaluateAdaptiveProbeVolume(posWS, normalWS, backNormalWS, reflDir, viewDir, positionSS, 0xFFFFFFFF, bakeDiffuseLighting, backBakeDiffuseLighting, lightingInReflDir); }
|
|
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 viewDir, in float2 positionSS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
|
|
{ EvaluateAdaptiveProbeVolume(posWS, normalWS, backNormalWS, viewDir, positionSS, 0xFFFFFFFF, bakeDiffuseLighting, backBakeDiffuseLighting); }
|
|
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 viewDir, in float2 positionSS, out float3 bakeDiffuseLighting)
|
|
{ EvaluateAdaptiveProbeVolume(posWS, normalWS, viewDir, positionSS, 0xFFFFFFFF, bakeDiffuseLighting); }
|
|
|
|
// -------------------------------------------------------------
|
|
// Reflection Probe Normalization functions
|
|
// -------------------------------------------------------------
|
|
// Same idea as in Rendering of COD:IW [Drobot 2017]
|
|
|
|
float EvaluateReflectionProbeSH(float3 sampleDir, float4 reflProbeSHL0L1, float4 reflProbeSHL2_1, float reflProbeSHL2_2)
|
|
{
|
|
float outFactor = 0;
|
|
float L0 = reflProbeSHL0L1.x;
|
|
float L1 = dot(reflProbeSHL0L1.yzw, sampleDir);
|
|
|
|
outFactor = L0 + L1;
|
|
|
|
#ifdef PROBE_VOLUMES_L2
|
|
|
|
// IMPORTANT: The encoding is unravelled C# side before being sent
|
|
|
|
float4 vB = sampleDir.xyzz * sampleDir.yzzx;
|
|
// First 4 coeff.
|
|
float L2 = dot(reflProbeSHL2_1, vB);
|
|
float vC = sampleDir.x * sampleDir.x - sampleDir.y * sampleDir.y;
|
|
L2 += reflProbeSHL2_2 * vC;
|
|
|
|
outFactor += L2;
|
|
#endif // PROBE_VOLUMES_L2
|
|
|
|
return outFactor;
|
|
}
|
|
|
|
float GetReflectionProbeNormalizationFactor(float3 lightingInReflDir, float3 sampleDir, float4 reflProbeSHL0L1, float4 reflProbeSHL2_1, float reflProbeSHL2_2)
|
|
{
|
|
float refProbeNormalization = EvaluateReflectionProbeSH(sampleDir, reflProbeSHL0L1, reflProbeSHL2_1, reflProbeSHL2_2);
|
|
|
|
float localNormalization = Luminance(real3(lightingInReflDir));
|
|
return lerp(1.f, clamp(SafeDiv(localNormalization, refProbeNormalization), _MinReflProbeNormalizationFactor, _MaxReflProbeNormalizationFactor), _Weight);
|
|
|
|
}
|
|
|
|
#endif // __PROBEVOLUME_HLSL__
|