Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DoF optimisation #8057

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ enum Pass
{
CoCCalculation,
CoCTemporalFilter,
downsampleInitialMaxCoC,
downsampleMaxCoC,
neighborMaxCoC,
DownsampleAndPrefilter,
BokehSmallKernel,
BokehMediumKernel,
BokehLargeKernel,
BokehVeryLargeKernel,
BokehDynamic,
PostFilter,
Combine,
DebugOverlay
Expand All @@ -106,6 +107,15 @@ enum Pass
readonly RenderTexture[][] m_CoCHistoryTextures = new RenderTexture[k_NumEyes][];
int[] m_HistoryPingPong = new int[k_NumEyes];

// The samples coordinates for kDiskAllKernels in DiskKernels.hlsl are normalized to 4 rings (coordinates with length 1 lie on the 4th ring).
// The ring placement are not evenly-spaced but:
// 1st ring: 8/29
// 2nd ring: 15/29
// 3rd ring: 22/29
// 4th ring: 29/29
static readonly float[] k_DisAllKernelRingOffsets = { 8f/29, 15f/29, 22f/29, 29f/29 };
static readonly int[] k_DiskAllKernelSizes = { 1, 8, 22, 43, 71 };

// Height of the 35mm full-frame format (36mm x 24mm)
// TODO: Should be set by a physical camera
const float k_FilmHeight = 0.024f;
Expand Down Expand Up @@ -135,17 +145,46 @@ RenderTextureFormat SelectFormat(RenderTextureFormat primary, RenderTextureForma
return RenderTextureFormat.Default;
}

float CalculateMaxCoCRadius(int screenHeight)
float CalculateMaxCoCRadius(int screenHeight, out int mipLevel)
{
// Estimate the allowable maximum radius of CoC from the kernel
// size (the equation below was empirically derived).
float radiusInPixels = (float)settings.kernelSize.value * 4f + 6f;

// Find the miplevel encasing the bokeh radius.
mipLevel = (int)(Mathf.Log(radiusInPixels * 2 - 1) / Mathf.Log(2));

// Applying a 5% limit to the CoC radius to keep the size of
// TileMax/NeighborMax small enough.
return Mathf.Min(0.05f, radiusInPixels / screenHeight);
}

void CalculateCoCKernelLimits(int screenHeight, out Vector4 cocKernelLimits)
{
// The sample points are grouped in 4 rings, but the distance between
// each ring is not even.
// Depending on a max CoC "distance", we can conservatively garantie
// only some rings need to be sampled.
// For instance, for a pixel C being processed, if the max CoC distance
// in the neighbouring pixels is less than ~14 pixels (at source image resolution),
// then the 4th ring does not need to be sampled.
// When sampling the half-resolution color texture, we sample the equivalent of
// 2 pixels radius from the full-resolution source image, thus the "spread" of
// each ring is 2 pixels wide in this diagram.
//
// Center pixel 1st ring 2nd ring 3rd ring 4th ring
// at 0 spread spread spread spread
// <-------> <-------> <-------> <-------> <------->
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---> pixel offset at full-resolution
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// ~a ~b ~c ~d

float a = k_DisAllKernelRingOffsets[0] * 16 + 2;
float b = k_DisAllKernelRingOffsets[1] * 16 + 2;
float c = k_DisAllKernelRingOffsets[2] * 16 + 2;
//float d = k_DisAllKernelRingOffsets[3] * 16 + 2;
cocKernelLimits = new Vector4(2 - 0.5f, a - 0.5f, b - 0.5f, c - 0.5f) / screenHeight;
}

RenderTexture CheckHistory(int eye, int id, PostProcessRenderContext context, RenderTextureFormat format)
{
var rt = m_CoCHistoryTextures[eye][id];
Expand All @@ -166,6 +205,9 @@ RenderTexture CheckHistory(int eye, int id, PostProcessRenderContext context, Re

public override void Render(PostProcessRenderContext context)
{
// Legacy: if KERNEL_SMALL is selected, then run a coarser fixed sample pattern (no dynamic branching).
bool useDynamicBokeh = settings.kernelSize.value != KernelSize.Small;

// The coc is stored in alpha so we need a 4 channels target. Note that using ARGB32
// will result in a very weak near-blur.
var colorFormat = context.camera.allowHDR ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32;
Expand All @@ -177,15 +219,56 @@ public override void Render(PostProcessRenderContext context)
var s1 = Mathf.Max(settings.focusDistance.value, f);
var aspect = (float)context.screenWidth / (float)context.screenHeight;
var coeff = f * f / (settings.aperture.value * (s1 - f) * scaledFilmHeight * 2f);
var maxCoC = CalculateMaxCoCRadius(context.screenHeight);
int maxCoCMipLevel;
var maxCoC = CalculateMaxCoCRadius(context.screenHeight, out maxCoCMipLevel);

// pad full-resolution screen so that the number of mips required by maxCoCMipLevel does not cause the downsampling chain to skip row or colums of pixels.
int tileSize = 1 << maxCoCMipLevel;
int paddedWidth = ((context.width + tileSize - 1) >> maxCoCMipLevel) << maxCoCMipLevel;
int paddedHeight = ((context.height + tileSize - 1) >> maxCoCMipLevel) << maxCoCMipLevel;

Vector4 cocKernelLimits;
CalculateCoCKernelLimits(context.screenHeight, out cocKernelLimits);
cocKernelLimits /= maxCoC;

// When the user clamps the bokeh size, the sample coordinates must be renormalized to the number of rings requested.
float kernelScaleReNormalization = 1f;
float fgAlphaFactor = 0f;

if (settings.kernelSize.value == KernelSize.Small)
{
kernelScaleReNormalization = 1f; // custom sampling pattern, does not use kDiskAllKernels array.
fgAlphaFactor = 0; // unused by shader
}
else if (settings.kernelSize.value == KernelSize.Medium)
{
kernelScaleReNormalization = 1f / k_DisAllKernelRingOffsets[1];
fgAlphaFactor = 1f / k_DiskAllKernelSizes[1];
}
else if (settings.kernelSize.value == KernelSize.Large)
{
kernelScaleReNormalization = 1f / k_DisAllKernelRingOffsets[2];
fgAlphaFactor = 1f / ((k_DiskAllKernelSizes[1] + k_DiskAllKernelSizes[2]) * 0.5f);
}
else if (settings.kernelSize.value == KernelSize.VeryLarge)
{
kernelScaleReNormalization = 1f / k_DisAllKernelRingOffsets[3];
fgAlphaFactor = 1f / k_DiskAllKernelSizes[2];
}

var sheet = context.propertySheets.Get(context.resources.shaders.depthOfField);
sheet.properties.Clear();
sheet.properties.SetFloat(ShaderIDs.Distance, s1);
sheet.properties.SetFloat(ShaderIDs.LensCoeff, coeff);
sheet.properties.SetVector(ShaderIDs.CoCKernelLimits, cocKernelLimits);
sheet.properties.SetVector(ShaderIDs.MaxCoCTexScale, new Vector4(paddedWidth / (float)context.width, paddedHeight / (float)context.height, context.width / (float)paddedWidth, context.height / (float)paddedHeight));
sheet.properties.SetVector(ShaderIDs.KernelScale, new Vector4(maxCoC * kernelScaleReNormalization / aspect, maxCoC * kernelScaleReNormalization, maxCoC * kernelScaleReNormalization, 0f));
sheet.properties.SetVector(ShaderIDs.MarginFactors, new Vector4(2f / (context.height >> 1), (context.height >> 1) / 2f, 0f, 0f));
sheet.properties.SetFloat(ShaderIDs.MaxCoC, maxCoC);
sheet.properties.SetFloat(ShaderIDs.RcpMaxCoC, 1f / maxCoC);
sheet.properties.SetFloat(ShaderIDs.RcpAspect, 1f / aspect);
sheet.properties.SetFloat(ShaderIDs.FgAlphaFactor, fgAlphaFactor);
sheet.properties.SetInteger(ShaderIDs.MaxRingIndex, (int)settings.kernelSize.value + 1);

var cmd = context.command;
cmd.BeginSample("DepthOfField");
Expand Down Expand Up @@ -213,13 +296,36 @@ public override void Render(PostProcessRenderContext context)
cmd.SetGlobalTexture(ShaderIDs.CoCTex, historyWrite);
}

// Generate a low-res maxCoC texture later used to infer how many samples are needed around any pixels to generate the bokeh effect.
if (useDynamicBokeh)
{
// Downsample MaxCoC.
context.GetScreenSpaceTemporaryRT(cmd, ShaderIDs.MaxCoCMips[1], 0, cocFormat, RenderTextureReadWrite.Linear, FilterMode.Point, paddedWidth >> 1, paddedHeight >> 1);
cmd.BlitFullscreenTriangle(ShaderIDs.CoCTex, ShaderIDs.MaxCoCMips[1], sheet, (int)Pass.downsampleInitialMaxCoC);

// Downsample until tile-size reaches CoC max radius.
for (int i = 2; i <= maxCoCMipLevel; ++i)
{
context.GetScreenSpaceTemporaryRT(cmd, ShaderIDs.MaxCoCMips[i], 0, cocFormat, RenderTextureReadWrite.Linear, FilterMode.Point, paddedWidth >> i, paddedHeight >> i);
cmd.BlitFullscreenTriangle(ShaderIDs.MaxCoCMips[i - 1], ShaderIDs.MaxCoCMips[i], sheet, (int)Pass.downsampleMaxCoC);
}

// Neighbor MaxCoC.
// We can then sample it during Bokeh simulation pass and dynamically adjust the number of samples (== number of rings) to generate the bokeh.
context.GetScreenSpaceTemporaryRT(cmd, ShaderIDs.MaxCoCTex, 0, cocFormat, RenderTextureReadWrite.Linear, FilterMode.Point, paddedWidth >> maxCoCMipLevel, paddedHeight >> maxCoCMipLevel);
cmd.BlitFullscreenTriangle(ShaderIDs.MaxCoCMips[maxCoCMipLevel], ShaderIDs.MaxCoCTex, sheet, (int)Pass.neighborMaxCoC);
}

// Downsampling and prefiltering pass
context.GetScreenSpaceTemporaryRT(cmd, ShaderIDs.DepthOfFieldTex, 0, colorFormat, RenderTextureReadWrite.Default, FilterMode.Bilinear, context.width / 2, context.height / 2);
cmd.BlitFullscreenTriangle(context.source, ShaderIDs.DepthOfFieldTex, sheet, (int)Pass.DownsampleAndPrefilter);

// Bokeh simulation pass
context.GetScreenSpaceTemporaryRT(cmd, ShaderIDs.DepthOfFieldTemp, 0, colorFormat, RenderTextureReadWrite.Default, FilterMode.Bilinear, context.width / 2, context.height / 2);
cmd.BlitFullscreenTriangle(ShaderIDs.DepthOfFieldTex, ShaderIDs.DepthOfFieldTemp, sheet, (int)Pass.BokehSmallKernel + (int)settings.kernelSize.value);
if (useDynamicBokeh)
cmd.BlitFullscreenTriangle(ShaderIDs.DepthOfFieldTex, ShaderIDs.DepthOfFieldTemp, sheet, (int)Pass.BokehDynamic);
else
cmd.BlitFullscreenTriangle(ShaderIDs.DepthOfFieldTex, ShaderIDs.DepthOfFieldTemp, sheet, (int)Pass.BokehSmallKernel);

// Postfilter pass
cmd.BlitFullscreenTriangle(ShaderIDs.DepthOfFieldTemp, ShaderIDs.DepthOfFieldTex, sheet, (int)Pass.PostFilter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,23 @@ static class ShaderIDs

internal static readonly int DepthOfFieldTemp = Shader.PropertyToID("_DepthOfFieldTemp");
internal static readonly int DepthOfFieldTex = Shader.PropertyToID("_DepthOfFieldTex");
internal static readonly int[] MaxCoCMips = new int[] {
Shader.PropertyToID("_CoCMip0"), Shader.PropertyToID("_CoCMip1"), Shader.PropertyToID("_CoCMip2"), Shader.PropertyToID("_CoCMip3"),
Shader.PropertyToID("_CoCMip4"), Shader.PropertyToID("_CoCMip5"), Shader.PropertyToID("_CoCMip6"), Shader.PropertyToID("_CoCMip7"),
Shader.PropertyToID("_CoCMip8"), Shader.PropertyToID("_CoCMip9"), Shader.PropertyToID("_CoCMip10"), Shader.PropertyToID("_CoCMip11")
};
internal static readonly int MaxCoCTex = Shader.PropertyToID("_MaxCoCTex");
internal static readonly int Distance = Shader.PropertyToID("_Distance");
internal static readonly int LensCoeff = Shader.PropertyToID("_LensCoeff");
internal static readonly int CoCKernelLimits = Shader.PropertyToID("_CoCKernelLimits");
internal static readonly int MaxCoCTexScale = Shader.PropertyToID("_MaxCoCTexScale");
internal static readonly int KernelScale = Shader.PropertyToID("_KernelScale");
internal static readonly int MarginFactors = Shader.PropertyToID("_MarginFactors");
internal static readonly int MaxCoC = Shader.PropertyToID("_MaxCoC");
internal static readonly int RcpMaxCoC = Shader.PropertyToID("_RcpMaxCoC");
internal static readonly int RcpAspect = Shader.PropertyToID("_RcpAspect");
internal static readonly int FgAlphaFactor = Shader.PropertyToID("_FgAlphaFactor");
internal static readonly int MaxRingIndex = Shader.PropertyToID("_MaxRingIndex");
internal static readonly int CoCTex = Shader.PropertyToID("_CoCTex");
internal static readonly int TaaParams = Shader.PropertyToID("_TaaParams");

Expand Down
Loading