Unity 커스텀 렌더 파이프라인 구현
Unity의 SRP(Scriptable Render Pipeline)는 C#으로 렌더링 파이프라인 전체를 제어할 수 있는 API입니다. URP/HDRP는 SRP 위에 구축된 구현체이며, 완전한 커스텀 파이프라인을 만들거나 URP를 ScriptableRendererFeature로 확장할 수 있습니다.
1. 최소 커스텀 SRP
섹션 제목: “1. 최소 커스텀 SRP”// 1. RenderPipelineAsset — 파이프라인 팩토리[CreateAssetMenu(menuName = "Rendering/My Pipeline Asset")]public class MyPipelineAsset : RenderPipelineAsset{ protected override RenderPipeline CreatePipeline() => new MyRenderPipeline();}
// 2. RenderPipeline — 프레임당 렌더링 로직public class MyRenderPipeline : RenderPipeline{ protected override void Render( ScriptableRenderContext context, Camera[] cameras) { foreach (var camera in cameras) RenderCamera(context, camera); }
void RenderCamera(ScriptableRenderContext context, Camera camera) { // 카메라 행렬 설정 context.SetupCameraProperties(camera);
// 컬링 if (!camera.TryGetCullingParameters(out var cullingParams)) return;
CullingResults culling = context.Cull(ref cullingParams);
// CommandBuffer로 렌더 타깃 클리어 var cmd = CommandBufferPool.Get("Clear"); cmd.ClearRenderTarget(true, true, Color.black); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd);
// 불투명 오브젝트 렌더 var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque };
var drawSettings = new DrawingSettings( new ShaderTagId("SRPDefaultUnlit"), sortingSettings);
var filterSettings = new FilteringSettings( RenderQueueRange.opaque);
context.DrawRenderers(culling, ref drawSettings, ref filterSettings);
// 스카이박스 context.DrawSkybox(camera);
// 반투명 오브젝트 (뒤에서 앞으로) sortingSettings.criteria = SortingCriteria.CommonTransparent; drawSettings.sortingSettings = sortingSettings; filterSettings = new FilteringSettings(RenderQueueRange.transparent);
context.DrawRenderers(culling, ref drawSettings, ref filterSettings);
context.Submit(); }}2. CommandBuffer — GPU 명령 배치
섹션 제목: “2. CommandBuffer — GPU 명령 배치”public class PostProcessPass{ private CommandBuffer _cmd; private Material _blitMat; private RenderTargetIdentifier _colorRT;
public void Setup(RenderTargetIdentifier colorRT, Material mat) { _colorRT = colorRT; _blitMat = mat; _cmd = CommandBufferPool.Get("PostProcess"); }
public void Execute(ScriptableRenderContext context) { int tempId = Shader.PropertyToID("_TempRT");
// 임시 RT 할당 _cmd.GetTemporaryRT(tempId, Screen.width, Screen.height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR);
// Blit: 소스 → 이펙트 적용 → 목적지 _cmd.Blit(_colorRT, tempId, _blitMat, 0); _cmd.Blit(tempId, _colorRT);
// 임시 RT 해제 _cmd.ReleaseTemporaryRT(tempId);
context.ExecuteCommandBuffer(_cmd); _cmd.Clear(); }
public void Cleanup() { CommandBufferPool.Release(_cmd); }}3. URP ScriptableRendererFeature 확장
섹션 제목: “3. URP ScriptableRendererFeature 확장”// URP 파이프라인에 커스텀 패스 추가public class OutlineFeature : ScriptableRendererFeature{ [System.Serializable] public class Settings { public Material OutlineMaterial; public RenderPassEvent RenderEvent = RenderPassEvent.AfterRenderingOpaques; }
[SerializeField] private Settings _settings = new(); private OutlinePass _pass;
public override void Create() { _pass = new OutlinePass(_settings.OutlineMaterial, _settings.RenderEvent); }
public override void AddRenderPasses( ScriptableRenderer renderer, ref RenderingData renderingData) { renderer.EnqueuePass(_pass); }}
public class OutlinePass : ScriptableRenderPass{ private readonly Material _mat; private RTHandle _tempRT;
public OutlinePass(Material mat, RenderPassEvent evt) { _mat = mat; renderPassEvent = evt; }
public override void OnCameraSetup( CommandBuffer cmd, ref RenderingData renderingData) { var desc = renderingData.cameraData.cameraTargetDescriptor; RenderingUtils.ReAllocateIfNeeded( ref _tempRT, desc, name: "_OutlineRT"); ConfigureTarget(_tempRT); }
public override void Execute( ScriptableRenderContext context, ref RenderingData renderingData) { var cmd = CommandBufferPool.Get("Outline");
// 카메라 컬러 버퍼 → 아웃라인 처리 → 다시 카메라로 var cameraTarget = renderingData.cameraData.renderer .cameraColorTargetHandle;
cmd.Blit(cameraTarget, _tempRT, _mat); cmd.Blit(_tempRT, cameraTarget);
context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); }
public override void OnCameraCleanup(CommandBuffer cmd) { // 클린업 }}4. 커스텀 셰이더 태그
섹션 제목: “4. 커스텀 셰이더 태그”// SRP에서 특정 셰이더만 렌더하기var drawSettings = new DrawingSettings( new ShaderTagId("MyCustomLit"), // 셰이더의 Tags { "LightMode" = "MyCustomLit" } sortingSettings);
// 여러 패스 렌더drawSettings.SetShaderPassName(1, new ShaderTagId("ShadowCaster"));5. GPU Instancing과 DrawMeshInstancedIndirect
섹션 제목: “5. GPU Instancing과 DrawMeshInstancedIndirect”public class InstancedRenderer : MonoBehaviour{ [SerializeField] private Mesh _mesh; [SerializeField] private Material _material;
private ComputeBuffer _argsBuffer; private ComputeBuffer _transformBuffer; private const int Count = 10000;
void Start() { // 인스턴스 데이터 업로드 var transforms = new Matrix4x4[Count]; for (int i = 0; i < Count; i++) transforms[i] = Matrix4x4.TRS( Random.insideUnitSphere * 50f, Random.rotation, Vector3.one);
_transformBuffer = new ComputeBuffer(Count, 64); _transformBuffer.SetData(transforms); _material.SetBuffer("_TransformBuffer", _transformBuffer);
// 인다이렉트 인수 버퍼 var args = new uint[] { _mesh.GetIndexCount(0), (uint)Count, 0, 0, 0 }; _argsBuffer = new ComputeBuffer( 1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments); _argsBuffer.SetData(args); }
void Update() { Graphics.DrawMeshInstancedIndirect( _mesh, 0, _material, new Bounds(Vector3.zero, Vector3.one * 200f), _argsBuffer); }
void OnDestroy() { _transformBuffer?.Release(); _argsBuffer?.Release(); }}커스텀 SRP는 RenderPipelineAsset → RenderPipeline.Render 순서로 구현하고, CommandBuffer로 GPU 명령을 배치해 드로우콜을 최소화하세요. URP 확장은 ScriptableRendererFeature + ScriptableRenderPass로 기존 파이프라인에 패스를 추가하는 방식이 가장 실용적입니다. 10만 개 이상의 오브젝트는 DrawMeshInstancedIndirect와 ComputeShader로 GPU 드리븐 렌더링을 구현하세요.