using System; using UnityEngine; using UnityEngine.Rendering; namespace Artngame.SKYMASTER.SSRT { [RequireComponent(typeof(Camera))] public class SSRT : MonoBehaviour { public enum DebugMode { AO = 4, BentNormal = 5, GI = 6, Combined = 7 } public enum FallbackMethod { Off = 0, StaticIrradianceCubemap = 1, DynamicCubemap = 2 } public enum ResolutionDownscale { Full = 1, Half = 2, Quarter = 4, Eight = 8 } public enum RenderPass { SSRT = 0, Upsample = 1, SampleReuse = 2, TemporalReproj = 3, DebugModeAO = 4, DebugModeBentNormal = 5, DebugModeGI = 6, DebugModeCombined = 7, GetDepth = 8, GetNormal = 9, GetLightmask = 10, CopyLightmask = 11 } public readonly string version = "1.0.0"; [Header("Sampling")] [Tooltip("Number of directionnal rotations applied during sampling.")] [Range(1f, 4f)] public int rotationCount = 4; [Tooltip("Number of samples taken along one edge of the current conic slice.")] [Range(1f, 16f)] public int stepCount = 8; [Tooltip("Effective sampling radius in world space. AO and GI can only have influence within that radius.")] [Range(1f, 25f)] public float radius = 3.5f; [Tooltip("Controls samples distribution. Exp Start is an initial multiplier on the step size, and Exp Factor is an exponent applied at each step. By using a start value < 1, and an exponent > 1, it's possible to get exponential step size.")] [Range(0.1f, 1f)] public float expStart = 1f; [Tooltip("Controls samples distribution. Exp Start is an initial multiplier on the step size, and Exp Factor is an exponent applied at each step. By using a start value < 1, and an exponent > 1, it's possible to get exponential step size.")] [Range(1f, 2f)] public float expFactor = 1f; [Tooltip("Applies some noise on sample positions to hide the banding artifacts that can occur when there is undersampling.")] public bool jitterSamples = true; [Header("GI")] [Tooltip("Intensity of the indirect diffuse light.")] [Range(0f, 75f)] public float GIBoost = 20f; [Tooltip("Using an HDR light buffer gives more accurate lighting but have an impact on performances.")] public bool lightBufferHDR; [Tooltip("Using lower resolution light buffer can help performances but can accentuate aliasing.")] public ResolutionDownscale lightBufferResolution = ResolutionDownscale.Half; [Tooltip("Bypass the dot(lightNormal, lightDirection) weighting.")] [Range(0f, 1f)] public float LnDlOffset; [Tooltip("Bypass the dot(normal, lightDirection) weighting.")] [Range(0f, 1f)] public float nDlOffset; [Header("Occlusion")] [Tooltip("Power function applied to AO to make it appear darker/lighter.")] [Range(1f, 8f)] public float power = 1.5f; [Tooltip("Constant thickness value of objects on the screen in world space. Is used to ignore occlusion past that thickness level, as if light can travel behind the object.")] [Range(0.1f, 10f)] public float thickness = 10f; [Tooltip("Occlusion falloff relative to distance.")] [Range(1f, 50f)] public float falloff = 1f; [Tooltip("Multi-Bounce analytic approximation from GTAO.")] public bool multiBounceAO; [Tooltip("Composite AO also on direct lighting.")] public bool directLightingAO; [Header("Offscreen Fallback")] [Tooltip("Ambient lighting to use. Off uses the Unity ambient lighting, but it's possible to use instead a static irradiance cubemap (pre-convolved), or render a cubemap around camera every frame (expensive).")] public FallbackMethod fallbackMethod; [Tooltip("Static irradiance cubemap to use if it's the chosen fallback.")] public Cubemap cubemapFallback; [Header("Filters")] [Tooltip("The resolution at which SSRT is computed. If lower than fullscreen the effect will be upscaled to fullscreen afterwards. Lower resolution can help performances but can also introduce more flickering/aliasing.")] public ResolutionDownscale resolutionDownscale = ResolutionDownscale.Half; [Tooltip("Number of neighbor pixel to reuse (helps reduce noise).")] [Range(1f, 8f)] public int reuseCount = 5; [Tooltip("Enable/Disable temporal reprojection")] public bool temporalEnabled = true; [Tooltip("Controls the speed of the accumulation, slower accumulation is more effective at removing noise but can introduce ghosting.")] [Range(0f, 1f)] public float temporalResponse = 0.35f; [Header("Debug Mode")] [Tooltip("View of the different SSRT buffers for debug purposes.")] public DebugMode debugMode = DebugMode.Combined; [Tooltip("If enabled will show only the radiance that affects the surface, if unchecked radiance will be multiplied by surface albedo.")] public bool lightOnly; private Camera cam; private Camera cubemapCamera; private Material ssrtMaterial; private CommandBuffer ssrtBuffer; private CommandBuffer storeAmbientBuffer; private CommandBuffer clearBuffer; private Mesh mesh; private Matrix4x4 lastFrameViewProjectionMatrix; private Matrix4x4 viewProjectionMatrix; private Matrix4x4 lastFrameInverseViewProjectionMatrix; private Vector2 cameraSize; private Vector2 renderResolution; private RenderTexture ambientTexture; private RenderTexture previousFrameTexture; private RenderTexture previousDepthTexture; private RenderTexture[] ssrtMrt = new RenderTexture[2]; private static readonly float[] temporalRotations = new float[6] { 60f, 300f, 180f, 240f, 120f, 0f }; private static readonly float[] spatialOffsets = new float[4] { 0f, 0.5f, 0.25f, 0.75f }; private void GenerateCommandBuffers() { ssrtBuffer.Clear(); storeAmbientBuffer.Clear(); clearBuffer.Clear(); clearBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget); clearBuffer.ClearRenderTarget(clearDepth: false, clearColor: true, Color.black); storeAmbientBuffer.Blit(BuiltinRenderTextureType.CameraTarget, ambientTexture); storeAmbientBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget); storeAmbientBuffer.ClearRenderTarget(clearDepth: false, clearColor: true, Color.black); int num = Shader.PropertyToID("_CameraTexture"); ssrtBuffer.GetTemporaryRT(num, (int)renderResolution.x, (int)renderResolution.y, 0, FilterMode.Point, RenderTextureFormat.DefaultHDR); ssrtBuffer.Blit(BuiltinRenderTextureType.CameraTarget, num); int num2 = Shader.PropertyToID("_LightmaskTexture"); ssrtBuffer.GetTemporaryRT(num2, (int)renderResolution.x / (int)lightBufferResolution, (int)renderResolution.y / (int)lightBufferResolution, 0, FilterMode.Point, lightBufferHDR ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.ARGB32); ssrtBuffer.SetRenderTarget(num2); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 10); RenderTargetIdentifier[] colors = new RenderTargetIdentifier[2] { ssrtMrt[0], ssrtMrt[1] }; ssrtBuffer.SetRenderTarget(colors, ssrtMrt[1]); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 0); int num3 = Shader.PropertyToID("_FilterTexture1"); ssrtBuffer.GetTemporaryRT(num3, (int)renderResolution.x, (int)renderResolution.y, 0, FilterMode.Point, RenderTextureFormat.DefaultHDR); int num4 = Shader.PropertyToID("_FilterTexture2"); ssrtBuffer.GetTemporaryRT(num4, (int)renderResolution.x, (int)renderResolution.y, 0, FilterMode.Point, RenderTextureFormat.DefaultHDR); if (resolutionDownscale != ResolutionDownscale.Full) { int num5 = Shader.PropertyToID("_CurrentDepth"); ssrtBuffer.GetTemporaryRT(num5, (int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, FilterMode.Point, RenderTextureFormat.RFloat, RenderTextureReadWrite.Linear); int num6 = Shader.PropertyToID("_CurrentNormal"); ssrtBuffer.GetTemporaryRT(num6, (int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, FilterMode.Point, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear); ssrtBuffer.SetRenderTarget(num5); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 8); ssrtBuffer.SetRenderTarget(num6); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 9); ssrtBuffer.SetRenderTarget(num3); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 1); } else { ssrtBuffer.Blit(ssrtMrt[1], num3); } if (reuseCount > 1) { ssrtBuffer.SetRenderTarget(num4); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 2); ssrtBuffer.CopyTexture(num4, num3); } if (temporalEnabled) { ssrtBuffer.SetRenderTarget(num4); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 3); ssrtBuffer.Blit(num4, previousFrameTexture); ssrtBuffer.SetRenderTarget(previousDepthTexture); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, 8); } else { ssrtBuffer.Blit(num3, num4); } ssrtBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget); ssrtBuffer.DrawMesh(mesh, Matrix4x4.identity, ssrtMaterial, 0, (int)debugMode); lastFrameViewProjectionMatrix = viewProjectionMatrix; lastFrameInverseViewProjectionMatrix = viewProjectionMatrix.inverse; } private void UpdateVariables() { Matrix4x4 worldToCameraMatrix = cam.worldToCameraMatrix; ssrtMaterial.SetMatrix("_CameraToWorldMatrix", worldToCameraMatrix.inverse); Matrix4x4 gPUProjectionMatrix = GL.GetGPUProjectionMatrix(cam.projectionMatrix, renderIntoTexture: false); ssrtMaterial.SetMatrix("_InverseProjectionMatrix", gPUProjectionMatrix.inverse); viewProjectionMatrix = gPUProjectionMatrix * worldToCameraMatrix; ssrtMaterial.SetMatrix("_InverseViewProjectionMatrix", viewProjectionMatrix.inverse); ssrtMaterial.SetMatrix("_LastFrameViewProjectionMatrix", lastFrameViewProjectionMatrix); ssrtMaterial.SetMatrix("_LastFrameInverseViewProjectionMatrix", lastFrameInverseViewProjectionMatrix); ssrtMaterial.SetInt("_RotationCount", rotationCount); ssrtMaterial.SetInt("_StepCount", stepCount); ssrtMaterial.SetFloat("_GIBoost", GIBoost); ssrtMaterial.SetFloat("_LnDlOffset", LnDlOffset); ssrtMaterial.SetFloat("_NDlOffset", nDlOffset); ssrtMaterial.SetFloat("_Radius", radius); ssrtMaterial.SetFloat("_ExpStart", expStart); ssrtMaterial.SetFloat("_ExpFactor", expFactor); ssrtMaterial.SetFloat("_Thickness", thickness); ssrtMaterial.SetFloat("_Falloff", falloff); ssrtMaterial.SetFloat("_Power", power); ssrtMaterial.SetFloat("_TemporalResponse", temporalResponse); ssrtMaterial.SetInt("_MultiBounceAO", multiBounceAO ? 1 : 0); ssrtMaterial.SetFloat("_DirectLightingAO", directLightingAO ? 1 : 0); ssrtMaterial.SetTexture("_CubemapFallback", cubemapFallback); ssrtMaterial.SetInt("_FallbackMethod", (int)fallbackMethod); ssrtMaterial.SetInt("_LightOnly", lightOnly ? 1 : 0); ssrtMaterial.SetInt("_ReuseCount", reuseCount); ssrtMaterial.SetInt("_JitterSamples", jitterSamples ? 1 : 0); float value = renderResolution.y / (Mathf.Tan(cam.fieldOfView * (MathF.PI / 180f) * 0.5f) * 2f) * 0.5f; ssrtMaterial.SetFloat("_HalfProjScale", value); ssrtMaterial.SetInt("_ResolutionDownscale", (int)resolutionDownscale); float num = temporalRotations[Time.frameCount % 6]; float value2 = spatialOffsets[Time.frameCount % ((resolutionDownscale == ResolutionDownscale.Full) ? 4 : 2)]; ssrtMaterial.SetFloat("_TemporalDirections", num / 360f); ssrtMaterial.SetFloat("_TemporalOffsets", value2); if (cameraSize != renderResolution / (float)resolutionDownscale) { cameraSize = renderResolution / (float)resolutionDownscale; if (ssrtMrt[0] != null) { ssrtMrt[0].Release(); } ssrtMrt[0] = new RenderTexture((int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, RenderTextureFormat.ARGBHalf); ssrtMrt[0].filterMode = FilterMode.Point; ssrtMrt[0].Create(); if (ssrtMrt[1] != null) { ssrtMrt[1].Release(); } ssrtMrt[1] = new RenderTexture((int)renderResolution.x / (int)resolutionDownscale, (int)renderResolution.y / (int)resolutionDownscale, 0, RenderTextureFormat.DefaultHDR); ssrtMrt[1].filterMode = FilterMode.Point; ssrtMrt[1].Create(); if (ambientTexture != null) { ambientTexture.Release(); } ambientTexture = new RenderTexture((int)renderResolution.x, (int)renderResolution.y, 0, RenderTextureFormat.DefaultHDR); if (previousFrameTexture != null) { previousFrameTexture.Release(); } previousFrameTexture = new RenderTexture((int)renderResolution.x, (int)renderResolution.y, 0, RenderTextureFormat.DefaultHDR); previousFrameTexture.filterMode = FilterMode.Point; previousFrameTexture.Create(); if (previousDepthTexture != null) { previousDepthTexture.Release(); } previousDepthTexture = new RenderTexture((int)renderResolution.x, (int)renderResolution.y, 0, RenderTextureFormat.RFloat); previousDepthTexture.filterMode = FilterMode.Point; previousDepthTexture.Create(); } ssrtMaterial.SetTexture("_BentNormalTexture", ssrtMrt[0]); ssrtMaterial.SetTexture("_GIOcclusionTexture", ssrtMrt[1]); ssrtMaterial.SetTexture("_AmbientTexture", ambientTexture); ssrtMaterial.SetTexture("_PreviousColor", previousFrameTexture); ssrtMaterial.SetTexture("_PreviousDepth", previousDepthTexture); } private void RenderCubemap() { if (cubemapCamera == null) { GameObject gameObject = new GameObject("CubemapCamera", typeof(Camera)); gameObject.transform.SetParent(cam.transform); cubemapCamera = gameObject.GetComponent(); cubemapCamera.CopyFrom(cam); cubemapCamera.enabled = false; cubemapCamera.renderingPath = RenderingPath.Forward; } if ((bool)cubemapFallback) { cubemapCamera.RenderToCubemap(cubemapFallback, 1 << Time.frameCount % 6); } } private void Awake() { if ((renderResolution.x % 2f == 1f || renderResolution.y % 2f == 1f) && resolutionDownscale != ResolutionDownscale.Full) { Debug.LogWarning("SSRT: Using uneven camera resolution (" + renderResolution.x + ", " + renderResolution.y + ") with downscaling can introduce artifacts! Use a fixed resolution instead of free aspect."); } cam = base.gameObject.GetComponent(); cam.depthTextureMode |= DepthTextureMode.Depth | DepthTextureMode.MotionVectors; ssrtMaterial = new Material(Shader.Find("Hidden/SSRT")); mesh = new Mesh(); mesh.vertices = new Vector3[4] { new Vector3(-1f, -1f, 1f), new Vector3(-1f, 1f, 1f), new Vector3(1f, 1f, 1f), new Vector3(1f, -1f, 1f) }; mesh.uv = new Vector2[4] { new Vector2(0f, 1f), new Vector2(0f, 0f), new Vector2(1f, 0f), new Vector2(1f, 1f) }; mesh.SetIndices(new int[4] { 0, 1, 2, 3 }, MeshTopology.Quads, 0); if (fallbackMethod == FallbackMethod.DynamicCubemap) { cubemapFallback = new Cubemap(32, TextureFormat.RGB24, mipChain: true); cubemapFallback.Apply(updateMipmaps: true); } } private void OnPreRender() { renderResolution = new Vector2(cam.pixelWidth, cam.pixelHeight); if (ssrtBuffer != null) { if (fallbackMethod == FallbackMethod.DynamicCubemap && Application.isPlaying) { RenderCubemap(); } UpdateVariables(); GenerateCommandBuffers(); } } private void OnEnable() { ssrtBuffer = new CommandBuffer(); ssrtBuffer.name = "SSRT"; storeAmbientBuffer = new CommandBuffer(); storeAmbientBuffer.name = "StoreAmbient"; clearBuffer = new CommandBuffer(); clearBuffer.name = "ClearBuffer"; cam.AddCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, ssrtBuffer); cam.AddCommandBuffer(CameraEvent.BeforeLighting, storeAmbientBuffer); cam.AddCommandBuffer(CameraEvent.BeforeGBuffer, clearBuffer); } private void OnDisable() { if (ssrtBuffer != null) { cam.RemoveCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, ssrtBuffer); ssrtBuffer = null; } if (storeAmbientBuffer != null) { cam.RemoveCommandBuffer(CameraEvent.BeforeLighting, storeAmbientBuffer); storeAmbientBuffer = null; } if (clearBuffer != null) { cam.RemoveCommandBuffer(CameraEvent.BeforeGBuffer, clearBuffer); clearBuffer = null; } } private void OnDestroy() { if (ssrtMrt[0] != null) { ssrtMrt[0].Release(); ssrtMrt[0] = null; } if (ssrtMrt[1] != null) { ssrtMrt[1].Release(); ssrtMrt[1] = null; } if (ambientTexture != null) { ambientTexture.Release(); ambientTexture = null; } if (previousFrameTexture != null) { previousFrameTexture.Release(); previousFrameTexture = null; } if (previousDepthTexture != null) { previousDepthTexture.Release(); previousDepthTexture = null; } if (ssrtBuffer != null) { ssrtBuffer.Dispose(); ssrtBuffer = null; } if (storeAmbientBuffer != null) { storeAmbientBuffer.Dispose(); storeAmbientBuffer = null; } if (clearBuffer != null) { clearBuffer.Dispose(); clearBuffer = null; } } } }