AnyPortrait > 메뉴얼 > Grab Pass를 이용한 모자이크 효과
AnyPortrait 및 유니티를 이용하여 특수한 효과들을 구현하기 위하여 텍스쳐 타입의 "현재 렌더링되고 있는 화면"이 필요할 때가 있습니다.
유니티에서는 화면을 텍스쳐 형태로 가져오기 위해 "Grab Pass"라는 것을 이용합니다.
이 페이지에서는 많은 사용자들의 문의를 주셨던 "모자이크 효과"를 구현하는 방법을 예시로 들어서 Grab Pass를 설명합니다.
모자이크 등의 효과를 구현하기 위해서는 모자이크 영역에 해당하는 메시가 렌더링되기 직전의 화면 텍스쳐를 가져와야 합니다.
즉, 렌더링이 진행되는 과정을 잠시 멈추고, 그 시점까지의 프레임 버퍼를 임시 텍스쳐로 복사해야합니다.
그것을 수행하는 것이 "Grab Pass"입니다. (유니티 메뉴얼)
그리고 이 Grab Pass에서 생성된 Grab Texture를 이용하면 화면 효과를 쉽게 구현할 수 있습니다.
그런데 Grab Pass를 이용하여 효과를 구현하는 방법이 프로젝트의 렌더 파이프라인에 따라 다릅니다.
Built-In 렌더 파이프라인에서는 메시들이 렌더링 되는 도중에 자유롭게 Grab Pass를 호출하여 Grab Texture를 가져올 수 있습니다.
Grab Texture를 필요로하는 쉐이더 (여기서는 모자이크 효과 쉐이더)에 Grab Pass를 명시하면 그 순간 Grab Texture가 생성되기 때문입니다.
그런데 URP는 기본적으로 Grab Pass를 지원하지 않습니다.
Grab Pass와 유사한 역할을 하는 쉐이더의 기능은 있지만, 이것은 "불투명 메시 (Opaque)"만 렌더링된 결과를 가지고 있으므로 이 경우에는 맞지 않습니다.
그래서 Grab Pass의 역할을 하는 "Renderer Feature"를 직접 작성해야합니다.
다만 Renderer Feature는 메시들의 렌더링 도중에 호출될 수 없으며, 카메라와 렌더링 이벤트 단위로만 호출될 수 있습니다.
따라서 "모자이크 이전까지의 메시들의 렌더링 결과"를 가져오기 위해서는 카메라와 레이어를 분리할 필요가 있습니다.
위와 같이 예제를 구성했습니다.
모자이크 영역이 될 이미지와 메시를 추가하고 "Mosaic Mask"라고 이름을 설정했습니다.
이제 모자이크 효과를 렌더링하는 쉐이더를 작성해봅시다. (커스텀 쉐이더 작성하기)
Shader "Custom Shader/Mosaic Shader - BuiltIn"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
// 모자이크 블록의 개수 (Y축 기준)
_NumMosaicBlocks ("Number of Mosaic Blocks (Y-Axis)", float) = 10.0
}
SubShader
{
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 200
// Grab Pass를 호출하여 Grab Texture를 가져옵니다.
GrabPass { "_CustomGrabTexture" }
Pass
{
Tags { "LightMode" = "ForwardBase" }
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// Grab Texture용 UV를 계산하고 전달하기 위한 시맨틱
float4 grabPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
// Grab Pass에서 생성된 Grab Texture
sampler2D _CustomGrabTexture;
// 모자이크 개수 프로퍼티 변수
float _NumMosaicBlocks;
v2f vert ( appdata v )
{
v2f o;
o.vertex = UnityObjectToClipPos( v.vertex );
o.uv = TRANSFORM_TEX( v.uv, _MainTex );
// Grab UV를 계산합니다.
o.grabPos = ComputeGrabScreenPos( o.vertex );
return o;
}
fixed4 frag ( v2f i ) : SV_Target
{
fixed4 col = tex2D( _MainTex, i.uv );
// Aspect Ratio를 계산하여 X축의 모자이크 블록의 개수를 계산합니다.
float aspect = _ScreenParams.x / _ScreenParams.y;
float2 nBlocks = floor ( float2 ( _NumMosaicBlocks * aspect, _NumMosaicBlocks ) );
// 모자이크 UV를 계산하여 Grab Texture에서 색상 가져옵니다.
fixed2 mosaicUV = round( i.grabPos.xy * nBlocks ) / nBlocks;
fixed4 grabCol = tex2D( _CustomGrabTexture, mosaicUV );
col.rgb = grabCol.rgb;
col.a *= _Color.a;
return col;
}
ENDCG
}
}
}
이 쉐이더는 Grab Texture를 가져와서 메시의 RGB에 적용하여 렌더링을 합니다.
이때, round 함수와 _NumMosaicBlocks 프로퍼티를 이용하여 UV가 특정 크기 단위로 변하도록 만들었습니다.
다시 AnyPortrait 에디터로 돌아옵니다.
(1) 메시 그룹을 선택한 상태에서 "Mosaic Mask" 메시를 선택합니다.
(2) Shader Setting 옵션에서 "Material Set" 값을 "Custom Shader"로 변경합니다.
(1) 앞서 작성한 모자이크 쉐이더를 할당합니다.
(2) 위 쉐이더 코드의 "_NumMosaicBlocks" 프로퍼티의 값을 여기서 설정합니다. Add Custom Property 버튼을 누르고 프로퍼티의 이름과 타입, 값을 설정합니다.
Bake를 하고 유니티 씬으로 돌아가 게임을 실행하면 위와 같이 모자이크 효과가 적용된 것을 볼 수 있습니다.
"Mosaic Mask" 메시가 AnyPortrait 내부의 객체이므로, 투명하게 만들거나 애니메이션에 따라 자유롭게 움직이게 만들 수 있습니다.
URP에서는 쉐이더를 포함한 다음의 3개의 스크립트를 작성해야합니다.
1. Grab Pass를 재현할 Renderer Feature
: URP에는 Grab Pass가 없습니다.
그래서 직접 Renderer Feature를 작성하여 Grab Pass를 구현해야합니다.
2. 메시의 레이어 변경 스크립트
: Renderer Feature는 카메라 및 레이어가 바뀔 때 동작합니다.
따라서 "일반 메시들"과 "모자이크 마스크 메시"가 별도의 레이어를 가지도록 만들어야 합니다.
3. 모자이크 쉐이더 그래프
: 쉐이더 그래프를 이용하여 모자이크 쉐이더를 작성해야합니다.
먼저, Renderer Feature를 작성하여 Grab Pass를 구현해봅시다.
그런데 Renderer Feature의 API가 URP 13에서 크게 바뀌었으며, 서로 호환되지 않습니다.
따라서 Renderer Feature를 작성하기에 앞서 URP의 버전을 확인해야합니다.
(관련 공식 메뉴얼)
(1) 유니티의 Package Manager를 열고 Packages를 "In Project"로 설정합니다.
(2) 설치된 유니티의 패키지가 보여집니다. 그 중 Universal RP를 선택합니다.
(3) 버전을 확인합니다. 위 이미지에서는 12.1.1이므로, URP 13보다 이전의 방식으로 작성된 스크립트가 필요합니다.
이 페이지에서는 Renderer Feature를 자세히 설명하기 보다는, 오픈소스로서 작성된 외부의 개발자의 스크립트를 활용해볼 것입니다.
URP의 버전이 만약 13보다 이전이라면 다음의 링크에서 Grab Pass용 Renderer Feature 스크립트를 구할 수 있습니다.
Refsa의 GrabScreenFeature (MIT License) : 원본 페이지, 사본 다운로드
만약 URP 버전이 13 혹은 그 이후 버전이라면 다음의 링크에서 Grab Pass용 Renderer Feature (Unity 6 포함) 스크립트를 구할 수 있습니다.
특히, 이 개발자는 두가지 버전의 Renderer Feature를 구현했습니다.
유니티 6의 Render Graph의 도입으로 API가 서로 다르게 되었는데, 후술할 Render Graph 사용 여부에 맞추어 다운로드하면 되겠습니다.
- Render Graph를 사용할 경우 : "GrabScreenFeatureRenderGraphAPI.cs"
- Render Graph를 사용하지 않을 경우 : "GrabScreenFeatureU6.cs"
Smajlovycc의 GrabPassU6 (MIT License) : 원본 페이지, 사본 다운로드
URP 버전에 맞게 Renderer Feature 스크립트를 준비했다면, 프로젝트에서 URP를 설정합니다.
Project Settings > Graphics > Default Render Pipeline에서 URP 에셋을 생성하여 지정합니다.
유니티 버전에 따라서는 플랫폼별 렌더 파이프라인도 설정해야할 것입니다.
유니티 6 또는 그 이후 버전에서는 Render Graph라는 새로운 기능이 추가되었는데, 이 기능의 사용 여부에 따라 Renderer Feature의 API가 바뀝니다.
Render Graph용 스크립트를 사용하지 않는다면 Compatibility Mode를 활성화하여 기존의 API로 작성된 Renderer Feature를 사용할 수 있습니다.
이 옵션을 켜면 Render Graph는 비활성화됩니다.
이제 Renderer Feature을 적용해봅시다.
(1) 프로젝트에 적용된 URP 에셋의 URP Renderer Data 에셋을 선택합니다.
(2) Add Renderer Feature 버튼을 누르고, 앞서 오픈소스에서 다운로드하거나 직접 작성한 Grab Pass용 Renderer Feature를 선택합니다.
Grab Pass용 Renderer Feature가 추가되었습니다.
일부 속성을 확인하거나 변경합니다.
1. Texture Name
: Grab Texture의 이름입니다.
이 값을 모자이크 효과를 위한 쉐이더 그래프에서 사용할 것이므로 이름을 기억해둡니다.
만약 필요한 경우 변경할 수 있습니다.
2. Layer Mask
: Grab Texture에 포함될 오브젝트들의 레이어를 지정합니다.
"None"이나 "Everything"이면 제대로 동작하지 않으므로, 모자이크에 포함시킬 대상 레이어만 선택하여 지정합니다.
여기서는 "Default" 레이어만 지정했습니다.
앞서, "일반 메시들"과 "모자이크 마스크 메시"는 구분되어 렌더링되어야 한다고 설명을 드렸습니다.
따라서 레이어와 카메라를 분리해서 "모자이크 마스크 메시"가 별도로 렌더링 되도록 만들어야 합니다.
(1) Project Settings > Tags and Layers를 엽니다.
(2) Layers 항목의 빈 칸에 "MosaicFX"라는 이름의 레이어를 생성합니다.
(1) 캐릭터를 렌더링하는 기존의 카메라 ("Main Camera")를 복제하고 "Mosaic Camera"라고 이름을 설정합니다. 2번째 카메라부터는 Audio Listener 컴포넌트도 삭제합니다.
(2) Render Type을 "Overlay"로 변경합니다.
(3) Culling Mask의 값에서 "MosaicFX" 레이어만 선택합니다.
(1) 다시 원래의 카메라인 "Main Camera"를 선택합니다.
(2) Culling Mask의 값에서 "MosaicFX" 레이어를 제외합니다.
(3) Stack > Cameras에서 "Mosaic Camera"를 등록합니다.
"모자이크 마스크 메시"를 구분하여 렌더링할 수 있도록 씬 구성이 끝났습니다.
하지만 "모자이크 마스크 메시"는 AnyPortrait 캐릭터 내에 있기 때문에 다른 메시와 동일한 레이어를 가집니다.
따라서 다음과 같은 스크립트를 새로 작성하여 해당 메시가 "MosaicFX" 레이어 및 "Mosaic Camera"에서 렌더링되도록 만들어야 합니다.
using UnityEngine;
using AnyPortrait;
public class SetPortraitMeshLayer : MonoBehaviour
{
public apPortrait portrait; // 대상이 되는 apPortrait
public string layerName = "Default"; // 할당할 레이어의 이름
public string[] meshNames; // 레이어를 지정할 메시들의 메시 그룹 내의 이름들
void Start()
{
SetPortraitMeshLayers();
this.enabled = false;
}
// 대상인 메시들을 찾아서 레이어를 변경하는 함수
private void SetPortraitMeshLayers()
{
int nMeshNames = (meshNames != null) ? meshNames.Length : 0;
if ( nMeshNames == 0 ) { return; }
// apPortrait의 초기화가 되지 않은 상태일 수 있으므로, 여기서 초기화를 시도합니다.
portrait.Initialize();
// 입력된 레이어의 이름을 int형 변수로 변경합니다.
int layer = LayerMask.NameToLayer( layerName );
for ( int i = 0; i < nMeshNames; i++ )
{
string meshName = meshNames[i];
apOptTransform targetTransform = portrait.GetOptTransform(meshName);
if ( targetTransform == null ) { continue; }
// 대상의 레이어를 변경하는 함수를 호출합니다.
SetGameObjectLayer(targetTransform.gameObject, layer);
}
}
// 재귀적으로 대상 및 자식 객체들의 레이어를 변경합니다.
private void SetGameObjectLayer(GameObject targetObject, int layer)
{
if ( targetObject == null ) { return; }
targetObject.layer = layer;
// 자식 객체들의 레이어도 변경합니다.
int nChildren = targetObject.transform.childCount;
if ( nChildren == 0 ) { return; }
for ( int i = 0; i < nChildren; i++)
{
// 재귀적으로 호출을 합니다.
SetGameObjectLayer(targetObject.transform.GetChild(i).gameObject, layer);
}
}
}
(1) 스크립트를 적용하기 위해 새로운 GameObject를 생성합니다.
(2) 작성한 스크립트를 추가하고 값을 설정합니다.
- Portrait : 대상 캐릭터를 지정합니다.
- Layer Name : 모자이크 마스크 메시가 렌더링될 레이어인 "MosaicFX"를 입력합니다.
- Mesh Names : 모자이크 마스크 메시의 메시 그룹 내에서의 이름을 입력합니다.
이제 마지막으로 "모자이크 쉐이더 그래프"를 만듭니다.
새로운 "Sprite Unlit, Transparent, Alpha Blend" 타입의 쉐이더 그래프를 만듭니다.
AnyPortrait용 쉐이더 그래프를 작성하는 방법에 대해서는 관련 페이지에서 볼 수 있으며, 여기서는 모자이크 효과 관련 프로퍼티만 소개합니다.
(1) 기본 프로퍼티들에 더해서 "_NumMosaicBlocks (Float)", "_GrabPassTransparent (Texture2D)" 프로퍼티를 추가합니다.
(2) 이 두개의 프로퍼티들의 속성을 다음과 같이 설정합니다.
1. _NumMosaicBlocks
: 모자이크 블록의 개수입니다.
프로퍼티의 이름은 앞서 소개된 Built-In의 모자이크 쉐이더 프로퍼티와 동일하며, AnyPortrait 캐릭터 내에서 모자이크 마스크 메시가 값을 전달할 수 있습니다.
2. _GrabPassTransparent
: Grab Texture의 프로퍼티 이름입니다.
앞서 Grab Pass로서 추가한 Renderer Feature의 "Texture Name" 값과 동일해야합니다.
Show In Inspector (또는 버전에 따라 Exposed) 옵션을 비활성화해야합니다.
위 이미지와 같이 쉐이더 그래프를 완성합니다. (큰 이미지 링크)
Built-In 예제에서의 모자이크 쉐이더를 그래프 형태로 다시 작성한다고 생각하시면 되겠습니다.
URP용 모자이크 쉐이더를 AnyPortrait 캐릭터에 적용합니다.
(1) "Mosaic Mask" 메시를 선택합니다.
(2) Built-In에서의 설정이 완료된 상태라면 Custom Shader에 방금 만든 "URP용 모자이크 쉐이더 그래프 에셋"을 할당합니다.
(만약 Custom Shader 옵션이 설정되지 않았다면 Built-In에서의 구현 설명의 해당 부분을 확인해주세요.)
(1) 루트 유닛을 선택합니다.
(2) Material Library 버튼을 누릅니다.
(3) URP용 재질 프리셋으로부터 재질 세트를 생성하고 선택합니다.
(4) Default Material 버튼을 눌러서 ON 상태로 만듭니다.
(1) Bake 버튼을 누릅니다.
(2) Setting 탭을 선택합니다.
(3) Render Pipeline의 값을 "Scriptable Render Pipeline"으로 변경합니다.
Bake를 하고 게임을 실행하면 URP에서도 모자이크 효과가 정상적으로 동작하는 것을 볼 수 있습니다.