본문 바로가기

Graphics/Ronja's Unity Shader tutorials

포스트프로세싱 기초

 

 

Postprocessing Basics

Summary We used all shaders we wrote in this tutorial until now to render models to the screen. Another way shaders are commonly used is to manipulate images with them. That includes the image we’re drawing to the screen as we render our game. When manip

www.ronja-tutorials.com

Ronja 님의 허락을 받고 번역한 튜토리얼입니다. 원문은 위 링크에서 확인하실 수 있습니다.
몇몇 부분은 생략·추가하였습니다.
의역과 오역이 넘쳐날 수 있으니 편하게 봐주시고 잘못된 부분은 알려주시면 감사하겠습니다!

 

목차

  • 개요
  • 포스트프로세싱 셰이더
  • 포스트프로세싱 C# 스크립트
  • 색상 반전 효과

 


 

개요

지금까지 이 튜토리얼에서 작성한 모든 셰이더는 모델을 화면에 렌더링하는데에 사용했습니다. 하지만 셰이더의 또 다른 일반적인 사용 방법은 이미지를 조작하는 것입니다. 여기에는 우리가 게임을 렌더링하면서 화면에 그리는 이미지도 포함됩니다.
객체를 화면에 렌더링한 이후 그 결과물을 조작하는 것을 포스트 프로세싱 (postprocessing, 후처리) 이라고 부릅니다.

포스트 프로세싱도 표면을 렌더링하는 셰이더와 동일한 셰이더 언어와 구조를 사용하므로, 먼저 표면 렌더링 방법을 알고 있는 것이 좋습니다. 제가 작성한 텍스처 렌더링 튜토리얼을 읽고 이해했다면 충분히 따라오실 수 있을 것입니다.

 

 


 

포스트 프로세싱 셰이더

포스트 프로세싱에 대한 간단한 입문으로, 이미지의 색상을 반전시키는 셰이더를 만드는 방법을 보여드리겠습니다.

구조의 대부분이 다른 셰이더와 동일하기 때문에, 이번에는 텍스처 셰이더를 기반으로 사용할 것입니다. 해당 셰이더는 여기에서 확인할 수 있습니다.

이 간단한 셰이더에는 우리가 표면을 렌더링하지 않는 경우 필요하지 않은 몇 가지 요소가 포함되어 있어서 이를 제거할 것입니다. 저는 틴트 색상을 제거할 것이며 (원한다면 이미지에 틴트를 적용할 수 있도록 유지할 수도 있습니다), 태그를 제거할 것입니다 (태그는 Unity가 객체를 언제, 어떻게 렌더링할지 알 수 있도록 하지만, 앞서 언급했듯 우리는 이 셰이더로 객체를 렌더링하지 않기 때문입니다). 텍스처 변환도 제거할 것입니다 (maintex 는 셰이더를 적용하기 전의 이미지이고, 우리는 항상 포스트 프로세싱이 씬 전체에 적용되길 원하기 때문입니다). 또한 transform tex 매크로를 제거할 것입니다(텍스처 변환에 사용되지만, 우리는 이를 사용하지 않기 때문입니다. 하지만 여전히 uv 좌표는 v2f 구조체에 기록해야 합니다). 마지막으로 틴트 색상이 연산되는 부분도 제거할 것입니다.

그 다음, 셰이더를 포스트 프로세싱 셰이더로서 더 잘 동작하도록 하기 위해 몇 가지 세부 사항을 추가할 것입니다. main texture 프로퍼에 hide in inspector 태그를 추가할 것인데, 이는 프로퍼티가 코드에서 설정되기 때문에 인스펙터에서 볼 필요가 없기 때문입니다. 그리고 Unity에 컬링을 수행하지 말고 뎁스 버퍼에 쓰거나 읽지 않도록 지시하는 마커를 추가할 것입니다.

이러한 변경을 거친 후엔, 셰이더는 대략 다음과 같은 형태가 됩니다.

 

Shader "Tutorial/016_Postprocessing"
{
    // 인스펙터(Inspector) 창에서 조절 가능한 값을 노출시킵니다
    Properties
    {
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {}
    }

    SubShader
    {
        // 컬링이나 뎁스 버퍼 읽기/쓰기가 필요 없음을 지정하는 마커
        Cull Off
        ZWrite Off
        ZTest Always

        Pass
        {
            CGPROGRAM
            // 유용한 셰이더 함수들을 포함해줍니다
            #include "UnityCG.cginc"

            // 버텍스와 프래그먼트 셰이더 정의
            #pragma vertex vert
            #pragma fragment frag

            // 텍스처와 그 변환 정보
            sampler2D _MainTex;

            // 버텍스 셰이더에 전달되는 오브젝트 데이터
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            // 프래그먼트를 생성할 때 사용되며, 프래그먼트 셰이더에서 읽을 수 있는 데이터
            struct v2f
            {
                float4 position : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            // 버텍스 셰이더
            v2f vert(appdata v)
            {
                v2f o;
                // 렌더될 수 있도록 버텍스 좌표를 오브젝트 공간에서 클립 공간으로 변환
                o.position = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            // 프래그먼트 셰이더
            fixed4 frag(v2f i) : SV_TARGET
            {
                // 텍스처에서 원본 색상을 가져옵니다
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }

            ENDCG
        }
    }
}

 


 

포스트 프로세싱 C# 스크립트

이제 포스트 프로세싱 셰이더의 기반을 만들었으니, 카메라가 이 셰이더를 사용하게 하는 C# 스크립트를 작성할 수 있습니다.

우리는 일반적인 MonoBehaviour가 필요하며, 그 안에는 OnRenderImage() 라는 메소드 하나만 있으면 됩니다. 이 메소드는 Unity에 의해 자동으로 호출됩니다. 메서드에는 두 개의 인자가 전달되는데, 하나는 렌더링된 이미지를 담고 있는 RenderTexture 이고, 다른 하나는 이후 렌더링된 이미지로 사용될 수 있도록 우리가 쓸 수 있는 RenderTexture 입니다. 한 RenderTexture에서 다른 RenderTexture로 이미지 데이터를 옮기기 위해서는 Blit() 메소드를 사용합니다.

 

using UnityEngine;

// 메인 카메라와 같은 게임 오브젝트에 위치해야 하는 동작 스크립트
public class Postprocessing : MonoBehaviour
{

    // 카메라 렌더링이 끝난 후 Unity가 자동으로 호출하는 메소드
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        // source 텍스처의 픽셀을 destination 텍스처에 그려 넣습니다
        Graphics.Blit(source, destination);
    }
}

 

지금까지는 이 스크립트에서 이미지를 전혀 변경하지 않기 때문에 아무 작업도 하지 않습니다. 이미지를 변경하려면, Blit() 함수에 텍스처를 그릴 때 사용할 머티리얼을 세 번째 매개변수로 전달하면 됩니다. 이를 위해 머티리얼을 직렬화된 (Serialized) 클래스 변수로 추가하고, Blit() 함수에 전달해 적용하도록 하겠습니다.

 

using UnityEngine;

// 메인 카메라와 같은 게임 오브젝트에 위치해야 하는 동작 스크립트
public class Postprocessing : MonoBehaviour
{
    // 포스트 프로세싱을 수행할 때 적용되는 머티리얼
    [SerializeField] private Material postprocessMaterial;

    // 카메라 렌더링이 끝난 후 Unity가 자동으로 호출하는 메소드
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        // source 텍스처의 픽셀을 destination 텍스처에 그려 넣습니다
        Graphics.Blit(source, destination, postprocessMaterial);
    }
}

 

이렇게 준비가 완료되면, 이제 씬을 구성할 수 있습니다. 먼저 프로젝트에 새로운 메테리얼을 추가하고, 여기에 우리가 만든 포스트 프로세싱 셰이더를 적용합니다.

 

 

그 다음, 카메라가 있는 게임 오브젝트에 작성한 C# 스크립트를 적용합니다. 그리고 새로 만든 메테리얼을 스크립트 컴포넌트에 추가해줍니다.

 

 


 

색상 반전 효과

이제 설정이 완료되었으므로, 이미지는 정상적으로 보여질 것입니다. 이 상태에서 이미지의 색상을 반전시키기 위해서는, 셰이더로 돌아가서 프래그먼트 함수를 수정하면 됩니다. 입력 텍스처의 색상을 그대로 반환하는 대신, 먼저 색상을 1에서 해당 색상 값을 빼는 방식으로 반전시키고, 그 값을 반환해줍니다.

 

// 프래그먼트 셰이더
fixed4 frag(v2f i) : SV_TARGET
{
    // 텍스처에서 원본 색상을 가져옵니다
    fixed4 col = tex2D(_MainTex, i.uv);
    // 색상을 반전합니다
    col = 1 - col;
    return col;
}

 

 

색상을 반전시키는 것은 자주 사용하는 효과는 아니지만, 앞으로 다양한 효과를 만들 수 있는 가능성을 열어줍니다. 그 중 일부는 앞으로도 보여드릴 예정입니다.

셰이더와 스크립트 코드 전문은 아래와 같습니다.

 

Shader "Tutorial/016_Postprocessing"
{
    // 인스펙터(Inspector) 창에서 조절 가능한 값을 노출시킵니다
    Properties
    {
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {}
    }

    SubShader
    {
        // 컬링이나 뎁스 버퍼 읽기/쓰기가 필요 없음을 지정하는 마커
        Cull Off
        ZWrite Off
        ZTest Always

        Pass
        {
            CGPROGRAM
            // 유용한 셰이더 함수들을 포함해줍니다
            #include "UnityCG.cginc"

            // 버텍스와 프래그먼트 셰이더 정의
            #pragma vertex vert
            #pragma fragment frag

            // 텍스처와 그 변환 정보
            sampler2D _MainTex;

            // 버텍스 셰이더에 전달되는 오브젝트 데이터
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            // 프래그먼트를 생성할 때 사용되며, 프래그먼트 셰이더에서 읽을 수 있는 데이터
            struct v2f
            {
                float4 position : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            // 버텍스 셰이더
            v2f vert(appdata v)
            {
                v2f o;
                // 렌더될 수 있도록 버텍스 좌표를 오브젝트 공간에서 클립 공간으로 변환
                o.position = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            // 프래그먼트 셰이더
            fixed4 frag(v2f i) : SV_TARGET
            {
                // 텍스처에서 원본 색상을 가져옵니다
                fixed4 col = tex2D(_MainTex, i.uv);
                // 색상을 반전합니다
                col = 1 - col;
                return col;
            }

            ENDCG
        }
    }
}
using UnityEngine;

// 메인 카메라와 같은 게임 오브젝트에 위치해야 하는 동작 스크립트
public class Postprocessing : MonoBehaviour
{
    // 포스트 프로세싱을 수행할 때 적용되는 머티리얼
    [SerializeField] private Material postprocessMaterial;

    // 카메라 렌더링이 끝난 후 Unity가 자동으로 호출하는 메소드
    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        // source 텍스처의 픽셀을 destination 텍스처에 그려 넣습니다
        Graphics.Blit(source, destination, postprocessMaterial);
    }
}