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() { } 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 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; RenderTextureDescriptor baseDescriptor = cameraData.cameraTargetDescriptor; baseDescriptor.colorFormat = RenderTextureFormat.ARGBHalf; 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; Vector2 viewDepthMinMax = Vector2.zero; 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", data.momentsPrecision == FloatPrecision._Half); 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); if (settings.trigonometric) { cmd.SetGlobalVector(wrappingZoneParametersID, wrappingZoneParameters); } cmd.SetGlobalFloat(biasID, momentBias); cmd.SetGlobalFloat(alphaToMaskAvailableID, 0.0f); cmd.ClearRenderTarget(false, true, Color.clear); var renderQueue = new RenderQueueRange() { lowerBound = settings.renderQueueMin, upperBound = settings.renderQueueMax }; var param = new RendererListParams(renderingData.cullResults, drawSettings, new FilteringSettings(renderQueue, settings.layerMask)); cmd.DrawRendererList(context.CreateRendererList(ref param)); cmd.EndSample("GenerateMoments"); 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"); cmd.BeginSample("Composite"); cmd.Blit(moitHandle, rdr.cameraColorTargetHandle, settings.compositeMaterial, 0); cmd.EndSample("Composite"); context.ExecuteCommandBuffer(cmd); cmd.Clear(); } } }