2026-02-02 21:07:49 +08:00

315 lines
13 KiB
C#

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();
}
}
}