560 lines
28 KiB
C#
560 lines
28 KiB
C#
using UnityEngine;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
using UnityEngine.Rendering.RenderGraphModule;
|
|
|
|
|
|
namespace YAMO
|
|
{
|
|
public class NiloToonCharToneAdjustFeature : ScriptableRendererFeature
|
|
{
|
|
public enum DebugMode
|
|
{
|
|
[InspectorName("Character Only (스텐실 마스킹)")]
|
|
Normal,
|
|
[InspectorName("Full Screen (전체 화면, 디버그)")]
|
|
FullScreen,
|
|
[InspectorName("Stencil View (마스크 시각화, 디버그)")]
|
|
StencilView,
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Settings
|
|
{
|
|
[Tooltip("YAMO/NiloToonCharToneAdjust 셰이더를 할당해주세요.")]
|
|
public Shader shader;
|
|
|
|
[Space]
|
|
[Tooltip("Normal: 캐릭터 픽셀에만 적용 (Pass 1 + StencilFill)\n" +
|
|
"FullScreen: 전체 화면에 적용 (Pass 2, 디버그)\n" +
|
|
"StencilView: 스텐실 마스크를 빨간 오버레이로 시각화 (디버그)")]
|
|
public DebugMode debugMode = DebugMode.Normal;
|
|
}
|
|
|
|
public Settings settings = new Settings();
|
|
CharToneAdjustPass _pass;
|
|
|
|
// ── Auto Match 원샷 요청 (에디터에서 설정, Pass에서 처리) ────
|
|
public static bool autoMatchRequested;
|
|
public static NiloToonCharToneAdjustVolume autoMatchTarget;
|
|
public static float autoMatchBrightnessStrength = 0.5f;
|
|
public static float autoMatchTintStrength = 0.5f;
|
|
public static float autoMatchSaturationStrength = 0.5f;
|
|
|
|
public override void Create()
|
|
{
|
|
if (settings.shader == null)
|
|
{
|
|
Debug.LogWarning("[NiloToonCharToneAdjust] Shader이 할당되지 않았습니다.");
|
|
return;
|
|
}
|
|
_pass = new CharToneAdjustPass(settings.shader);
|
|
_pass.renderPassEvent = (RenderPassEvent)((int)RenderPassEvent.AfterRenderingTransparents + 1);
|
|
}
|
|
|
|
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
|
{
|
|
if (_pass == null) return;
|
|
var cameraType = renderingData.cameraData.cameraType;
|
|
if (cameraType == CameraType.Preview || cameraType == CameraType.Reflection) return;
|
|
var volume = VolumeManager.instance.stack.GetComponent<NiloToonCharToneAdjustVolume>();
|
|
if (volume == null || !volume.IsActive()) return;
|
|
|
|
_pass.debugMode = settings.debugMode;
|
|
renderer.EnqueuePass(_pass);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing) => _pass?.Dispose();
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
class CharToneAdjustPass : ScriptableRenderPass
|
|
{
|
|
static readonly ShaderTagId _stencilFillTagId =
|
|
new ShaderTagId("NiloToonCharacterAreaStencilBufferFill");
|
|
|
|
readonly Material _material;
|
|
|
|
// ── Pass 인덱스 ─────────────────────────────────────────
|
|
const int k_PassCopy = 0;
|
|
const int k_PassToneAdjust = 1;
|
|
const int k_PassToneAdjustFull = 2;
|
|
const int k_PassStencilView = 3;
|
|
const int k_PassDebugStencilFill = 4;
|
|
const int k_PassMeshStencilFill = 5;
|
|
const int k_PassMaskedDownsample = 7;
|
|
const int k_PassWeightedDownsample = 8;
|
|
const int k_PassStencilToMask = 9;
|
|
|
|
public DebugMode debugMode = DebugMode.Normal;
|
|
|
|
// ── 셰이더 프로퍼티 ID ──────────────────────────────────
|
|
static readonly int _MainTexId = Shader.PropertyToID("_MainTex");
|
|
static readonly int _MainTex_TexelSizeId = Shader.PropertyToID("_MainTex_TexelSize");
|
|
static readonly int _MaskTexId = Shader.PropertyToID("_MaskTex");
|
|
static readonly int _InvertMaskId = Shader.PropertyToID("_InvertMask");
|
|
static readonly int _LiftId = Shader.PropertyToID("_CharToneAdjust_Lift");
|
|
static readonly int _GammaId = Shader.PropertyToID("_CharToneAdjust_Gamma");
|
|
static readonly int _GainId = Shader.PropertyToID("_CharToneAdjust_Gain");
|
|
static readonly int _ShadowsId = Shader.PropertyToID("_CharToneAdjust_Shadows");
|
|
static readonly int _MidtonesId = Shader.PropertyToID("_CharToneAdjust_Midtones");
|
|
static readonly int _HighlightsId = Shader.PropertyToID("_CharToneAdjust_Highlights");
|
|
static readonly int _SMHRangeId = Shader.PropertyToID("_CharToneAdjust_SMHRange");
|
|
static readonly int _SaturationId = Shader.PropertyToID("_CharToneAdjust_Saturation");
|
|
static readonly int _PostExposureId = Shader.PropertyToID("_CharToneAdjust_PostExposure");
|
|
static readonly int _BlendAmountId = Shader.PropertyToID("_CharToneAdjust_BlendAmount");
|
|
|
|
static readonly Vector3 LumaW = new Vector3(0.2126729f, 0.7151522f, 0.0721750f);
|
|
|
|
// ── 원샷 분석 상태 ──────────────────────────────────────
|
|
enum AnalysisPhase { Idle, Running, ReadbackPending }
|
|
AnalysisPhase _analysisPhase = AnalysisPhase.Idle;
|
|
int _analysisStartFrame;
|
|
RenderTexture _charResult1x1;
|
|
RenderTexture _bgResult1x1;
|
|
Color _capturedCharAvg;
|
|
Color _capturedBgAvg;
|
|
int _readbackCount;
|
|
bool _charValid, _bgValid;
|
|
|
|
// ── PassData ────────────────────────────────────────────
|
|
class PassData
|
|
{
|
|
public TextureHandle colorHandle;
|
|
public TextureHandle depthHandle;
|
|
public TextureHandle tempHandle;
|
|
public RendererListHandle stencilList;
|
|
public Material material;
|
|
public NiloToonCharToneAdjustVolume volume;
|
|
public DebugMode debugMode;
|
|
|
|
// 원샷 분석 (Match 버튼)
|
|
public bool runAnalysis;
|
|
public TextureHandle maskHandle;
|
|
public TextureHandle down64, down16, down4;
|
|
public RenderTexture charResult1x1, bgResult1x1;
|
|
public int cameraWidth, cameraHeight;
|
|
}
|
|
|
|
// ── 생성/파기 ───────────────────────────────────────────
|
|
public CharToneAdjustPass(Shader shader)
|
|
{
|
|
_material = CoreUtils.CreateEngineMaterial(shader);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
CoreUtils.Destroy(_material);
|
|
if (_charResult1x1 != null) { _charResult1x1.Release(); Object.DestroyImmediate(_charResult1x1); }
|
|
if (_bgResult1x1 != null) { _bgResult1x1.Release(); Object.DestroyImmediate(_bgResult1x1); }
|
|
}
|
|
|
|
void EnsureAnalysisRTs()
|
|
{
|
|
if (_charResult1x1 != null) return;
|
|
var desc = new RenderTextureDescriptor(1, 1, RenderTextureFormat.ARGBFloat, 0);
|
|
_charResult1x1 = new RenderTexture(desc) { name = "_CharResult1x1", filterMode = FilterMode.Point };
|
|
_bgResult1x1 = new RenderTexture(desc) { name = "_BgResult1x1", filterMode = FilterMode.Point };
|
|
_charResult1x1.Create();
|
|
_bgResult1x1.Create();
|
|
}
|
|
|
|
// ── 원샷 리드백 요청 ───────────────────────────────────
|
|
void RequestOneShotReadback()
|
|
{
|
|
_readbackCount = 0;
|
|
_charValid = false;
|
|
_bgValid = false;
|
|
_capturedCharAvg = Color.black;
|
|
_capturedBgAvg = Color.black;
|
|
|
|
AsyncGPUReadback.Request(_charResult1x1, 0, req =>
|
|
{
|
|
if (!req.hasError)
|
|
{
|
|
var data = req.GetData<Color>();
|
|
if (data.Length > 0 && data[0].a > 0.001f)
|
|
{
|
|
_capturedCharAvg = data[0];
|
|
_charValid = true;
|
|
}
|
|
}
|
|
_readbackCount++;
|
|
if (_readbackCount >= 2) OnReadbackComplete();
|
|
});
|
|
|
|
AsyncGPUReadback.Request(_bgResult1x1, 0, req =>
|
|
{
|
|
if (!req.hasError)
|
|
{
|
|
var data = req.GetData<Color>();
|
|
if (data.Length > 0 && data[0].a > 0.001f)
|
|
{
|
|
_capturedBgAvg = data[0];
|
|
_bgValid = true;
|
|
}
|
|
}
|
|
_readbackCount++;
|
|
if (_readbackCount >= 2) OnReadbackComplete();
|
|
});
|
|
}
|
|
|
|
// ── 리드백 완료 → 값 적용 ─────────────────────────────
|
|
void OnReadbackComplete()
|
|
{
|
|
_analysisPhase = AnalysisPhase.Idle;
|
|
|
|
if (!_charValid || !_bgValid)
|
|
{
|
|
Debug.LogWarning("[CharToneAdjust] Auto Match: 리드백 실패 (유효 픽셀 부족)");
|
|
autoMatchTarget = null;
|
|
return;
|
|
}
|
|
|
|
var volume = autoMatchTarget;
|
|
autoMatchTarget = null;
|
|
if (volume == null) return;
|
|
|
|
float sBright = autoMatchBrightnessStrength;
|
|
float sTint = autoMatchTintStrength;
|
|
float sSat = autoMatchSaturationStrength;
|
|
|
|
Vector3 cRGB = new Vector3(_capturedCharAvg.r, _capturedCharAvg.g, _capturedCharAvg.b);
|
|
Vector3 bRGB = new Vector3(_capturedBgAvg.r, _capturedBgAvg.g, _capturedBgAvg.b);
|
|
float cLum = Vector3.Dot(cRGB, LumaW);
|
|
float bLum = Vector3.Dot(bRGB, LumaW);
|
|
|
|
Debug.Log($"[CharToneAdjust AutoMatch] char=({cRGB.x:F3},{cRGB.y:F3},{cRGB.z:F3}) lum={cLum:F3}" +
|
|
$" | bg=({bRGB.x:F3},{bRGB.y:F3},{bRGB.z:F3}) lum={bLum:F3}" +
|
|
$" | strength B={sBright:F2} T={sTint:F2} S={sSat:F2}");
|
|
|
|
if (cLum < 1e-4f || bLum < 1e-4f)
|
|
{
|
|
Debug.LogWarning("[CharToneAdjust] Auto Match: 휘도가 너무 낮아 보정 불가");
|
|
return;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
UnityEditor.Undo.RecordObject(volume, "Auto Match Tone Adjust");
|
|
#endif
|
|
|
|
// 밝기 보정 → PostExposure (EV)
|
|
if (sBright > 0.001f)
|
|
{
|
|
float ev = Mathf.Log(bLum / cLum, 2f) * sBright;
|
|
volume.postExposure.value = Mathf.Clamp(ev, -5f, 5f);
|
|
volume.postExposure.overrideState = true;
|
|
}
|
|
|
|
// 색조 보정 → Midtones
|
|
if (sTint > 0.001f)
|
|
{
|
|
Vector3 cTint = cRGB / cLum;
|
|
Vector3 bTint = bRGB / bLum;
|
|
Vector3 diff = (bTint - cTint) * sTint;
|
|
volume.midtones.value = new Vector4(
|
|
1f + Mathf.Clamp(diff.x, -0.5f, 0.5f),
|
|
1f + Mathf.Clamp(diff.y, -0.5f, 0.5f),
|
|
1f + Mathf.Clamp(diff.z, -0.5f, 0.5f), 0f);
|
|
volume.midtones.overrideState = true;
|
|
}
|
|
|
|
// 채도 보정 → Saturation
|
|
if (sSat > 0.001f)
|
|
{
|
|
float cChroma = (Mathf.Max(cRGB.x, cRGB.y, cRGB.z) -
|
|
Mathf.Min(cRGB.x, cRGB.y, cRGB.z)) / Mathf.Max(cLum, 1e-4f);
|
|
float bChroma = (Mathf.Max(bRGB.x, bRGB.y, bRGB.z) -
|
|
Mathf.Min(bRGB.x, bRGB.y, bRGB.z)) / Mathf.Max(bLum, 1e-4f);
|
|
float satRatio = bChroma / Mathf.Max(cChroma, 1e-4f);
|
|
volume.saturation.value = Mathf.Clamp(Mathf.Lerp(1f, satRatio, sSat), 0f, 2f);
|
|
volume.saturation.overrideState = true;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorUtility.SetDirty(volume);
|
|
#endif
|
|
|
|
Debug.Log($"[CharToneAdjust AutoMatch] 적용 완료 — " +
|
|
$"PostExposure={volume.postExposure.value:F3}, " +
|
|
$"Midtones={volume.midtones.value}, " +
|
|
$"Saturation={volume.saturation.value:F3}");
|
|
}
|
|
|
|
// ── RecordRenderGraph ───────────────────────────────────
|
|
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
|
{
|
|
if (_material == null) return;
|
|
|
|
var resourceData = frameData.Get<UniversalResourceData>();
|
|
var renderingData = frameData.Get<UniversalRenderingData>();
|
|
var cameraData = frameData.Get<UniversalCameraData>();
|
|
|
|
if (cameraData.camera.cameraType == CameraType.Preview) return;
|
|
|
|
var volume = VolumeManager.instance.stack.GetComponent<NiloToonCharToneAdjustVolume>();
|
|
if (volume == null || !volume.IsActive()) return;
|
|
|
|
// ── 원샷 분석: 대상 카메라 판별 ────────────────────
|
|
// Play 모드 → Game 카메라, Edit 모드 → SceneView 카메라
|
|
var camType = cameraData.camera.cameraType;
|
|
bool isTargetCamera = Application.isPlaying
|
|
? camType == CameraType.Game
|
|
: camType == CameraType.SceneView;
|
|
|
|
// ── 원샷 분석 상태 머신 ────────────────────────────
|
|
if (_analysisPhase == AnalysisPhase.Running && isTargetCamera)
|
|
{
|
|
if (Time.frameCount >= _analysisStartFrame + 2)
|
|
{
|
|
RequestOneShotReadback();
|
|
_analysisPhase = AnalysisPhase.ReadbackPending;
|
|
}
|
|
#if UNITY_EDITOR
|
|
else
|
|
{
|
|
UnityEditor.EditorApplication.QueuePlayerLoopUpdate();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 새 요청 수락 (대상 카메라 + Idle 상태 + FullScreen이 아닐 때)
|
|
bool runAnalysis = false;
|
|
if (autoMatchRequested && _analysisPhase == AnalysisPhase.Idle
|
|
&& debugMode != DebugMode.FullScreen && isTargetCamera)
|
|
{
|
|
autoMatchRequested = false;
|
|
runAnalysis = true;
|
|
_analysisPhase = AnalysisPhase.Running;
|
|
_analysisStartFrame = Time.frameCount;
|
|
EnsureAnalysisRTs();
|
|
}
|
|
|
|
var colorHandle = resourceData.activeColorTexture;
|
|
var depthHandle = resourceData.activeDepthTexture;
|
|
var camDesc = cameraData.cameraTargetDescriptor;
|
|
|
|
// 임시 컬러 텍스처
|
|
var tempHandle = renderGraph.CreateTexture(new TextureDesc(camDesc.width, camDesc.height)
|
|
{
|
|
format = camDesc.graphicsFormat,
|
|
filterMode = FilterMode.Bilinear,
|
|
name = "_CharToneAdjustTemp",
|
|
});
|
|
|
|
// 스텐실 재마킹용 RendererList (항상 Pass 5: stencil only)
|
|
var sortSettings = new SortingSettings(cameraData.camera) { criteria = SortingCriteria.CommonOpaque };
|
|
var drawSettings = new DrawingSettings(_stencilFillTagId, sortSettings)
|
|
{
|
|
overrideMaterial = _material,
|
|
overrideMaterialPassIndex = k_PassMeshStencilFill,
|
|
};
|
|
var filterSettings = new FilteringSettings(RenderQueueRange.opaque);
|
|
var rlParams = new RendererListParams(renderingData.cullResults, drawSettings, filterSettings);
|
|
var stencilList = renderGraph.CreateRendererList(rlParams);
|
|
|
|
// 분석용 텍스처 (Match 요청 시에만 생성)
|
|
TextureHandle maskHandle = TextureHandle.nullHandle;
|
|
TextureHandle down64 = TextureHandle.nullHandle;
|
|
TextureHandle down16 = TextureHandle.nullHandle;
|
|
TextureHandle down4 = TextureHandle.nullHandle;
|
|
|
|
if (runAnalysis)
|
|
{
|
|
maskHandle = renderGraph.CreateTexture(new TextureDesc(camDesc.width, camDesc.height)
|
|
{
|
|
format = GraphicsFormat.R8_UNorm,
|
|
filterMode = FilterMode.Bilinear,
|
|
name = "_CharMask",
|
|
});
|
|
var fmt = GraphicsFormat.R16G16B16A16_SFloat;
|
|
down64 = renderGraph.CreateTexture(new TextureDesc(64, 64) { format = fmt, filterMode = FilterMode.Bilinear, name = "_Down64" });
|
|
down16 = renderGraph.CreateTexture(new TextureDesc(16, 16) { format = fmt, filterMode = FilterMode.Bilinear, name = "_Down16" });
|
|
down4 = renderGraph.CreateTexture(new TextureDesc(4, 4) { format = fmt, filterMode = FilterMode.Bilinear, name = "_Down4" });
|
|
}
|
|
|
|
// ── UnsafePass 등록 ─────────────────────────────────
|
|
using (var builder = renderGraph.AddUnsafePass<PassData>(
|
|
"NiloToonCharToneAdjust", out var pd))
|
|
{
|
|
pd.colorHandle = colorHandle;
|
|
pd.depthHandle = depthHandle;
|
|
pd.tempHandle = tempHandle;
|
|
pd.stencilList = stencilList;
|
|
pd.material = _material;
|
|
pd.volume = volume;
|
|
pd.debugMode = debugMode;
|
|
pd.runAnalysis = runAnalysis;
|
|
pd.maskHandle = maskHandle;
|
|
pd.down64 = down64;
|
|
pd.down16 = down16;
|
|
pd.down4 = down4;
|
|
pd.charResult1x1 = _charResult1x1;
|
|
pd.bgResult1x1 = _bgResult1x1;
|
|
pd.cameraWidth = camDesc.width;
|
|
pd.cameraHeight = camDesc.height;
|
|
|
|
builder.UseTexture(colorHandle, AccessFlags.ReadWrite);
|
|
builder.UseTexture(depthHandle, AccessFlags.ReadWrite);
|
|
builder.UseTexture(tempHandle, AccessFlags.ReadWrite);
|
|
builder.UseRendererList(stencilList);
|
|
if (runAnalysis)
|
|
{
|
|
builder.UseTexture(maskHandle, AccessFlags.ReadWrite);
|
|
builder.UseTexture(down64, AccessFlags.ReadWrite);
|
|
builder.UseTexture(down16, AccessFlags.ReadWrite);
|
|
builder.UseTexture(down4, AccessFlags.ReadWrite);
|
|
}
|
|
builder.AllowPassCulling(false);
|
|
|
|
builder.SetRenderFunc(static (PassData data, UnsafeGraphContext ctx) =>
|
|
{
|
|
var cmd = ctx.cmd;
|
|
SetMaterialProperties(data.volume, data.material);
|
|
|
|
if (data.debugMode == DebugMode.FullScreen)
|
|
ExecuteFullScreen(data, cmd);
|
|
else
|
|
ExecuteStencilMode(data, cmd);
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── FullScreen 모드 렌더링 ──────────────────────────────
|
|
static void ExecuteFullScreen(PassData data, UnsafeCommandBuffer cmd)
|
|
{
|
|
cmd.SetRenderTarget(data.tempHandle,
|
|
RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.colorHandle);
|
|
cmd.DrawProcedural(Matrix4x4.identity, data.material,
|
|
k_PassCopy, MeshTopology.Triangles, 3);
|
|
|
|
cmd.SetRenderTarget(data.colorHandle,
|
|
RenderBufferLoadAction.Load, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.tempHandle);
|
|
cmd.DrawProcedural(Matrix4x4.identity, data.material,
|
|
k_PassToneAdjustFull, MeshTopology.Triangles, 3);
|
|
}
|
|
|
|
// ── Stencil(Normal/StencilView) 모드 렌더링 ─────────────
|
|
static void ExecuteStencilMode(PassData data, UnsafeCommandBuffer cmd)
|
|
{
|
|
// ── Step 1: 스텐실 재마킹 (Pass 5, ColorMask 0) ────
|
|
cmd.SetRenderTarget(
|
|
data.colorHandle,
|
|
RenderBufferLoadAction.Load, RenderBufferStoreAction.Store,
|
|
data.depthHandle,
|
|
RenderBufferLoadAction.Load, RenderBufferStoreAction.Store);
|
|
cmd.DrawRendererList(data.stencilList);
|
|
|
|
// ── Step 1b+2: 원샷 분석 (Match 버튼 요청 시) ──────
|
|
if (data.runAnalysis)
|
|
{
|
|
// 스텐실 → 마스크 변환 (Pass 9)
|
|
cmd.SetRenderTarget(
|
|
data.maskHandle,
|
|
RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store,
|
|
data.depthHandle,
|
|
RenderBufferLoadAction.Load, RenderBufferStoreAction.Store);
|
|
cmd.ClearRenderTarget(false, true, Color.clear);
|
|
cmd.DrawProcedural(Matrix4x4.identity, data.material,
|
|
k_PassStencilToMask, MeshTopology.Triangles, 3);
|
|
|
|
// 캐릭터 분석: full-res → 64 → 16 → 4 → 1
|
|
RunAnalysisChain(data, cmd, invertMask: 0f, resultRT: data.charResult1x1);
|
|
// 배경 분석: full-res → 64 → 16 → 4 → 1
|
|
RunAnalysisChain(data, cmd, invertMask: 1f, resultRT: data.bgResult1x1);
|
|
}
|
|
|
|
// ── Step 3: color → temp 복사 ───────────────────────
|
|
cmd.SetRenderTarget(data.tempHandle,
|
|
RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.colorHandle);
|
|
cmd.DrawProcedural(Matrix4x4.identity, data.material,
|
|
k_PassCopy, MeshTopology.Triangles, 3);
|
|
|
|
// ── Step 4: temp → color 색조 보정 (스텐실) ─────────
|
|
int passIdx = data.debugMode == DebugMode.StencilView
|
|
? k_PassStencilView : k_PassToneAdjust;
|
|
cmd.SetRenderTarget(
|
|
data.colorHandle,
|
|
RenderBufferLoadAction.Load, RenderBufferStoreAction.Store,
|
|
data.depthHandle,
|
|
RenderBufferLoadAction.Load, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.tempHandle);
|
|
cmd.DrawProcedural(Matrix4x4.identity, data.material,
|
|
passIdx, MeshTopology.Triangles, 3);
|
|
}
|
|
|
|
// ── 분석 다운샘플 체인 ──────────────────────────────────
|
|
static void RunAnalysisChain(PassData data, UnsafeCommandBuffer cmd,
|
|
float invertMask, RenderTexture resultRT)
|
|
{
|
|
var mat = data.material;
|
|
|
|
// 첫 단계: MaskedDownsample (full-res → 64x64)
|
|
cmd.SetGlobalVector(_MainTex_TexelSizeId,
|
|
new Vector4(1f / data.cameraWidth, 1f / data.cameraHeight,
|
|
data.cameraWidth, data.cameraHeight));
|
|
cmd.SetRenderTarget(data.down64,
|
|
RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.colorHandle);
|
|
cmd.SetGlobalTexture(_MaskTexId, data.maskHandle);
|
|
cmd.SetGlobalFloat(_InvertMaskId, invertMask);
|
|
cmd.DrawProcedural(Matrix4x4.identity, mat,
|
|
k_PassMaskedDownsample, MeshTopology.Triangles, 3);
|
|
|
|
// 64 → 16
|
|
cmd.SetGlobalVector(_MainTex_TexelSizeId, new Vector4(1f/64, 1f/64, 64, 64));
|
|
cmd.SetRenderTarget(data.down16,
|
|
RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.down64);
|
|
cmd.DrawProcedural(Matrix4x4.identity, mat,
|
|
k_PassWeightedDownsample, MeshTopology.Triangles, 3);
|
|
|
|
// 16 → 4
|
|
cmd.SetGlobalVector(_MainTex_TexelSizeId, new Vector4(1f/16, 1f/16, 16, 16));
|
|
cmd.SetRenderTarget(data.down4,
|
|
RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.down16);
|
|
cmd.DrawProcedural(Matrix4x4.identity, mat,
|
|
k_PassWeightedDownsample, MeshTopology.Triangles, 3);
|
|
|
|
// 4 → 1 (persistent RT)
|
|
cmd.SetGlobalVector(_MainTex_TexelSizeId, new Vector4(1f/4, 1f/4, 4, 4));
|
|
cmd.SetRenderTarget(resultRT,
|
|
RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
|
cmd.SetGlobalTexture(_MainTexId, data.down4);
|
|
cmd.DrawProcedural(Matrix4x4.identity, mat,
|
|
k_PassWeightedDownsample, MeshTopology.Triangles, 3);
|
|
}
|
|
|
|
// ── 셰이더 프로퍼티 설정 ───────────────────────────────
|
|
static void SetMaterialProperties(NiloToonCharToneAdjustVolume v, Material mat)
|
|
{
|
|
Vector4 lv = v.lift.value;
|
|
mat.SetVector(_LiftId, new Vector4(lv.x-1f+lv.w, lv.y-1f+lv.w, lv.z-1f+lv.w, 0f));
|
|
Vector4 gv = v.gamma.value;
|
|
mat.SetVector(_GammaId, new Vector4(gv.x+gv.w, gv.y+gv.w, gv.z+gv.w, 0f));
|
|
Vector4 gn = v.gain.value;
|
|
mat.SetVector(_GainId, new Vector4(gn.x+gn.w, gn.y+gn.w, gn.z+gn.w, 0f));
|
|
|
|
Vector4 sv = v.shadows.value;
|
|
mat.SetVector(_ShadowsId, new Vector4(sv.x-1f+sv.w, sv.y-1f+sv.w, sv.z-1f+sv.w, 0f));
|
|
Vector4 mv = v.midtones.value;
|
|
mat.SetVector(_MidtonesId, new Vector4(mv.x-1f+mv.w, mv.y-1f+mv.w, mv.z-1f+mv.w, 0f));
|
|
Vector4 hv = v.highlights.value;
|
|
mat.SetVector(_HighlightsId, new Vector4(hv.x-1f+hv.w, hv.y-1f+hv.w, hv.z-1f+hv.w, 0f));
|
|
|
|
mat.SetVector(_SMHRangeId, new Vector4(
|
|
v.shadowsStart.value, v.shadowsEnd.value,
|
|
v.highlightsStart.value, v.highlightsEnd.value));
|
|
|
|
mat.SetFloat(_SaturationId, v.saturation.value);
|
|
mat.SetFloat(_PostExposureId, Mathf.Pow(2f, v.postExposure.value));
|
|
mat.SetFloat(_BlendAmountId, v.blendAmount.value);
|
|
}
|
|
}
|
|
}
|
|
}
|