Feat: SSCC Render Graph 지원 추가 (Unity 6.3 호환)

- 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 <noreply@anthropic.com>
This commit is contained in:
user 2026-01-08 00:34:19 +09:00
parent 6393b12072
commit 79a18adfe5

View File

@ -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<UniversalResourceData>();
var cameraData = frameData.Get<UniversalCameraData>();
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<PassData>("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<PassData>("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<PassData>("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;
}
}
}