300 lines
15 KiB
C#
300 lines
15 KiB
C#
// very similar to URP's DepthNormalOnlyPass.cs, but only draw NiloToon character shader's "LightMode" = "NiloToonPrepassBuffer" pass.("LightMode"= ShaderTagId in C#)
|
|
// this pass's on/off is handled by NiloToonAllInOneRendererFeature.cs's AddRenderPasses() function
|
|
|
|
// *see DBufferRenderPass.cs to learn how to "ReAllocateIfNeeded" & "Dispose" RTHandle correctly
|
|
|
|
/*
|
|
_NiloToonPrepassBufferRT is storing the following data
|
|
-r: face (legacy, kept for compatibility)
|
|
-g: character visible area (for NiloToon Bloom / NiloToon Tonemapping) - includes face, body, and fur
|
|
-b: unused
|
|
-a: unused
|
|
|
|
* We may draw IsFace,IsSkin,RimArea into this RT in future versions
|
|
* Fur mask is now integrated: renders fur shells to G channel
|
|
* All character areas (face, body, fur) use G channel for unified character mask
|
|
*/
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.RendererUtils;
|
|
using UnityEngine.Rendering.Universal;
|
|
|
|
#if UNITY_6000_0_OR_NEWER
|
|
using UnityEngine.Rendering.RenderGraphModule;
|
|
#endif
|
|
|
|
namespace NiloToon.NiloToonURP
|
|
{
|
|
public class NiloToonPrepassBufferRTPass : ScriptableRenderPass
|
|
{
|
|
NiloToonRendererFeatureSettings allSettings;
|
|
|
|
#if UNITY_2022_2_OR_NEWER
|
|
RTHandle prepassBufferRTHColor;
|
|
RTHandle prepassBufferRTHDepth;
|
|
#else
|
|
RenderTargetHandle prepassBufferRTH;
|
|
#endif
|
|
|
|
// Constants
|
|
static readonly ShaderTagId shaderTagId = new ShaderTagId("NiloToonPrepassBuffer");
|
|
static readonly ShaderTagId furShellMaskShaderTagId = new ShaderTagId("NiloToonFurShellMask");
|
|
|
|
// Fur mask settings
|
|
bool enableFurMask = true;
|
|
|
|
// Constructor(will not call every frame)
|
|
public NiloToonPrepassBufferRTPass(NiloToonRendererFeatureSettings allSettings)
|
|
{
|
|
this.allSettings = allSettings;
|
|
|
|
#if !UNITY_2022_2_OR_NEWER
|
|
prepassBufferRTH.Init("_NiloToonPrepassBufferRT"); // this is RT's name, different to the texture name used in shader
|
|
#endif
|
|
|
|
base.profilingSampler = new ProfilingSampler(nameof(NiloToonPrepassBufferRTPass));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable or disable fur mask rendering
|
|
/// </summary>
|
|
public void SetFurMaskEnabled(bool enabled)
|
|
{
|
|
enableFurMask = enabled;
|
|
}
|
|
|
|
// This method is called before executing the render pass.
|
|
// It can be used to configure render targets and their clear state. Also to create temporary render target textures.
|
|
// When empty this render pass will render to the active camera render target.
|
|
// You should never call CommandBuffer.SetRenderTarget. Instead call <c>ConfigureTarget</c> and <c>ConfigureClear</c>.
|
|
// The render pipeline will ensure target setup and clearing happens in a performant manner.
|
|
#if UNITY_6000_0_OR_NEWER
|
|
[Obsolete]
|
|
#endif
|
|
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
|
|
{
|
|
// When doing prepass rendering, the RT's depth format(depth bit/depthStencilFormat/MSAA) need to be exactly matching formats from _CameraDepthTexture, else when rejecting blocked character pixels(using _CameraDepthTexture) will fail
|
|
RenderTextureDescriptor renderTextureDescriptor = renderingData.cameraData.cameraTargetDescriptor;
|
|
var cameraData = renderingData.cameraData;
|
|
|
|
// In URP12's DepthNormalOnlyPass.cs->Setup(...)
|
|
// or
|
|
// In URP14's UniversalRenderer.cs->Setup(...)
|
|
// depthHandle's msaaSamples is forcibly set to 1(No MSAA),
|
|
// so we need to follow it to make both _NiloToonPrepassBufferTex and _CameraDepthTexture's depthStencilFormat and MSAA 100% match,
|
|
// so that the depth comparision in prepass buffer shader is 100% correct
|
|
// TODO: if we use DBufferRenderPass as reference, we should "ReAllocateIfNeeded" all RTHandle inside a SetUp() method, and let NiloToonAllInOneRendererFeature's SetupRenderPasses() to call it
|
|
#if UNITY_2022_2_OR_NEWER
|
|
// see URP14 DBufferRenderPass.cs->Setup(...) for reference about how to set up color and depth Descriptor and RenderingUtils.ReAllocateIfNeeded() RTHandle
|
|
|
|
// [color RTHandle's Descriptor]
|
|
var colorDesc = cameraData.cameraTargetDescriptor;
|
|
colorDesc.graphicsFormat = GraphicsFormat.R8G8B8A8_UNorm; // we don't need HDR color, since this RT's color is just a mask, so we change colorFormat to R8G8B8A8_UNorm
|
|
colorDesc.depthBufferBits = 0;
|
|
colorDesc.msaaSamples = 1;
|
|
|
|
// [depth RTHandle's Descriptor]
|
|
var depthDesc = cameraData.cameraTargetDescriptor;
|
|
depthDesc.graphicsFormat = GraphicsFormat.None; //Depth only rendering
|
|
depthDesc.depthStencilFormat = cameraData.cameraTargetDescriptor.depthStencilFormat;
|
|
depthDesc.msaaSamples = 1;
|
|
#else
|
|
renderTextureDescriptor.colorFormat = RenderTextureFormat.ARGB32;
|
|
renderTextureDescriptor.depthStencilFormat = cameraData.cameraTargetDescriptor.depthStencilFormat;
|
|
renderTextureDescriptor.msaaSamples = 1;
|
|
#endif
|
|
|
|
#if UNITY_2022_2_OR_NEWER
|
|
RenderingUtils.ReAllocateIfNeeded(ref prepassBufferRTHColor, colorDesc, name: "_NiloToonPrepassBufferColor");
|
|
RenderingUtils.ReAllocateIfNeeded(ref prepassBufferRTHDepth, depthDesc, name: "_NiloToonPrepassBufferDepth");
|
|
|
|
//set global RT
|
|
cmd.SetGlobalTexture("_NiloToonPrepassBufferTex", prepassBufferRTHColor);
|
|
cmd.SetGlobalTexture("_NiloToonPrepassBufferDepthTex", prepassBufferRTHDepth);
|
|
#else
|
|
cmd.GetTemporaryRT(prepassBufferRTH.id, renderTextureDescriptor);
|
|
|
|
//set global RT
|
|
cmd.SetGlobalTexture("_NiloToonPrepassBufferTex", prepassBufferRTH.Identifier());
|
|
#endif
|
|
|
|
|
|
#if UNITY_2022_2_OR_NEWER
|
|
ConfigureTarget(prepassBufferRTHColor,prepassBufferRTHDepth);
|
|
#else
|
|
ConfigureTarget(new RenderTargetIdentifier(prepassBufferRTH.Identifier(),0, CubemapFace.Unknown, -1));
|
|
#endif
|
|
|
|
ConfigureClear(ClearFlag.All, Color.black);
|
|
}
|
|
|
|
// Here you can implement the rendering logic.
|
|
// Use <c>ScriptableRenderContext</c> to issue drawing commands or execute command buffers
|
|
// https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html
|
|
// You don't have to call ScriptableRenderContext.submit, the render pipeline will call it at specific points in the pipeline.
|
|
#if UNITY_6000_0_OR_NEWER
|
|
[Obsolete]
|
|
#endif
|
|
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
|
{
|
|
// since this pass is only enqueued when conditions are met in NiloToonAllInOneRendererFeature's AddRenderPasses method.
|
|
// we don't need to do any "Should render?" check in this pass
|
|
|
|
// Never draw in Preview
|
|
Camera camera = renderingData.cameraData.camera;
|
|
if (camera.cameraType == CameraType.Preview)
|
|
return;
|
|
|
|
// NOTE: Do NOT mix ProfilingScope with named CommandBuffers i.e. CommandBufferPool.Get("name").
|
|
// Currently there's an issue which results in mismatched markers.
|
|
var cmd = CommandBufferPool.Get();
|
|
using (new ProfilingScope(cmd, base.profilingSampler))
|
|
{
|
|
/*
|
|
Note : should always ExecuteCommandBuffer at least once before using
|
|
ScriptableRenderContext functions (e.g. DrawRenderers) even if you
|
|
don't queue any commands! This makes sure the frame debugger displays
|
|
everything under the correct title.
|
|
*/
|
|
// https://www.cyanilux.com/tutorials/custom-renderer-features/?fbclid=IwAR27j2f3VVo0IIYDa32Dh76G9KPYzwb8j1J5LllpSnLXJiGf_UHrQ_lDtKg
|
|
context.ExecuteCommandBuffer(cmd);
|
|
cmd.Clear();
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 1. Draw all nilotoon character renderer's "NiloToonPrepassBuffer" pass using SRP batching
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
var filterSetting = new FilteringSettings(RenderQueueRange.opaque); //TODO: should include transparent also?
|
|
|
|
var sortFlags = renderingData.cameraData.defaultOpaqueSortFlags;
|
|
var drawSettings = CreateDrawingSettings(shaderTagId, ref renderingData, sortFlags);
|
|
drawSettings.perObjectData = PerObjectData.None;
|
|
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSetting);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// 2. Draw fur shell mask (NiloToonFurShellMask pass) - adds fur area to G and B channels
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
if (enableFurMask)
|
|
{
|
|
var furFilterSetting = new FilteringSettings(RenderQueueRange.all);
|
|
var furDrawSettings = CreateDrawingSettings(furShellMaskShaderTagId, ref renderingData, SortingCriteria.CommonTransparent);
|
|
furDrawSettings.perObjectData = PerObjectData.None;
|
|
context.DrawRenderers(renderingData.cullResults, ref furDrawSettings, ref furFilterSetting);
|
|
}
|
|
}
|
|
|
|
// must write these line after using{} finished, to ensure profiler and frame debugger display correctness
|
|
context.ExecuteCommandBuffer(cmd);
|
|
cmd.Clear();
|
|
CommandBufferPool.Release(cmd);
|
|
}
|
|
|
|
// Cleanup any allocated resources that were created during the execution of this render pass.
|
|
public override void OnCameraCleanup(CommandBuffer cmd)
|
|
{
|
|
// To Release a RTHandle, do it in ScriptableRendererFeature's Dispose(), don't do it in OnCameraCleanup(...)
|
|
//https://www.cyanilux.com/tutorials/custom-renderer-features/#oncameracleanup
|
|
|
|
#if !UNITY_2022_2_OR_NEWER
|
|
cmd.ReleaseTemporaryRT(prepassBufferRTH.id);
|
|
#endif
|
|
}
|
|
|
|
#if UNITY_2022_2_OR_NEWER
|
|
public void Dispose()
|
|
{
|
|
prepassBufferRTHColor?.Release();
|
|
prepassBufferRTHDepth?.Release();
|
|
}
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// RG support
|
|
/////////////////////////////////////////////////////////////////////
|
|
#if UNITY_6000_0_OR_NEWER
|
|
private class PrepassBufferPassData
|
|
{
|
|
public UniversalCameraData cameraData;
|
|
public TextureHandle colorTarget;
|
|
public TextureHandle depthTarget;
|
|
public RendererListHandle rendererList;
|
|
public RendererListHandle furRendererList;
|
|
public bool enableFurMask;
|
|
}
|
|
|
|
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameContext)
|
|
{
|
|
var cameraData = frameContext.Get<UniversalCameraData>();
|
|
var resourceData = frameContext.Get<UniversalResourceData>();
|
|
var renderingData = frameContext.Get<UniversalRenderingData>();
|
|
|
|
// Skip Preview cameras - same check as Execute
|
|
if (cameraData.camera.cameraType == CameraType.Preview)
|
|
return;
|
|
|
|
using (var builder = renderGraph.AddRasterRenderPass<PrepassBufferPassData>("NiloToon Prepass Buffer", out var passData, base.profilingSampler))
|
|
{
|
|
passData.cameraData = cameraData;
|
|
passData.enableFurMask = enableFurMask;
|
|
|
|
// Create render textures for RG - EXACT same descriptors as OnCameraSetup
|
|
var colorDesc = cameraData.cameraTargetDescriptor;
|
|
colorDesc.graphicsFormat = GraphicsFormat.R8G8B8A8_UNorm; // Match: we don't need HDR color, since this RT's color is just a mask
|
|
colorDesc.depthBufferBits = 0;
|
|
colorDesc.msaaSamples = 1;
|
|
|
|
var depthDesc = cameraData.cameraTargetDescriptor;
|
|
depthDesc.graphicsFormat = GraphicsFormat.None; // Match: Depth only rendering
|
|
depthDesc.depthStencilFormat = cameraData.cameraTargetDescriptor.depthStencilFormat;
|
|
depthDesc.msaaSamples = 1;
|
|
|
|
passData.colorTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, colorDesc, "_NiloToonPrepassBufferColor", false);
|
|
passData.depthTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, depthDesc, "_NiloToonPrepassBufferDepth", false);
|
|
|
|
// Create renderer list for character prepass - EXACT same settings as Execute
|
|
var renderListDesc = new RendererListDesc(shaderTagId, renderingData.cullResults, cameraData.camera)
|
|
{
|
|
sortingCriteria = cameraData.defaultOpaqueSortFlags,
|
|
renderQueueRange = RenderQueueRange.opaque,
|
|
};
|
|
passData.rendererList = renderGraph.CreateRendererList(renderListDesc);
|
|
|
|
// Create renderer list for fur shell mask
|
|
var furRenderListDesc = new RendererListDesc(furShellMaskShaderTagId, renderingData.cullResults, cameraData.camera)
|
|
{
|
|
sortingCriteria = SortingCriteria.CommonTransparent,
|
|
renderQueueRange = RenderQueueRange.all,
|
|
};
|
|
passData.furRendererList = renderGraph.CreateRendererList(furRenderListDesc);
|
|
|
|
// Set up render targets - Match ConfigureTarget behavior
|
|
builder.SetRenderAttachment(passData.colorTarget, 0, AccessFlags.Write);
|
|
builder.SetRenderAttachmentDepth(passData.depthTarget, AccessFlags.Write);
|
|
|
|
builder.UseRendererList(passData.rendererList);
|
|
builder.UseRendererList(passData.furRendererList);
|
|
|
|
// Set global textures - Match cmd.SetGlobalTexture behavior
|
|
builder.SetGlobalTextureAfterPass(passData.colorTarget, Shader.PropertyToID("_NiloToonPrepassBufferTex"));
|
|
builder.SetGlobalTextureAfterPass(passData.depthTarget, Shader.PropertyToID("_NiloToonPrepassBufferDepthTex"));
|
|
|
|
builder.SetRenderFunc((PrepassBufferPassData data, RasterGraphContext context) =>
|
|
{
|
|
// Match ConfigureClear(ClearFlag.All, Color.black) behavior
|
|
context.cmd.ClearRenderTarget(RTClearFlags.All, Color.black, 1.0f, 0);
|
|
|
|
// 1. Draw character prepass
|
|
context.cmd.DrawRendererList(data.rendererList);
|
|
|
|
// 2. Draw fur shell mask (adds fur area to G and B channels)
|
|
if (data.enableFurMask)
|
|
{
|
|
context.cmd.DrawRendererList(data.furRendererList);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
} |