SSS URP RenderGraph 전환 연구(planned)

Mazeline·2025년 10월 27일

컨설팅

목록 보기
2/2
post-thumbnail

프로젝트 배경

본 연구는 고객사 NTRANCE로부터 의뢰받은 SSS URP(Subsurface Scattering for Universal Render Pipeline) 시스템의 기술적 현대화 작업으로, 현재 레거시 ScriptableRenderPass API 기반으로 구현된 스킨 렌더링 파이프라인을 Unity 6.2+의 RenderGraph 아키텍처로 마이그레이션하는 것을 목적으로 합니다.

Unity Technologies는 Unity 6.0부터 차세대 렌더링 아키텍처인 RenderGraph API를 공식 도입하였으며, 이는 기존 CommandBuffer 기반의 즉시 실행(immediate-mode) 방식에서 선언적(declarative) 렌더링 모델로의 패러다임 전환을 의미합니다. 이러한 변화에 따라 레거시 커스텀 렌더 패스들은 향후 deprecation 대상으로 분류되었으며, 특히 수동 리소스 관리 방식(GetTemporaryRT/ReleaseTemporaryRT)은 다음과 같은 기술적 한계를 내포하고 있습니다.

첫째, 개발자가 텍스처 라이프타임을 명시적으로 관리해야 하므로 메모리 누수(memory leak)의 위험이 상존하며, 둘째, 렌더 패스 간 의존성을 수동으로 추적해야 하는 구조적 복잡도가 존재합니다. 셋째, 런타임에 동적으로 메모리를 할당/해제하는 방식으로 인해 메모리 단편화(fragmentation) 및 할당 오버헤드가 발생할 수 있습니다.

반면 RenderGraph는 컴파일 타임에 전체 렌더링 플로우를 분석하여 자동으로 리소스 aliasing, 의존성 해결, 불필요한 패스 제거 등의 최적화를 수행하는 그래프 기반 스케줄링 시스템입니다. 따라서 본 프로젝트를 통해 SSS URP를 RenderGraph로 전환함으로써, 최신 렌더링 파이프라인과의 기술적 호환성을 확보하고 시스템 전반의 효율성을 향상시키고자 합니다.

프로젝트 개요

본 프로젝트는 실시간 스킨 렌더링을 위한 Subsurface Scattering 시스템을 Unity 6.2 이상의 RenderGraph API 체계로 완전히 재설계하는 것을 핵심 목표로 합니다. 현재 시스템은 RenderGraph를 지원하지 않는 레거시 CommandBuffer 및 Blit 기반 구현으로 되어 있어, 리소스의 명시적 관리가 필수적이며 RenderGraph가 제공하는 자동화된 최적화 메커니즘을 활용할 수 없는 구조적 제약이 존재합니다.

본 전환 작업을 통해 기대할 수 있는 기술적 이점은 다음과 같이 정리됩니다.

첫째, 메모리 효율성 개선입니다. RenderGraph의 자동 텍스처 aliasing 메커니즘을 통해 동일한 해상도와 포맷을 가진 임시 텍스처들이 메모리 공간을 공유할 수 있게 되어, GPU 메모리 사용량을 유의미하게 절감할 수 있습니다.

둘째, 의존성 관리 자동화입니다. 렌더 패스 간의 리소스 의존성을 RenderGraph가 자동으로 분석 및 관리하게 되어, 개발자가 수동으로 실행 순서를 제어하던 복잡도가 제거되며 유지보수성이 향상됩니다.

셋째, 자동 패스 컬링입니다. RenderGraph는 정적 분석을 통해 최종 출력에 기여하지 않는 렌더 패스를 런타임에 자동으로 제거하여, 불필요한 연산 오버헤드를 감소시킵니다.

넷째, 플랫폼 호환성 확보입니다. Unity 6.2 이상의 최신 렌더링 파이프라인과 완벽하게 호환되어 향후 엔진 업데이트에 따른 기술적 부채(technical debt)를 사전에 해소할 수 있습니다.


현재 시스템 분석

렌더링 파이프라인 구조

Pass 1: Light Pass

역할: SSS 오브젝트의 디퓨즈 라이팅만 별도로 렌더링

구현:

  • RenderWithShader.cs 사용
  • LightPass.shader로 씬 재렌더링
  • Layer Mask 필터링 (예: Skin 레이어)

출력: _SSS_LightPass 텍스처

레거시 코드:

// Configure()
cmd.GetTemporaryRT(targetId, width, height, 24, FilterMode.Bilinear, RT_Format);
ConfigureTarget(RT);

// Execute()
drawingSettings.overrideShader = PassShader;
context.DrawRenderers(cullResults, ref drawingSettings, ref filterSettings);
cmd.SetGlobalTexture(targetName, RT);

// FrameCleanup()
cmd.ReleaseTemporaryRT(targetId);

Pass 2: Profile Pass

역할: SSS 프로파일 정보 렌더링

출력 데이터:

  • RGB: Profile Color (서브서피스 스캐터링 색상)
  • Alpha: Blur Radius (픽셀별 가변 블러 반경)

ProfilePass.shader 핵심:

// Fragment Output
Color = (profileTexture * ProfileColor).rgb;
Alpha = (profileTexture.a * _Blur * ProfileColor.a);

출력: _SSS_ProfilePass 텍스처

Pass 3: SSS Blur Pass

역할: Edge-aware 스크린 스페이스 블러

핵심 기능:

1. Ping-Pong 블러

// 2개의 임시 텍스처 생성
cmd.GetTemporaryRT(tmpId1, width/downsample, height/downsample, 0, ...);
cmd.GetTemporaryRT(tmpId2, width/downsample, height/downsample, 0, ...);

// CopyLight: _SSS_LightPass → tmpRT1
cmd.Blit(null, tmpRT1, CopyLight);

// Ping-pong iteration
for (int i = 0; i < blurPasses; i++)
{
    cmd.Blit(tmpRT1, tmpRT2, blurMaterial);
    // swap
    var tmp = tmpRT1;
    tmpRT1 = tmpRT2;
    tmpRT2 = tmp;
}

// 최종 결과
cmd.SetGlobalTexture("_SSS_Blur", tmpRT2);

2. Depth-Aware Blur

float DepthTest(float d0, float d1)
{
    float diff = abs(d0 - d1);
    return diff < _DepthTest ? 1.0 : 0.0;
}

3. Profile-Aware Blur (조건부)

#ifdef PROFILE_TEST
float ProfileTest(float4 center, float4 sample)
{
    float colorDiff = distance(center.rgb, sample.rgb);
    float radiusDiff = abs(center.a - sample.a);
    return (colorDiff < ProfileColorTest && radiusDiff < ProfileRadiusTest);
}
#endif

4. Normal-Aware Blur (조건부)

#ifdef NORMAL_TEST
float NormalTest(float4 n0, float4 n1)
{
    float dotResult = dot(

5. Dithered Sampling

// 블루 노이즈 기반 샘플링 랜덤화 (밴딩 감소)
float2 random = tex2D(_NoiseTexture, uv * DitherScale + _Time.xx * 200).xy;
float2x2 RotationMatrix = float2x2(random.x, random.y, -random.y, random.x);
offset = mul(offset, RotationMatrix);

6. Pixel Leak Fix

// Edge에서 오프셋 감소
float2 Offset1 = offset * EdgeTest1 * _FixPixelLeak;

7. 가우시안 가중치

// 프로파일 기반 가중치
float3 weight = exp(-Pow2(step / profile.rgb));
weightSum += weight;
col.rgb += sample.rgb * weight;

// 정규화
col.rgb = col.rgb / weightSum;

레거시 vs RenderGraph 비교

항목레거시 방식RenderGraph 방식
텍스처 생성cmd.GetTemporaryRT()builder.CreateTransientTexture()
텍스처 해제cmd.ReleaseTemporaryRT() (수동)자동 관리
의존성 관리수동 (패스 순서)builder.UseTexture() 자동
메모리 최적화없음자동 Aliasing
Culling없음불필요한 패스 자동 스킵
리소스 라이프타임수동 추적RenderGraph 자동 관리
디버깅CommandBuffer 추적RenderGraph Viewer 활용

RenderGraph 전환 설계

프로토타입 디렉토리 구조

Assets/SSS_URP_RenderGraph/
│
├── Core/
│   ├── SSSRenderGraphFeature.cs          // 통합 Renderer Feature
│   ├── SSSLightPassRG.cs                  // Light Pass (RenderGraph)
│   ├── SSSProfilePassRG.cs                // Profile Pass (RenderGraph)
│   ├── SSSBlurPassRG.cs                   // Blur Pass (RenderGraph)
│   └── SSSPassData.cs                     // 공통 PassData 구조체
│
├── Shaders/
│   ├── LightPass.shader                   // 기존 셰이더 재사용
│   ├── ProfilePass.shader                 // 기존 셰이더 재사용
│   ├── SSS_Blur.shader                    // 기존 셰이더 재사용
│   └── SSS_Common.hlsl                    // 공통 함수
│
├── Volume/
│   └── SSSVolumeComponent.cs              // Volume override
│
├── Utilities/
│   ├── RenderGraphTexturePool.cs          // TextureHandle 헬퍼
│   └── SSSDebugger.cs                     // 디버깅 도구
│
└── Documentation/
    ├── MIGRATION_[GUIDE.md](http://GUIDE.md)

핵심 클래스 설계 (의사 코드)

SSSRenderGraphFeature.cs

public class SSSRenderGraphFeature : ScriptableRendererFeature
{
    public SSSSettings settings;
    
    public override void RecordRenderGraph(RenderGraph renderGraph, ...)
    {
        // Pass 1: Light Pass
        TextureHandle lightPassOutput = 
            SSSLightPassRG.Record(renderGraph, settings);
        
        // Pass 2: Profile Pass
        TextureHandle profilePassOutput = 
            SSSProfilePassRG.Record(renderGraph, settings);
        
        // Pass 3: Blur Pass (여러 iteration)
        TextureHandle blurredOutput = 
            SSSBlurPassRG.Record(renderGraph, lightPassOutput, 
                                  profilePassOutput, settings);
        
        // 최종 결과
        renderGraph.SetGlobalTexture("_SSS_Blur", blurredOutput);
    }
}

SSSPassData.cs

class LightPassData
{
    public RendererListHandle rendererList;
    public TextureHandle outputTexture;
    public Material overrideShader;
    public FilteringSettings filterSettings;
}

class BlurPassData
{
    public TextureHandle inputTexture;
    public TextureHandle outputTexture;
    public TextureHandle depthTexture;
    public TextureHandle normalTexture;
    public TextureHandle profileTexture;
    public Material blurMaterial;
    public Vector4 blurParams;
}

SSSBlurPassRG.cs - Ping-pong 블러

public static TextureHandle Record(
    RenderGraph renderGraph,
    TextureHandle input,
    TextureHandle profile,
    SSSSettings settings)
{
    TextureHandle current = input;
    TextureHandle temp;
    
    // Ping-pong iteration
    for (int i = 0; i < settings.blurPasses; i++)
    {
        // Transient texture 생성 (자동 메모리 aliasing)
        temp = renderGraph.CreateTransientTexture(desc);
        
        using (var builder = renderGraph.AddRasterRenderPass<BlurPassData>(...))
        {
            builder.UseTexture(current);
            builder.UseTexture(profile);
            builder.UseTexture(depthTexture);
            builder.SetRenderAttachment(temp, 0);
            
            builder.SetRenderFunc((BlurPassData data, RasterGraphContext ctx) =>
            {
                ctx.cmd.Blit(data.inputTexture, data.outputTexture, data.blurMaterial);
            });
        }
        
        current = temp; // Swap
    }
    
    return current;
}

마일스톤 로드맵

Phase 1: 기반 연구 및 분석 (1-2주) - 완료

연구 과제:

  • Unity 6.2 RenderGraph API 학습
  • 현재 시스템 심층 분석
  • 렌더링 플로우 매핑
  • 리소스 의존성 분석

산출물:

  • RenderGraph API 핵심 개념 정리
  • 현재 렌더링 플로우 다이어그램
  • 리소스 의존성 맵
  • 프로토타입 디렉토리 구조

Phase 2: 프로토타입 구현 (2-3주)

목표: 핵심 렌더 패스의 RenderGraph 버전 구현

구현 과제:

  1. RenderWithShader RenderGraph 전환
    • RenderWithShaderRG.cs 신규 클래스
    • RecordRenderGraph 메서드 구현
    • TextureHandle 리소스 관리
    • RenderGraphBuilder 리소스 선언
  2. SSS_Blur RenderGraph 전환
    • SSS_BlurRG.cs 신규 클래스
    • Ping-pong 블러를 RenderGraph 패스 체인으로 재구성
    • Transient texture 최적화
    • Volume override 시스템 유지
  3. 리소스 디스크립터 시스템
    • RenderTextureDescriptorTextureDesc 전환
    • 다운샘플링 로직 재구현

산출물:

  • RenderWithShaderRG.cs
  • SSS_BlurRG.cs
  • 기본 동작 프로토타입

Phase 3: 통합 및 호환성 (2-3주)

목표: 전체 시스템 통합 및 기존 기능 유지

통합 과제:

  1. 셰이더 호환성 검증
    • Global texture 바인딩 확인
    • Include 파일 검증
  2. Volume 시스템 통합
    • SSS_Volume.cs와 RenderGraph 연동
    • 런타임 파라미터 오버라이드
  3. 레이어 마스킹 및 필터링
    • FilteringSettings 전환
    • Rendering Layer Mask 지원
  4. Cascade 가중치 시스템
    • Translucency cascade weight 유지

산출물:

  • 완전 통합된 RenderGraph 버전
  • 레거시 기능 100% 호환성

Phase 4: 최적화 및 검증 (1-2주)

목표: 성능 최적화 및 품질 검증

최적화 과제:

  1. RenderGraph 자동 최적화 활용
    • Culling 효과 측정
    • Memory aliasing 검증
    • Transient resource 재사용 확인
  2. 성능 프로파일링
    • Frame Debugger 패스 순서 확인
    • RenderGraph Viewer 리소스 라이프타임 분석
    • GPU 메모리 사용량 비교
  3. 품질 검증
    • 모든 데모 씬 테스트
    • Visual diff 체크
    • Edge case 테스트

산출물:

  • 성능 비교 리포트
  • 최적화된 최종 버전

Phase 5: 문서화 및 마이그레이션 가이드 (1주)

목표: 개발자 문서 작성

문서화 과제:

  1. API 변경사항 문서
    • 레거시 vs RenderGraph API 매핑표
    • Breaking changes 정리
  2. 마이그레이션 가이드
    • 기존 설정 이전 방법
    • 트러블슈팅 가이드
  3. 코드 주석 및 예제

산출물:

  • MIGRATION_[GUIDE.md](http://GUIDE.md)
  • 주석이 잘 달린 최종 코드

전체 타임라인

Week 1-2  : Phase 1 (연구) - 완료
Week 3-5  : Phase 2 (프로토타입)
Week 6-8  : Phase 3 (통합)
Week 9-10 : Phase 4 (최적화)
Week 11   : Phase 5 (문서화)

총 소요 기간: 약 11주 (2.5개월)


주요 리스크 및 대응 방안

리스크영향도대응 방안
RenderGraph API 변경높음최신 문서 지속 모니터링
Ping-pong 블러 복잡도중간단계별 디버깅, Frame Debugger 활용
레거시 셰이더 호환성중간점진적 통합, A/B 테스트
성능 회귀낮음RenderGraph 자동 최적화로 개선 예상

핵심 인사이트

1. Ping-pong 블러가 가장 복잡한 전환 포인트

  • 여러 iteration의 TextureHandle 관리 필요
  • Transient texture 전략 중요

2. 셰이더는 100% 재사용 가능

  • RenderGraph는 C# 코드만 변경
  • 기존 HLSL 코드 완전 호환

3. Volume 시스템은 독립적

  • RenderGraph와 독립적으로 동작
  • 기존 로직 그대로 유지 가능

4. 주요 변경사항

// Before (Legacy)
cmd.GetTemporaryRT(targetId, ...);
cmd.Blit(source, target, material);
cmd.ReleaseTemporaryRT(targetId);

// After (RenderGraph)
TextureHandle target = builder.CreateTransientTexture(desc);
builder.UseTexture(sourceTexture);
builder.SetRenderAttachment(target, 0);
// 자동 해제 - 코드 불필요

참고 자료

RenderGraph API

  • RecordRenderGraph(): 렌더 패스 기록
  • AddRasterRenderPass<T>(): 래스터 패스 추가
  • CreateTransientTexture(): 임시 텍스처 생성
  • UseTexture(): 텍스처 리소스 의존성 선언
  • SetRenderAttachment(): 렌더 타겟 설정
  • SetRenderFunc(): 실제 렌더링 함수 정의

RenderGraph 장점

  • 패스 간 의존성 자동 해결
  • 메모리 자동 최적화 (aliasing, culling)
  • 불필요한 패스 자동 스킵
  • 리소스 라이프타임 자동 관리

다음 아이템

즉시 시작 가능한 작업:

  1. [완료] RenderGraph 레퍼런스 수집 완료
  2. [완료] 현재 렌더링 플로우 분석 완료
  3. [완료] 프로토타입 디렉토리 구조 설계 완료
  4. [대기] SSSRenderGraphFeature.cs 프로토타입 작성 (Phase 2)
  5. [대기] Light Pass RenderGraph 구현 (Phase 2)

작성일: 2025-10-27

버전: 1.0

상태: Phase 1 완료, Phase 2 대기 중

profile
테크아트 컨설팅 전문 회사 "메이즈라인" 입니다.

0개의 댓글