AnyPortrait > 메뉴얼 > 커맨드 버퍼 작성하기

커맨드 버퍼 작성하기


1.4.0

유니티 엔진에서 렌더링은 자동으로 수행되지만, 스크립트를 작성해서 기능을 확장할 수도 있습니다.
이를 위한 다양한 방법 중 하나는 "커맨드 버퍼(Command Buffer)"를 이용하는 것입니다.
커맨드 버퍼를 쉽게 요약하면, "대상인 메시를 지정된 조건으로 렌더링해달라는 요청을 예약하는 것"입니다.
유니티 공식 메뉴얼에서 자세한 설명을 확인하실 수 있습니다.
- 커맨드 버퍼를 사용하여 빌트인 렌더 파이프라인 확장
- 스크립터블 렌더 파이프라인에서 렌더링 커맨드 예약 및 실행
- 커맨드 버퍼 API
- CameraEvent API


유니티의 "CommandBuffer" 클래스를 이용하면 AnyPortrait의 캐릭터를 커맨드 버퍼를 통해서 렌더링할 수 있지만, 사용자가 접근하기 어려운 데이터들이 많으므로 그것은 구현하기 어렵습니다.
AnyPortrait v1.4.0에 추가된 "apCustomCommandBuffer" 클래스는 "CommandBuffer" 클래스의 AnyPortrait용 버전입니다.
이 클래스를 이용해서 다양한 기법을 쉽게 구현할 수 있습니다.


이 페이지에서는 apCustomCommandBuffer를 사용하여 스크립트를 작성하는 방법과 몇가지 재미있는 기법을 구현하는 내용을 다룹니다.
apCustomCommandBuffer 클래스의 함수에 대한 자세한 설명은 관련 페이지에서 확인할 수 있습니다.




커맨드 버퍼로 다른 카메라에 캐릭터 그리기




구현에 앞서서, 커맨드 버퍼가 전달되는 방식을 간단히 이해해봅시다.
스크립트에서 커맨드 버퍼를 생성하여 유니티의 렌더링 파이프라인에 전달하면, 렌더링 파이프라인이 동작할때 예약된 커맨드 버퍼의 요청사항에 따라 추가적으로 렌더링이 실행됩니다.
이때 전달되어야 하는 필수 요소는 "그려질 메시"와 "그리는 카메라"입니다.
이 점을 기억하며 구현을 해봅시다.




커맨드 버퍼를 이용하는 가장 쉽고 간단한 예제를 만들어봅시다.
카메라(Camera 1)가 캐릭터의 메시들(Original Meshes)을 렌더링하는 환경을 준비합니다.
그리고 또다른 카메라(Camera 2)에서 커맨드 버퍼에 의해 캐릭터(Cloned Meshes)가 보여지도록 만들어봅시다.
커맨드 버퍼를 만들기 위해서 필요한 요소로서 "Original Meshes"와 "Camera 2"를 기억합시다.




설명에 맞게 유니티 씬을 구성했습니다.
(1) 2개의 카메라와 1개의 AnyPortrait 캐릭터가 존재하는 것을 볼 수 있습니다.
(2) AnyPortrait 캐릭터입니다.
(3) 캐릭터를 그리는 메인 카메라입니다. 앞선 도식에서의 "Camera 1"과 같은 역할을 합니다.
(4) 메인 카메라가 렌더링을 끝내면 추가적으로 렌더링을 하는 오버레이용 카메라입니다. 앞선 도식에서 "Camera 2"와 같은 역할을 합니다.




각 카메라의 설정입니다.
Camera 1은 메인 카메라로서 먼저 렌더링하도록 설정되어 있습니다.
Camera 2는 "Depth" 값에 의해서 Camera 1보다 나중에 렌더링이 되며, "Culling Mask"에 의해서 캐릭터가 렌더링되지 않습니다.




따라서 게임을 실행한다면 캐릭터는 메인 카메라에서만 그려질 것입니다.


이제 커맨드 버퍼를 생성하고 업데이트하는 간단한 스크립트를 작성해봅시다.
다음의 스크립트는 "입력된 apPortrait 캐릭터"를 "입력된 카메라"에서 그리도록 만드는 아주 간단한 역할을 수행합니다.



스크립트의 핵심 구문들을 하나씩 살펴봅시다.



멤버 객체들입니다.
(1) 커맨드 버퍼의 필수 요소인 apPortrait와 카메라를 유니티 씬으로부터 연결할 수 있도록 public 변수로 만듭니다.
(2) 가장 중요한 커맨드 버퍼를 apCustomCommandBuffer 타입의 변수를 이용하여 생성하고 제어합니다.



커맨드 버퍼를 생성하고 렌더링 파이프라인에 등록하는 코드입니다.
(1) 예제에서는 Start 함수에서 초기화가 실행하도록 작성하였기 때문에, apPortrait가 초기화되지 않았을 수 있어서 초기화 코드를 작성했습니다. 필수 코드는 아닙니다.
(2) 커맨드 버퍼 인스턴스를 생성할 때 인자로서 카메라와 apPortrait를 입력하고, 커맨드 버퍼의 이름을 추가로 입력합니다.
(3) "AddToCamera" 함수를 이용하여 렌더링 파이프라인에 커맨드 버퍼를 등록합니다.
이때, 렌더링 과정에서 어느 시점에 커맨드 버퍼의 요청이 처리될지를 설정합니다.
만약 "Scriptable Render Pipeline (SRP)" 환경이라면 "AddToCameraSRP"를 대신 사용해야합니다.



만약 커맨드 버퍼를 구성하는 요소인 "캐릭터", "카메라" 및 커맨드 버퍼를 관리하는 이 스크립트 중에 하나라도 씬에 존재하지 않게 된다면, 위 코드를 꼭 호출해서 커맨드 버퍼를 렌더링 파이프라인으로부터 제거해야합니다.



커맨드 버퍼의 요소들에 변경 사항이 있다면 커맨드 버퍼의 내용을 다시 작성해야합니다.
AnyPortrait는 매프레임마다 메시가 갱신되므로, Update나 LateUpdate 함수에서 커맨드 버퍼의 내용을 다시 작성해야합니다.
(1) 커맨드 버퍼의 내용을 작성하기 위해서는 기존의 버퍼 내용을 삭제해야합니다.
(2) 렌더링하는 View-Projection Matrix를 커맨드 버퍼에 입력합니다. Unity 2019보다 이전 버전에서는 이 함수가 지원되지 않습니다.
(3) apPortrait 내에서 어떤 메시들을 렌더링할 것인지 커맨드 버퍼에 입력합니다. DrawAllMeshes는 가능한 현재 보여지는 모든 메시들을 렌더링하도록 요청합니다.




작성한 스크립트를 유니티 씬에 넣어봅시다.
(1) 새로운 GameObject를 생성합니다.
(2) 작성한 스크립트를 컴포넌트로 등록하고, "apPortrait 캐릭터"와 "오버레이 카메라(Camera 2)"를 각각 입력합니다.




다시 게임을 실행하면, 커맨드 버퍼에 의해 이제 두번째 카메라에서도 캐릭터가 그려지는 것을 볼 수 있습니다.




커맨드 버퍼의 내용을 작성하는 코드를 조금만 바꾸어도 렌더링 결과는 크게 달라집니다.
시험삼아 다음과 같이 코드를 한줄 추가해봅시다.





오버레이 카메라(Camera 2)에서 커맨드 버퍼에 의해 캐릭터가 렌더링 되기 직전에 렌더링 타겟, 즉 화면의 색상을 보라색(Magenta)으로 초기화를 했기 때문에 위와 같은 결과를 볼 수 있습니다.




단색으로 캐릭터를 렌더링하기


커맨드 버퍼를 이용하는 것은 주로 특수한 렌더링 효과를 만들기 위함입니다.
렌더링 효과를 만들기 위해서는 별도의 재질이 커맨드 버퍼에서 활용될 수 있어야 합니다.
이번에는 커맨드 버퍼로 캐릭터를 회색톤의 단색으로 렌더링하도록 구현해봅시다.


이번에는 캐릭터를 단색으로 렌더링하는 커스텀 쉐이더와 앞서 설명된 커맨드 버퍼 스크립트를 작성해야합니다.
먼저 다음의 커스텀 쉐이더를 새로 작성합니다.



앞서 설명한 커맨드 버퍼 스크립트를 수정하거나 새로 작성합니다.
이번에는 외부의 재질을 가져와서 렌더링에 사용되도록 작성해야 합니다.





(1) 완성된 커스텀 쉐이더를 이용하는 재질(Material) 에셋을 만듭니다.




(1) 앞서 만든 GameObject를 선택합니다.
(2) 스크립트의 추가된 변수에 커스텀 쉐이더를 이용하는 재질 에셋을 할당합니다.




완성된 결과입니다.
커맨드 버퍼에 의해서 렌더링이 될 때, 다른 재질로 교체되서 렌더링이 되는 것을 볼 수 있습니다.
이 방법을 응용하면 다양한 렌더링 기법을 사용할 수 있을 것입니다.




외곽선이 있는 캐릭터 렌더링하기


위의 설명들을 조금 더 응용하면 실용적인 예제를 만들 수 있습니다.
대표적인 기법은 "외곽선"입니다.
여기서는 UV를 이용하여 외곽선을 그리는 간단 커스텀 쉐이더를 이용하여 간단히 구현할 수 있습니다.
추가적으로 커맨드 버퍼 생성시 렌더링이 되는 시점을 변경해야합니다.
마지막으로, 앞의 예제와 다르게 이 기법에서는 메인 카메라를 대상으로 커맨드 버퍼가 실행되도록 만들 것입니다.


외곽선을 그리는 커스텀 쉐이더를 만듭니다.
다양한 방식으로 외곽선을 그리지만, 여기서는 간단히 기준으로 주변의 UV에서의 Alpha 값을 이용하여 색상을 확장하는 방식을 이용해볼 것입니다.
새로운 쉐이더 에셋을 만들고 아래와 같이 작성합니다.



다음으로, 커맨드 버퍼 스크립트를 작성합니다.
앞의 예제의 코드와 거의 동일하게 작성하면 되는데, 렌더링 시점이 변경되어야 합니다.
작성한 커스텀 쉐이더는 캐릭터 이미지를 조금 더 확장해서 단색으로 그리는 코드를 가지기 때문에, 이것이 외곽선으로서 역할을 하려면 "캐릭터보다 먼저 그려져야" 합니다.





(1) 외곽선 쉐이더가 적용된 재질 에셋을 생성합니다.
(2) 재질 에셋을 선택하고 외곽선의 굵기와 색상을 설정합니다.




(1) 커맨드 버퍼를 제어하는 GameObject를 선택합니다.
(2) 외곽선의 경우 오버레이 카메라가 아닌, 캐릭터를 렌더링하는 메인 카메라가 대상이 되도록 설정해야합니다.
(3) 외곽선을 그리는 재질 에셋을 할당합니다.




게임을 실행하면 적당한 퀄리티의 외곽선이 그려지는 것을 볼 수 있습니다.




두개 이상의 이미지를 가지는 캐릭터를 렌더링하기


앞의 설명을 통해서 커맨드 버퍼와 별도의 재질을 이용하여 렌더링을 하는 과정을 익혔습니다.
이번에는 캐릭터의 이미지가 2개 이상인 경우에 외부의 재질을 할당하고자 하는 경우입니다.
이미지에 따라서 다른 "보조 텍스쳐(Secondary Texture)"를 적용하여 렌더링을 하고자 한다면, 준비해야하는 재질들과 커맨드 버퍼의 초기화 코드를 수정해야합니다.




왼쪽의 두개의 텍스쳐를 사용하는 캐릭터를 준비했습니다.
각각의 이미지에 대응하는 오른쪽의 보조 텍스쳐의 색상이 곱해지는 재질을 만들어서 커맨드 버퍼를 통해 렌더링을 해봅시다.


이번에도 마찬가지로 커스텀 쉐이더를 만들어봅시다.
"_MainTex" 속성은 AnyPortrait에 의해서 관리되므로 사용되지 못하므로, 별도의 텍스쳐 속성을 만들어야 합니다.





커맨드 버퍼를 제어하는 스크립트를 작성하기에 앞서서, 재질을 만들어 봅시다.
(1) 이미지 개수에 맞게 재질들을 만듭니다.
(2) "_SecondaryTex" 속성에 각각의 보조 텍스쳐를 할당합니다.


이제 2개 이상의 재질을 커맨드 버퍼에 지정하여 렌더링하도록 스크립트를 작성해봅시다.





(1) 2개의 이미지를 사용하는 캐릭터가 등장하는 유니티 씬입니다.
(2) 유니티 씬은 앞의 설명에서의 구성과 동일합니다. 스크립트가 추가된 GameObject를 선택합니다.
(3) apPortrait 캐릭터가 사용하는 이미지의 이름과 그에 맞는 커스텀 쉐이더의 재질을 순서대로 할당합니다.




게임을 실행하면 메시의 이미지에 해당하는 대체 재질이 적절히 적용되어 위와 같은 결과를 볼 수 있습니다.




투명한 배경의 렌더 텍스쳐로 렌더링하기


이 이슈는 여러 사용자들로부터 요청을 받았던 내용입니다.
캐릭터를 화면이 아닌 렌더 텍스쳐(Render Texture)을 대상으로 렌더링을 할 때, 렌더 텍스쳐의 배경을 투명하게 할 경우 메시가 사라지는 문제가 발생합니다.
렌더 텍스쳐로 캐릭터를 렌더링하는 방법에 대해서는 관련 페이지에서 자세한 설명을 확인하세요.
이 페이지에서는 렌더 텍스쳐로 렌더링을 할때의 문제를 커맨드 버퍼로 해결하는 방법을 소개하면서 "렌더 타겟(Render Target)"을 제어하는 스크립트를 설명합니다.




캐릭터를 렌더링한 결과를 렌더 텍스쳐에 저장한 후, 이것을 다른 메시에 입혀서 렌더링하는 방식에 대한 도식입니다.
캐릭터가 렌더링된 렌더 텍스쳐는 게임 내에서 다양하게 활용될 수 있기 때문에 자주 사용되는 기법입니다.
여기서는 아직 커맨드 버퍼가 사용되지 않습니다.




도식에 맞게 씬을 구상했습니다.
두개의 레이어 (UI, Default)로 구성했으며 AnyPortrait 캐릭터, 2개의 카메라, 그리고 렌더 텍스쳐가 입혀진 사각 메시가 씬에 배치되어있습니다.
(1) 캐릭터와 이를 렌더링하는 첫번째 카메라입니다. 메인 카메라에서는 렌더링되지 않습니다.
(2) 렌더 텍스쳐가 적용된 사각 메시("RT Quad")메인 카메라입니다.




(1) 그리고 가장 핵심이 되는 "렌더 텍스쳐(Render Texture)" 에셋입니다.
(2) 이 렌더 텍스쳐는 캐릭터를 렌더링하는 첫번째 카메라의 "Target Texture"으로서 등록됩니다.




캐릭터는 메인 카메라에서 렌더링되지 않도록 Default가 아닌 레이어(여기서는 UI)로 설정되어 있습니다.




(1) 메인 카메라에서 렌더링되는 사각 메시입니다.
(2) 이 메시의 재질의 텍스쳐는 첫번째 카메라에서 사용되는 "렌더 텍스쳐" 에셋입니다.
이 사각 메시의 재질과 첫번째 카메라가 렌더 텍스쳐를 공유하기 때문에, 첫번째 카메라로 렌더링하는 결과가 사각 메시에 나타나는 것입니다.




게임을 실행하면 사각형 메시에 첫번째 카메라의 렌더링 결과, 즉 캐릭터가 정상적으로 보여지는 것을 확인할 수 있습니다.




문제는 렌더 텍스쳐의 배경 색상이 불투명하지 않은 경우에 발생합니다.
(1) 캐릭터를 렌더링하는 첫번째 카메라를 선택합니다.
(2) 배경 색상 속성을 선택합니다. (Clear Flags가 Solid Color여야 합니다.)
(3) 배경색의 Alpha 값을 줄입니다.
(4) 배경은 물론이고 캐릭터를 포함한 렌더 텍스쳐 전체가 점점 투명해지는 것을 볼 수 있습니다.




이 문제를 해결하는 방법입니다.
불투명한 배경의 렌더 텍스쳐을 완성하고, 이와 별도로 "마스크 텍스쳐"를 커맨드 버퍼를 이용하여 생성합니다.
마스크를 이용하여 "배경"과 "캐릭터" 영역을 구분할 수 있습니다.
이 마스크 텍스쳐를 렌더 텍스쳐와 합쳐서 배경만 투명한 이미지가 렌더링되도록 만듭니다.


참고
명확히 구분하기 위해 카메라에서 생성하는 기존의 렌더 텍스쳐를 "색상 렌더 텍스쳐"로 부르고, 커맨드 버퍼가 생성하는 마스크 역할을 하는 렌더 텍스쳐를 "마스크 렌더 텍스쳐"로 부릅니다.


이 기법을 구현하기 위해서는 많은 작업이 필요합니다.
(1) Alpha값을 단색(Grayscale)로 저장하는 마스크 렌더링용 커스텀 쉐이더를 작성합니다.
(2) 마스크 렌더 텍스쳐를 추가로 생성합니다.
(3) 마스크 렌더 텍스쳐를 렌더링하도록 만드는 커맨드 버퍼 스크립트를 작성합니다.
(4) 두개의 렌더 텍스쳐를 병합하는 사각 메시에 입혀질 쉐이더와 재질을 만듭니다.


가장 먼저 커맨드 버퍼에서 사용할 커스텀 쉐이더, 즉 Alpha 값을 단색으로 저장하는 커스텀 쉐이더를 작성해봅시다.



이어서 커맨드 버퍼를 제어하는 스크립트를 작성합니다.
이전의 예제의 코드와 대부분 동일하지만, "렌더 타겟"으로서 임의의 "렌더 텍스쳐"를 지정한다는 점에서 차이가 있습니다.



마지막으로, 사각 메시에 입힐 재질의 쉐이더를 만듭니다.
"색상 렌더 텍스쳐"와 "마스크 렌더 텍스쳐"를 조합하여 배경만 투명하게 만드는 역할을 합니다.





(1) 커맨드 버퍼에서 사용할 "마스크 렌더 텍스쳐"와 "재질"을 생성합니다. Alpha 값을 색상으로 저장하는 커스텀 쉐이더를 재질에 적용해주세요.
(2) 커맨드 버퍼를 제어하는 GameObject를 생성합니다.
(3) 커맨드 버퍼를 제어하는 스크립트를 추가하고 "apPortrait 캐릭터", "첫번째 카메라"를 할당하고 "(1)"에서 생성했던 마스크 생성용 "재질"과 "마스크 렌더 텍스쳐"를 할당합니다.




전체 이미지의 투명도를 마스크 렌더 텍스쳐에서 계산하기 때문에, "색상 렌더 텍스쳐"의 배경은 원래대로 불투명해야합니다.
(1) 첫번째 카메라를 선택합니다.
(2) 배경색을 선택하여 (3) 불투명하게 만듭니다.
이때, 이미지의 가장자리보다 조금 더 어두운 색상으로 설정하면 더욱 깔끔한 결과를 얻을 수 있습니다.




(1) 그리고 사각 메시에 입혀질 재질을 만들고 두개의 렌더 텍스쳐를 조합하는 커스텀 쉐이더를 적용합니다.
(2) 첫번째 프로퍼티(_MainTex)에는 "색상 렌더 텍스쳐"를 할당합니다.
(3) 두번째 프로퍼티(_MaskTex)에는 "마스크 렌더 텍스쳐"를 할당합니다.




(1) 사각 메시를 선택합니다.
(2) 앞서 만든 "두개의 렌더 텍스쳐를 조합하는 재질"을 이 메시에 적용합니다.




완성된 결과입니다.
투명한 배경에서 캐릭터만 선명하게 렌더링되는 멋진 결과를 얻을 수 있습니다.




"색상 렌더 텍스쳐"와 "마스크 렌더 텍스쳐"가 합쳐져서 렌더링 문제를 해결함을 알 수 있습니다.
커맨드 버퍼는 위와 같이 렌더링 문제를 해결하기 위해 추가적인 렌더 텍스쳐 등을 생성할 때 효과적으로 사용됩니다.