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.
389 lines
15 KiB
389 lines
15 KiB
#ifndef VOLUMETRIC_CLOUDS_DENOISING_H
|
|
#define VOLUMETRIC_CLOUDS_DENOISING_H
|
|
|
|
// Need a max to avoid infinitely far away points
|
|
#define MAX_VOLUMETRIC_CLOUDS_DISTANCE 200000.0f
|
|
|
|
// Half resolution volumetric cloud texture
|
|
TEXTURE2D_X(_VolumetricCloudsTexture);
|
|
TEXTURE2D_X(_DepthStatusTexture);
|
|
|
|
// Clouds data
|
|
TEXTURE2D_X(_CloudsLightingTexture);
|
|
TEXTURE2D_X(_CloudsDepthTexture);
|
|
|
|
// Half resolution depth buffer
|
|
TEXTURE2D_X(_HalfResDepthBuffer);
|
|
|
|
// Given that the sky is virtually a skybox, we cannot use the motion vector buffer
|
|
float2 EvaluateCloudMotionVectors(float2 fullResCoord, float deviceDepth, float positionFlag)
|
|
{
|
|
float3 V = GetCloudViewDirWS(fullResCoord);
|
|
|
|
float depth = min(DecodeInfiniteDepth(deviceDepth, _CloudNearPlane), MAX_VOLUMETRIC_CLOUDS_DISTANCE);
|
|
float4 worldPos = float4(V * depth, positionFlag);
|
|
float4 prevPos = worldPos;
|
|
|
|
float4 curClipPos = mul(UNITY_MATRIX_UNJITTERED_VP, worldPos);
|
|
float4 prevClipPos = mul(_CameraPrevViewProjection[unity_StereoEyeIndex], prevPos);
|
|
|
|
float2 previousPositionCS = prevClipPos.xy / prevClipPos.w;
|
|
float2 positionCS = curClipPos.xy / curClipPos.w;
|
|
|
|
// Convert from Clip space (-1..1) to NDC 0..1 space
|
|
float2 velocity = (positionCS - previousPositionCS) * 0.5;
|
|
#if UNITY_UV_STARTS_AT_TOP
|
|
velocity.y = -velocity.y;
|
|
#endif
|
|
return velocity;
|
|
}
|
|
|
|
bool EvaluateDepthDifference(float depthPrev, float depthCurr)
|
|
{
|
|
if ((depthPrev == UNITY_RAW_FAR_CLIP_VALUE) != (depthCurr == UNITY_RAW_FAR_CLIP_VALUE))
|
|
return false;
|
|
else
|
|
{
|
|
float linearDepthP = Linear01Depth(depthPrev, _ZBufferParams);
|
|
float linearDepthC = Linear01Depth(depthCurr, _ZBufferParams);
|
|
//return abs(linearDepthP - linearDepthC) <= linearDepthC * 0.2;
|
|
return abs(linearDepthP/linearDepthC - 1.0f) <= 0.2;
|
|
}
|
|
}
|
|
|
|
float4 ClipCloudsToRegion(float4 history, float4 minimum, float4 maximum, inout float validityFactor)
|
|
{
|
|
// The transmittance is overriden using a clamp
|
|
float clampedTransmittance = clamp(history.w, minimum.w, maximum.w);
|
|
|
|
// The lighting is overriden using a clip
|
|
float3 center = 0.5 * (maximum.xyz + minimum.xyz);
|
|
float3 extents = 0.5 * (maximum.xyz - minimum.xyz);
|
|
|
|
// This is actually `distance`, however the keyword is reserved
|
|
float3 offset = history.xyz - center;
|
|
float3 v_unit = offset.xyz / extents.xyz;
|
|
float3 absUnit = abs(v_unit);
|
|
float maxUnit = Max3(absUnit.x, absUnit.y, absUnit.z);
|
|
|
|
// We make the history less valid if we had to clip it
|
|
validityFactor *= maxUnit > 1.0 ? 0.5 : 1.0;
|
|
|
|
if (maxUnit > 1.0)
|
|
return float4(center + (offset / maxUnit), clampedTransmittance);
|
|
else
|
|
return float4(history.xyz, clampedTransmittance);
|
|
}
|
|
|
|
float4 Linear01Depth(float4 depth, float4 zBufferParam)
|
|
{
|
|
return float4(
|
|
Linear01Depth(depth.x, zBufferParam),
|
|
Linear01Depth(depth.y, zBufferParam),
|
|
Linear01Depth(depth.z, zBufferParam),
|
|
Linear01Depth(depth.w, zBufferParam));
|
|
}
|
|
|
|
#ifndef WITHOUT_LDS
|
|
// Our dispatch is a 8x8 tile. We can access up to 3x3 values at dispatch's half resolution
|
|
// around the center pixel which represents a total of 36 uniques values for the tile.
|
|
groupshared float4 gs_cacheRGBA[36];
|
|
groupshared float gs_cacheDP[36];
|
|
groupshared float gs_cacheDC[36];
|
|
groupshared float gs_cachePS[36];
|
|
|
|
// Init LDS
|
|
void FillCloudReprojectionLDS(uint groupIndex, uint2 groupOrigin)
|
|
{
|
|
// Define which value we will be acessing with this worker thread
|
|
int acessCoordX = groupIndex % 6;
|
|
int acessCoordY = groupIndex / 6;
|
|
|
|
// Everything we are accessing is in trace res (quarter rez).
|
|
uint2 traceGroupOrigin = groupOrigin / 2;
|
|
|
|
// The initial position of the access
|
|
int2 originXY = traceGroupOrigin - int2(1, 1);
|
|
|
|
// Compute the sample position
|
|
int2 sampleCoord = clamp(originXY + int2(acessCoordX, acessCoordY), 0, _TraceScreenSize.xy - 1);
|
|
|
|
// The representative coordinate to use depends if we are using the checkerboard integration pattern (or not)
|
|
int2 representativeCoord = sampleCoord * 2;
|
|
if (_EnableIntegration)
|
|
representativeCoord += ComputeCheckerBoardOffset(sampleCoord, _SubPixelIndex);
|
|
|
|
// Read the sample values
|
|
float sampleDP = LOAD_TEXTURE2D_X(_CameraDepthTexture, _ReprojDepthMipOffset + representativeCoord).x;
|
|
float3 sampleVal = LOAD_TEXTURE2D_X(_CloudsLightingTexture, sampleCoord).xyz;
|
|
float2 sampleDC_TR = LOAD_TEXTURE2D_X(_CloudsDepthTexture, sampleCoord).xy;
|
|
|
|
// Store into the LDS
|
|
gs_cacheRGBA[groupIndex] = float4(sampleVal.rgb, sampleDC_TR.y);
|
|
gs_cacheDP[groupIndex] = sampleDP;
|
|
gs_cacheDC[groupIndex] = sampleDC_TR.x;
|
|
}
|
|
|
|
void FillLDSUpscale(uint groupIndex, uint2 groupOrigin)
|
|
{
|
|
// Define which value we will be acessing with this worker thread
|
|
int acessCoordX = groupIndex % 6;
|
|
int acessCoordY = groupIndex / 6;
|
|
|
|
// Everything we are accessing is in intermediate res (half rez).
|
|
uint2 traceGroupOrigin = groupOrigin / 2;
|
|
|
|
// The initial position of the access
|
|
int2 originXY = traceGroupOrigin - int2(1, 1);
|
|
|
|
// Compute the sample position
|
|
int2 sampleCoord = clamp(originXY + int2(acessCoordX, acessCoordY), 0, _IntermediateScreenSize.xy - 1);
|
|
|
|
// Read the sample value
|
|
float3 sampleVal = LOAD_TEXTURE2D_X(_VolumetricCloudsTexture, sampleCoord).xyz;
|
|
float4 depthStatusValue = LOAD_TEXTURE2D_X(_DepthStatusTexture, sampleCoord);
|
|
|
|
// Store into the LDS
|
|
gs_cacheRGBA[groupIndex] = float4(sampleVal.rgb, depthStatusValue.a);
|
|
gs_cacheDP[groupIndex] = depthStatusValue.y;
|
|
gs_cachePS[groupIndex] = saturate(depthStatusValue.x);
|
|
gs_cacheDC[groupIndex] = depthStatusValue.z;
|
|
}
|
|
|
|
uint OffsetToLDSAdress(uint2 groupThreadId, int2 offset)
|
|
{
|
|
// Compute the tap coordinate in the 6x6 grid
|
|
uint2 tapAddress = (uint2)((int2)(groupThreadId / 2 + 1) + offset);
|
|
return clamp((uint)(tapAddress.x) % 6 + tapAddress.y * 6, 0, 35);
|
|
}
|
|
#endif
|
|
|
|
#ifdef WITHOUT_LDS
|
|
float GetCloudDepth(int2 traceCoord, int2 offset)
|
|
{
|
|
traceCoord = clamp(traceCoord + offset, 0, _TraceScreenSize.xy - 1);
|
|
return LOAD_TEXTURE2D_X(_CloudsDepthTexture, traceCoord).x;
|
|
}
|
|
|
|
float4 GetCloudLighting(int2 traceCoord, int2 offset)
|
|
{
|
|
float4 cloudLighting;
|
|
traceCoord = clamp(traceCoord + offset, 0, _TraceScreenSize.xy - 1);
|
|
|
|
cloudLighting.xyz = LOAD_TEXTURE2D_X(_CloudsLightingTexture, traceCoord).xyz;
|
|
cloudLighting.a = LOAD_TEXTURE2D_X(_CloudsDepthTexture, traceCoord).y;
|
|
return cloudLighting;
|
|
}
|
|
#else
|
|
float GetCloudDepth(uint2 groupThreadId, int2 offset)
|
|
{
|
|
return gs_cacheDC[OffsetToLDSAdress(groupThreadId, offset)];
|
|
}
|
|
|
|
float4 GetCloudLighting(uint2 groupThreadId, int2 offset)
|
|
{
|
|
uint ldsTapAddress = OffsetToLDSAdress(groupThreadId, offset);
|
|
return gs_cacheRGBA[ldsTapAddress];
|
|
}
|
|
#endif
|
|
|
|
struct CloudReprojectionData
|
|
{
|
|
float4 cloudLighting;
|
|
float pixelDepth;
|
|
float cloudDepth;
|
|
};
|
|
|
|
#ifdef WITHOUT_LDS
|
|
CloudReprojectionData GetCloudReprojectionDataSample(int2 traceCoord, int2 offset)
|
|
{
|
|
traceCoord = clamp(traceCoord + offset, 0, _TraceScreenSize.xy - 1);
|
|
|
|
float3 color = LOAD_TEXTURE2D_X(_CloudsLightingTexture, traceCoord).xyz;
|
|
float2 depthTransmittance = LOAD_TEXTURE2D_X(_CloudsDepthTexture, traceCoord).xy;
|
|
|
|
int2 representativeCoord = traceCoord * 2 + ComputeCheckerBoardOffset(traceCoord, _SubPixelIndex);
|
|
|
|
CloudReprojectionData outVal;
|
|
outVal.cloudLighting.rgb = color;
|
|
outVal.cloudLighting.a = depthTransmittance.y;
|
|
outVal.cloudDepth = depthTransmittance.x;
|
|
outVal.pixelDepth = LOAD_TEXTURE2D_X(_CameraDepthTexture, _ReprojDepthMipOffset + representativeCoord).x;
|
|
return outVal;
|
|
}
|
|
#else
|
|
CloudReprojectionData GetCloudReprojectionDataSample(uint index)
|
|
{
|
|
CloudReprojectionData outVal;
|
|
outVal.cloudLighting = gs_cacheRGBA[index];
|
|
outVal.pixelDepth = gs_cacheDP[index];
|
|
outVal.cloudDepth = gs_cacheDC[index];
|
|
return outVal;
|
|
}
|
|
|
|
CloudReprojectionData GetCloudReprojectionDataSample(uint2 groupThreadId, int2 offset)
|
|
{
|
|
return GetCloudReprojectionDataSample(OffsetToLDSAdress(groupThreadId, offset));
|
|
}
|
|
#endif
|
|
|
|
// Function that fills the struct as we cannot use arrays
|
|
void FillCloudReprojectionNeighborhoodData(int2 threadCoord, int subRegionIdx, out NeighborhoodUpsampleData3x3 neighborhoodData)
|
|
{
|
|
// Fill the sample data
|
|
CloudReprojectionData data = GetCloudReprojectionDataSample(threadCoord, int2(-1, -1));
|
|
neighborhoodData.lowValue0 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.x = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.x = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.x = _DistanceBasedWeights[subRegionIdx * 3 + 0].x;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(0, -1));
|
|
neighborhoodData.lowValue1 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.y = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.y = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.y = _DistanceBasedWeights[subRegionIdx * 3 + 0].y;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(1, -1));
|
|
neighborhoodData.lowValue2 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.z = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.z = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.z = _DistanceBasedWeights[subRegionIdx * 3 + 0].z;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(-1, 0));
|
|
neighborhoodData.lowValue3 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.w = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.w = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.w = _DistanceBasedWeights[subRegionIdx * 3 + 0].w;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(0, 0));
|
|
neighborhoodData.lowValue4 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.x = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.x = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.x = _DistanceBasedWeights[subRegionIdx * 3 + 1].x;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(1, 0));
|
|
neighborhoodData.lowValue5 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.y = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.y = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.y = _DistanceBasedWeights[subRegionIdx * 3 + 1].y;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(-1, 1));
|
|
neighborhoodData.lowValue6 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.z = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.z = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.z = _DistanceBasedWeights[subRegionIdx * 3 + 1].z;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(0, 1));
|
|
neighborhoodData.lowValue7 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.w = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.w = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.w = _DistanceBasedWeights[subRegionIdx * 3 + 1].w;
|
|
|
|
data = GetCloudReprojectionDataSample(threadCoord, int2(1, 1));
|
|
neighborhoodData.lowValue8 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueC = data.cloudDepth;
|
|
neighborhoodData.lowDepthC = data.pixelDepth;
|
|
neighborhoodData.lowWeightC = _DistanceBasedWeights[subRegionIdx * 3 + 2].x;
|
|
}
|
|
|
|
struct CloudUpscaleData
|
|
{
|
|
float4 cloudLighting;
|
|
float pixelDepth;
|
|
float pixelStatus;
|
|
float cloudDepth;
|
|
};
|
|
|
|
#ifdef WITHOUT_LDS
|
|
CloudUpscaleData GetCloudUpscaleDataSample(int2 intermediateCoord, int2 offset)
|
|
{
|
|
intermediateCoord = clamp(intermediateCoord + offset, 0, _IntermediateScreenSize.xy - 1);
|
|
|
|
float3 lightingVal = LOAD_TEXTURE2D_X(_VolumetricCloudsTexture, intermediateCoord).xyz;
|
|
float4 depthStatusValue = LOAD_TEXTURE2D_X(_DepthStatusTexture, intermediateCoord);
|
|
|
|
CloudUpscaleData outVal;
|
|
outVal.cloudLighting.rgb = lightingVal;
|
|
outVal.cloudLighting.a = depthStatusValue.a;
|
|
outVal.pixelDepth = depthStatusValue.y;
|
|
outVal.pixelStatus = saturate(depthStatusValue.x);
|
|
outVal.cloudDepth = depthStatusValue.z;
|
|
return outVal;
|
|
}
|
|
#else
|
|
CloudUpscaleData GetCloudUpscaleDataSample(uint index)
|
|
{
|
|
CloudUpscaleData outVal;
|
|
outVal.cloudLighting = gs_cacheRGBA[index];
|
|
outVal.pixelDepth = gs_cacheDP[index];
|
|
outVal.pixelStatus = gs_cachePS[index];
|
|
outVal.cloudDepth = gs_cacheDC[index];
|
|
return outVal;
|
|
}
|
|
|
|
CloudUpscaleData GetCloudUpscaleDataSample(uint2 groupThreadId, int2 offset)
|
|
{
|
|
return GetCloudUpscaleDataSample(OffsetToLDSAdress(groupThreadId, offset));
|
|
}
|
|
#endif
|
|
|
|
// Function that fills the struct as we cannot use arrays
|
|
void FillCloudUpscaleNeighborhoodData(int2 threadCoord, int subRegionIdx, out NeighborhoodUpsampleData3x3 neighborhoodData)
|
|
{
|
|
// Fill the sample data
|
|
CloudUpscaleData data = GetCloudUpscaleDataSample(threadCoord, int2(-1, -1));
|
|
neighborhoodData.lowValue0 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.x = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.x = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.x = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 0].x;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(0, -1));
|
|
neighborhoodData.lowValue1 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.y = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.y = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.y = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 0].y;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(1, -1));
|
|
neighborhoodData.lowValue2 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.z = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.z = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.z = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 0].z;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(-1, 0));
|
|
neighborhoodData.lowValue3 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueA.w = data.cloudDepth;
|
|
neighborhoodData.lowDepthA.w = data.pixelDepth;
|
|
neighborhoodData.lowWeightA.w = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 0].w;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(0, 0));
|
|
neighborhoodData.lowValue4 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.x = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.x = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.x = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 1].x;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(1, 0));
|
|
neighborhoodData.lowValue5 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.y = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.y = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.y = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 1].y;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(-1, 1));
|
|
neighborhoodData.lowValue6 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.z = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.z = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.z = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 1].z;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(0, 1));
|
|
neighborhoodData.lowValue7 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueB.w = data.cloudDepth;
|
|
neighborhoodData.lowDepthB.w = data.pixelDepth;
|
|
neighborhoodData.lowWeightB.w = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 1].w;
|
|
|
|
data = GetCloudUpscaleDataSample(threadCoord, int2(1, 1));
|
|
neighborhoodData.lowValue8 = data.cloudLighting;
|
|
neighborhoodData.lowDepthValueC = data.cloudDepth;
|
|
neighborhoodData.lowDepthC = data.pixelDepth;
|
|
neighborhoodData.lowWeightC = data.pixelStatus * _DistanceBasedWeights[subRegionIdx * 3 + 2].x;
|
|
}
|
|
|
|
#endif // VOLUMETRIC_CLOUDS_DENOISING_H
|