user 010beaea75 Chore: NiloToonURP 업데이트 및 배경 썸네일 갱신
- NiloToonURP 외부 에셋 업데이트
- 배경 씬 썸네일 16:9 해상도로 갱신
- 렌더 파이프라인 설정 업데이트
- 외부 셰이더 그래프 업데이트 (LEDScreen, PIDI Planar Reflections)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 01:28:02 +09:00

359 lines
18 KiB
C#

using System;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_6000_0_OR_NEWER
using UnityEngine.Rendering.RenderGraphModule;
#endif
using UnityEngine.Rendering.Universal;
using UnityEngine.XR;
namespace NiloToon.NiloToonURP
{
public class NiloToonToonOutlinePass : ScriptableRenderPass
{
#if !UNITY_6000_4_OR_NEWER
// 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)
{
// do nothing
}
// 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)
{
renderClassicOutline(context, renderingData);
}
#endif
// Cleanup any allocated resources that were created during the execution of this render pass.
public override void OnCameraCleanup(CommandBuffer cmd)
{
// do nothing
}
static readonly ShaderTagId toonOutlineLightModeShaderTagId = new ShaderTagId("NiloToonOutline");
static readonly int _GlobalShouldRenderOutline_SID = Shader.PropertyToID("_GlobalShouldRenderOutline");
static readonly int _GlobalOutlineWidthMultiplier_SID = Shader.PropertyToID("_GlobalOutlineWidthMultiplier");
static readonly int _GlobalOutlineTintColor_SID = Shader.PropertyToID("_GlobalOutlineTintColor");
static readonly int _GlobalOutlineWidthAutoAdjustToCameraDistanceAndFOV_SID = Shader.PropertyToID("_GlobalOutlineWidthAutoAdjustToCameraDistanceAndFOV");
[Serializable]
public class Settings
{
[Header("Classic Outline")]
[Tooltip( "Enable to let characters render 'Classic Outline'.\n" +
"Can turn OFF to improve performance.\n\n" +
"Default: ON")]
[OverrideDisplayName("Enable?")]
[Revertible]
public bool ShouldRenderOutline = true;
[Tooltip("Optional 'Classic Outline' width multiplier for all Classic Outline.\n\n" +
"Default: 1")]
[Revertible]
[RangeOverrideDisplayName(" Width", 0, 4)]
public float outlineWidthMultiplier = 1;
[Tooltip("VR will apply an extra 'Classic Outline' width multiplier, due to high FOV(90)\n\n" +
"Default: 0.5")]
[Revertible]
[RangeOverrideDisplayName(" Width multiplier(XR)",0, 4)]
public float outlineWidthExtraMultiplierForXR = 0.5f;
[Tooltip("Optional outline color multiplier.\n\n" +
"Default: White")]
[Revertible]
[ColorUsageOverrideDisplayName(" Tint Color", false, true)]
public Color outlineTintColor = Color.white;
[Tooltip( "Should 'Classic Outline' width auto adjust to camera distance & FOV?\n\n" +
"If set to 1:\n" +
"- When camera is closer to character or camera FOV is lower, outline width in world space will be smaller automatically\n" +
"- When camera is further away from character or camera FOV is higher, outline width in world space will be larger automatically\n\n" +
"If set to 0:\n" +
"- Outline width will be always constant in world space\n" +
"\n" +
"Default: 1 (apply 100% adjustment)")]
[Revertible]
[RangeOverrideDisplayName(" Auto width adjustment",0, 1)]
public float outlineWidthAutoAdjustToCameraDistanceAndFOV = 1;
[Tooltip( "Usually it is recommended to disable 'Classic Outline' in planar reflection's rendering due to performance reason.\n\n" +
"If this toggle is disabled, NiloToon will stop rendering character's 'Classic Outline' when the camera GameObject's name contains any of these:\n" +
" - Reflect\n" +
" - Mirror\n" +
" - Planar\n" +
"\n" +
"Default: OFF")]
[OverrideDisplayName(" Draw in planar reflection?")]
[Revertible]
public bool allowClassicOutlineInPlanarReflection = false;
//-----------------------------------------------------------------------
[Header("Screen Space Outline")]
[Tooltip("Enable this will\n" +
"- enable URP's Depth and Normals Texture's rendering(can be slow)\n" +
"- allow Screen Space Outline's rendering in Game window, since Depth and Normal textures are now rendered.\n\n" +
"Default: OFF")]
[OverrideDisplayName("Allow render?")]
[Revertible]
public bool AllowRenderScreenSpaceOutline = false;
// TODO: when the minimum support version for NiloToon is Unity2021.3, we should move this to a global setting file, similar to URP12's global setting
[Tooltip("Screen space outline may be very disturbing in scene view window(scene view window = high fov, small window, lowest resolution), this toggle allows you to turn it on/off.\n\n" +
"Default: OFF")]
[OverrideDisplayName(" Allow in Scene View?")]
[Revertible]
public bool AllowRenderScreenSpaceOutlineInSceneView = false;
//-----------------------------------------------------------------------
[Header("Extra Thick Outline")]
[Tooltip("Usually you only need to consider \"AfterRenderingTransparents\" or \"BeforeRenderingTransparents\".\n\n" +
"If you set to AfterRenderingTransparents:\n" +
"- extra thick outline will render on top of transparent material (= extra thick outline covering transparent material).\n" +
"If you set to BeforeRenderingTransparents\n" +
"- extra thick outline will NOT render on top of transparent material (= extra thick outline covered by transparent material)\n\n" +
"*You can also control extra thick outline's ZWrite in each NiloToonPerCharacterRenderController.\n\n" +
"Default: AfterRenderingTransparents")]
[Revertible]
[OverrideDisplayName("RenderPassEvent")]
public RenderPassEvent extraThickOutlineRenderTiming = RenderPassEvent.AfterRenderingTransparents;
}
NiloToonRendererFeatureSettings allSettings;
Settings settings;
ProfilingSampler m_ProfilingSamplerClassicOutline;
RenderQueueRange renderQueueRange;
// constructor(will not construct on every frame)
public NiloToonToonOutlinePass(NiloToonRendererFeatureSettings allSettings, RenderQueueRange renderQueueRange, string ProfilingSamplerName)
{
this.allSettings = allSettings;
this.settings = allSettings.outlineSettings;
m_ProfilingSamplerClassicOutline = new ProfilingSampler(ProfilingSamplerName);
this.renderQueueRange = renderQueueRange;
}
// NOTE: [how to use ProfilingSampler to correctly]
/*
// [write as class member]
ProfilingSampler m_ProfilingSampler;
// [call once in constrcutor]
m_ProfilingSampler = new ProfilingSampler("NiloToonToonOutlinePass");
// [call in execute]
// NOTE: Do NOT mix ProfilingScope with named CommandBuffers i.e. CommandBufferPool.Get("name").
// Currently there's an issue which results in mismatched markers.
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, m_ProfilingSampler))
{
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings);
cmd.SetGlobalTexture("_CameraNormalRT", _normalRT.Identifier());
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
*/
private void renderClassicOutline(ScriptableRenderContext context, RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, m_ProfilingSamplerClassicOutline))
{
/*
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();
ExecutePassShared(context, renderingData, cmd);
}
// must write these line after using{} finished, to ensure profiler and frame debugger display correctness
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
CommandBufferPool.Release(cmd);
}
private bool GetShouldRender(Camera camera)
{
bool shouldRenderClassicOutline = settings.ShouldRenderOutline && !allSettings.MiscSettings.ForceNoOutline && !(allSettings.MiscSettings.ForceMinimumShader || NiloToonSetToonParamPass.ForceMinimumShader);
if (NiloToonPlanarReflectionHelper.IsPlanarReflectionCamera(camera) && !settings.allowClassicOutlineInPlanarReflection)
{
shouldRenderClassicOutline = false;
}
return shouldRenderClassicOutline;
}
private void ExecutePassShared(ScriptableRenderContext context, RenderingData renderingData, CommandBuffer cmd)
{
// early exit if no render needed
if (!GetShouldRender(renderingData.cameraData.camera))
{
// set the only required cmd
cmd.SetGlobalFloat(_GlobalShouldRenderOutline_SID, 0);
// no draw
// (X)
return;
}
var volumeEffect = VolumeManager.instance.stack.GetComponent<NiloToonCharRenderingControlVolume>();
// set default value first because volume may not exist in scene
float outlineWidthMultiplierResult = settings.outlineWidthMultiplier * volumeEffect.charOutlineWidthMultiplier.value;
Color outlineTintColor = settings.outlineTintColor * volumeEffect.charOutlineMulColor.value;
float outlineWidthAutoAdjustToCameraDistanceAndFOV = settings.outlineWidthAutoAdjustToCameraDistanceAndFOV * volumeEffect.charOutlineWidthAutoAdjustToCameraDistanceAndFOV.value;
// extra outline control if XR
if (XRSettings.isDeviceActive)
{
outlineWidthMultiplierResult *= volumeEffect.charOutlineWidthExtraMultiplierForXR.overrideState ? volumeEffect.charOutlineWidthExtraMultiplierForXR.value : settings.outlineWidthExtraMultiplierForXR;
}
// set
cmd.SetGlobalFloat(_GlobalShouldRenderOutline_SID, 1);
cmd.SetGlobalFloat(_GlobalOutlineWidthMultiplier_SID, outlineWidthMultiplierResult);
cmd.SetGlobalColor(_GlobalOutlineTintColor_SID, outlineTintColor);
cmd.SetGlobalFloat(_GlobalOutlineWidthAutoAdjustToCameraDistanceAndFOV_SID, outlineWidthAutoAdjustToCameraDistanceAndFOV);
// execute cmd before DrawRenderers, to avoid 1 frame delay
context.ExecuteCommandBuffer(cmd);
cmd.Clear();
// draw self classic outline
DrawingSettings drawingSettings = CreateDrawingSettings(toonOutlineLightModeShaderTagId, ref renderingData, SortingCriteria.CommonOpaque);
FilteringSettings filteringSettings = new FilteringSettings(renderQueueRange);
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
}
/////////////////////////////////////////////////////////////////////
// RG support
/////////////////////////////////////////////////////////////////////
// copy and edit of https://docs.unity3d.com/6000.0/Documentation/Manual/urp/render-graph-draw-objects-in-a-pass.html
#if UNITY_6000_0_OR_NEWER
class PassData
{
// Create a field to store the list of objects to draw
public RendererListHandle rendererListHandle;
public Camera camera;
public bool shouldRender;
public float outlineWidthMultiplierResult;
public Color outlineTintColor;
public float outlineWidthAutoAdjustToCameraDistanceAndFOV;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
string passName = "NiloToonClassicOutline(RG)";
using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out var passData))
{
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
UniversalRenderingData renderingData = frameData.Get<UniversalRenderingData>();
UniversalLightData lightData = frameData.Get<UniversalLightData>();
SortingCriteria sortFlags = SortingCriteria.CommonOpaque; //cameraData.defaultOpaqueSortFlags;
RenderQueueRange renderQueueRange = this.renderQueueRange;
FilteringSettings filterSettings = new FilteringSettings(renderQueueRange, ~0);
// Redraw only objects that have their LightMode tag set to toonOutlineLightModeShaderTagId
ShaderTagId shadersToOverride = toonOutlineLightModeShaderTagId;
// Create drawing settings
DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(shadersToOverride, renderingData, cameraData, lightData, sortFlags);
// Create the list of objects to draw
var rendererListParameters = new RendererListParams(renderingData.cullResults, drawSettings, filterSettings);
//--------------------------------------
// fill in passData
//--------------------------------------
var volumeEffect = VolumeManager.instance.stack.GetComponent<NiloToonCharRenderingControlVolume>();
passData.rendererListHandle = renderGraph.CreateRendererList(rendererListParameters);
passData.camera = cameraData.camera;
passData.shouldRender = GetShouldRender(cameraData.camera);
passData.outlineWidthMultiplierResult = settings.outlineWidthMultiplier * volumeEffect.charOutlineWidthMultiplier.value;
passData.outlineTintColor = settings.outlineTintColor * volumeEffect.charOutlineMulColor.value;
passData.outlineWidthAutoAdjustToCameraDistanceAndFOV = settings.outlineWidthAutoAdjustToCameraDistanceAndFOV * volumeEffect.charOutlineWidthAutoAdjustToCameraDistanceAndFOV.value;
// extra outline control if XR
if (XRSettings.isDeviceActive)
{
passData.outlineWidthMultiplierResult *= volumeEffect.charOutlineWidthExtraMultiplierForXR.overrideState ? volumeEffect.charOutlineWidthExtraMultiplierForXR.value : settings.outlineWidthExtraMultiplierForXR;
}
// Set the render target as the color and depth textures of the active camera texture
builder.UseRendererList(passData.rendererListHandle);
// IsValid() is needed, because some assets:
// - PIDI Planar Reflection
// - VLB
// will render Depth only pass without color buffer
if(resourceData.activeColorTexture.IsValid())
builder.SetRenderAttachment(resourceData.activeColorTexture, 0);
if(resourceData.activeDepthTexture.IsValid())
builder.SetRenderAttachmentDepth(resourceData.activeDepthTexture, AccessFlags.Write);
builder.AllowGlobalStateModification(true);
builder.SetRenderFunc((PassData data, RasterGraphContext context) => ExecutePassRG(data, context));
}
}
// run cmd similar to ExecutePassShared()
static void ExecutePassRG(PassData data, RasterGraphContext context)
{
var cmd = context.cmd;
// early exit if no render needed
if (!data.shouldRender)
{
// set the only required cmd
cmd.SetGlobalFloat(_GlobalShouldRenderOutline_SID, 0);
// no draw
// (X)
return;
}
// set cmd
cmd.SetGlobalFloat(_GlobalShouldRenderOutline_SID, 1);
cmd.SetGlobalFloat(_GlobalOutlineWidthMultiplier_SID, data.outlineWidthMultiplierResult);
cmd.SetGlobalColor(_GlobalOutlineTintColor_SID, data.outlineTintColor);
cmd.SetGlobalFloat(_GlobalOutlineWidthAutoAdjustToCameraDistanceAndFOV_SID, data.outlineWidthAutoAdjustToCameraDistanceAndFOV);
// Draw the objects in the list
cmd.DrawRendererList(data.rendererListHandle);
}
#endif
}
}