// NiloToon Fur Mask Pass // Renders fur shells to a dedicated mask buffer for fur area detection // Separate from main fur rendering for cleaner architecture // Based on NiloToonPrepassBufferRTPass pattern using System; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.RendererUtils; #if UNITY_6000_0_OR_NEWER using UnityEngine.Rendering.RenderGraphModule; #endif using UnityEngine.Rendering.Universal; namespace NiloToon.NiloToonURP { public class NiloToonFurMaskPass : ScriptableRenderPass { static readonly ShaderTagId furShellMaskLightModeShaderTagId = new ShaderTagId("NiloToonFurShellMask"); static readonly int FurMaskTexId = Shader.PropertyToID("_NiloToonFurMaskTex"); [Serializable] public class Settings { [Tooltip("Create a mask buffer for fur area detection.\n" + "This creates _NiloToonFurMaskTex which can be used for post-processing.\n\nDefault: ON")] public bool Enabled = true; } Settings settings; RenderQueueRange renderQueueRange; #if UNITY_2022_2_OR_NEWER RTHandle furMaskRTHColor; RTHandle furMaskRTHDepth; #else RenderTargetHandle furMaskRTHandle; #endif public NiloToonFurMaskPass(Settings settings) { this.settings = settings; this.renderQueueRange = RenderQueueRange.all; #if !UNITY_2022_2_OR_NEWER furMaskRTHandle.Init("_NiloToonFurMaskRT"); #endif base.profilingSampler = new ProfilingSampler(nameof(NiloToonFurMaskPass)); } #if UNITY_6000_0_OR_NEWER [Obsolete] #endif public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { if (!settings.Enabled) return; var cameraData = renderingData.cameraData; #if UNITY_2022_2_OR_NEWER // [color RTHandle's Descriptor] - same pattern as NiloToonPrepassBufferRTPass var colorDesc = cameraData.cameraTargetDescriptor; colorDesc.graphicsFormat = GraphicsFormat.R8G8B8A8_UNorm; colorDesc.depthBufferBits = 0; colorDesc.msaaSamples = 1; // [depth RTHandle's Descriptor] - create own depth buffer like PrepassBufferRTPass var depthDesc = cameraData.cameraTargetDescriptor; depthDesc.graphicsFormat = GraphicsFormat.None; // Depth only rendering depthDesc.depthStencilFormat = cameraData.cameraTargetDescriptor.depthStencilFormat; depthDesc.msaaSamples = 1; RenderingUtils.ReAllocateIfNeeded(ref furMaskRTHColor, colorDesc, name: "_NiloToonFurMaskColor"); RenderingUtils.ReAllocateIfNeeded(ref furMaskRTHDepth, depthDesc, name: "_NiloToonFurMaskDepth"); // Set global RT cmd.SetGlobalTexture(FurMaskTexId, furMaskRTHColor); // Configure target with own color and depth buffers ConfigureTarget(furMaskRTHColor, furMaskRTHDepth); ConfigureClear(ClearFlag.All, Color.black); #else var furMaskDesc = cameraData.cameraTargetDescriptor; furMaskDesc.colorFormat = RenderTextureFormat.ARGB32; furMaskDesc.depthStencilFormat = cameraData.cameraTargetDescriptor.depthStencilFormat; furMaskDesc.msaaSamples = 1; cmd.GetTemporaryRT(furMaskRTHandle.id, furMaskDesc); cmd.SetGlobalTexture(FurMaskTexId, furMaskRTHandle.Identifier()); ConfigureTarget(new RenderTargetIdentifier(furMaskRTHandle.Identifier(), 0, CubemapFace.Unknown, -1)); ConfigureClear(ClearFlag.All, Color.black); #endif } #if UNITY_6000_0_OR_NEWER [Obsolete] #endif public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (!settings.Enabled) return; // Never draw in Preview Camera camera = renderingData.cameraData.camera; if (camera.cameraType == CameraType.Preview) return; var cmd = CommandBufferPool.Get(); using (new ProfilingScope(cmd, base.profilingSampler)) { // Execute command buffer before DrawRenderers (frame debugger requirement) context.ExecuteCommandBuffer(cmd); cmd.Clear(); // Draw all fur shells with NiloToonFurShellMask pass var filterSetting = new FilteringSettings(renderQueueRange); var sortFlags = SortingCriteria.CommonTransparent; var drawSettings = CreateDrawingSettings(furShellMaskLightModeShaderTagId, ref renderingData, sortFlags); drawSettings.perObjectData = PerObjectData.None; context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSetting); } context.ExecuteCommandBuffer(cmd); cmd.Clear(); CommandBufferPool.Release(cmd); } public override void OnCameraCleanup(CommandBuffer cmd) { // To Release a RTHandle, do it in ScriptableRendererFeature's Dispose(), don't do it in OnCameraCleanup(...) #if !UNITY_2022_2_OR_NEWER if (settings.Enabled) { cmd.ReleaseTemporaryRT(furMaskRTHandle.id); } #endif } #if UNITY_2022_2_OR_NEWER public void Dispose() { furMaskRTHColor?.Release(); furMaskRTHDepth?.Release(); } #endif #if UNITY_6000_0_OR_NEWER private class FurMaskPassData { public UniversalCameraData cameraData; public TextureHandle colorTarget; public TextureHandle depthTarget; public RendererListHandle rendererList; } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { if (!settings.Enabled) return; var cameraData = frameData.Get(); var resourceData = frameData.Get(); var renderingData = frameData.Get(); // Skip Preview cameras - same check as Execute if (cameraData.camera.cameraType == CameraType.Preview) return; using (var builder = renderGraph.AddRasterRenderPass("NiloToon Fur Mask Buffer", out var passData, base.profilingSampler)) { passData.cameraData = cameraData; // Create render textures for RG - EXACT same descriptors as OnCameraSetup var colorDesc = cameraData.cameraTargetDescriptor; colorDesc.graphicsFormat = GraphicsFormat.R8G8B8A8_UNorm; colorDesc.depthBufferBits = 0; colorDesc.msaaSamples = 1; var depthDesc = cameraData.cameraTargetDescriptor; depthDesc.graphicsFormat = GraphicsFormat.None; // Depth only rendering depthDesc.depthStencilFormat = cameraData.cameraTargetDescriptor.depthStencilFormat; depthDesc.msaaSamples = 1; passData.colorTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, colorDesc, "_NiloToonFurMaskColor", false); passData.depthTarget = UniversalRenderer.CreateRenderGraphTexture(renderGraph, depthDesc, "_NiloToonFurMaskDepth", false); // Create renderer list - EXACT same settings as Execute var renderListDesc = new RendererListDesc(furShellMaskLightModeShaderTagId, renderingData.cullResults, cameraData.camera) { sortingCriteria = SortingCriteria.CommonTransparent, renderQueueRange = renderQueueRange, }; passData.rendererList = renderGraph.CreateRendererList(renderListDesc); // Set up render targets - Match ConfigureTarget behavior builder.SetRenderAttachment(passData.colorTarget, 0, AccessFlags.Write); builder.SetRenderAttachmentDepth(passData.depthTarget, AccessFlags.Write); builder.UseRendererList(passData.rendererList); // Set global textures - Match cmd.SetGlobalTexture behavior builder.SetGlobalTextureAfterPass(passData.colorTarget, FurMaskTexId); builder.SetRenderFunc((FurMaskPassData data, RasterGraphContext context) => { // Match ConfigureClear(ClearFlag.All, Color.black) behavior context.cmd.ClearRenderTarget(RTClearFlags.All, Color.black, 1.0f, 0); // Match context.DrawRenderers behavior context.cmd.DrawRendererList(data.rendererList); }); } } #endif } }