

개요
많은 게임의 연출에서 이펙트 뿐만아니라 흑백 반전, 집중선 등 화면에 다양한 이펙트 표현을 볼 수 있습니다.
이러한 효과를 구현하는 방법은 여러가지가 있지만 Unity URP 환경에서 일반적으로 FullscreenRendererFeature & VolumeComponent 방식으로 제작한다면 ScriptableRenderPass, Shader를 직접 구현해야 하기 때문에 개발에 필요한 지식이 높고 관리도 어렵습니다.
이번 포스팅은 화면 효과 구현에 대한 다양한 방법과 제가 생각하는 최적의 구현 방법을 소개합니다.
화면 버퍼 복제 효과 - FrameBufferFetch 활용


흑백 반전 효과는 URP OpaqueTexture 혹은 직접 화면 버퍼를 복제해서 FullScreenShader에서 화면 버퍼를 샘플링하는 방식으로 구현됩니다. 많은 이펙트 연출 효과에서 사용되고 구현 원리가 단순하지만 화면 버퍼를 추가로 생성하고 샘플링해야하기 때문에 메모리 비용과 대역폭 부담이 있는 효과입니다.
Unity6에서 FrameBufferFetch 기능을 활용한다면 샘플링에 의한 대역폭 사용 없이 굉장히 가볍게 구현 가능하게 됩니다.
추가 메모리 비용은 플랫폼에 따라 다르지만 FrameBufferFetch를 제대로 지원한다면 복제하는 화면 버퍼는 Memoryless로 생성되어 메모리 부담도 없게 됩니다.
FrameBufferFetch에 대한 자세한 내용은 아래 포스팅을 참고해 주세요.
Unity6 RenderGraph 호환 RenderPass 간단 예제 & 주요 포인트 설명
개요Unity6 RenderGraph 호환하기 위한 ScriptableRenderPass 개발 간단 예제입니다.RenderGraph 미지원 & RenderGraph Unsafe & RasterPass + Framebufferfetch 기반으로 간단한 흑백 화면 효과 구현합니다.Unity 6000.1.12f1 버전
darkcatgame.tistory.com



1 Pass Blending 활용 효과


위 예시와 같이 화면 효과 중에 단순히 화면 위에 블랜딩하거나 덮는 효과는 화면 버퍼 복제 없이, 블랜드 옵션이 활성화된 FullScreen Shader를 활용할 수 있습니다. 반투명 오브젝트를 화면에 렌더링하면 되기 때문입니다.
FullScreenShader에서는 구현하고자하는 효과에 알맞는 Blend 옵션을 설정합니다.
그리고 ScriptableRenderPass에서 SetRenderTarget 함수에 colorLoadAction 옵션을 Load로 세팅하면, 일반적인 알파블랜드 렌더링을 실행하게 됩니다.


SetRenderTarget 함수는 위와 같이 Execute 함수 혹은 RenderGraph의 UnsafePass 구현할때는 선언 필요하지만, 아래와 같이 RasterPass로 구현한다면 따로 세팅하지 않아도 RenderGraph에서 자동으로 세팅됩니다.

위와 같이 구현 결과, 화면 버퍼 복제 과정 없이 FullScreenShader를 화면에 직접 렌더링하게 되어 1 Pass로 처리 및 추가 렌더텍스처 생성하지 않아 메모리까지 최적화됩니다.

추가로 SetRenderTarget LoadAction에 대한 내용은 아래 포스팅 참고해 주세요.
https://darkcatgame.tistory.com/190
Unity RenderBufferLoadAction & StoreAction 옵션 설명
개요Unity 엔진에서 ScriptableRenderPass 등, 커맨드 버퍼에 RenderTarget 설정 할때 RenderBufferLoadAction & StoreAction 옵션에 대한 설명입니다. 해당 옵션은 SetRenderTarget으로 설정한 렌더버퍼에 대한 데이터를
darkcatgame.tistory.com
Mesh를 활용한 픽셀 비용 최적화
일반적으로 FullScreenShader 구현할때 PolarCoordinate 등, UV를 Fragment에서 재구성하는 작업을 흔하게 하게 됩니다.
하지만 이런한 UV 재구성 작업은 높은 연산 부하를 발생하는 이슈가 있습니다.
이런 문제를 해결하고자, FullScreenShader 렌더링에 직접 UV를 세팅한 화면 Mesh를 활용하는 방법을 소개합니다.



Blender 및 3dsMax와 같은 DCC 툴에서 원하는 효과에 알맞는 메쉬를 모델링합니다.


메쉬 사이즈는 유니티 기준 2m X 2m 사이즈에 맞추는 것을 권장합니다.
(Unity Cube는 1m 사이즈)

Axis 축은 유니티 Quad 기준으로 FrontFace가 -Z 방향을 향하도록 합니다.


ScriptableRenderPass에서는 이전까지 사용하던 DrawProcedural이 아닌, DrawMesh 함수를 활용해서 원하는 메쉬를 렌더링 할 수 있도록 합니다.

FullScreenShader 설명입니다. Vertex Stage에서는 positionOS 오브젝트 공간 버텍스 좌표를 그대로 positionCS 클립 스페이스로 보냅니다. 이때 Y 값에 _ProjectionParams.x를 곱하는데 이는 OpenGL & DX와 같이 좌표계가 다른 API를 대응하기 위함입니다. UNITY_UV_STARTS_AT_TOP 디파인 조건문을 통해 UV Y를 뒤집는 방법도 있지만, 평면이 아닌 입체감 있는 메쉬까지 대응하기 위해서는 _ProjectionParams를 활용하는 것을 권장합니다.
Fragment에서는 이미 PolarCoordinate와 같이 UV가 구성된 Mesh UV 정보를 사용하기 때문에 별도의 UV 재구성이 필요 없습니다.

결과 화면입니다.


리소스 최적화 세팅
FullScreenShader에 사용하는 메쉬 및 텍스처에 대한 최적화 세팅 내용입니다. 아래 소개하는 세팅은 제작하는 화면 효과에 따라 권장 세팅이 달라질 수 있습니다.
메쉬는 일반적으로 Normal & Tangent 정보가 필요 없기 때문에 제거합니다.



텍스처는 Mipmap을 비활성화하고, 단일 채널 무압축 포맷으로 세팅해서 용량 대비 품질을 높이도록 합니다.
FullScreenShader에 사용되는 텍스처는 화면에 크게 렌더링되기 때문에 타일링 텍스처를 사용해도 어느 정도 이상 품질이 필요하지만, 이와 같이 단일 채널로 사용한다면 용량 & 품질 둘다 이점 가질 수 있습니다.

결과 화면입니다.


Mesh를 활용한 픽셀 비용 최적화 - ShockWave 이펙트 구현
다음, 실제 게임 이펙트 연출에 사용될 수 있는 ShockWave 화면 이펙트 효과를 구현해봅니다.
화면 메쉬 제작
목표한 효과는 이전에 제작한 Polarcoordinate 형태의 메쉬를 사용해도 되지만, 더 나은 최적화를 위해 가운데를 뚫은 형태의 화면 메쉬를 제작합니다.


추가로 VertexColor 알파 정보를 추가해서 별도 알파 텍스처 없이 화면 가운데에서 투명해지면서 밖으로 퍼질 수 있도록 합니다.


Dissovle Shader 제작
레퍼런스 효과 구현에 알맞는 FullScreenShader 제작합니다.
간단한 Dissolve 효과를 구현했으며 FragmentShader 최적화를 위해 UV Scroll 계산은 Vertex Stage에서 처리합니다.
half Level(half value, half add, half mul)
{
return saturate(add + (value * mul));
}
half Linearstep(half a, half b, half x)
{
return saturate((x - a) / (b - a));
}
half Dissolve(half alpha, half alphaProgress, half sharpness)
{
half dissolveWidth = sharpness;
half fadeProgress = lerp(-dissolveWidth, 1.0 + dissolveWidth, alphaProgress);
return Linearstep(fadeProgress - dissolveWidth, fadeProgress + dissolveWidth, alpha);
}
Varyings Vert(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
float4 pos = float4(input.positionOS.x, input.positionOS.y * _ProjectionParams.x, 0.0, 1.0);
float2 uv = input.texcoord0;
float time = fmod(_Time.y, 100.0);
output.positionCS = pos;
output.color = input.color;
output.texcoord.xy = uv * _DissolveMap1_ST.xy + (_DissolveMap1_ST.zw * time);
output.texcoord.zw = uv * _DissolveMap2_ST.xy + (_DissolveMap2_ST.zw * time);
return output;
}
half4 Frag(Varyings input) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
float2 uv_0 = input.texcoord.xy;
float2 uv_1 = input.texcoord.zw;
half4 vertexColor = input.color;
half dissolve_2 = SAMPLE_TEXTURE2D_X_LOD(_DissolveMap2, sampler_DissolveMap2, uv_1, 0).r;
dissolve_2 = Level(dissolve_2, _DissolveMap2_LevelAdd, _DissolveMap2_LevelMul);
half dissolve_1 = SAMPLE_TEXTURE2D_X_LOD(_DissolveMap1, sampler_DissolveMap1, uv_0, 0).r;
dissolve_1 = Level(dissolve_1, _DissolveMap1_LevelAdd, _DissolveMap1_LevelMul);
dissolve_1 *= dissolve_2 * vertexColor.a;
half dissolveMask = Dissolve(dissolve_1, _AlphaProgress, _Sharpness);
return dissolveMask * _Color;
}
결과 화면입니다. 사용하는 텍스처, 파라미터 세팅에 따라서 다양한 화면 연출 효과 구현 가능합니다.


VAT 활용한 화면 깨짐 연출 효과 구현
다음은 VAT(Vertex Animation Texture) 기법을 활용한 화면 깨짐 연출 효과를 구현합니다.
VAT 리소스 제작
VAT(Vertex Animation Texture)는 메쉬 애니메이션의 버텍스 정보를 텍스처에 기록해서 Vertex Stage에서 샘플링하는 기법입니다. VAT 리소스 제작은 Blender, 3dsMax, 후디니 등 다양한 방법이 존재하며 저는 Blender를 활용했습니다.
Blender에서 Cell Fracture라는 에드온으로 파괴 시물레이션을 구현했습니다.
참고 : https://www.youtube.com/watch?v=mEMW5elom_4&ab_channel=SILVERPUNK

목표하는 연출에 맞게 파괴 시물레이션 애니메이션 제작 후, VAT 리소스로 추출합니다.

제작하는 효과는 유리 표현을 위해 Fresnel 표현이 필요하기 때문에 Normal정보까지 필요합니다.

VAT Shader 제작
VAT Shader는 이전 Mesh FullScreenShader와 동일하지만, PositionOS에 VAT Position 샘플 데이터를 Offset 처리합니다.
VAT Normal 샘플 데이터는 NormalOS로 그대로 사용합니다.

우선 테스트 삼아, CopyColor 텍스처를 샘플링해서 효과 구현해보면 아래와 같이 화면이 깨지는 효과 구현이 완료됩니다.

유리 깨지는 표현 강화
Shader에서 추가로 Fresnel 효과와 화면 버퍼 샘플링 UV에 Normal 방향으로 Offset 처리해서 유리의 굴절과 반사 효과를 구현해서 유리 재질 표현을 강화합니다.



앞에서 임시로 CopyColor를 샘플링하도록 했지만, Opaque 패스 이후에 복제된 버퍼이기 때문에 Transparnet 패스까지 대응하기 위해 새로 화면 버퍼를 복제합니다.
아래 예시에서는 renderGraph.AddCopyPass 함수를 활용했으며, 해당 함수를 사용하면 FrameBufferFetch로 화면 버퍼를 복제할 수 있습니다.

애니메이션 제작 & 결과 화면
VAT Shader의 파라미터를 제어해서 Animation을 하나 제작합니다.


화면 연출 효과를 위한 Renderer 컴포넌트 제작
앞에서 소개한 화면 연출 효과는 항상 렌더링되는 것이 아닌, 일반 Particle 이펙트와 같이 일시적으로 생성되고 제거되는 용도입니다. 쉽게 말해서 Bloom, SSAO와 같은 Postprocess와 용도 자체가 다릅니다.
하지만 일반적으로 ScriptableRendererFeature에서 렌더패스를 호출하는 방식으로 제작한다면 항상 Universal Renderer에 객체가 유지되고 Volume Componenet 등을 통해서 활성화 여부와 파라미터를 제어할 수 밖에 없는데 이러면 따로 제어하는 시스템을 구현 할 수밖에 없는 비효율이 발생합니다.

이번에 소개할 내용은 ScriptableRendererFeature를 사용하지 않고 ScriptableRenderPass를 직접 호출하는 Monobehaviour 객체를 구현해서 오브젝트로 효과를 관리할 수 있는 방법을 소개합니다.

RenderPipelineManager.beginCameraRendering
RenderPipelineManager.beginCameraRendering 이벤트 함수 등을 활용해서 URP 내부 렌더링 로직 호출 시점에 원하는 로직이 호출 가능합니다. 이를 활용해 scriptableRenderer에 원하는 ScriptableRenderPass 객체를 렌더링 Queue에 등록하는 방식으로 ScriptableRendererFeature 없이 렌더 패스 호출하도록 구현합니다.


아래 코드는 실제 유리 파괴 효과를 렌더링하도록 구현한 예시입니다.
[ExecuteInEditMode]
public class GlassBreakFX : MonoBehaviour
{
[SerializeField] private RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
[SerializeField] private Camera targetCamera;
[SerializeField] private Material material;
[SerializeField] private Mesh mesh;
[SerializeField] private GlassBreakFXRenderPass.MaterialProperties materialProperties = GlassBreakFXRenderPass.MaterialProperties.Default();
private GlassBreakFXRenderPass _renderPass;
private void OnEnable()
{
if(!targetCamera) targetCamera = Camera.main;
_renderPass = new GlassBreakFXRenderPass();
RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
}
private void OnDisable()
{
_renderPass?.Dispose();
_renderPass = null;
RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
}
private void OnBeginCameraRendering(ScriptableRenderContext context, Camera cam)
{
if (material == null || mesh==null ||
targetCamera == null || targetCamera != cam ||
!targetCamera.isActiveAndEnabled) return;
var cameraData = cam.GetUniversalAdditionalCameraData();
if (cameraData.renderType != CameraRenderType.Base) return;
_renderPass.renderPassEvent = renderPassEvent;
_renderPass.material = material;
_renderPass.mesh = mesh;
_renderPass.materialProperties = materialProperties;
cameraData.scriptableRenderer.EnqueuePass(_renderPass);
}
}
리소스 관리
Monobehaviour 클래스에서 FullScreenShader의 파라미터를 노출 시키면 이펙트 작업자는 Particle System 등으로 이펙트 제작과 함께 화면 연출 효과 객체를 다룰 수 있으며, Particle System과 함께 Prefab으로 묶어서 관리 가능합니다.



'Unity' 카테고리의 다른 글
| Unity6 RenderGraph 호환 RenderPass 간단 예제 & 주요 포인트 설명 (1) | 2025.07.16 |
|---|---|
| Unity PerObjectRenderTexture UI 시스템 개발 과정 (1) | 2025.07.15 |
| Unity6 Dynamic Resolution 소개 및 대응 방법 (1) | 2025.07.10 |
| Unity RenderBufferLoadAction & StoreAction 옵션 설명 (0) | 2025.06.11 |
| Unity Cubemap Shadow (0) | 2025.04.28 |
WRITTEN BY
- CatDarkGame
Technical Artist dhwlgn12@gmail.com


