From 79a18adfe54be0aba7f4292b34ae8c6bf7252b4a Mon Sep 17 00:00:00 2001 From: user Date: Thu, 8 Jan 2026 00:34:19 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20SSCC=20Render=20Graph=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20=EC=B6=94=EA=B0=80=20(Unity=206.3=20=ED=98=B8?= =?UTF-8?q?=ED=99=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RecordRenderGraph() 메서드 구현으로 Unity 6.3 Render Graph API 지원 - PassData 클래스 추가 (패스 간 데이터 전달용) - SetupRenderPasses() 오버라이드 추가 - 기존 Execute() 메서드는 Compatibility Mode용으로 유지 - UnsafePass 사용하여 기존 렌더링 로직 보존 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../SRP/URP/Runtime/SSCCRendererFeature.cs | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/Assets/External/Screen Space Cavity Curvature/SRP/URP/Runtime/SSCCRendererFeature.cs b/Assets/External/Screen Space Cavity Curvature/SRP/URP/Runtime/SSCCRendererFeature.cs index 53ae2cf7..6073fd72 100644 --- a/Assets/External/Screen Space Cavity Curvature/SRP/URP/Runtime/SSCCRendererFeature.cs +++ b/Assets/External/Screen Space Cavity Curvature/SRP/URP/Runtime/SSCCRendererFeature.cs @@ -6,6 +6,7 @@ using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; +using UnityEngine.Rendering.RenderGraphModule; namespace ScreenSpaceCavityCurvature.Universal { @@ -148,6 +149,7 @@ namespace ScreenSpaceCavityCurvature.Universal #endif } + [System.Obsolete("This rendering path is for compatibility mode only (render graph disabled). Use RecordRenderGraph with RenderGraph.")] public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (mat == null) @@ -306,6 +308,163 @@ namespace ScreenSpaceCavityCurvature.Universal cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, material, 0, passIndex); } + // ===== Render Graph Implementation ===== + + private class PassData + { + public Material material; + public TextureHandle cameraColorTarget; + public TextureHandle cavityTex; + public TextureHandle tempTex; + public TextureHandle ssccOutputTex; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + if (mat == null) + { + Debug.LogError("SSCC material has not been correctly initialized..."); + return; + } + + FetchVolumeComponent(); + if (sscc == null || !sscc.IsActive()) return; + + var resourceData = frameData.Get(); + var cameraData = frameData.Get(); + + var sourceWidth = cameraData.cameraTargetDescriptor.width; + var sourceHeight = cameraData.cameraTargetDescriptor.height; + int div = sscc.cavityResolution.value == SSCC.CavityResolution.Full ? 1 : 2; + bool outputToTexture = Output == SSCC.OutputEffectTo._SSCCTexture; + + // Update material properties + mat.SetVector(ShaderProperties.inputTexelSize, new Vector4(1f / sourceWidth, 1f / sourceHeight, sourceWidth, sourceHeight)); + mat.SetVector(ShaderProperties.cavityTexTexelSize, new Vector4(1f / (sourceWidth / div), 1f / (sourceHeight / div), sourceWidth / div, sourceHeight / div)); + mat.SetMatrix(ShaderProperties.worldToCameraMatrix, cameraData.camera.worldToCameraMatrix); + mat.SetFloat(ShaderProperties.effectIntensity, sscc.effectIntensity.value); + mat.SetFloat(ShaderProperties.distanceFade, sscc.distanceFade.value); + mat.SetFloat(ShaderProperties.curvaturePixelRadius, new float[] { 0f, 0.5f, 1f, 1.5f, 2.5f }[sscc.curvaturePixelRadius.value]); + mat.SetFloat(ShaderProperties.curvatureRidge, sscc.curvatureBrights.value == 0f ? 999f : (5f - sscc.curvatureBrights.value)); + mat.SetFloat(ShaderProperties.curvatureValley, sscc.curvatureDarks.value == 0f ? 999f : (5f - sscc.curvatureDarks.value)); + mat.SetFloat(ShaderProperties.cavityWorldRadius, sscc.cavityRadius.value); + mat.SetFloat(ShaderProperties.cavityRidge, sscc.cavityBrights.value * 2f); + mat.SetFloat(ShaderProperties.cavityValley, sscc.cavityDarks.value * 2f); + + // Update keywords + bool debugEffect = sscc.debugMode.value == SSCC.DebugMode.EffectOnly; + bool debugNormals = sscc.debugMode.value == SSCC.DebugMode.ViewNormals; + CoreUtils.SetKeyword(mat, "DEBUG_EFFECT", debugEffect); + CoreUtils.SetKeyword(mat, "DEBUG_NORMALS", debugNormals); + CoreUtils.SetKeyword(mat, "CHARACTER_MASKING", sscc.enableNiloToonMasking.value); + CoreUtils.SetKeyword(mat, "ORTHOGRAPHIC_PROJECTION", cameraData.camera.orthographic); + CoreUtils.SetKeyword(mat, "NORMALS_RECONSTRUCT", sscc.normalsSource.value == SSCC.PerPixelNormals.ReconstructedFromDepth); + CoreUtils.SetKeyword(mat, "SATURATE_CAVITY", sscc.saturateCavity.value); + CoreUtils.SetKeyword(mat, "OUTPUT_TO_TEXTURE", outputToTexture); + CoreUtils.SetKeyword(mat, "UPSCALE_CAVITY", sscc.cavityResolution.value == SSCC.CavityResolution.HalfUpscaled); + CoreUtils.SetKeyword(mat, "CAVITY_SAMPLES_6", sscc.cavitySamples.value == SSCC.CavitySamples.Low6); + CoreUtils.SetKeyword(mat, "CAVITY_SAMPLES_8", sscc.cavitySamples.value == SSCC.CavitySamples.Medium8); + CoreUtils.SetKeyword(mat, "CAVITY_SAMPLES_12", sscc.cavitySamples.value == SSCC.CavitySamples.High12); + CoreUtils.SetKeyword(mat, "CAVITY_SAMPLES_20", sscc.cavitySamples.value == SSCC.CavitySamples.VeryHigh20); + + // Create texture descriptors + var cavityDesc = new TextureDesc(sourceWidth / div, sourceHeight / div) + { + colorFormat = GraphicsFormat.R32G32B32A32_SFloat, + depthBufferBits = DepthBits.None, + filterMode = FilterMode.Bilinear, + name = "SSCC_CavityTex" + }; + + // Create cavity texture handle (shared between passes) + TextureHandle cavityTexHandle = renderGraph.CreateTexture(cavityDesc); + + // Pass 1: Generate Cavity + using (var builder = renderGraph.AddUnsafePass("SSCC Generate Cavity", out var passData)) + { + passData.material = mat; + passData.cavityTex = cavityTexHandle; + + builder.UseTexture(passData.cavityTex, AccessFlags.Write); + + builder.SetRenderFunc((PassData data, UnsafeGraphContext context) => + { + var cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); + cmd.SetRenderTarget(data.cavityTex, 0, CubemapFace.Unknown, -1); + cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, data.material, 0, Pass.GenerateCavity); + }); + } + + // Pass 2: Final composite + if (outputToTexture) + { + var outputDesc = new TextureDesc(sourceWidth, sourceHeight) + { + colorFormat = GraphicsFormat.R16G16B16A16_SFloat, + depthBufferBits = DepthBits.None, + filterMode = FilterMode.Bilinear, + name = "_SSCCTexture" + }; + + using (var builder2 = renderGraph.AddUnsafePass("SSCC Final Output", out var passData2)) + { + passData2.material = mat; + passData2.cavityTex = cavityTexHandle; + passData2.ssccOutputTex = renderGraph.CreateTexture(outputDesc); + + builder2.UseTexture(passData2.cavityTex, AccessFlags.Read); + builder2.UseTexture(passData2.ssccOutputTex, AccessFlags.Write); + + builder2.SetRenderFunc((PassData data, UnsafeGraphContext context) => + { + var cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); + cmd.SetGlobalTexture(ShaderProperties.cavityTex, data.cavityTex); + cmd.SetRenderTarget(data.ssccOutputTex, 0, CubemapFace.Unknown, -1); + cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, data.material, 0, Pass.Final); + cmd.SetGlobalTexture(ShaderProperties.globalSSCCTexture, data.ssccOutputTex); + }); + } + } + else + { + var tempDesc = new TextureDesc(sourceWidth, sourceHeight) + { + colorFormat = cameraData.cameraTargetDescriptor.graphicsFormat, + depthBufferBits = DepthBits.None, + filterMode = FilterMode.Bilinear, + name = "SSCC_TempTex" + }; + + using (var builder2 = renderGraph.AddUnsafePass("SSCC Final Screen", out var passData2)) + { + passData2.material = mat; + passData2.cavityTex = cavityTexHandle; + passData2.cameraColorTarget = resourceData.activeColorTexture; + passData2.tempTex = renderGraph.CreateTexture(tempDesc); + + builder2.UseTexture(passData2.cavityTex, AccessFlags.Read); + builder2.UseTexture(passData2.cameraColorTarget, AccessFlags.ReadWrite); + builder2.UseTexture(passData2.tempTex, AccessFlags.ReadWrite); + + builder2.SetRenderFunc((PassData data, UnsafeGraphContext context) => + { + var cmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); + + // Copy camera color to temp + cmd.SetGlobalTexture(ShaderProperties.mainTex, data.cameraColorTarget); + cmd.SetRenderTarget(data.tempTex, 0, CubemapFace.Unknown, -1); + cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, data.material, 0, Pass.Copy); + + // Final composite from temp to camera color + cmd.SetGlobalTexture(ShaderProperties.mainTex, data.tempTex); + cmd.SetGlobalTexture(ShaderProperties.cavityTex, data.cavityTex); + cmd.SetRenderTarget(data.cameraColorTarget, 0, CubemapFace.Unknown, -1); + cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, data.material, 0, Pass.Final); + }); + } + } + } + } [SerializeField] @@ -356,5 +515,19 @@ namespace ScreenSpaceCavityCurvature.Universal renderer.EnqueuePass(renderPass); } } + + public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData) + { + if (renderPass == null) return; + + shader = Shader.Find("Hidden/Universal Render Pipeline/SSCC"); + if (shader == null) return; + + renderPass.Setup(shader, renderer, renderingData); + renderPass.ConfigureInput(ScriptableRenderPassInput.Depth | ScriptableRenderPassInput.Normal); + + // Render Graph requires intermediate texture for post-processing effects + renderPass.requiresIntermediateTexture = true; + } } }