13 KiB (Stored with Git LFS)
13 KiB (Stored with Git LFS)
NiloToonCharToneAdjust 작업 요약
새 대화에서 이 내용을 읽고 이어서 작업할 수 있도록 작성된 인수인계 문서입니다.
프로젝트 환경
| 항목 | 내용 |
|---|---|
| 작업 환경 | Unity 6000.3.8f1 (Unity 6) / URP 17.0.3 |
| NiloToon 버전 | 프로젝트 내 로컬 패키지 (Assets/NiloToonURP/) |
| 애드온 경로 | Assets/YAMO/Shader/NiloToonCharToneAdjust/ |
구현 목표
NiloToon 캐릭터 셰이더를 사용하는 오브젝트에만 색조 보정을 적용하는 URP Volume 애드온.
- Unity의 Lift Gamma Gain + Shadows Midtones Highlights Volume과 동일한 사용감
- NiloToon 원본 소스 무수정 (완전 애드온 방식)
- 배경·소품 등 비캐릭터 오브젝트는 영향 없음
- Auto Match: 캐릭터/배경 색상을 분석하여 보정 파라미터를 자동 추천하는 원샷 버튼
구현 방식: 스텐실 마스킹 포스트프로세스
NiloToon이 캐릭터 픽셀에 stencil bit7 = 128 을 기록하는 구조를 활용합니다.
[NiloToonExtraThickToonOutlinePass] AfterRenderingTransparents = 600
1. CharacterAreaStencilBufferFill -> 캐릭터 픽셀: stencil = 128
2. ExtraThickOutline -> stencil != 128 픽셀에 아웃라인
3. CharacterAreaColorFill -> stencil = 128 픽셀에 색 오버레이
-> 끝나면 Invert -> stencil = 0 <-- 여기서 지워짐
[우리 Pass] AfterRenderingTransparents + 1 = 601
Step 1: DrawRendererList(overrideMaterial=Pass 5) -> stencil = 128 복원
Step 1b: (Match 요청 시) Pass 9 StencilToMask -> 마스크 텍스처 생성
Step 2: (Match 요청 시) 분석 다운샘플 체인 (full-res -> 64 -> 16 -> 4 -> 1)
Step 3: colorHandle -> tempHandle 복사 (Pass 0)
Step 4: tempHandle -> colorHandle, Stencil Equal(128)로 색조 보정 (Pass 1)
[NiloToonUberPostProcessPass] BeforeRenderingPostProcessing = 900
-> Bloom, Tonemapping (우리 보정 결과에 적용됨)
스텐실 구조
bit7 (=128): NiloToon 내부 예약 -- 캐릭터 영역 마킹bit0~6 (=127): 사용자 제어 영역
알려진 한계
- 반투명(Transparent queue) 캐릭터 파츠는 스텐실 마스킹 제외
- NiloToon의
CharacterAreaStencilBufferFill이 Opaque queue만 처리 (의도적 설계)
- NiloToon의
파일 목록
Assets/YAMO/Shader/NiloToonCharToneAdjust/
+-- NiloToonCharToneAdjustVolume.cs <- VolumeComponent 정의
+-- NiloToonCharToneAdjustFeature.cs <- ScriptableRendererFeature + RenderGraph Pass
+-- NiloToonCharToneAdjust.shader <- 풀스크린 색조 보정 셰이더 (Pass 0~9)
+-- Editor/
| +-- NiloToonCharToneAdjustVolumeEditor.cs <- 색상 휠 + Reset/Match 버튼 커스텀 Inspector
| +-- YAMOTrackballUIDrawer.cs <- URP 내장 TrackballUIDrawer 복사본
+-- WORK_SUMMARY.md <- 이 파일
각 파일 상세
NiloToonCharToneAdjustVolume.cs
- 네임스페이스:
YAMO - 메뉴 경로:
NiloToon/Char Tone Adjust (YAMO) VolumeComponent상속,active는 베이스 클래스의bool active사용 (URP 17)
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
lift |
Vector4Parameter | (1,1,1,0) | 어두운 영역 색조 |
gamma |
Vector4Parameter | (1,1,1,0) | 중간 영역 감마 |
gain |
Vector4Parameter | (1,1,1,0) | 밝은 영역 배율 |
shadows |
Vector4Parameter | (1,1,1,0) | 어두운 영역 색조 (SMH) |
midtones |
Vector4Parameter | (1,1,1,0) | 중간 영역 색조 (SMH) |
highlights |
Vector4Parameter | (1,1,1,0) | 밝은 영역 색조 (SMH) |
shadowsStart/End |
ClampedFloat | 0.0 / 0.3 | SMH 그림자 범위 |
highlightsStart/End |
ClampedFloat | 0.55 / 1.0 | SMH 하이라이트 범위 |
saturation |
ClampedFloat | 1.0 | 채도 (0=흑백, 1=원본, 2=과채화) |
postExposure |
ClampedFloat | 0.0 | 노출 보정 (EV) |
blendAmount |
ClampedFloat | 1.0 | 보정 강도 |
파라미터 변환 규칙 (Volume -> 셰이더):
- Lift/Shadows/Midtones/Highlights:
shader = (xyz - 1) + w(중립값 = 0) - Gamma/Gain:
shader = xyz + w(중립값 = 1)
NiloToonCharToneAdjustFeature.cs
- 네임스페이스:
YAMO - renderPassEvent:
AfterRenderingTransparents + 1 - RenderGraph 방식:
AddUnsafePass<PassData>단일 PassUnsafeCommandBuffer를 통해 render target을 명시적으로 관리- 3개 RasterPass 분리 시 pass 간 stencil 상태 유실 문제 -> UnsafePass 단일 통합으로 해결
Settings
debugMode:DebugModeenumNormal-> 스텐실 재마킹 + 복사 + 캐릭터 픽셀만 색조 보정 (Pass 1)FullScreen-> 전체 화면 색조 보정 (Pass 2, 디버그)StencilView-> 스텐실 재마킹 + 복사 + 마젠타 오버레이 (Pass 3, 디버그)
Auto Match 원샷 시스템
에디터 Inspector의 Match 버튼 클릭으로 작동하는 원샷 분석 시스템:
- 요청: 에디터에서
autoMatchRequested = true설정 (static 필드) - 분석 (Frame N):
RecordRenderGraph에서 분석 체인 포함한 UnsafePass 등록- Pass 5 (MeshStencilFill): DrawRendererList로 캐릭터 스텐실 재마킹
- Pass 9 (StencilToMask): 스텐실 128 픽셀을 마스크 텍스처에 1.0 기록
- Pass 7 (MaskedDownsample): 마스크 기반 가중 다운샘플 (full-res -> 64x64)
- Pass 8 (WeightedDownsample): 알파 기반 다운샘플 (64 -> 16 -> 4 -> 1x1 persistent RT)
- 캐릭터(_InvertMask=0)와 배경(_InvertMask=1) 각각 별도 체인 실행
- 리드백 (Frame N+1):
AsyncGPUReadback.Request로 1x1 RT 읽기Time.frameCount비교로 같은 프레임 멀티카메라 중복 방지
- 값 적용: 리드백 완료 시
OnReadbackComplete()에서 Volume 파라미터 직접 입력- PostExposure:
log2(bgLum / charLum) * strength - Midtones:
(bgTint - charTint) * strength-> Vector4(1+dx, 1+dy, 1+dz, 0) - Saturation:
lerp(1, bgChroma/charChroma, strength) - Undo 지원
- PostExposure:
중요 구현 노트:
_InvertMask는 반드시cmd.SetGlobalFloat()로 설정해야 함mat.SetFloat()는 커맨드 버퍼 실행 시점에 마지막 값만 남음 (타이밍 이슈)
- 분석 상태 머신:
Idle -> Running -> ReadbackPending -> Idle_analysisStartFrame으로 프레임 경계 확인 (멀티카메라 안전)
NiloToonCharToneAdjust.shader
- 셰이더 경로:
YAMO/NiloToonCharToneAdjust Properties {}비워둠 --_MainTex를 Properties에 선언하면 Material 기본값이 Global 값보다 우선하여 복사가 안 됨
| Pass | 이름 | 스텐실 | 설명 |
|---|---|---|---|
| 0 | Copy | 없음 | color -> temp 단순 복사 |
| 1 | ToneAdjust | Equal 128 | 캐릭터 픽셀만 색조 보정 |
| 2 | ToneAdjustFull | 없음 | 전체 화면 색조 보정 (디버그) |
| 3 | StencilView | Equal 128 | 마젠타 오버레이 (디버그) |
| 4 | DebugStencilFill | Always->Replace | 전체 화면 stencil=128 기록 (진단) |
| 5 | MeshStencilFill | Always->Replace | 메시 기반 stencil=128 기록 (ColorMask 0) |
| 6 | MeshMaskFill | Always->Replace | 미사용 (Pass 5+9 조합으로 대체) |
| 7 | MaskedDownsample | 없음 | 마스크 기반 4x4 가중 다운샘플 |
| 8 | WeightedDownsample | 없음 | 알파 기반 4x4 가중 다운샘플 |
| 9 | StencilToMask | Equal 128 | stencil=128 -> color=1.0 마스크 변환 |
셰이더 적용 순서: PostExposure -> LiftGammaGain -> ShadowsMidtonesHighlights -> Saturation -> Blend
Editor/NiloToonCharToneAdjustVolumeEditor.cs
VolumeComponentEditor상속,[CustomEditor(typeof(NiloToonCharToneAdjustVolume))]YAMOTrackballUIDrawer로 Lift/Gamma/Gain, Shadows/Midtones/Highlights를 색상 휠 UI로 표시- Reset All Parameters 버튼: 모든 파라미터를 기본값으로 초기화 (Undo 지원)
- Auto Match 접이식 섹션:
- Strength 슬라이더 (0~1)
- Affect Brightness / Tint / Saturation 토글
- Match 버튼 (에디터/플레이 모드 모두 사용 가능)
- 에디터 모드에서는
SceneView.RepaintAll()로 렌더링 트리거
Editor/YAMOTrackballUIDrawer.cs
- URP 내장
TrackballUIDrawer의 복사본 (internal sealed이라 직접 사용 불가) - URP 내장 셰이더
Hidden/Universal Render Pipeline/Editor/Trackball재사용
URP 17 핵심 API 메모
// RenderGraph UnsafePass 기본 패턴
using (var builder = renderGraph.AddUnsafePass<PassData>("PassName", out var pd))
{
builder.UseTexture(handle, AccessFlags.ReadWrite);
builder.UseRendererList(listHandle);
builder.AllowPassCulling(false);
builder.SetRenderFunc(static (PassData data, UnsafeGraphContext ctx) => { ... });
}
// UnsafeCommandBuffer 텍스처/프로퍼티 설정
ctx.cmd.SetGlobalTexture(id, data.textureHandle); // TextureHandle 직접 지원
ctx.cmd.SetGlobalFloat(id, value); // 커맨드 버퍼에 순서대로 기록
// 주의: mat.SetFloat()는 즉시 적용되므로 draw 사이에 값이 바뀌는 경우 cmd.SetGlobalFloat 사용
// RendererList 생성
var rlParams = new RendererListParams(cullResults, drawSettings, filterSettings);
var handle = renderGraph.CreateRendererList(rlParams);
// DrawRendererList + overrideMaterial (스텐실 재마킹용)
var drawSettings = new DrawingSettings(shaderTagId, sortSettings)
{
overrideMaterial = material,
overrideMaterialPassIndex = passIndex,
};
// 임시 텍스처 생성 (프레임 스코프)
renderGraph.CreateTexture(new TextureDesc(w, h)
{
format = graphicsFormat, filterMode = FilterMode.Bilinear, name = "name"
});
사용 방법
- URP Renderer Asset -> Renderer Features ->
+->NiloToonCharToneAdjustFeature추가 - Feature의 Settings > Shader에
YAMO/NiloToonCharToneAdjust셰이더 할당 - Scene의 Volume 오브젝트 -> Add Override ->
NiloToon/Char Tone Adjust (YAMO) - Volume 컴포넌트 체크박스(베이스 클래스
active) ON - 파라미터 수동 조절 또는 Auto Match 사용
Auto Match 워크플로우
- Volume Inspector 하단 Auto Match 섹션 펼치기
- Strength, Affect 옵션 설정
- Match 버튼 클릭 -> PostExposure, Midtones, Saturation 값 자동 입력
- Blend Amount로 적용 강도 조절
- 필요시 Match를 반복하여 최적값 탐색
- 초기화가 필요하면 Reset All Parameters 버튼 사용
디버그 순서: Feature Inspector에서
Debug Mode선택
Full Screen-> 전체 화면 동작 확인Stencil View-> 스텐실 재마킹 정상 여부 확인 (캐릭터 마젠타 오버레이)Character Only-> 정식 모드, 캐릭터에만 색조 보정 적용
트러블슈팅 기록
DrawRendererList + NiloToon 스텐실 패스가 동작하지 않는 문제
- 증상:
DrawRendererList로 NiloToon의NiloToonCharacterAreaStencilBufferFill패스를 재실행해도 stencil=128이 기록되지 않음 - 원인: UnsafePass 컨텍스트에서 NiloToon 셰이더의 스텐실 패스가 정상 동작하지 않음 (정확한 원인 미상)
- 해결:
DrawingSettings.overrideMaterial로 우리 셰이더의 Pass 5 (MeshStencilFill)를 강제 적용
MeshMaskFill(Pass 6)로 마스크+스텐실 동시 기록 실패
- 증상: Pass 6으로 DrawRendererList 실행 시 캐릭터가 하얗게 빛남, charAvg==bgAvg
- 원인: UnsafePass에서 maskHandle+depthHandle을 바인딩하고 메시를 렌더링하면 colorHandle에도 영향
- 해결: 2단계 분리 -- Pass 5 (stencil only) + Pass 9 (fullscreen StencilToMask)
mat.SetFloat()로 _InvertMask 설정 시 char/bg 결과 동일
- 증상: 캐릭터 분석과 배경 분석 결과가 완전히 동일
- 원인:
mat.SetFloat()는 머티리얼 프로퍼티를 즉시 변경하지만,cmd.DrawProcedural()은 나중에 실행됨. 두 체인 실행 전 마지막 값(invertMask=1)만 GPU에 적용됨 - 해결:
cmd.SetGlobalFloat()로 변경하여 커맨드 버퍼에 순서대로 기록
멀티카메라 환경에서 리드백 실패
- 증상: Auto Match 리드백 실패 (유효 픽셀 부족) -- 간헐적
- 원인: Game+Scene 뷰 동시 사용 시 같은 프레임 내
RecordRenderGraph가 2회 호출되어 GPU 실행 전 리드백 요청 - 해결:
Time.frameCount비교로 최소 1프레임 경과 후에만 리드백 요청
현재 상태 (2026-04-06)
| 항목 | 상태 |
|---|---|
| VolumeComponent + 에디터 GUI (색상 휠) | 완료 |
| ScriptableRendererFeature + RenderGraph UnsafePass | 완료 |
| 셰이더 (Pass 0~9) | 완료 |
| 전체 화면 적용 (FullScreen 모드) | 동작 확인 |
| 스텐실 마스크 시각화 (StencilView 모드) | 동작 확인 |
| 캐릭터에만 적용 (Normal 모드, 스텐실 마스킹) | 동작 확인 |
| Auto Match (원샷 분석 + 값 자동 입력) | 동작 확인 |
| Reset 버튼 | 완료 |