콘텐츠로 이동

Unity Render Texture & Custom Post-Processing

Render Texture는 카메라 출력 결과를 일반 텍스처처럼 다룰 수 있게 해 주는 Unity의 핵심 렌더링 기능입니다. 미니맵, 감시 카메라, 포털, 리플렉션, 커스텀 후처리 파이프라인 등에 광범위하게 쓰입니다. URP(Universal Render Pipeline)에서는 ScriptableRendererFeature API를 통해 렌더 패스를 직접 삽입해 완전한 커스텀 후처리를 구현할 수 있습니다.


using UnityEngine;
public class MiniMapSetup : MonoBehaviour
{
[SerializeField] private Camera minimapCamera;
[SerializeField] private RawImage minimapDisplay; // UI에 표시
private RenderTexture _rt;
void Awake()
{
// 해상도, 비트 심도, 스텐실 포맷 지정
_rt = new RenderTexture(512, 512, 24, RenderTextureFormat.ARGB32);
_rt.antiAliasing = 1;
_rt.filterMode = FilterMode.Bilinear;
_rt.Create();
minimapCamera.targetTexture = _rt;
minimapDisplay.texture = _rt;
}
void OnDestroy()
{
if (_rt != null)
{
minimapCamera.targetTexture = null;
_rt.Release();
Destroy(_rt);
}
}
}

RenderTexture.Create()를 명시적으로 호출해야 GPU 메모리가 실제로 할당됩니다. Release()Destroy()를 함께 호출해야 메모리 누수를 막을 수 있습니다.

1.2 임시 Render Texture (RenderTexture.GetTemporary)

섹션 제목: “1.2 임시 Render Texture (RenderTexture.GetTemporary)”

후처리 패스에서 중간 버퍼가 필요할 때는 GetTemporary를 사용합니다. Unity 내부 풀에서 재사용하므로 할당 비용이 없습니다.

void OnRenderImage(RenderTexture src, RenderTexture dest)
{
RenderTexture temp = RenderTexture.GetTemporary(
src.width, src.height, 0, src.format);
Graphics.Blit(src, temp, blurMaterial, 0); // 가로 블러
Graphics.Blit(temp, dest, blurMaterial, 1); // 세로 블러
RenderTexture.ReleaseTemporary(temp);
}

URP에서는 OnRenderImage가 동작하지 않습니다. 대신 ScriptableRendererFeature + ScriptableRenderPass 조합을 사용합니다.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class GrayscaleRenderPass : ScriptableRenderPass
{
private readonly Material _material;
private RTHandle _tempHandle;
public GrayscaleRenderPass(Material mat)
{
_material = mat;
renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
var descriptor = renderingData.cameraData.cameraTargetDescriptor;
descriptor.depthBufferBits = 0;
RenderingUtils.ReAllocateIfNeeded(ref _tempHandle, descriptor, name: "_TempGrayscale");
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (_material == null) return;
CommandBuffer cmd = CommandBufferPool.Get("GrayscalePass");
RTHandle cameraTarget = renderingData.cameraData.renderer.cameraColorTargetHandle;
Blitter.BlitCameraTexture(cmd, cameraTarget, _tempHandle, _material, 0);
Blitter.BlitCameraTexture(cmd, _tempHandle, cameraTarget);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public override void OnCameraCleanup(CommandBuffer cmd)
{
// RTHandle은 OnCameraSetup에서 ReAllocateIfNeeded로 관리하므로
// 여기서는 별도 해제 불필요
}
}
using UnityEngine;
using UnityEngine.Rendering.Universal;
[CreateAssetMenu(menuName = "Rendering/Grayscale Feature")]
public class GrayscaleFeature : ScriptableRendererFeature
{
[SerializeField] private Material grayscaleMaterial;
private GrayscaleRenderPass _pass;
public override void Create()
{
_pass = new GrayscaleRenderPass(grayscaleMaterial);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (grayscaleMaterial == null) return;
renderer.EnqueuePass(_pass);
}
protected override void Dispose(bool disposing)
{
// 패스 내부 RTHandle 해제
_pass = null;
}
}

URP Renderer Asset의 Renderer Features 섹션에 이 컴포넌트를 추가하면 모든 카메라에 자동으로 적용됩니다.


항목권장
해상도목적에 맞는 최소 해상도 사용 (미니맵 256~512)
포맷HDR 불필요 시 RenderTextureFormat.Default
안티앨리어싱Render Texture에서는 보통 1 (MSAA 비활성)
Mipmap정적 용도라면 rt.useMipMap = false
임시 버퍼중간 패스는 반드시 GetTemporary / ReAllocateIfNeeded 활용
// 나쁜 예: 매 프레임 CPU-GPU 동기화 발생
void Update()
{
Texture2D tex = new Texture2D(rt.width, rt.height);
RenderTexture.active = rt;
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
tex.Apply();
}
// 좋은 예: 필요할 때만, AsyncGPUReadback 사용
void RequestCapture()
{
AsyncGPUReadback.Request(_rt, 0, TextureFormat.RGBA32, OnReadbackComplete);
}
void OnReadbackComplete(AsyncGPUReadbackRequest request)
{
if (request.hasError) return;
var data = request.GetData<Color32>();
// 데이터 처리
}

포털 효과는 두 카메라의 Render Texture를 상대방 포털 메시의 머티리얼에 할당하는 방식으로 구현합니다.

public class PortalRenderer : MonoBehaviour
{
[SerializeField] private Camera portalCamera;
[SerializeField] private Renderer portalMesh;
private RenderTexture _rt;
private static readonly int MainTex = Shader.PropertyToID("_MainTex");
void Start()
{
_rt = new RenderTexture(Screen.width / 2, Screen.height / 2, 24);
portalCamera.targetTexture = _rt;
portalMesh.material.SetTexture(MainTex, _rt);
}
void LateUpdate()
{
// 플레이어 카메라의 상대 위치를 포털 카메라에 반영
Transform playerCam = Camera.main.transform;
Transform linkedPortal = /* 반대쪽 포털 */ transform;
portalCamera.transform.SetPositionAndRotation(
linkedPortal.TransformPoint(
transform.InverseTransformPoint(playerCam.position)),
linkedPortal.rotation * Quaternion.Inverse(transform.rotation) * playerCam.rotation
);
}
}

  • RenderTexture는 GPU 메모리에 직접 할당되므로 Release() + Destroy() 쌍을 반드시 호출한다.
  • URP에서는 ScriptableRendererFeature + ScriptableRenderPass로 커스텀 렌더 패스를 주입한다.
  • 중간 버퍼는 RenderTexture.GetTemporary 또는 RenderingUtils.ReAllocateIfNeeded를 사용해 풀링한다.
  • CPU 픽셀 읽기가 필요하면 AsyncGPUReadback으로 비동기 처리해 프레임 히치를 방지한다.