verus/Verus/src/Shaders/Blur.hlsl

502 lines
13 KiB
HLSL

// Copyright (C) 2021-2022, Dmitry Maluev (dmaluev@gmail.com). All rights reserved.
#include "Lib.hlsl"
#include "LibColor.hlsl"
#include "LibDeferredShading.hlsl"
#include "LibDepth.hlsl"
#include "Blur.inc.hlsl"
CBUFFER(0, UB_BlurVS, g_ubBlurVS)
CBUFFER(1, UB_BlurFS, g_ubBlurFS)
CBUFFER(2, UB_ExtraBlurFS, g_ubExtraBlurFS)
Texture2D g_tex : REG(t1, space1, t0);
SamplerState g_sam : REG(s1, space1, s0);
Texture2D g_texGBuffer1 : REG(t1, space2, t1);
SamplerState g_samGBuffer1 : REG(s1, space2, s1);
Texture2D g_texDepth : REG(t2, space2, t2);
SamplerState g_samDepth : REG(s2, space2, s2);
struct VSI
{
VK_LOCATION_POSITION float4 pos : POSITION;
};
struct VSO
{
float4 pos : SV_Position;
float2 tc0 : TEXCOORD0;
};
struct FSO
{
float4 color : SV_Target0;
};
// <DefaultBlur>
#ifdef _VS
VSO mainVS(VSI si)
{
VSO so;
// Standard quad:
so.pos = float4(mul(si.pos, g_ubBlurVS._matW), 1);
so.tc0 = mul(si.pos, g_ubBlurVS._matV).xy;
return so;
}
#endif
#ifdef _FS
FSO mainFS(VSO si)
{
FSO so;
const int sampleCount = g_ubBlurFS._sampleCount;
const float radius = g_ubBlurFS._radius_invRadius_stride.x;
const float invRadius = g_ubBlurFS._radius_invRadius_stride.y;
const float stride = g_ubBlurFS._radius_invRadius_stride.z;
float4 acc = 0.0;
float2 offset_weightSum = float2(-radius, 0);
for (int i = 0; i < sampleCount; ++i)
{
// Poor man's gaussian kernel:
const float ratio = 1.0 - abs(offset_weightSum.x * invRadius);
const float curve = smoothstep(0.0, 1.0, ratio);
const float weight = lerp(ratio, curve * curve * curve, 0.8);
#ifdef DEF_U
const float2 tc = si.tc0 + float2(offset_weightSum.x, 0);
#else
const float2 tc = si.tc0 + float2(0, offset_weightSum.x);
#endif
acc += g_tex.SampleLevel(g_sam, tc, 0.0) * weight;
offset_weightSum += float2(stride, weight);
}
acc *= 1.0 / offset_weightSum.y;
so.color = acc;
return so;
}
#endif
// </DefaultBlur>
// <SsaoBlur>
#ifdef _VS
VSO mainSsaoVS(VSI si)
{
VSO so;
// Standard quad:
so.pos = float4(mul(si.pos, g_ubBlurVS._matW), 1);
so.tc0 = mul(si.pos, g_ubBlurVS._matV).xy;
return so;
}
#endif
#ifdef _FS
FSO mainSsaoFS(VSO si)
{
FSO so;
// 4x4 box blur, using texture filtering.
float acc = 0.0;
[unroll] for (int y = -1; y <= 1; y += 2)
{
[unroll] for (int x = -1; x <= 1; x += 2)
{
acc += g_tex.SampleLevel(g_sam, si.tc0, 0.0, int2(x, y)).r;
}
}
acc *= (1.0 / 4.0);
so.color = acc;
return so;
}
#endif
// </SsaoBlur>
// <ResolveDithering>
#ifdef _VS
VSO mainResolveDitheringVS(VSI si)
{
VSO so;
// Standard quad:
so.pos = float4(mul(si.pos, g_ubBlurVS._matW), 1);
so.tc0 = mul(si.pos, g_ubBlurVS._matV).xy;
return so;
}
#endif
#ifdef _FS
FSO mainResolveDitheringFS(VSO si)
{
FSO so;
float accMask = 0.0;
[unroll] for (int y = -1; y <= 1; y++)
{
[unroll] for (int x = -1; x <= 1; x++)
{
accMask += floor(g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0, int2(x, y)).a);
}
}
if (accMask >= 1.0)
{
const float invExposure = max(1024.0, 8192.0 - 2048.0 * accMask);
const float exposure = 1.0 / invExposure;
// 3x3 blur:
float3 acc = 0.0;
[unroll] for (int y = -1; y <= 1; y++)
{
[unroll] for (int x = -1; x <= 1; x++)
{
const float2 weights = 1.0 - 0.5 * abs(float2(x, y));
const float weight = weights.x * weights.y;
const float3 color = g_tex.SampleLevel(g_sam, si.tc0, 0.0, int2(x, y)).rgb;
acc += ToneMappingReinhard(color * exposure) * weight;
}
}
acc *= (1.0 / 4.0);
so.color.rgb = ToneMappingInvReinhard(acc) * invExposure;
so.color.a = 1.0;
}
else
{
so.color.rgb = g_tex.SampleLevel(g_sam, si.tc0, 0.0).rgb;
so.color.a = 1.0;
}
return so;
}
#endif
// </ResolveDithering>
// <Sharpen>
#ifdef _VS
VSO mainSharpenVS(VSI si)
{
VSO so;
// Standard quad:
so.pos = float4(mul(si.pos, g_ubBlurVS._matW), 1);
so.tc0 = mul(si.pos, g_ubBlurVS._matV).xy;
return so;
}
#endif
#ifdef _FS
FSO mainSharpenFS(VSO si)
{
FSO so;
float accMask = 0.0;
[unroll] for (int y = -1; y <= 1; y++)
{
[unroll] for (int x = -1; x <= 1; x++)
{
accMask += floor(g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0, int2(x, y)).a);
}
}
if (accMask >= 1.0)
{
const float invExposure = max(1024.0, 8192.0 - 2048.0 * accMask);
const float exposure = 1.0 / invExposure;
// 3x3 sharpen:
float3 acc = 0.0;
[unroll] for (int y = -1; y <= 1; y++)
{
[unroll] for (int x = -1; x <= 1; x++)
{
float weight = -1.0 / 8.0;
if (abs(x) + abs(y) == 0)
weight = 2.0;
const float3 color = g_tex.SampleLevel(g_sam, si.tc0, 0.0, int2(x, y)).rgb;
acc += ToneMappingReinhard(color * exposure) * weight;
}
}
so.color.rgb = ToneMappingInvReinhard(acc) * invExposure;
so.color.a = 1.0;
}
else
{
so.color.rgb = g_tex.SampleLevel(g_sam, si.tc0, 0.0).rgb;
so.color.a = 1.0;
}
return so;
}
#endif
// </Sharpen>
// <DepthOfFieldBlur>
float DepthToCircleOfConfusion(float depth, float focusDist)
{
return abs((depth - focusDist * 2.0) / depth + 1.0);
}
#ifdef _VS
VSO mainDofVS(VSI si)
{
VSO so;
// Standard quad:
so.pos = float4(mul(si.pos, g_ubBlurVS._matW), 1);
so.tc0 = mul(si.pos, g_ubBlurVS._matV).xy;
return so;
}
#endif
#ifdef _FS
FSO mainDofFS(VSO si)
{
FSO so;
const float focusDist = g_ubExtraBlurFS._focusDist_blurStrength.x;
const float blurStrength = g_ubExtraBlurFS._focusDist_blurStrength.y;
const float originDepthSam = g_texDepth.SampleLevel(g_samDepth, si.tc0, 0.0).r;
const float originDepth = ToLinearDepth(originDepthSam, g_ubExtraBlurFS._zNearFarEx);
const float scale = DepthToCircleOfConfusion(originDepth, focusDist) * blurStrength;
const int sampleCount = clamp(g_ubBlurFS._sampleCount * scale, 3, 31);
const float radius = g_ubBlurFS._radius_invRadius_stride.x * scale;
const float invRadius = 1.0 / radius;
const float2 blurDir = g_ubExtraBlurFS._blurDir.xy;
const float2 blurDir2 = g_ubExtraBlurFS._blurDir.zw * radius;
const float stride = radius * 2.0 / (sampleCount - 1);
const float2 stride2D = stride * blurDir;
float4 acc = 0.0;
float2 offset_weightSum = float2(-radius, 0);
float2 tc = si.tc0 - blurDir * radius;
for (int i = 0; i < sampleCount; ++i)
{
const float origin = abs(offset_weightSum.x) * invRadius;
const float kernelDepthSam = g_texDepth.SampleLevel(g_samDepth, tc, 0.0).r;
const float kernelDepth = ToLinearDepth(kernelDepthSam, g_ubExtraBlurFS._zNearFarEx);
const float kernelDeeper = kernelDepth - originDepth;
const float kernelScale = DepthToCircleOfConfusion(kernelDepth, focusDist) * blurStrength;
// Blurry area should not sample sharp area unless it is closer to the camera.
float weight = min(scale, min(2.0, max(kernelScale, kernelDeeper)));
#ifdef DEF_U // 1st pass - make a rhombus:
weight *= 1.0 - 0.5 * origin;
const float4 colorA = g_tex.SampleLevel(g_sam, tc - blurDir2 + blurDir2 * origin, 0.0);
const float4 colorB = g_tex.SampleLevel(g_sam, tc + blurDir2 - blurDir2 * origin, 0.0);
acc += lerp(colorA, colorB, 0.5) * weight;
#else // 2nd pass - blur the rhombus:
acc += g_tex.SampleLevel(g_sam, tc, 0.0) * weight;
#endif
offset_weightSum += float2(stride, weight);
tc += stride2D;
}
acc *= 1.0 / offset_weightSum.y;
so.color = acc;
return so;
}
#endif
// </DepthOfFieldBlur>
#ifdef _VS
VSO mainAntiAliasingVS(VSI si)
{
VSO so;
// Standard quad:
so.pos = float4(mul(si.pos, g_ubBlurVS._matW), 1);
so.tc0 = mul(si.pos, g_ubBlurVS._matV).xy;
return so;
}
#endif
#ifdef _FS
FSO mainAntiAliasingFS(VSO si)
{
FSO so;
const float4 gBuffer1Sam = g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0);
const float3 normalWV = DS_GetNormal(gBuffer1Sam);
// <DepthBased>
float originDeeper;
float depthBasedEdge;
{
const float originDepthSam = g_texDepth.SampleLevel(g_samDepth, si.tc0, 0.0).r;
const float originDepth = ToLinearDepth(originDepthSam, g_ubExtraBlurFS._zNearFarEx);
const float4 kernelDepthsSam = float4(
g_texDepth.SampleLevel(g_samDepth, si.tc0, 0.0, int2(-1, +0)).r, // L
g_texDepth.SampleLevel(g_samDepth, si.tc0, 0.0, int2(+0, -1)).r, // T
g_texDepth.SampleLevel(g_samDepth, si.tc0, 0.0, int2(+1, +0)).r, // R
g_texDepth.SampleLevel(g_samDepth, si.tc0, 0.0, int2(+0, +1)).r); // B
const float4 kernelDepths = ToLinearDepth(kernelDepthsSam, g_ubExtraBlurFS._zNearFarEx);
const float minDepth = min(min(kernelDepths[0], kernelDepths[1]), min(kernelDepths[2], kernelDepths[3]));
const float equalize = 1.0 / originDepth;
originDeeper = saturate((originDepth - minDepth) * equalize);
const float4 depthOffsets = abs((originDepth - kernelDepths) * equalize);
depthBasedEdge = saturate(dot(depthOffsets, 1.0));
}
// </DepthBased>
// <NormalBased>
float normalBasedEdge;
{
const float4 nrmSamLT = float4(
g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0, int2(-1, +0)).rg,
g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0, int2(+0, -1)).rg);
const float4 nrmSamRB = float4(
g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0, int2(+1, +0)).rg,
g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0, int2(+0, +1)).rg);
const float4 diffA = nrmSamLT - nrmSamRB;
const float4 diffB = nrmSamLT - nrmSamRB.zwxy;
const float4 dots = float4(
dot(diffA.xy, diffA.xy),
dot(diffA.zw, diffA.zw),
dot(diffB.xy, diffB.xy),
dot(diffB.zw, diffB.zw));
const float maxDot = max(max(dots.x, dots.y), max(dots.z, dots.w));
normalBasedEdge = saturate(maxDot * maxDot * 10.0);
}
// </NormalBased>
const float edge = max(normalBasedEdge, depthBasedEdge);
// Directional blur:
// {y, -x} is perpendicular vector. Also flip Y axis: normal XY to texture UV.
const float3 perp = normalWV.yxz;
const float omni = max(perp.z * perp.z * perp.z, originDeeper);
const float2 dirs[4] =
{
lerp(perp.xy * +3.0, float2(-0.6, -0.2), omni),
lerp(perp.xy * -1.0, float2(+0.2, -0.6), omni),
lerp(perp.xy * -3.0, float2(-0.2, +0.6), omni),
lerp(perp.xy * +1.0, float2(+0.6, +0.2), omni)
};
const float2 offsetScale = g_ubExtraBlurFS._textureSize.zw * edge;
const float3 kernelColors[4] =
{
g_tex.SampleLevel(g_sam, si.tc0 + dirs[0] * offsetScale, 0.0).rgb,
g_tex.SampleLevel(g_sam, si.tc0 + dirs[1] * offsetScale, 0.0).rgb,
g_tex.SampleLevel(g_sam, si.tc0 + dirs[2] * offsetScale, 0.0).rgb,
g_tex.SampleLevel(g_sam, si.tc0 + dirs[3] * offsetScale, 0.0).rgb
};
so.color.rgb = (kernelColors[0] + kernelColors[1] + kernelColors[2] + kernelColors[3]) * 0.25;
so.color.a = 1.0;
return so;
}
#endif
#ifdef _VS
VSO mainMotionVS(VSI si)
{
VSO so;
// Standard quad:
so.pos = float4(mul(si.pos, g_ubBlurVS._matW), 1);
so.tc0 = mul(si.pos, g_ubBlurVS._matV).xy;
return so;
}
#endif
#ifdef _FS
FSO mainMotionFS(VSO si)
{
FSO so;
const float3 rand = Rand(si.pos.xy);
const float2 ndcPos = ToNdcPos(si.tc0);
const float offsetScale = 0.6 + 0.1 * rand.x; // Blur 60% - 70% of frame time.
#if _SHADER_QUALITY <= _Q_LOW
const int sampleCount = 6;
#elif _SHADER_QUALITY <= _Q_MEDIUM
const int sampleCount = 8;
#elif _SHADER_QUALITY <= _Q_HIGH
const int sampleCount = 12;
#elif _SHADER_QUALITY <= _Q_ULTRA
const int sampleCount = 16;
#endif
const float4 gBuffer1Sam = g_texGBuffer1.SampleLevel(g_samGBuffer1, si.tc0, 0.0);
const float originDepthSam = g_texDepth.SampleLevel(g_samDepth, si.tc0, 0.0).r;
const float originDepth = ToLinearDepth(originDepthSam, g_ubExtraBlurFS._zNearFarEx);
const float equalize = 1.0 / originDepth;
float2 tcFrom;
{
const float3 posW = DS_GetPosition(originDepthSam, g_ubExtraBlurFS._matInvVP, ndcPos);
const float4 prevClipSpacePos = mul(float4(posW, 1), g_ubExtraBlurFS._matPrevVP);
const float2 prevNdcPos = prevClipSpacePos.xy / prevClipSpacePos.w;
tcFrom = ToTexCoords(prevNdcPos);
}
const float2 stride = (si.tc0 - tcFrom) * offsetScale / (sampleCount - 1);
const float2 tcOrigin = lerp(tcFrom, si.tc0, 0.675); // Start from this location up to offsetScale.
float4 acc = float4(g_tex.SampleLevel(g_sam, si.tc0, 0.0).rgb, 1); // Must have at least one sample.
[unroll] for (int i = 0; i < sampleCount; ++i)
{
if (i == sampleCount / 2)
continue; // Midpoint already sampled.
const float2 kernelCoords = tcOrigin + stride * i;
const float kernelDepthSam = g_texDepth.SampleLevel(g_samDepth, kernelCoords, 0.0).r;
const float kernelDepth = ToLinearDepth(kernelDepthSam, g_ubExtraBlurFS._zNearFarEx);
const float kernelDeeper = kernelDepth - originDepth;
const float allowed = saturate(1.0 + kernelDeeper * equalize) * gBuffer1Sam.a; // Closer points require extra care.
const float weight = 1.0 + saturate(kernelDeeper); // To fix the seam between foreground and background.
const float3 kernelColorSam = g_tex.SampleLevel(g_sam, kernelCoords, 0.0).rgb;
acc += lerp(0.0, float4(kernelColorSam * weight, weight), allowed);
}
acc /= acc.a;
so.color = float4(acc.rgb, 1);
return so;
}
#endif
//@main:#U U
//@main:#V V
//@mainSsao:#Ssao
//@mainResolveDithering:#ResolveDithering
//@mainSharpen:#Sharpen
//@mainDof:#UDoF U
//@mainDof:#VDoF V
//@mainAntiAliasing:#AntiAliasing
//@mainMotion:#Motion