Created
June 7, 2016 08:33
-
-
Save BichengLUO/6b3fecf14121b4598f641fd0b8af07fb to your computer and use it in GitHub Desktop.
Implement depth of field in Unity3D
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
namespace UnityStandardAssets.ImageEffects | |
{ | |
[ExecuteInEditMode] | |
[RequireComponent (typeof(Camera))] | |
[AddComponentMenu ("Image Effects/Camera/Depth of Field (Lens Blur, Scatter, DX11)") ] | |
public class DepthOfField : PostEffectsBase { | |
public bool visualizeFocus = false; | |
public float focalLength = 10.0f; | |
public float focalSize = 0.05f; | |
public float aperture = 0.5f; | |
public Transform focalTransform = null; | |
public float maxBlurSize = 2.0f; | |
public bool highResolution = false; | |
public enum BlurType { | |
DiscBlur = 0, | |
DX11 = 1, | |
} | |
public enum BlurSampleCount { | |
Low = 0, | |
Medium = 1, | |
High = 2, | |
} | |
public BlurType blurType = BlurType.DiscBlur; | |
public BlurSampleCount blurSampleCount = BlurSampleCount.High; | |
public bool nearBlur = false; | |
public float foregroundOverlap = 1.0f; | |
public Shader dofHdrShader; | |
private Material dofHdrMaterial = null; | |
public Shader dx11BokehShader; | |
private Material dx11bokehMaterial; | |
public float dx11BokehThreshold = 0.5f; | |
public float dx11SpawnHeuristic = 0.0875f; | |
public Texture2D dx11BokehTexture = null; | |
public float dx11BokehScale = 1.2f; | |
public float dx11BokehIntensity = 2.5f; | |
private float focalDistance01 = 10.0f; | |
private ComputeBuffer cbDrawArgs; | |
private ComputeBuffer cbPoints; | |
private float internalBlurWidth = 1.0f; | |
private Camera cachedCamera; | |
public override bool CheckResources () { | |
CheckSupport (true); // only requires depth, not HDR | |
dofHdrMaterial = CheckShaderAndCreateMaterial (dofHdrShader, dofHdrMaterial); | |
if (supportDX11 && blurType == BlurType.DX11) { | |
dx11bokehMaterial = CheckShaderAndCreateMaterial(dx11BokehShader, dx11bokehMaterial); | |
CreateComputeResources (); | |
} | |
if (!isSupported) | |
ReportAutoDisable (); | |
return isSupported; | |
} | |
void OnEnable () { | |
cachedCamera = GetComponent<Camera>(); | |
cachedCamera.depthTextureMode |= DepthTextureMode.Depth; | |
} | |
void OnDisable () { | |
ReleaseComputeResources (); | |
if (dofHdrMaterial) DestroyImmediate(dofHdrMaterial); | |
dofHdrMaterial = null; | |
if (dx11bokehMaterial) DestroyImmediate(dx11bokehMaterial); | |
dx11bokehMaterial = null; | |
} | |
void ReleaseComputeResources () { | |
if (cbDrawArgs != null) cbDrawArgs.Release(); | |
cbDrawArgs = null; | |
if (cbPoints != null) cbPoints.Release(); | |
cbPoints = null; | |
} | |
void CreateComputeResources () { | |
if (cbDrawArgs == null) | |
{ | |
cbDrawArgs = new ComputeBuffer (1, 16, ComputeBufferType.DrawIndirect); | |
var args= new int[4]; | |
args[0] = 0; args[1] = 1; args[2] = 0; args[3] = 0; | |
cbDrawArgs.SetData (args); | |
} | |
if (cbPoints == null) | |
{ | |
cbPoints = new ComputeBuffer (90000, 12+16, ComputeBufferType.Append); | |
} | |
} | |
float FocalDistance01 ( float worldDist) { | |
return cachedCamera.WorldToViewportPoint((worldDist-cachedCamera.nearClipPlane) * cachedCamera.transform.forward + cachedCamera.transform.position).z / (cachedCamera.farClipPlane-cachedCamera.nearClipPlane); | |
} | |
private void WriteCoc ( RenderTexture fromTo, bool fgDilate) { | |
dofHdrMaterial.SetTexture("_FgOverlap", null); | |
if (nearBlur && fgDilate) { | |
int rtW = fromTo.width/2; | |
int rtH = fromTo.height/2; | |
// capture fg coc | |
RenderTexture temp2 = RenderTexture.GetTemporary (rtW, rtH, 0, fromTo.format); | |
Graphics.Blit (fromTo, temp2, dofHdrMaterial, 4); | |
// special blur | |
float fgAdjustment = internalBlurWidth * foregroundOverlap; | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, fgAdjustment , 0.0f, fgAdjustment)); | |
RenderTexture temp1 = RenderTexture.GetTemporary (rtW, rtH, 0, fromTo.format); | |
Graphics.Blit (temp2, temp1, dofHdrMaterial, 2); | |
RenderTexture.ReleaseTemporary(temp2); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (fgAdjustment, 0.0f, 0.0f, fgAdjustment)); | |
temp2 = RenderTexture.GetTemporary (rtW, rtH, 0, fromTo.format); | |
Graphics.Blit (temp1, temp2, dofHdrMaterial, 2); | |
RenderTexture.ReleaseTemporary(temp1); | |
// "merge up" with background COC | |
dofHdrMaterial.SetTexture("_FgOverlap", temp2); | |
fromTo.MarkRestoreExpected(); // only touching alpha channel, RT restore expected | |
Graphics.Blit (fromTo, fromTo, dofHdrMaterial, 13); | |
RenderTexture.ReleaseTemporary(temp2); | |
} | |
else { | |
// capture full coc in alpha channel (fromTo is not read, but bound to detect screen flip) | |
fromTo.MarkRestoreExpected(); // only touching alpha channel, RT restore expected | |
Graphics.Blit (fromTo, fromTo, dofHdrMaterial, 0); | |
} | |
} | |
void OnRenderImage (RenderTexture source, RenderTexture destination) { | |
if (!CheckResources ()) { | |
Graphics.Blit (source, destination); | |
return; | |
} | |
// clamp & prepare values so they make sense | |
if (aperture < 0.0f) aperture = 0.0f; | |
if (maxBlurSize < 0.1f) maxBlurSize = 0.1f; | |
focalSize = Mathf.Clamp(focalSize, 0.0f, 2.0f); | |
internalBlurWidth = Mathf.Max(maxBlurSize, 0.0f); | |
// focal & coc calculations | |
focalDistance01 = (focalTransform) ? (cachedCamera.WorldToViewportPoint (focalTransform.position)).z / (cachedCamera.farClipPlane) : FocalDistance01 (focalLength); | |
dofHdrMaterial.SetVector("_CurveParams", new Vector4(1.0f, focalSize, (1.0f / (1.0f - aperture) - 1.0f), focalDistance01)); | |
// possible render texture helpers | |
RenderTexture rtLow = null; | |
RenderTexture rtLow2 = null; | |
RenderTexture rtSuperLow1 = null; | |
RenderTexture rtSuperLow2 = null; | |
float fgBlurDist = internalBlurWidth * foregroundOverlap; | |
if (visualizeFocus) | |
{ | |
// | |
// 2. | |
// visualize coc | |
// | |
// | |
WriteCoc (source, true); | |
Graphics.Blit (source, destination, dofHdrMaterial, 16); | |
} | |
else if ((blurType == BlurType.DX11) && dx11bokehMaterial) | |
{ | |
// | |
// 1. | |
// optimized dx11 bokeh scatter | |
// | |
// | |
if (highResolution) { | |
internalBlurWidth = internalBlurWidth < 0.1f ? 0.1f : internalBlurWidth; | |
fgBlurDist = internalBlurWidth * foregroundOverlap; | |
rtLow = RenderTexture.GetTemporary (source.width, source.height, 0, source.format); | |
var dest2= RenderTexture.GetTemporary (source.width, source.height, 0, source.format); | |
// capture COC | |
WriteCoc (source, false); | |
// blur a bit so we can do a frequency check | |
rtSuperLow1 = RenderTexture.GetTemporary(source.width>>1, source.height>>1, 0, source.format); | |
rtSuperLow2 = RenderTexture.GetTemporary(source.width>>1, source.height>>1, 0, source.format); | |
Graphics.Blit(source, rtSuperLow1, dofHdrMaterial, 15); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, 1.5f , 0.0f, 1.5f)); | |
Graphics.Blit (rtSuperLow1, rtSuperLow2, dofHdrMaterial, 19); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (1.5f, 0.0f, 0.0f, 1.5f)); | |
Graphics.Blit (rtSuperLow2, rtSuperLow1, dofHdrMaterial, 19); | |
// capture fg coc | |
if (nearBlur) | |
Graphics.Blit (source, rtSuperLow2, dofHdrMaterial, 4); | |
dx11bokehMaterial.SetTexture ("_BlurredColor", rtSuperLow1); | |
dx11bokehMaterial.SetFloat ("_SpawnHeuristic", dx11SpawnHeuristic); | |
dx11bokehMaterial.SetVector ("_BokehParams", new Vector4(dx11BokehScale, dx11BokehIntensity, Mathf.Clamp(dx11BokehThreshold, 0.005f, 4.0f), internalBlurWidth)); | |
dx11bokehMaterial.SetTexture ("_FgCocMask", nearBlur ? rtSuperLow2 : null); | |
// collect bokeh candidates and replace with a darker pixel | |
Graphics.SetRandomWriteTarget (1, cbPoints); | |
Graphics.Blit (source, rtLow, dx11bokehMaterial, 0); | |
Graphics.ClearRandomWriteTargets (); | |
// fg coc blur happens here (after collect!) | |
if (nearBlur) { | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, fgBlurDist , 0.0f, fgBlurDist)); | |
Graphics.Blit (rtSuperLow2, rtSuperLow1, dofHdrMaterial, 2); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (fgBlurDist, 0.0f, 0.0f, fgBlurDist)); | |
Graphics.Blit (rtSuperLow1, rtSuperLow2, dofHdrMaterial, 2); | |
// merge fg coc with bg coc | |
Graphics.Blit (rtSuperLow2, rtLow, dofHdrMaterial, 3); | |
} | |
// NEW: LAY OUT ALPHA on destination target so we get nicer outlines for the high rez version | |
Graphics.Blit (rtLow, dest2, dofHdrMaterial, 20); | |
// box blur (easier to merge with bokeh buffer) | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (internalBlurWidth, 0.0f , 0.0f, internalBlurWidth)); | |
Graphics.Blit (rtLow, source, dofHdrMaterial, 5); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, internalBlurWidth, 0.0f, internalBlurWidth)); | |
Graphics.Blit (source, dest2, dofHdrMaterial, 21); | |
// apply bokeh candidates | |
Graphics.SetRenderTarget (dest2); | |
ComputeBuffer.CopyCount (cbPoints, cbDrawArgs, 0); | |
dx11bokehMaterial.SetBuffer ("pointBuffer", cbPoints); | |
dx11bokehMaterial.SetTexture ("_MainTex", dx11BokehTexture); | |
dx11bokehMaterial.SetVector ("_Screen", new Vector3(1.0f/(1.0f*source.width), 1.0f/(1.0f*source.height), internalBlurWidth)); | |
dx11bokehMaterial.SetPass (2); | |
Graphics.DrawProceduralIndirect (MeshTopology.Points, cbDrawArgs, 0); | |
Graphics.Blit (dest2, destination); // hackaround for DX11 high resolution flipfun (OPTIMIZEME) | |
RenderTexture.ReleaseTemporary(dest2); | |
RenderTexture.ReleaseTemporary(rtSuperLow1); | |
RenderTexture.ReleaseTemporary(rtSuperLow2); | |
} | |
else { | |
rtLow = RenderTexture.GetTemporary (source.width>>1, source.height>>1, 0, source.format); | |
rtLow2 = RenderTexture.GetTemporary (source.width>>1, source.height>>1, 0, source.format); | |
fgBlurDist = internalBlurWidth * foregroundOverlap; | |
// capture COC & color in low resolution | |
WriteCoc (source, false); | |
source.filterMode = FilterMode.Bilinear; | |
Graphics.Blit (source, rtLow, dofHdrMaterial, 6); | |
// blur a bit so we can do a frequency check | |
rtSuperLow1 = RenderTexture.GetTemporary(rtLow.width>>1, rtLow.height>>1, 0, rtLow.format); | |
rtSuperLow2 = RenderTexture.GetTemporary(rtLow.width>>1, rtLow.height>>1, 0, rtLow.format); | |
Graphics.Blit(rtLow, rtSuperLow1, dofHdrMaterial, 15); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, 1.5f , 0.0f, 1.5f)); | |
Graphics.Blit (rtSuperLow1, rtSuperLow2, dofHdrMaterial, 19); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (1.5f, 0.0f, 0.0f, 1.5f)); | |
Graphics.Blit (rtSuperLow2, rtSuperLow1, dofHdrMaterial, 19); | |
RenderTexture rtLow3 = null; | |
if (nearBlur) { | |
// capture fg coc | |
rtLow3 = RenderTexture.GetTemporary (source.width>>1, source.height>>1, 0, source.format); | |
Graphics.Blit (source, rtLow3, dofHdrMaterial, 4); | |
} | |
dx11bokehMaterial.SetTexture ("_BlurredColor", rtSuperLow1); | |
dx11bokehMaterial.SetFloat ("_SpawnHeuristic", dx11SpawnHeuristic); | |
dx11bokehMaterial.SetVector ("_BokehParams", new Vector4(dx11BokehScale, dx11BokehIntensity, Mathf.Clamp(dx11BokehThreshold, 0.005f, 4.0f), internalBlurWidth)); | |
dx11bokehMaterial.SetTexture ("_FgCocMask", rtLow3); | |
// collect bokeh candidates and replace with a darker pixel | |
Graphics.SetRandomWriteTarget (1, cbPoints); | |
Graphics.Blit (rtLow, rtLow2, dx11bokehMaterial, 0); | |
Graphics.ClearRandomWriteTargets (); | |
RenderTexture.ReleaseTemporary(rtSuperLow1); | |
RenderTexture.ReleaseTemporary(rtSuperLow2); | |
// fg coc blur happens here (after collect!) | |
if (nearBlur) { | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, fgBlurDist , 0.0f, fgBlurDist)); | |
Graphics.Blit (rtLow3, rtLow, dofHdrMaterial, 2); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (fgBlurDist, 0.0f, 0.0f, fgBlurDist)); | |
Graphics.Blit (rtLow, rtLow3, dofHdrMaterial, 2); | |
// merge fg coc with bg coc | |
Graphics.Blit (rtLow3, rtLow2, dofHdrMaterial, 3); | |
} | |
// box blur (easier to merge with bokeh buffer) | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (internalBlurWidth, 0.0f , 0.0f, internalBlurWidth)); | |
Graphics.Blit (rtLow2, rtLow, dofHdrMaterial, 5); | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, internalBlurWidth, 0.0f, internalBlurWidth)); | |
Graphics.Blit (rtLow, rtLow2, dofHdrMaterial, 5); | |
// apply bokeh candidates | |
Graphics.SetRenderTarget (rtLow2); | |
ComputeBuffer.CopyCount (cbPoints, cbDrawArgs, 0); | |
dx11bokehMaterial.SetBuffer ("pointBuffer", cbPoints); | |
dx11bokehMaterial.SetTexture ("_MainTex", dx11BokehTexture); | |
dx11bokehMaterial.SetVector ("_Screen", new Vector3(1.0f/(1.0f*rtLow2.width), 1.0f/(1.0f*rtLow2.height), internalBlurWidth)); | |
dx11bokehMaterial.SetPass (1); | |
Graphics.DrawProceduralIndirect (MeshTopology.Points, cbDrawArgs, 0); | |
// upsample & combine | |
dofHdrMaterial.SetTexture ("_LowRez", rtLow2); | |
dofHdrMaterial.SetTexture ("_FgOverlap", rtLow3); | |
dofHdrMaterial.SetVector ("_Offsets", ((1.0f*source.width)/(1.0f*rtLow2.width)) * internalBlurWidth * Vector4.one); | |
Graphics.Blit (source, destination, dofHdrMaterial, 9); | |
if (rtLow3) RenderTexture.ReleaseTemporary(rtLow3); | |
} | |
} | |
else | |
{ | |
// | |
// 2. | |
// poisson disc style blur in low resolution | |
// | |
// | |
source.filterMode = FilterMode.Bilinear; | |
if (highResolution) internalBlurWidth *= 2.0f; | |
WriteCoc (source, true); | |
rtLow = RenderTexture.GetTemporary (source.width >> 1, source.height >> 1, 0, source.format); | |
rtLow2 = RenderTexture.GetTemporary (source.width >> 1, source.height >> 1, 0, source.format); | |
int blurPass = (blurSampleCount == BlurSampleCount.High || blurSampleCount == BlurSampleCount.Medium) ? 17 : 11; | |
if (highResolution) { | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, internalBlurWidth, 0.025f, internalBlurWidth)); | |
Graphics.Blit (source, destination, dofHdrMaterial, blurPass); | |
} | |
else { | |
dofHdrMaterial.SetVector ("_Offsets", new Vector4 (0.0f, internalBlurWidth, 0.1f, internalBlurWidth)); | |
// blur | |
Graphics.Blit (source, rtLow, dofHdrMaterial, 6); | |
Graphics.Blit (rtLow, rtLow2, dofHdrMaterial, blurPass); | |
// cheaper blur in high resolution, upsample and combine | |
dofHdrMaterial.SetTexture("_LowRez", rtLow2); | |
dofHdrMaterial.SetTexture("_FgOverlap", null); | |
dofHdrMaterial.SetVector ("_Offsets", Vector4.one * ((1.0f*source.width)/(1.0f*rtLow2.width)) * internalBlurWidth); | |
Graphics.Blit (source, destination, dofHdrMaterial, blurSampleCount == BlurSampleCount.High ? 18 : 12); | |
} | |
} | |
if (rtLow) RenderTexture.ReleaseTemporary(rtLow); | |
if (rtLow2) RenderTexture.ReleaseTemporary(rtLow2); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment