289 lines
14 KiB
C#
289 lines
14 KiB
C#
using System;
|
|
using UnityEngine.Rendering.RenderGraphModule;
|
|
using System.Runtime.CompilerServices; // AggressiveInlining
|
|
|
|
namespace UnityEngine.Rendering.Universal
|
|
{
|
|
internal sealed class DepthOfFieldBokehPostProcessPass : PostProcessPass
|
|
{
|
|
public const string k_TargetName = "_DoFTarget";
|
|
const int k_DownSample = 2;
|
|
|
|
Material m_Material;
|
|
bool m_IsValid;
|
|
|
|
Vector4[] m_BokehKernel;
|
|
int m_BokehHash;
|
|
|
|
// Needed if the device changes its render target width/height (ex, Mobile platform allows change of orientation)
|
|
float m_BokehMaxRadius;
|
|
float m_BokehRcpAspect;
|
|
|
|
bool m_UseFastSRGBLinearConversion;
|
|
|
|
public DepthOfFieldBokehPostProcessPass(Shader shader)
|
|
{
|
|
this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing - 1;
|
|
this.profilingSampler = null;
|
|
|
|
m_Material = PostProcessUtils.LoadShader(shader, passName);
|
|
m_IsValid = m_Material != null;
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
CoreUtils.Destroy(m_Material);
|
|
m_IsValid = false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Setup(bool useFastSRGBLinearConversion)
|
|
{
|
|
m_UseFastSRGBLinearConversion = useFastSRGBLinearConversion;
|
|
}
|
|
|
|
private class DoFBokehPassData
|
|
{
|
|
internal Material material;
|
|
|
|
// Setup
|
|
internal Vector4[] bokehKernel;
|
|
internal int downSample;
|
|
internal float uvMargin;
|
|
internal Vector4 cocParams;
|
|
internal bool useFastSRGBLinearConversion;
|
|
internal bool enableAlphaOutput;
|
|
// Inputs
|
|
internal TextureHandle sourceTexture;
|
|
internal TextureHandle depthTexture;
|
|
// Pass textures
|
|
internal TextureHandle halfCoCTexture;
|
|
internal TextureHandle fullCoCTexture;
|
|
internal TextureHandle pingTexture;
|
|
internal TextureHandle pongTexture;
|
|
// Output texture
|
|
internal TextureHandle destinationTexture;
|
|
};
|
|
|
|
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
|
{
|
|
if (!m_IsValid)
|
|
return;
|
|
|
|
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
|
|
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
|
|
|
|
var depthOfField = volumeStack.GetComponent<DepthOfField>();
|
|
|
|
var sourceTexture = resourceData.cameraColor;
|
|
var destinationTexture = PostProcessUtils.CreateCompatibleTexture(renderGraph, sourceTexture, k_TargetName, true, FilterMode.Bilinear);
|
|
var srcDesc = sourceTexture.GetDescriptor(renderGraph);
|
|
|
|
int wh = srcDesc.width / k_DownSample;
|
|
int hh = srcDesc.height / k_DownSample;
|
|
|
|
// Pass Textures
|
|
var fullCoCTextureDesc = PostProcessUtils.GetCompatibleDescriptor(srcDesc, srcDesc.width, srcDesc.height, Experimental.Rendering.GraphicsFormat.R8_UNorm);
|
|
var fullCoCTexture = PostProcessUtils.CreateCompatibleTexture(renderGraph, fullCoCTextureDesc, "_FullCoCTexture", true, FilterMode.Bilinear);
|
|
var pingTextureDesc = PostProcessUtils.GetCompatibleDescriptor(srcDesc, wh, hh, Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat);
|
|
var pingTexture = PostProcessUtils.CreateCompatibleTexture(renderGraph, pingTextureDesc, "_PingTexture", true, FilterMode.Bilinear);
|
|
var pongTextureDesc = PostProcessUtils.GetCompatibleDescriptor(srcDesc, wh, hh, Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat);
|
|
var pongTexture = PostProcessUtils.CreateCompatibleTexture(renderGraph, pongTextureDesc, "_PongTexture", true, FilterMode.Bilinear);
|
|
|
|
using (var builder = renderGraph.AddUnsafePass<DoFBokehPassData>("Depth of Field - Bokeh", out var passData))
|
|
{
|
|
// Setup
|
|
// "A Lens and Aperture Camera Model for Synthetic Image Generation" [Potmesil81]
|
|
float F = depthOfField.focalLength.value / 1000f;
|
|
float A = depthOfField.focalLength.value / depthOfField.aperture.value;
|
|
float P = depthOfField.focusDistance.value;
|
|
float maxCoC = (A * F) / (P - F);
|
|
float maxRadius = GetMaxBokehRadiusInPixels(srcDesc.height);
|
|
float rcpAspect = 1f / (wh / (float)hh);
|
|
|
|
// Prepare the bokeh kernel constant buffer
|
|
int hash = depthOfField.GetHashCode();
|
|
if (hash != m_BokehHash || maxRadius != m_BokehMaxRadius || rcpAspect != m_BokehRcpAspect)
|
|
{
|
|
m_BokehHash = hash;
|
|
m_BokehMaxRadius = maxRadius;
|
|
m_BokehRcpAspect = rcpAspect;
|
|
PrepareBokehKernel( ref m_BokehKernel,
|
|
depthOfField.bladeCount.value,
|
|
depthOfField.bladeCurvature.value,
|
|
depthOfField.bladeRotation.value,
|
|
maxRadius, rcpAspect);
|
|
}
|
|
float uvMargin = (1.0f / srcDesc.height) * k_DownSample;
|
|
|
|
passData.bokehKernel = m_BokehKernel;
|
|
passData.downSample = k_DownSample;
|
|
passData.uvMargin = uvMargin;
|
|
passData.cocParams = new Vector4(P, maxCoC, maxRadius, rcpAspect);
|
|
passData.useFastSRGBLinearConversion = m_UseFastSRGBLinearConversion;
|
|
passData.enableAlphaOutput = cameraData.isAlphaOutputEnabled;
|
|
|
|
// Inputs
|
|
passData.sourceTexture = sourceTexture;
|
|
builder.UseTexture(sourceTexture, AccessFlags.Read);
|
|
|
|
passData.depthTexture = resourceData.cameraDepthTexture;
|
|
builder.UseTexture(resourceData.cameraDepthTexture, AccessFlags.Read);
|
|
|
|
passData.material = m_Material;
|
|
|
|
// Pass Textures
|
|
passData.fullCoCTexture = fullCoCTexture;
|
|
builder.UseTexture(fullCoCTexture, AccessFlags.ReadWrite);
|
|
passData.pingTexture = pingTexture;
|
|
builder.UseTexture(pingTexture, AccessFlags.ReadWrite);
|
|
passData.pongTexture = pongTexture;
|
|
builder.UseTexture(pongTexture, AccessFlags.ReadWrite);
|
|
|
|
// Outputs
|
|
passData.destinationTexture = destinationTexture;
|
|
builder.UseTexture(destinationTexture, AccessFlags.Write);
|
|
|
|
// TODO RENDERGRAPH: properly setup dependencies between passes
|
|
builder.SetRenderFunc(static (DoFBokehPassData data, UnsafeGraphContext context) =>
|
|
{
|
|
var dofMat = data.material;
|
|
var cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
|
|
RTHandle sourceTextureHdl = data.sourceTexture;
|
|
RTHandle dst = data.destinationTexture;
|
|
|
|
// Setup
|
|
using (new ProfilingScope(ProfilingSampler.Get(URPProfileId.RG_SetupDoF)))
|
|
{
|
|
Vector4 sourceSize = PostProcessUtils.CalcShaderSourceSize(data.sourceTexture);
|
|
|
|
dofMat.SetVector(ShaderConstants._CoCParams, data.cocParams);
|
|
dofMat.SetVectorArray(ShaderConstants._BokehKernel, data.bokehKernel);
|
|
dofMat.SetVector(ShaderConstants._DownSampleScaleFactor, new Vector4(1.0f / data.downSample, 1.0f / data.downSample, data.downSample,data.downSample));
|
|
dofMat.SetVector(ShaderConstants._BokehConstants, new Vector4(data.uvMargin, data.uvMargin * 2.0f));
|
|
dofMat.SetVector(ShaderConstants._SourceSize, sourceSize);
|
|
|
|
CoreUtils.SetKeyword(dofMat, ShaderKeywordStrings.UseFastSRGBLinearConversion, data.useFastSRGBLinearConversion);
|
|
CoreUtils.SetKeyword(dofMat, ShaderKeywordStrings._ENABLE_ALPHA_OUTPUT, data.enableAlphaOutput);
|
|
}
|
|
|
|
// Compute CoC
|
|
using (new ProfilingScope(ProfilingSampler.Get(URPProfileId.RG_DOFComputeCOC)))
|
|
{
|
|
dofMat.SetTexture(ShaderConstants._CameraDepthTextureID, data.depthTexture);
|
|
Blitter.BlitCameraTexture(cmd, sourceTextureHdl, data.fullCoCTexture, dofMat, ShaderPass.k_ComputeCoc);
|
|
}
|
|
|
|
// Downscale and Prefilter Color + CoC
|
|
using (new ProfilingScope(ProfilingSampler.Get(URPProfileId.RG_DOFDownscalePrefilter)))
|
|
{
|
|
dofMat.SetTexture(ShaderConstants._FullCoCTexture, data.fullCoCTexture);
|
|
Blitter.BlitCameraTexture(cmd, sourceTextureHdl, data.pingTexture, dofMat, ShaderPass.k_DownscalePrefilter);
|
|
}
|
|
|
|
// Blur
|
|
using (new ProfilingScope(ProfilingSampler.Get(URPProfileId.RG_DOFBlurBokeh)))
|
|
{
|
|
Blitter.BlitCameraTexture(cmd, data.pingTexture, data.pongTexture, dofMat, ShaderPass.k_Blur);
|
|
}
|
|
|
|
// Post Filtering
|
|
using (new ProfilingScope(ProfilingSampler.Get(URPProfileId.RG_DOFPostFilter)))
|
|
{
|
|
Blitter.BlitCameraTexture(cmd, data.pongTexture, data.pingTexture, dofMat, ShaderPass.k_PostFilter);
|
|
}
|
|
|
|
// Composite
|
|
using (new ProfilingScope(ProfilingSampler.Get(URPProfileId.RG_DOFComposite)))
|
|
{
|
|
dofMat.SetTexture(ShaderConstants._DofTexture, data.pingTexture);
|
|
Blitter.BlitCameraTexture(cmd, sourceTextureHdl, dst, dofMat, ShaderPass.k_Composite);
|
|
}
|
|
});
|
|
}
|
|
|
|
resourceData.cameraColor = destinationTexture;
|
|
}
|
|
|
|
static void PrepareBokehKernel(ref Vector4[] bokehKernel, int bladeCount, float bladeCurvature, float bladeRotation, float maxRadius, float rcpAspect)
|
|
{
|
|
const int kRings = 4;
|
|
const int kPointsPerRing = 7;
|
|
|
|
// Check the existing array
|
|
if (bokehKernel == null)
|
|
bokehKernel = new Vector4[42];
|
|
|
|
// Fill in sample points (concentric circles transformed to rotated N-Gon)
|
|
int idx = 0;
|
|
float bladeCountf = bladeCount;
|
|
float curvature = 1f - bladeCurvature;
|
|
float rotation = bladeRotation * Mathf.Deg2Rad;
|
|
const float PI = Mathf.PI;
|
|
const float TWO_PI = Mathf.PI * 2f;
|
|
|
|
for (int ring = 1; ring < kRings; ring++)
|
|
{
|
|
float bias = 1f / kPointsPerRing;
|
|
float radius = (ring + bias) / (kRings - 1f + bias);
|
|
int points = ring * kPointsPerRing;
|
|
|
|
for (int point = 0; point < points; point++)
|
|
{
|
|
// Angle on ring
|
|
float phi = 2f * PI * point / points;
|
|
|
|
// Transform to rotated N-Gon
|
|
// Adapted from "CryEngine 3 Graphics Gems" [Sousa13]
|
|
float nt = Mathf.Cos(PI / bladeCountf);
|
|
float dt = Mathf.Cos(phi - (TWO_PI / bladeCountf) * Mathf.Floor((bladeCountf * phi + Mathf.PI) / TWO_PI));
|
|
float r = radius * Mathf.Pow(nt / dt, curvature);
|
|
float u = r * Mathf.Cos(phi - rotation);
|
|
float v = r * Mathf.Sin(phi - rotation);
|
|
|
|
float uRadius = u * maxRadius;
|
|
float vRadius = v * maxRadius;
|
|
float uRadiusPowTwo = uRadius * uRadius;
|
|
float vRadiusPowTwo = vRadius * vRadius;
|
|
float kernelLength = Mathf.Sqrt((uRadiusPowTwo + vRadiusPowTwo));
|
|
float uRCP = uRadius * rcpAspect;
|
|
|
|
bokehKernel[idx] = new Vector4(uRadius, vRadius, kernelLength, uRCP);
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static float GetMaxBokehRadiusInPixels(float viewportHeight)
|
|
{
|
|
// Estimate the maximum radius of bokeh (empirically derived from the ring count)
|
|
const float kRadiusInPixels = 14f;
|
|
return Mathf.Min(0.05f, kRadiusInPixels / viewportHeight);
|
|
}
|
|
|
|
// Precomputed shader ids to same some CPU cycles (mostly affects mobile)
|
|
public static class ShaderConstants
|
|
{
|
|
public static readonly int _CameraDepthTextureID = Shader.PropertyToID("_CameraDepthTexture");
|
|
public static readonly int _SourceSize = Shader.PropertyToID("_SourceSize");
|
|
|
|
public static readonly int _FullCoCTexture = Shader.PropertyToID("_FullCoCTexture");
|
|
public static readonly int _DofTexture = Shader.PropertyToID("_DofTexture");
|
|
public static readonly int _CoCParams = Shader.PropertyToID("_CoCParams");
|
|
public static readonly int _BokehKernel = Shader.PropertyToID("_BokehKernel");
|
|
public static readonly int _BokehConstants = Shader.PropertyToID("_BokehConstants");
|
|
public static readonly int _DownSampleScaleFactor = Shader.PropertyToID("_DownSampleScaleFactor");
|
|
}
|
|
|
|
public static class ShaderPass
|
|
{
|
|
public const int k_ComputeCoc = 0;
|
|
public const int k_DownscalePrefilter = 1;
|
|
public const int k_Blur = 2;
|
|
public const int k_PostFilter = 3;
|
|
public const int k_Composite = 4;
|
|
}
|
|
}
|
|
}
|