2021-09-09 20:42:29 -04:00

292 lines
13 KiB
Plaintext

#pragma kernel SpatialFilterHalf SPATIAL_FILTER=SpatialFilterHalf HALF_RES
#pragma kernel SpatialFilter SPATIAL_FILTER=SpatialFilter
#pragma kernel TemporalFilterHalf TEMPORAL_FILTER=TemporalFilterHalf HALF_RES
#pragma kernel TemporalFilter TEMPORAL_FILTER=TemporalFilter
#pragma kernel CopyHistory COPY_HISTORY=CopyHistory
// Common includes
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Sampling/Sampling.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
// HDRP includes
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Builtin/BuiltinData.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/TemporalAntialiasing.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Denoising/BilateralFilter.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Denoising/DenoisingUtils.hlsl"
// #pragma only_renderers d3d11
// #pragma enable_d3d11_debug_symbols
// Tile size of this compute shaders
#define SSGI_FILTER_TILE_SIZE 8
// Noisy buffer input
TEXTURE2D_X(_InputNoisyBuffer0);
TEXTURE2D_X(_InputNoisyBuffer1);
// Constant buffer that should hold all the scalar values require
CBUFFER_START(UnityScreenSpaceGlobalIllumination)
float2 _DepthPyramidFirstMipLevelOffset;
float4 _HalfScreenSize;
int _IndirectDiffuseSpatialFilter;
float _HistoryValidity;
float _PixelSpreadAngleTangent;
CBUFFER_END
// Denoised output buffer
RW_TEXTURE2D_X(float4, _OutputFilteredBuffer0);
RW_TEXTURE2D_X(float4, _OutputFilteredBuffer1);
#define SPATIAL_FILTER_SIGMA_RATIO 0.9
#define DEPTH_WEIGHT_MULTIPLIER 100.0f
#define NORMAL_WEIGHT_MULTIPLIER 5.0
// Number of samples to compelte the accumulation loop
#define NUM_SAMPLE_LOOP 8.0
#define EXPONENTIAL_ACCUMULATION_FACTOR (NUM_SAMPLE_LOOP/(NUM_SAMPLE_LOOP + 1.0f))
[numthreads(SSGI_FILTER_TILE_SIZE, SSGI_FILTER_TILE_SIZE, 1)]
void SPATIAL_FILTER(uint3 dispatchThreadId : SV_DispatchThreadID , uint2 groupThreadId : SV_GroupThreadID , uint2 groupId : SV_GroupID)
{
UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z);
// Fetch the current pixel coordinates
int2 centerCoord = dispatchThreadId.xy;
#if HALF_RES
// Get the posinputs of the current version of the pixel
float depth = LOAD_TEXTURE2D_X(_DepthTexture, asuint(_DepthPyramidFirstMipLevelOffset) + centerCoord).r;
NormalData normalData;
DecodeFromNormalBuffer(centerCoord * 2, normalData);
#else
// Get the posinputs of the current version of the pixel
float depth = LOAD_TEXTURE2D_X(_DepthTexture, centerCoord).r;
NormalData normalData;
DecodeFromNormalBuffer(centerCoord, normalData);
#endif
// If the current point we are processing is a background point or the whole history should be discarded for an other reason, we invalidate the history
if (depth == UNITY_RAW_FAR_CLIP_VALUE)
{
_OutputFilteredBuffer0[COORD_TEXTURE2D_X(centerCoord)] = LOAD_TEXTURE2D_X(_InputNoisyBuffer0, centerCoord);
_OutputFilteredBuffer1[COORD_TEXTURE2D_X(centerCoord)] = LOAD_TEXTURE2D_X(_InputNoisyBuffer1, centerCoord);
return;
}
// Convert the depth into linear for weight evaluation
float centerz01 = Linear01Depth(depth, _ZBufferParams);
// Initialize the accumulation buffers
float4 colorSum0 = 0.0;
float3 colorSum1 = 0.0;
float wSum = 0.0;
const float sigma = _IndirectDiffuseSpatialFilter * SPATIAL_FILTER_SIGMA_RATIO;
// Loop through the neighboord and do our filter
for (int y = -_IndirectDiffuseSpatialFilter; y <= _IndirectDiffuseSpatialFilter; ++y)
{
for (int x = -_IndirectDiffuseSpatialFilter; x <= _IndirectDiffuseSpatialFilter; ++x)
{
// This could probably be avoided, but the shader is badwidth bound anyway, so..
float r = sqrt(x * x + y * y);
// Compute the absolute tap coord
int2 tapCoord = centerCoord + int2(x, y);
// We should not tap outside of the screen (given that its a unit, if we go below zero we wrap around)
#if HALF_RES
if (tapCoord.x >= _HalfScreenSize.x
|| tapCoord.x < 0
|| tapCoord.y >= _HalfScreenSize.y
|| tapCoord.y < 0)
continue;
#else
if (tapCoord.x >= _ScreenSize.x
|| tapCoord.x < 0
|| tapCoord.y >= _ScreenSize.y
|| tapCoord.y < 0)
continue;
#endif
// Read the depth of the tap pixel
#if HALF_RES
float tapDepth = LOAD_TEXTURE2D_X(_DepthTexture, asuint(_DepthPyramidFirstMipLevelOffset) + tapCoord).r;
NormalData normalDataTap;
DecodeFromNormalBuffer(tapCoord * 2, normalDataTap);
#else
float tapDepth = LOAD_TEXTURE2D_X(_DepthTexture, tapCoord).r;
NormalData normalDataTap;
DecodeFromNormalBuffer(tapCoord, normalDataTap);
#endif
if (tapDepth == UNITY_RAW_FAR_CLIP_VALUE)
continue;
// Convert the tapped depth to linear for weight evaluation
float tapz01 = Linear01Depth(tapDepth, _ZBufferParams);
// Compute the depth for this pixel
float depthWeight = max(0.0, 1.0 - abs(tapz01 - centerz01) * DEPTH_WEIGHT_MULTIPLIER);
const float normalCloseness = sqr(sqr(max(0.0, dot(normalDataTap.normalWS, normalData.normalWS))));
const float normalError = 1.0 - normalCloseness;
const float normalWeight = max(0.0, (1.0 - normalError * NORMAL_WEIGHT_MULTIPLIER));
// Compute the weight (skip computation for the center)
const float w = r ? gaussian(r, sigma) * depthWeight * normalWeight : 1.0;
// Accumuate this value
colorSum0 += LOAD_TEXTURE2D_X(_InputNoisyBuffer0, tapCoord).xyzw * w;
colorSum1 += LOAD_TEXTURE2D_X(_InputNoisyBuffer1, tapCoord).xyz * w;
wSum += w;
}
}
// Output the result to the buffer and propagate the w channel as is.
// TODO: We could save bandwidth by doing this using a 111110 texture and storing the w in a different texture
_OutputFilteredBuffer0[COORD_TEXTURE2D_X(centerCoord)] = float4(colorSum0 / wSum);
_OutputFilteredBuffer1[COORD_TEXTURE2D_X(centerCoord)] = float4(colorSum1 / wSum, LOAD_TEXTURE2D_X(_InputNoisyBuffer1, centerCoord).w);
}
// History buffer input
TEXTURE2D_X(_HistoryBuffer0);
TEXTURE2D_X(_HistoryBuffer1);
// Depth buffer of the previous frame, this is either the full res depth or half res based on the variant of the shader
TEXTURE2D_X(_HistoryDepthTexture);
[numthreads(SSGI_FILTER_TILE_SIZE, SSGI_FILTER_TILE_SIZE, 1)]
void TEMPORAL_FILTER(uint3 dispatchThreadId : SV_DispatchThreadID
, uint2 groupThreadId : SV_GroupThreadID
, uint2 groupId : SV_GroupID)
{
UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z);
int2 centerCoord = dispatchThreadId.xy;
// Read the color as early as possible
float4 color0 = LOAD_TEXTURE2D_X(_InputNoisyBuffer0, centerCoord);
float4 color1 = LOAD_TEXTURE2D_X(_InputNoisyBuffer1, centerCoord);
#if HALF_RES
// We need the full res coordinate for the inputs
uint2 fullResCoord = centerCoord * 2;
// Get the posinputs of the current version of the pixel
float depth = LOAD_TEXTURE2D_X(_DepthTexture, asuint(_DepthPyramidFirstMipLevelOffset) + centerCoord).r;
PositionInputs posInputs = GetPositionInput(fullResCoord, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, GetWorldToViewMatrix());
#else
// Get the posinputs of the current version of the pixel
float depth = LOAD_TEXTURE2D_X(_DepthTexture, centerCoord).r;
PositionInputs posInputs = GetPositionInput(centerCoord, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, GetWorldToViewMatrix());
#endif
// Initialize the output buffer in case of an early exit.
_OutputFilteredBuffer0[COORD_TEXTURE2D_X(centerCoord)] = color0;
_OutputFilteredBuffer1[COORD_TEXTURE2D_X(centerCoord)] = float4(color1.xyz, 1.0);
// If the current point we are processing is a background point or the whole history should be discarded for an other reason, we invalidate the history
if (depth == UNITY_RAW_FAR_CLIP_VALUE)
return;
// Decode the velocity of the pixel
float2 velocity = float2(0.0, 0.0);
#if HALF_RES
DecodeMotionVector(LOAD_TEXTURE2D_X(_CameraMotionVectorsTexture, (float2)fullResCoord), velocity);
#else
DecodeMotionVector(LOAD_TEXTURE2D_X(_CameraMotionVectorsTexture, (float2)centerCoord), velocity);
#endif
// Compute the pixel coordinate for the history tapping
int2 historyTapCoord = (int2)((posInputs.positionNDC - velocity) * _ScreenSize.xy);
#if HALF_RES
historyTapCoord *= 0.5;
// If the pixel was outside of the screen during the previous frame, invalidate the history
if (historyTapCoord.x >= _HalfScreenSize.x || historyTapCoord.x < 0
|| historyTapCoord.y >= _HalfScreenSize.y || historyTapCoord.y < 0)
return;
#else
// If the pixel was outside of the screen during the previous frame, invalidate the history
if (historyTapCoord.x >= _ScreenSize.x || historyTapCoord.x < 0
|| historyTapCoord.y >= _ScreenSize.y || historyTapCoord.y < 0)
return;
#endif
// Fetch the depth of the history pixel. If the history position was a background point, invalidate the history
float historyDepth = LOAD_TEXTURE2D_X(_HistoryDepthTexture, historyTapCoord).r;
// If the history was a background pixel, skip it
bool invalidHistory = false;
if (historyDepth == UNITY_RAW_FAR_CLIP_VALUE || _HistoryValidity == 0.0)
invalidHistory = true;
// Compute the world space position (from previous frame)
float3 historyPositionWS = ComputeWorldSpacePosition(posInputs.positionNDC - velocity, historyDepth, UNITY_MATRIX_PREV_I_VP);
// Real the normal data for this pixel
NormalData normalData;
DecodeFromNormalBuffer(centerCoord, normalData);
// Compute the max reprojection distance. This is evaluated as the max between a fixed radius value and an approximation of the footprint of the pixel.
float maxRadius = ComputeMaxReprojectionWorldRadius(posInputs.positionWS, normalData.normalWS, _PixelSpreadAngleTangent);
// Is it too far from the current position?
if (length(historyPositionWS - posInputs.positionWS) > maxRadius)
invalidHistory = true;
// Fetch history data
float4 history0 = LOAD_TEXTURE2D_X(_HistoryBuffer0, historyTapCoord);
float4 history1 = LOAD_TEXTURE2D_X(_HistoryBuffer1, historyTapCoord);
float sampleCount = history1.w;
// Accumulation factor that tells us how much we need to keep the history data
float accumulationFactor = 0.0;
// If the history is invalid
if (invalidHistory || sampleCount == 0.0)
{
// We only take the current value
accumulationFactor = 0.0;
// To avoid nan values
history0 = 0.0;
history1 = float4(0.501960784f, 0.501960784f, 0.0, 0.0);
sampleCount = 1.0;
}
else
{
// Otherwise we compute the accumulation factor
accumulationFactor = sampleCount >= NUM_SAMPLE_LOOP ? EXPONENTIAL_ACCUMULATION_FACTOR : (sampleCount / (sampleCount + 1.0));
// Update the sample count
sampleCount = min(sampleCount + 1.0, NUM_SAMPLE_LOOP);
// If the history pixel was moving, we descide to throw partially the history
// TODO: Expose this as a parameter
if (color1.w > 0.0)
{
sampleCount = 5.0;
accumulationFactor = sampleCount / (sampleCount + 1.0);
}
}
// Do the accumulation based on the values we computed and store the new sample count in the w channel
_OutputFilteredBuffer0[COORD_TEXTURE2D_X(centerCoord)] = float4(color0 * (1.0 - accumulationFactor) + history0 * accumulationFactor);
_OutputFilteredBuffer1[COORD_TEXTURE2D_X(centerCoord)] = float4(color1.xyz * (1.0 - accumulationFactor) + history1.xyz * accumulationFactor, sampleCount);
}
[numthreads(SSGI_FILTER_TILE_SIZE, SSGI_FILTER_TILE_SIZE, 1)]
void COPY_HISTORY(uint3 dispatchThreadId : SV_DispatchThreadID)
{
UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z);
if (any(dispatchThreadId.xy > uint2(_ScreenSize.xy)))
return; // Out of bounds, discard
_OutputFilteredBuffer0[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = LOAD_TEXTURE2D_X(_InputNoisyBuffer0, dispatchThreadId.xy);
_OutputFilteredBuffer1[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = LOAD_TEXTURE2D_X(_InputNoisyBuffer1, dispatchThreadId.xy);
}