using UnityEngine; namespace UnityStandardAssets.CinematicEffects { [ExecuteInEditMode] [RequireComponent(typeof(Camera))] [AddComponentMenu("Image Effects/Other/SMAA")] public class AntiAliasing : MonoBehaviour { public enum DebugDisplay { Off, Edges, Weights, Depth, Accumulation } public enum EdgeType { Luminance, Color, Depth } public enum TemporalType { Off, SMAA_2x, Standard_2x, Standard_4x, Standard_8x, Standard_16x } private enum Passes { Copy = 0, LumaDetection = 1, ClearToBlack = 2, WeightCalculation = 3, WeightsAndBlend1 = 4, WeightsAndBlend2 = 5, ColorDetection = 6, MergeFrames = 7, DepthDetection = 8, DebugDepth = 9 } public DebugDisplay displayType = DebugDisplay.Off; // we could make this public, but color and luma are less reliable so we can // hardcode this to depth for now public EdgeType edgeType = EdgeType.Depth; public Texture2D areaTex; public Texture2D searchTex; // temporal AA parameters private Matrix4x4 m_BaseProjectionMatrix; private Matrix4x4 m_PrevViewProjMat; private Camera m_AACamera; private int m_SampleIndex; [Range(0, 80)] public float K = 1.0f; public TemporalType temporalType = TemporalType.Off; [Range(0, 1)] public float temporalAccumulationWeight = 0.3f; // This should be hidden from view when EdgeType is not Depth [Range(0.01f, 1.0f)] public float depthThreshold = 0.1f; private static Matrix4x4 CalculateViewProjection(Camera camera, Matrix4x4 prjMatrix) { Matrix4x4 viewMat = camera.worldToCameraMatrix; Matrix4x4 projMat = GL.GetGPUProjectionMatrix(prjMatrix, true); return projMat * viewMat; } private void StoreBaseProjectionMatrix(Matrix4x4 prjMatrix) { m_BaseProjectionMatrix = prjMatrix; } private void StorePreviousViewProjMatrix(Matrix4x4 viewPrjMatrix) { m_PrevViewProjMat = viewPrjMatrix; } private Camera aaCamera { get { if (m_AACamera == null) m_AACamera = GetComponent(); return m_AACamera; } } public void UpdateSampleIndex() { int numSamples = 1; if (temporalType == TemporalType.SMAA_2x || temporalType == TemporalType.Standard_2x) { numSamples = 2; } else if (temporalType == TemporalType.Standard_4x) { numSamples = 4; } else if (temporalType == TemporalType.Standard_8x) { numSamples = 8; } else if (temporalType == TemporalType.Standard_16x) { numSamples = 16; } m_SampleIndex = (m_SampleIndex + 1) % numSamples; } private Vector2 GetJitterStandard2X() { int[,] samples = { {4, 4}, {-4, -4}, }; int sampleX = samples[m_SampleIndex, 0]; int sampleY = samples[m_SampleIndex, 1]; float v0 = sampleX / 16.0f; float v1 = sampleY / 16.0f; return new Vector2(v0, v1); } private Vector2 GetJitterStandard4X() { int[,] samples = { {-2, -6}, {6, -2}, {-6, 2}, {2, 6} }; int sampleX = samples[m_SampleIndex, 0]; int sampleY = samples[m_SampleIndex, 1]; float v0 = sampleX / 16.0f; float v1 = sampleY / 16.0f; return new Vector2(v0, v1); } private Vector2 GetJitterStandard8X() { int[,] samples = { {7, -7}, {-3, -5}, {3, 7}, {-7, -1}, {5, 1}, {-1, 3}, {1, -3}, {-5, 5} }; int sampleX = samples[m_SampleIndex, 0]; int sampleY = samples[m_SampleIndex, 1]; float v0 = sampleX / 16.0f; float v1 = sampleY / 16.0f; return new Vector2(v0, v1); } private Vector2 GetJitterStandard16X() { int[,] samples = { {7, -4}, {-1, -3}, {3, -5}, {-5, -2}, {6, 7}, {-2, 6}, {2, 5}, {-6, -4}, {4, -1}, {-3, 2}, {1, 1}, {-8, 0}, {5, 3}, {-4, -6}, {0, -7}, {-7, -8} }; int sampleX = samples[m_SampleIndex, 0]; int sampleY = samples[m_SampleIndex, 1]; float v0 = (sampleX + .5f) / 16.0f; float v1 = (sampleY + .5f) / 16.0f; return new Vector2(v0, v1); } private Vector2 GetJitterSMAAX2() { float jitterAmount = .25f; jitterAmount *= (m_SampleIndex == 0) ? -1.0f : 1.0f; //Debug.Log("Jitter"); //jitterAmount = 0.0f; float v0 = jitterAmount; float v1 = -jitterAmount; return new Vector2(v0, v1); } private Vector2 GetCurrentJitter() { // add a quarter pixel diagonal translation Vector2 jitterOffset = new Vector2(0.0f, 0.0f); if (temporalType == TemporalType.SMAA_2x) { jitterOffset = GetJitterSMAAX2(); } else if (temporalType == TemporalType.Standard_2x) { jitterOffset = GetJitterStandard2X(); } else if (temporalType == TemporalType.Standard_4x) { jitterOffset = GetJitterStandard4X(); } else if (temporalType == TemporalType.Standard_8x) { jitterOffset = GetJitterStandard8X(); } else if (temporalType == TemporalType.Standard_16x) { jitterOffset = GetJitterStandard16X(); } return jitterOffset; } private void OnPreCull() { StoreBaseProjectionMatrix(aaCamera.projectionMatrix); if (temporalType != TemporalType.Off) { // flip UpdateSampleIndex(); Vector2 jitterOffset = GetCurrentJitter(); Matrix4x4 offset = Matrix4x4.identity; offset.m03 = jitterOffset.x * 2.0f / aaCamera.pixelWidth; offset.m13 = jitterOffset.y * 2.0f / aaCamera.pixelHeight; var offsetMatrix = offset * m_BaseProjectionMatrix; aaCamera.projectionMatrix = offsetMatrix; } } private void OnPostRender() { aaCamera.ResetProjectionMatrix(); } // usual & internal stuff public Shader smaaShader = null; private Material m_SmaaMaterial; public Material smaaMaterial { get { if (m_SmaaMaterial == null) m_SmaaMaterial = ImageEffectHelper.CheckShaderAndCreateMaterial(smaaShader); return m_SmaaMaterial; } } // accumulation render texture private RenderTexture m_RtAccum; protected void OnEnable() { if (smaaShader == null) smaaShader = Shader.Find("Hidden/SMAA"); if (!ImageEffectHelper.IsSupported(smaaShader, true, true, this)) { enabled = false; Debug.LogWarning("The image effect " + ToString() + " has been disabled as it's not supported on the current platform."); return; } aaCamera.depthTextureMode |= DepthTextureMode.Depth; } private void OnDisable() { aaCamera.ResetProjectionMatrix(); if (m_SmaaMaterial) { DestroyImmediate(m_SmaaMaterial); m_SmaaMaterial = null; } if (m_RtAccum) { DestroyImmediate(m_RtAccum); m_RtAccum = null; } } private void OnRenderImage(RenderTexture source, RenderTexture destination) { if (smaaMaterial == null) { Graphics.Blit(source, destination); return; } bool isFirst = false; // relying on short-circuit evaluation here if (m_RtAccum == null || (m_RtAccum.width != source.width || m_RtAccum.height != source.height)) { if (m_RtAccum != null) RenderTexture.ReleaseTemporary(m_RtAccum); m_RtAccum = RenderTexture.GetTemporary(source.width, source.height, 0, source.format); m_RtAccum.hideFlags = HideFlags.DontSave; isFirst = true; } // the values for jitter offset are hardcoded based on the SMAA shader int actualJitterOffset = 0; if (temporalType == TemporalType.SMAA_2x) actualJitterOffset = m_SampleIndex < 1 ? 1 : 2; var sizeX = source.width; var sizeY = source.height; // should this always be RGBA8? const RenderTextureFormat rtFormat = RenderTextureFormat.ARGB32; var rtEdges = RenderTexture.GetTemporary(sizeX, sizeY, 0, rtFormat); var rtWeights = RenderTexture.GetTemporary(sizeX, sizeY, 0, rtFormat); // motion blur matrix var matrix = CalculateViewProjection(aaCamera, m_BaseProjectionMatrix); Matrix4x4 invViewPrj = Matrix4x4.Inverse(matrix); smaaMaterial.SetMatrix("_ToPrevViewProjCombined", m_PrevViewProjMat * invViewPrj); smaaMaterial.SetInt("_JitterOffset", actualJitterOffset); smaaMaterial.SetTexture("areaTex", areaTex); smaaMaterial.SetTexture("searchTex", searchTex); smaaMaterial.SetTexture("colorTex", source); smaaMaterial.SetVector("_PixelSize", new Vector4(1.0f / source.width, 1.0f / source.height, 0.0f, 0.0f)); Vector2 pixelOffset = GetCurrentJitter(); smaaMaterial.SetVector("_PixelOffset", new Vector4(pixelOffset.x / source.width, pixelOffset.y / source.height, 0.0f, 0.0f)); smaaMaterial.SetTexture("edgesTex", rtEdges); smaaMaterial.SetTexture("blendTex", rtWeights); smaaMaterial.SetFloat("K", K); smaaMaterial.SetFloat("_TemporalAccum", temporalAccumulationWeight); // clear Graphics.Blit(source, rtEdges, smaaMaterial, (int)Passes.ClearToBlack); if (edgeType == EdgeType.Luminance) { // luma detect Graphics.Blit(source, rtEdges, smaaMaterial, (int)Passes.LumaDetection); } else if (edgeType == EdgeType.Color) { // color detect Graphics.Blit(source, rtEdges, smaaMaterial, (int)Passes.ColorDetection); } else { smaaMaterial.SetFloat("_DepthThreshold", 0.01f * depthThreshold); // depth detect Graphics.Blit(source, rtEdges, smaaMaterial, (int)Passes.DepthDetection); } // calculate weights Graphics.Blit(rtEdges, rtWeights, smaaMaterial, (int)Passes.WeightCalculation); if (temporalType == TemporalType.Off) { Graphics.Blit(source, destination, smaaMaterial, (int)Passes.WeightsAndBlend1); } else { // temporal blending RenderTexture rtTemp = RenderTexture.GetTemporary(source.width, source.height, 0, source.format); // render for this frame if (temporalType == TemporalType.SMAA_2x) { // set the accumulation texture smaaMaterial.SetTexture("accumTex", m_RtAccum); if (isFirst) { // if we are the first frame, just copy Graphics.Blit(source, rtTemp, smaaMaterial, (int)Passes.WeightsAndBlend1); } else { // if not first, then blend with accumulation Graphics.Blit(source, rtTemp, smaaMaterial, (int)Passes.WeightsAndBlend2); } // copy to accumulation Graphics.Blit(rtTemp, m_RtAccum, smaaMaterial, (int)Passes.Copy); // copy to destination Graphics.Blit(rtTemp, destination, smaaMaterial, (int)Passes.Copy); } else { // solve SMAA as 1x Graphics.Blit(source, rtTemp, smaaMaterial, 4); if (isFirst) { Graphics.Blit(rtTemp, m_RtAccum, smaaMaterial, 0); } // set the accumulation texture smaaMaterial.SetTexture("accumTex", m_RtAccum); smaaMaterial.SetTexture("smaaTex", rtTemp); rtTemp.filterMode = FilterMode.Bilinear; RenderTexture rtTemp2 = RenderTexture.GetTemporary(source.width, source.height, 0, source.format); // copy to accumulation Graphics.Blit(rtTemp, rtTemp2, smaaMaterial, (int)Passes.MergeFrames); // copy to accumulation Graphics.Blit(rtTemp2, m_RtAccum, smaaMaterial, (int)Passes.Copy); // copy to destination Graphics.Blit(rtTemp2, destination, smaaMaterial, (int)Passes.Copy); RenderTexture.ReleaseTemporary(rtTemp2); } RenderTexture.ReleaseTemporary(rtTemp); } if (displayType == DebugDisplay.Edges) { // copy to accumulation Graphics.Blit(rtEdges, destination, smaaMaterial, (int)Passes.Copy); } else if (displayType == DebugDisplay.Weights) { // copy to accumulation Graphics.Blit(rtWeights, destination, smaaMaterial, (int)Passes.Copy); } else if (displayType == DebugDisplay.Depth) { Graphics.Blit(null, destination, smaaMaterial, (int)Passes.DebugDepth); } else if (displayType == DebugDisplay.Accumulation) { Graphics.Blit(m_RtAccum, destination); } RenderTexture.ReleaseTemporary(rtEdges); RenderTexture.ReleaseTemporary(rtWeights); // store matrix for next frame StorePreviousViewProjMatrix(matrix); } } }