using System; using System.Drawing.Drawing2D; using System.Runtime.ConstrainedExecution; using System.Security.Cryptography; using UnityEngine; using UnityEngine.Experimental.Rendering.RenderGraphModule; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; [DisallowMultipleRendererFeature("Moment Based Order-Independent Transparency")] public class MOITFeature : ScriptableRendererFeature { public enum MomentsCount { _4 = 4, _6 = 6, _8 = 8 } public enum FloatPrecision { _Half = 16, _Single = 32 } public enum BoundsType { NearFarPlanes, FindObjects, Register // ideally add JustIterateThroughTheRendererListHandle at some point } [Serializable] class Settings { [SerializeField] internal RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingTransparents; [SerializeField] internal MOITSettings settings; [SerializeField] internal MOITBias biasSettings; } [SerializeField] Settings settings; MOITPass pass; public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { var cam = renderingData.cameraData; if (cam.cameraType != CameraType.Game && cam.cameraType != CameraType.SceneView) { return; } renderer.EnqueuePass(pass); } public override void Create() { pass = new(settings); } protected override void Dispose(bool disposing) { base.Dispose(disposing); pass.Dispose(); } class MOITPass : ScriptableRenderPass, IDisposable { private static readonly int b0TextureID = Shader.PropertyToID("_B0"); private static readonly int b1TextureID = Shader.PropertyToID("_B1"); private static readonly int b2TextureID = Shader.PropertyToID("_B2"); private static readonly int logViewMinDeltaID = Shader.PropertyToID("_LogViewDepthMinDelta"); private static readonly int wrappingZoneParametersID = Shader.PropertyToID("_WrappingZoneParameters"); private static readonly int biasID = Shader.PropertyToID("_MOIT_MomentBias"); private static readonly int alphaToMaskAvailableID = Shader.PropertyToID("_AlphaToMaskAvailable"); const string generatePassName = "MOIT Generate Moments Pass"; const string resolvePassName = "MOIT Resolve Moments Pass"; const string compositePassName = "MOIT Composite Pass"; private static readonly int scaleBiasRt = Shader.PropertyToID("_ScaleBiasRt"); private Settings featureSettings; private ProfilingSampler profiler; private CommandBuffer commandBuffer; public MOITPass(Settings settings) { this.featureSettings = settings; renderPassEvent = settings.renderPassEvent; profiler = new(nameof(MOITPass)); commandBuffer = new CommandBuffer(); commandBuffer.name = profiler.name; } public void Dispose() { moitHandle?.Release(); b0?.Release(); b1?.Release(); b2?.Release(); commandBuffer?.Release(); } RTHandle moitHandle; RTHandle b0; RTHandle b1; RTHandle b2; private ShaderTagId shaderTagIdGenerateMoments = new ShaderTagId("GenerateMoments"); private ShaderTagId shaderTagIdResolveMoments = new ShaderTagId("ResolveMoments"); RenderTargetIdentifier[] mrts2 = new RenderTargetIdentifier[2]; RenderTargetIdentifier[] mrts3 = new RenderTargetIdentifier[3]; private const float M_PI = 3.14159265358979323f; private static float CircleToParameter(float angle, out float maxParameter) { float x = Mathf.Cos(angle); float y = Mathf.Sin(angle); float result = Mathf.Abs(y) - Mathf.Abs(x); result = (x < 0.0f) ? (2.0f - result) : result; result = (y < 0.0f) ? (6.0f - result) : result; result += (angle >= 2.0f * M_PI) ? 8.0f : 0.0f; maxParameter = 7.0f; // why? return result; } public static Vector4 ComputeWrappingZoneParameters(float newWrappingZoneAngle = 0.1f * M_PI) { Vector4 result = new Vector4(); result.x = newWrappingZoneAngle; result.y = M_PI - 0.5f * newWrappingZoneAngle; if (newWrappingZoneAngle <= 0.0f) { result.z = 0.0f; result.w = 0.0f; } else { float zoneEndParameter; float zoneBeginParameter = CircleToParameter(2.0f * M_PI - newWrappingZoneAngle, out zoneEndParameter); result.z = 1.0f / (zoneEndParameter - zoneBeginParameter); result.w = 1.0f - zoneEndParameter * result.z; } return result; } Vector4 wrappingZoneParameters = ComputeWrappingZoneParameters(); public static bool GetViewDepthMinMaxWithRenderQueue(Camera camera, RenderQueueRange range, out Vector2 minMax) { minMax = Vector2.zero; bool b = false; Bounds bounds = new Bounds(); Renderer[] coms = Renderer.FindObjectsOfType(); if (null == coms || 0 == coms.Length) { return false; } foreach (var p in coms) { Renderer r = p.GetComponent(); if (null != r && r.enabled && r.sharedMaterial.renderQueue >= range.lowerBound && r.sharedMaterial.renderQueue <= range.upperBound) { if (r is SkinnedMeshRenderer) { (r as SkinnedMeshRenderer).sharedMesh.RecalculateBounds(); } Bounds rb = r.bounds; if (b) { bounds.Encapsulate(rb); } else { bounds = rb; b = true; } } } if (!b) { return false; } Vector3 fwd = camera.transform.forward; Vector3 c2b = bounds.center - camera.transform.position; float c2bDis = Vector3.Dot(fwd, c2b); float bs = bounds.extents.magnitude; minMax.x = Mathf.Max(0, c2bDis - bs); minMax.y = c2bDis + bs; return true; } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var biasSettings = featureSettings.biasSettings; if(biasSettings == null ) { return; } var settings = featureSettings.settings; bool isHalfPrecision = settings.momentPrecision == FloatPrecision._Half; // prevent the use of half precision power moments as quantization relies on ROVs if (isHalfPrecision && !settings.trigonometric) isHalfPrecision = false; var cmd = commandBuffer; using var scp = new ProfilingScope(cmd, profiler); var rdr = renderingData.cameraData.renderer; ref var cameraData = ref renderingData.cameraData; var renderQueue = new RenderQueueRange() { lowerBound = settings.renderQueueMin, upperBound = settings.renderQueueMax }; if (!GetViewDepthMinMaxWithRenderQueue(cameraData.camera, renderQueue, out Vector2 viewDepthMinMax)) { return; } RenderTextureDescriptor baseDescriptor = cameraData.cameraTargetDescriptor; baseDescriptor.colorFormat = RenderTextureFormat.ARGBHalf; baseDescriptor.depthBufferBits = 0; RenderingUtils.ReAllocateIfNeeded(ref moitHandle, baseDescriptor, name: "_MOIT_Texture"); RenderTextureDescriptor descriptorFloat4; RenderTextureDescriptor descriptorFloat2; RenderTextureDescriptor descriptorFloat; if (isHalfPrecision) { baseDescriptor.colorFormat = RenderTextureFormat.ARGBHalf; descriptorFloat4 = baseDescriptor; baseDescriptor.colorFormat = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.RGHalf) ? RenderTextureFormat.RGHalf : RenderTextureFormat.ARGBHalf; descriptorFloat2 = baseDescriptor; baseDescriptor.colorFormat = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.RHalf) ? RenderTextureFormat.RHalf : RenderTextureFormat.ARGBHalf; descriptorFloat = baseDescriptor; } else // single precision { baseDescriptor.colorFormat = RenderTextureFormat.ARGBFloat; descriptorFloat4 = baseDescriptor; baseDescriptor.colorFormat = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.RGFloat) ? RenderTextureFormat.RGFloat : RenderTextureFormat.ARGBFloat; descriptorFloat2 = baseDescriptor; baseDescriptor.colorFormat = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.RFloat) ? RenderTextureFormat.RFloat : RenderTextureFormat.ARGBFloat; descriptorFloat = baseDescriptor; } FilterMode filterMode = FilterMode.Bilinear; RenderingUtils.ReAllocateIfNeeded(ref b0, descriptorFloat, name: "_MOIT_B0", filterMode: filterMode); RenderingUtils.ReAllocateIfNeeded(ref b1, descriptorFloat4, name: "_MOIT_B1", filterMode: filterMode); if (settings.momentsCount == MomentsCount._8) { RenderingUtils.ReAllocateIfNeeded(ref b1, descriptorFloat4, name: "_MOIT_B2", filterMode: filterMode); } else if (settings.momentsCount == MomentsCount._6) { RenderingUtils.ReAllocateIfNeeded(ref b1, descriptorFloat2, name: "_MOIT_B2", filterMode: filterMode); } SortingCriteria sortingCritera = settings.sortBackToFront ? SortingCriteria.BackToFront | SortingCriteria.OptimizeStateChanges : SortingCriteria.OptimizeStateChanges; float momentBias = 0; if (isHalfPrecision) { if (settings.momentsCount == MomentsCount._4) momentBias = settings.trigonometric ? biasSettings.Trigonometric2Half : biasSettings.Moments4Half; else if (settings.momentsCount == MomentsCount._6) momentBias = settings.trigonometric ? biasSettings.Trigonometric3Half : biasSettings.Moments6Half; else momentBias = settings.trigonometric ? biasSettings.Trigonometric4Half : biasSettings.Moments8Half; } else { if (settings.momentsCount == MomentsCount._4) momentBias = settings.trigonometric ? biasSettings.Trigonometric2Single : biasSettings.Moments4Single; else if (settings.momentsCount == MomentsCount._6) momentBias = settings.trigonometric ? biasSettings.Trigonometric3Single : biasSettings.Moments6Single; else momentBias = settings.trigonometric ? biasSettings.Trigonometric4Single : biasSettings.Moments8Single; } DrawingSettings drawSettings = CreateDrawingSettings(shaderTagIdGenerateMoments, ref renderingData, sortingCritera); cmd.BeginSample("GenerateMoments"); mrts2[0] = mrts3[0] = b0; mrts2[1] = mrts3[1] = b1; cmd.SetGlobalTexture(b0TextureID, b0); cmd.SetGlobalTexture(b1TextureID, b1); if (settings.momentsCount != MomentsCount._4) { mrts3[2] = b2; cmd.SetRenderTarget(mrts3, rdr.cameraDepthTargetHandle); cmd.SetGlobalTexture(b2TextureID, b2); } else { cmd.SetRenderTarget(mrts2, rdr.cameraDepthTargetHandle); } var isYFlipped = cameraData.IsRenderTargetProjectionMatrixFlipped(rdr.cameraColorTargetHandle); float flipSign = isYFlipped ? -1.0f : 1.0f; // scaleBias.x = flipSign // scaleBias.y = scale // scaleBias.z = bias // scaleBias.w = unused Vector4 scaleBias = (flipSign < 0.0f) ? new Vector4(flipSign, 1.0f, -1.0f, 1.0f) : new Vector4(flipSign, 0.0f, 1.0f, 1.0f); cmd.SetGlobalVector(scaleBiasRt, scaleBias); // setup keywords CoreUtils.SetKeyword(cmd, "_MOMENT6", settings.momentsCount == MomentsCount._6); CoreUtils.SetKeyword(cmd, "_MOMENT8", settings.momentsCount == MomentsCount._8); CoreUtils.SetKeyword(cmd, "_MOMENT_HALF_PRECISION", isHalfPrecision); CoreUtils.SetKeyword(cmd, "_MOMENT_SINGLE_PRECISION", !isHalfPrecision); CoreUtils.SetKeyword(cmd, "_TRIGONOMETRIC", settings.trigonometric); Vector2 logViewDepthMinDelta = new Vector2(Mathf.Log(viewDepthMinMax.x), Mathf.Log(viewDepthMinMax.y)); logViewDepthMinDelta.y = logViewDepthMinDelta.y - logViewDepthMinDelta.x; cmd.SetGlobalVector(logViewMinDeltaID, logViewDepthMinDelta); var param = new RendererListParams(renderingData.cullResults, drawSettings, new FilteringSettings(renderQueue, settings.layerMask)); { if (settings.trigonometric) { cmd.SetGlobalVector(wrappingZoneParametersID, wrappingZoneParameters); } cmd.SetGlobalFloat(biasID, momentBias); cmd.SetGlobalFloat(alphaToMaskAvailableID, 0.0f); cmd.ClearRenderTarget(false, true, Color.clear); cmd.DrawRendererList(context.CreateRendererList(ref param)); cmd.EndSample("GenerateMoments"); context.ExecuteCommandBuffer(cmd); cmd.Clear(); } { cmd.BeginSample("ResolveMoments"); if (settings.debugMakeMOITTexGlobal) { cmd.SetGlobalTexture("_MOIT", moitHandle); } cmd.SetRenderTarget(moitHandle, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); cmd.ClearRenderTarget(false, true, Color.clear); drawSettings = CreateDrawingSettings(shaderTagIdResolveMoments, ref renderingData, sortingCritera); param = new RendererListParams(renderingData.cullResults, drawSettings, new FilteringSettings(renderQueue, settings.layerMask)); cmd.DrawRendererList(context.CreateRendererList(ref param)); cmd.EndSample("ResolveMoments"); context.ExecuteCommandBuffer(cmd); cmd.Clear(); } { cmd.BeginSample("Composite"); cmd.SetGlobalTexture("_MOIT", moitHandle); cmd.SetRenderTarget(rdr.cameraColorTargetHandle, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare); cmd.DrawProcedural(Matrix4x4.identity, settings.compositeMaterial, 0, MeshTopology.Triangles, 3); cmd.EndSample("Composite"); } context.ExecuteCommandBuffer(cmd); cmd.Clear(); } } }