콘텐츠로 이동

Unity 커스텀 렌더 파이프라인 구현

Unity의 SRP(Scriptable Render Pipeline)는 C#으로 렌더링 파이프라인 전체를 제어할 수 있는 API입니다. URP/HDRP는 SRP 위에 구축된 구현체이며, 완전한 커스텀 파이프라인을 만들거나 URP를 ScriptableRendererFeature로 확장할 수 있습니다.


// 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();
}
}

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);
}
}

// 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)
{
// 클린업
}
}

// 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는 RenderPipelineAssetRenderPipeline.Render 순서로 구현하고, CommandBuffer로 GPU 명령을 배치해 드로우콜을 최소화하세요. URP 확장은 ScriptableRendererFeature + ScriptableRenderPass로 기존 파이프라인에 패스를 추가하는 방식이 가장 실용적입니다. 10만 개 이상의 오브젝트는 DrawMeshInstancedIndirect와 ComputeShader로 GPU 드리븐 렌더링을 구현하세요.