353 lines
15 KiB
C#
353 lines
15 KiB
C#
using Codice.Client.GameUI.Update;
|
|
using System;
|
|
using Unity.Burst.CompilerServices;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
|
|
namespace X.Rendering.Feature
|
|
{
|
|
public class HierarchicalZFeature : ScriptableRendererFeature
|
|
{
|
|
public enum EDepthPyramidFunc
|
|
{
|
|
CopyDepth,
|
|
Compute,
|
|
SPD
|
|
}
|
|
|
|
[Serializable]
|
|
public class Settings
|
|
{
|
|
public EDepthPyramidFunc PyramidFunc = EDepthPyramidFunc.CopyDepth;
|
|
public bool SkipThreeMip;
|
|
public bool UseThreeFrameReadback;
|
|
public ComputeShader ComputeShader;
|
|
public ComputeShader Spd;
|
|
public Material CopyDepth;
|
|
}
|
|
|
|
[SerializeField]
|
|
private Settings settings;
|
|
|
|
private HizPass hizPass;
|
|
|
|
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
|
{
|
|
if (renderingData.cameraData.cameraType == UnityEngine.CameraType.Game)
|
|
{
|
|
renderer.EnqueuePass(hizPass);
|
|
}
|
|
}
|
|
|
|
public override void Create()
|
|
{
|
|
hizPass = new(settings);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
if(hizPass != null)
|
|
{
|
|
hizPass.Dispose();
|
|
}
|
|
}
|
|
|
|
internal class HizPass : ScriptableRenderPass, IDisposable
|
|
{
|
|
private static readonly int depthTextureId = Shader.PropertyToID("_CameraDepthTexture");
|
|
private static readonly int depthInputId = Shader.PropertyToID("_InputDepth");
|
|
private static readonly int depthPyramidTexId = Shader.PropertyToID("_DepthPyramidTexture");
|
|
private static readonly int inputScaleAndMaxIndexId = Shader.PropertyToID("_InputScaleAndMaxIndex");
|
|
|
|
private readonly Settings settings;
|
|
private ProfilingSampler profiler;
|
|
|
|
private Vector2Int cachedHardwareTextureSize;
|
|
private bool cachedSkipThreeMip;
|
|
private Vector2Int mip0SizeNOP;
|
|
private Vector2Int depthPyramidTextureSize;
|
|
private int mipLevelCount;
|
|
private Vector2Int[] mipLevelOffsets;
|
|
private Vector2Int[] mipLevelSizes;
|
|
private Vector4[] mipOffsetAndSizes = new Vector4[32];
|
|
|
|
private RTHandle[] depthPyramidTexs = new RTHandle[3];
|
|
private static string[] depthPyramidNames = new string[3] { "_DepthPyramidTextureA", "_DepthPyramidTextureB", "_DepthPyramidTextureC" };
|
|
|
|
public HizPass(Settings settings)
|
|
{
|
|
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
|
profiler = new("DepthPyramid");
|
|
this.settings = settings;
|
|
mipLevelOffsets = new Vector2Int[32];
|
|
mipLevelSizes = new Vector2Int[32];
|
|
}
|
|
|
|
public static int HizIndex { get; private set; }
|
|
|
|
private int GetHizIndex()
|
|
{
|
|
if(settings.UseThreeFrameReadback)
|
|
{
|
|
HizIndex = Time.frameCount % 3;
|
|
}
|
|
else
|
|
{
|
|
HizIndex = 0;
|
|
}
|
|
|
|
return HizIndex;
|
|
}
|
|
|
|
public void ComputePackedMipChainInfo(Vector2Int viewportSize)
|
|
{
|
|
if (cachedHardwareTextureSize == viewportSize && cachedSkipThreeMip == settings.SkipThreeMip)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cachedHardwareTextureSize = viewportSize;
|
|
cachedSkipThreeMip = settings.SkipThreeMip;
|
|
|
|
mip0SizeNOP = viewportSize;
|
|
int resizeX = Mathf.IsPowerOfTwo(viewportSize.x) ? viewportSize.x : Mathf.NextPowerOfTwo(viewportSize.x);
|
|
int resizeY = Mathf.IsPowerOfTwo(viewportSize.y) ? viewportSize.y : Mathf.NextPowerOfTwo(viewportSize.y);
|
|
Vector2Int hardwareTextureSize = new Vector2Int(resizeX, resizeY);
|
|
mipLevelOffsets[0] = Vector2Int.zero;
|
|
mipLevelSizes[0] = Vector2Int.zero;
|
|
int mipLevel = 0;
|
|
Vector2Int mipSize = hardwareTextureSize;
|
|
Vector2Int texSize = Vector2Int.zero;
|
|
do
|
|
{
|
|
mipLevel++;
|
|
|
|
// Round up.
|
|
mipSize.x = System.Math.Max(1, (mipSize.x + 1) >> 1);
|
|
mipSize.y = System.Math.Max(1, (mipSize.y + 1) >> 1);
|
|
|
|
mipLevelSizes[mipLevel] = mipSize;
|
|
|
|
Vector2Int prevMipBegin = mipLevelOffsets[mipLevel - 1];
|
|
Vector2Int prevMipEnd = prevMipBegin + mipLevelSizes[mipLevel - 1];
|
|
|
|
Vector2Int mipBegin = new Vector2Int();
|
|
|
|
if ((mipLevel & 1) != 0)
|
|
{
|
|
mipBegin.x = prevMipBegin.x;
|
|
mipBegin.y = prevMipEnd.y;
|
|
}
|
|
else
|
|
{
|
|
mipBegin.x = prevMipEnd.x;
|
|
mipBegin.y = prevMipBegin.y;
|
|
}
|
|
if (settings.SkipThreeMip && mipLevel == 4) //跳过前面3级的Mip,从第四级开始算
|
|
{
|
|
mipBegin = Vector2Int.zero;
|
|
texSize = Vector2Int.zero;
|
|
}
|
|
|
|
mipLevelOffsets[mipLevel] = mipBegin;
|
|
|
|
texSize.x = System.Math.Max(texSize.x, mipBegin.x + mipSize.x);
|
|
texSize.y = System.Math.Max(texSize.y, mipBegin.y + mipSize.y);
|
|
}
|
|
while ((mipSize.x > 1) || (mipSize.y > 1));
|
|
mipLevelSizes[0] = hardwareTextureSize;
|
|
//RT实际大小
|
|
depthPyramidTextureSize = new Vector2Int((int)Mathf.Ceil((float)texSize.x), (int)Mathf.Ceil((float)texSize.y));
|
|
mipLevelCount = mipLevel + 1;
|
|
}
|
|
|
|
private static int[] depthMipId = null;
|
|
|
|
private void DoCopyDepth(CommandBuffer cmd, Texture depthTex)
|
|
{
|
|
if (depthMipId == null)
|
|
{
|
|
depthMipId = new int[32];
|
|
for (int i = 0; i < depthMipId.Length; i++)
|
|
{
|
|
depthMipId[i] = Shader.PropertyToID("_DepthMip" + i);
|
|
}
|
|
}
|
|
|
|
var sampleName = "DepthDownSample";
|
|
cmd.BeginSample(sampleName);
|
|
cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);
|
|
cmd.SetGlobalTexture(depthInputId, depthTex);
|
|
for (int i = 1; i < mipLevelCount; i++)
|
|
{
|
|
var mipSize = mipLevelSizes[i];
|
|
var inputMipSize = i == 1 ? mip0SizeNOP : mipLevelSizes[i - 1];
|
|
var texId = depthMipId[i];
|
|
cmd.SetGlobalVector(inputScaleAndMaxIndexId, new Vector4(inputMipSize.x / (float)mipSize.x, inputMipSize.y / (float)mipSize.y, inputMipSize.x - 1, inputMipSize.y - 1));
|
|
cmd.GetTemporaryRT(texId, mipSize.x, mipSize.y, 0, FilterMode.Point, RenderTextureFormat.RFloat);
|
|
cmd.SetRenderTarget(texId, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, settings.CopyDepth, 0, 0);
|
|
cmd.SetGlobalTexture(depthInputId, texId);
|
|
}
|
|
|
|
cmd.EndSample(sampleName);
|
|
|
|
sampleName = "DepthCombine";
|
|
cmd.BeginSample(sampleName);
|
|
var hizIndex = GetHizIndex();
|
|
RTHandle hizRt = depthPyramidTexs[hizIndex];
|
|
|
|
RenderingUtils.ReAllocateIfNeeded(ref hizRt, new RenderTextureDescriptor()
|
|
{
|
|
width = depthPyramidTextureSize.x,
|
|
height = depthPyramidTextureSize.y,
|
|
dimension = TextureDimension.Tex2D,
|
|
colorFormat = RenderTextureFormat.RFloat,
|
|
msaaSamples = 1,
|
|
}, filterMode : FilterMode.Point, name: depthPyramidNames[hizIndex]);
|
|
depthPyramidTexs[hizIndex] = hizRt;
|
|
|
|
cmd.SetRenderTarget(hizRt, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
for (int i = 1; i < mipLevelCount; i++)
|
|
{
|
|
var texId = depthMipId[i];
|
|
cmd.SetGlobalTexture(depthInputId, texId);
|
|
var mipSize = mipLevelSizes[i];
|
|
var mipOffset = mipLevelOffsets[i];
|
|
mipOffsetAndSizes[i] = new (mipOffset.x, mipOffset.y, mipSize.x, mipSize.y);
|
|
cmd.SetViewport(new Rect(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y));
|
|
cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, settings.CopyDepth, 0, 1);
|
|
}
|
|
|
|
cmd.SetGlobalTexture(depthPyramidTexId, hizRt);
|
|
cmd.EndSample(sampleName);
|
|
|
|
for (int i = 0; i < depthMipId.Length; i++)
|
|
{
|
|
var texId = depthMipId[i];
|
|
cmd.ReleaseTemporaryRT(texId);
|
|
}
|
|
}
|
|
|
|
private void DoComputeDepth(CommandBuffer cmd, Texture depthTex)
|
|
{
|
|
var hizIndex = GetHizIndex();
|
|
RTHandle hizBuffer = depthPyramidTexs[hizIndex];
|
|
|
|
RenderingUtils.ReAllocateIfNeeded(ref hizBuffer, new RenderTextureDescriptor()
|
|
{
|
|
width = depthPyramidTextureSize.x,
|
|
height = depthPyramidTextureSize.y,
|
|
dimension = TextureDimension.Tex2D,
|
|
colorFormat = RenderTextureFormat.RFloat,
|
|
msaaSamples = 1,
|
|
enableRandomWrite = true,
|
|
}, filterMode: FilterMode.Point, name: depthPyramidNames[hizIndex]);
|
|
depthPyramidTexs[hizIndex] = hizBuffer;
|
|
|
|
int outputMipCnt = mipLevelCount - 1;
|
|
int dispatchCnt = Mathf.CeilToInt(outputMipCnt / 4f);
|
|
int mipCnt = outputMipCnt;
|
|
Matrix4x4 matrix4X4 = new Matrix4x4();
|
|
for (int i = 0; i <= dispatchCnt; i++)
|
|
{
|
|
int startMip = i * 4;
|
|
int mip1 = startMip + 1;
|
|
int mip2 = startMip + 2;
|
|
int mip3 = startMip + 3;
|
|
int mip4 = startMip + 4;
|
|
var outputMipSize = mipLevelSizes[mip1];
|
|
if(outputMipSize.x == 0 || outputMipSize.y == 0)
|
|
{
|
|
break;
|
|
}
|
|
int kernelId = 0;
|
|
if (i == 0)
|
|
{
|
|
kernelId = 1;
|
|
if (settings.SkipThreeMip)
|
|
{
|
|
kernelId = 2;
|
|
}
|
|
}
|
|
|
|
cmd.SetComputeTextureParam(settings.ComputeShader, kernelId, depthInputId, i == 0 ? depthTex : hizBuffer);
|
|
cmd.SetComputeTextureParam(settings.ComputeShader, kernelId, "_DepthMipChain", hizBuffer);
|
|
|
|
int inputMipIndex = startMip;
|
|
var inputMipOffset = mipLevelOffsets[inputMipIndex];
|
|
var inputMipSize = inputMipIndex == 0 ? mip0SizeNOP : mipLevelSizes[inputMipIndex];
|
|
|
|
cmd.SetComputeVectorParam(settings.ComputeShader, "_InputMipOffsetAndSize", new Vector4(inputMipOffset.x, inputMipOffset.y, inputMipSize.x, inputMipSize.y));
|
|
cmd.SetComputeIntParam(settings.ComputeShader, "_MipCount", Mathf.Min(mipCnt, 4));
|
|
|
|
ref var mipOffset = ref mipLevelOffsets[mip1];
|
|
ref var mipSize = ref mipLevelSizes[mip1];
|
|
matrix4X4.SetRow(0, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y));
|
|
mipOffset = ref mipLevelOffsets[mip2];
|
|
mipSize = ref mipLevelSizes[mip2];
|
|
matrix4X4.SetRow(1, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y));
|
|
mipOffset = ref mipLevelOffsets[mip3];
|
|
mipSize = ref mipLevelSizes[mip3];
|
|
matrix4X4.SetRow(2, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y));
|
|
mipOffset = ref mipLevelOffsets[mip4];
|
|
mipSize = ref mipLevelSizes[mip4];
|
|
matrix4X4.SetRow(3, new Vector4(mipOffset.x, mipOffset.y, mipSize.x, mipSize.y));
|
|
|
|
cmd.SetComputeMatrixParam(settings.ComputeShader, "_MipOffsetAndSizeArray", matrix4X4);
|
|
|
|
// XXX: 可以测试是否有正面作用
|
|
// cmd.SetExecutionFlags(CommandBufferExecutionFlags.AsyncCompute);
|
|
cmd.DispatchCompute(settings.ComputeShader, kernelId, Mathf.CeilToInt(outputMipSize.x / 8f), Mathf.CeilToInt(outputMipSize.y / 8f), 1);
|
|
mipCnt = mipCnt - 4;
|
|
}
|
|
cmd.SetGlobalTexture(depthPyramidTexId, hizBuffer);
|
|
}
|
|
|
|
private void DoSpdDepth(CommandBuffer cmd, Texture depthTex)
|
|
{
|
|
|
|
}
|
|
|
|
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
|
{
|
|
var cmd = renderingData.commandBuffer;
|
|
using var soc = new ProfilingScope(cmd, profiler);
|
|
Texture depthTex = Shader.GetGlobalTexture(depthTextureId);
|
|
if(depthTex == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ComputePackedMipChainInfo(new Vector2Int(depthTex.width, depthTex.height));
|
|
|
|
switch (settings.PyramidFunc)
|
|
{
|
|
case EDepthPyramidFunc.CopyDepth:
|
|
DoCopyDepth(cmd, depthTex);
|
|
break;
|
|
case EDepthPyramidFunc.Compute:
|
|
DoComputeDepth(cmd, depthTex);
|
|
break;
|
|
case EDepthPyramidFunc.SPD:
|
|
DoSpdDepth(cmd, depthTex);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
for (int i = 0; i < depthPyramidTexs.Length; i++)
|
|
{
|
|
var rt = depthPyramidTexs[i];
|
|
rt?.Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|