mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-12-02 17:01:50 +00:00
207 lines
6.7 KiB
HLSL
207 lines
6.7 KiB
HLSL
/*
|
|
===========================================================================
|
|
Copyright (C) 2023-2024 Gian 'myT' Schellenbaum
|
|
|
|
This file is part of Challenge Quake 3 (CNQ3).
|
|
|
|
Challenge Quake 3 is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Challenge Quake 3 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
// gather depth of field: near-field and far-field blur
|
|
|
|
|
|
#include "common.hlsli"
|
|
#include "gatherdof.hlsli"
|
|
|
|
|
|
cbuffer RootConstants : register(b0)
|
|
{
|
|
uint colorTextureIndex;
|
|
uint nearColorTextureIndex;
|
|
uint nearMaxCocTextureIndex; // tile
|
|
uint nearCocTextureIndex; // blurry
|
|
uint nearOutputTextureIndex;
|
|
uint farColorTextureIndex;
|
|
uint farCocTextureIndex; // sharp
|
|
uint farOutputTextureIndex;
|
|
uint samplerIndex; // linear/clamp
|
|
float brightnessScale;
|
|
float bladeCount;
|
|
float bokehAngleRad;
|
|
};
|
|
|
|
// the input is in [0,1]^2, the output polygon is centered at the origin
|
|
float2 MapUnitSquareToPolygon(float2 square, float apertureBladeCount, float apertureAngleRad)
|
|
{
|
|
// needed to avoid inf/nan propagation through theta for samples
|
|
// that are exactly in the middle of the quad on either axis
|
|
// (i.e. square.x|y == 0.5 gets remapped to 0.0)
|
|
const float epsilon = 0.000001;
|
|
|
|
// morph into a square in [-1,1]^2
|
|
square = square * 2.0 - 1.0;
|
|
|
|
// morph the square into a disk
|
|
// "A Low Distortion Map Between Disk and Square" by Peter Shirley and Kenneth Chiu
|
|
float radius, angle;
|
|
float2 square2 = square * square;
|
|
if(square2.x > square2.y)
|
|
{
|
|
// left and right quadrants
|
|
radius = square.x;
|
|
angle = (square.y * PI_D4) / (square.x + epsilon);
|
|
}
|
|
else
|
|
{
|
|
// top and bottom quadrants
|
|
radius = square.y;
|
|
angle = PI_D2 - (square.x * PI_D4) / (square.y + epsilon);
|
|
}
|
|
if(radius < 0.0)
|
|
{
|
|
radius = -radius;
|
|
angle += PI;
|
|
}
|
|
|
|
// morph the disk into a polygon
|
|
// "Graphics Gems from CryENGINE 3" by Tiago Sousa
|
|
float edgeCount = apertureBladeCount;
|
|
if(edgeCount >= 3.0)
|
|
{
|
|
float num = cos(PI / edgeCount);
|
|
float den0 = PI_M2 / edgeCount;
|
|
float den1 = (angle * edgeCount + PI) / PI_M2;
|
|
float den = angle - (den0 * floor(den1));
|
|
radius *= num / cos(den);
|
|
angle += apertureAngleRad;
|
|
}
|
|
|
|
float2 disk;
|
|
sincos(angle, disk.y, disk.x);
|
|
disk *= radius;
|
|
|
|
return disk;
|
|
}
|
|
|
|
float4 BlurFarField(Texture2D inTexture, SamplerState samplerState, float coc, float2 tc01, float2 pixelSize)
|
|
{
|
|
const int TAP_COUNT_BLUR = 16;
|
|
float2 tcScale = 16.0 * coc * pixelSize;
|
|
|
|
float4 result = inTexture.SampleLevel(samplerState, tc01, 0);
|
|
for(int y = 0; y < TAP_COUNT_BLUR; ++y)
|
|
{
|
|
for(int x = 0; x < TAP_COUNT_BLUR; ++x)
|
|
{
|
|
float2 tcQuad = float2(x, y) / float(TAP_COUNT_BLUR - 1);
|
|
float2 tcOffset = MapUnitSquareToPolygon(tcQuad, bladeCount, bokehAngleRad) * tcScale;
|
|
float4 sampleValue = inTexture.SampleLevel(samplerState, tc01 + tcOffset, 0);
|
|
result += sampleValue;
|
|
}
|
|
}
|
|
|
|
result /= result.a;
|
|
|
|
return result;
|
|
}
|
|
|
|
float4 BlurNearField(Texture2D inTexture, SamplerState samplerState, float tileMaxCoc, float2 tc01, float2 pixelSize)
|
|
{
|
|
const int TAP_COUNT_BLUR = 15; // must be odd so we generate 1 sample at 0.5, 0.5 in the quad
|
|
float2 tcScale = 16.0 * tileMaxCoc * pixelSize;
|
|
float insideCount = 0.0;
|
|
float totalCount = 1.0 + float(TAP_COUNT_BLUR * TAP_COUNT_BLUR);
|
|
float4 result = float4(0, 0, 0, 0);
|
|
float weightSum = 0.0;
|
|
|
|
for(int y = 0; y < TAP_COUNT_BLUR; ++y)
|
|
{
|
|
for(int x = 0; x < TAP_COUNT_BLUR; ++x)
|
|
{
|
|
float2 tcQuad = float2(x, y) / float(TAP_COUNT_BLUR - 1);
|
|
float2 tcOffset = MapUnitSquareToPolygon(tcQuad, bladeCount, bokehAngleRad) * tcScale;
|
|
float4 sampleValue = inTexture.SampleLevel(samplerState, tc01 + tcOffset, 0);
|
|
float inside = sampleValue.a > 0.0 ? 1.0 : 0.0;
|
|
float brightnessWeight = 1.0 + brightnessScale * Brightness(sampleValue.rgb);
|
|
float colorWeight = (sampleValue.a / tileMaxCoc) * brightnessWeight;
|
|
insideCount += inside;
|
|
weightSum += inside * colorWeight;
|
|
result += inside * float4(colorWeight.xxx, 1) * sampleValue;
|
|
}
|
|
}
|
|
|
|
if(insideCount >= 1.0)
|
|
{
|
|
result.rgb /= weightSum;
|
|
result.a /= insideCount;
|
|
result.a *= EaseInOutCubic(saturate(2.0 * (insideCount / totalCount)));
|
|
}
|
|
else
|
|
{
|
|
result = float4(1, 1, 0, 0);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
[numthreads(8, 8, 1)]
|
|
void cs(uint3 dtid : SV_DispatchThreadID)
|
|
{
|
|
uint2 tc = dtid.xy;
|
|
RWTexture2D<float4> nearOutputTexture = ResourceDescriptorHeap[nearOutputTextureIndex];
|
|
RWTexture2D<float4> farOutputTexture = ResourceDescriptorHeap[farOutputTextureIndex];
|
|
uint width, height;
|
|
farOutputTexture.GetDimensions(width, height);
|
|
if(any(dtid.xy >= uint2(width, height)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SamplerState samplerState = SamplerDescriptorHeap[samplerIndex];
|
|
Texture2D colorTexture = ResourceDescriptorHeap[colorTextureIndex];
|
|
Texture2D nearColorTexture = ResourceDescriptorHeap[nearColorTextureIndex];
|
|
Texture2D farColorTexture = ResourceDescriptorHeap[farColorTextureIndex];
|
|
Texture2D<float> nearCocTexture = ResourceDescriptorHeap[nearCocTextureIndex];
|
|
Texture2D<float> nearMaxCocTexture = ResourceDescriptorHeap[nearMaxCocTextureIndex];
|
|
Texture2D<float> farCocTexture = ResourceDescriptorHeap[farCocTextureIndex];
|
|
RWTexture2D<float4> nearOutTexture = ResourceDescriptorHeap[nearOutputTextureIndex];
|
|
RWTexture2D<float4> farOutTexture = ResourceDescriptorHeap[farOutputTextureIndex];
|
|
float2 tc01 = (float2(dtid.xy) + float2(0.5, 0.5)) / float2(width, height);
|
|
float2 pixelSize = float2(1, 1) / float2(width, height);
|
|
float nearCoc = nearCocTexture.SampleLevel(samplerState, tc01, 0);
|
|
float nearMaxCoc = nearMaxCocTexture.SampleLevel(samplerState, tc01, 0);
|
|
float farCoc = farCocTexture.SampleLevel(samplerState, tc01, 0);
|
|
float4 color = colorTexture.SampleLevel(samplerState, tc01, 0);
|
|
|
|
if(nearMaxCoc > 0.0)
|
|
{
|
|
nearOutTexture[tc] = BlurNearField(nearColorTexture, samplerState, nearMaxCoc, tc01, pixelSize);
|
|
}
|
|
else
|
|
{
|
|
// A must be 0 to disable the near field from being blended
|
|
nearOutTexture[tc] = float4(color.rgb, 0);
|
|
}
|
|
|
|
if(farCoc > 0.0)
|
|
{
|
|
farOutTexture[tc] = BlurFarField(farColorTexture, samplerState, farCoc, tc01, pixelSize);
|
|
}
|
|
else
|
|
{
|
|
// RGB must be 0 to not mess up the fill pass of neighbor pixels that are inside the near field
|
|
// A must be 0 to disable the far field from being blended
|
|
farOutTexture[tc] = float4(0, 0, 0, 0);
|
|
}
|
|
}
|