게임을 만드는데 있어서 반투명 그래픽은 필수적이며 여러가지 활용하는 곳이 많습니다, 반투명이라는 것은 뒤의 픽셀과 블렌딩(Blending)된다는 의미입니다. 

이번 포스팅에서는 알파에 관한 이론과 알파를 사용할때 일어나는 이슈와 문제해결에 대해서 다뤄보겠습니다.

 

 

 

 

Texture Alpha Blending

https://docs.unity3d.com/kr/current/Manual/SL-Blend.html

알파블랜딩은 위 사진 처럼 파이프라인의 맨 마지막 단계에서 처리합니다, 이유는 먼저 그려진 픽셀과 합쳐져야 투명 효과를 낼수 있기 때문 입니다.

 

 

-알파 블랜딩 쉐이더 작성하기-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Shader "Custom/Ala"
{
    Properties
    {
        _Color ("Color"Color= (1,1,1,1)
        _MainTex ("Albedo (RGB)"2D= "white" {}
       
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }    //! 쉐이더 타입을 Transparent로 변경, Render Queue도 같이
        blend SrcAlpha OneMinusSrcAlpha    //! Blending 옵션 설정
 
        CGPROGRAM
      
        #pragma surface surf Lambert keepalpha    //! keepalpha는 Unity 5.0부터  SurfaceShader는 기본적으로 알파에 1.0값이 입력되는데 그것을 막아줍니다.
 
    
        sampler2D _MainTex;
 
        struct Input
        {
            float2 uv_MainTex;
        };
 
        fixed4 _Color;
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs

간단하게 알파 블랜딩 쉐이더를 작성했습니다.

위 코드에서 자세하게 설명드릴 것은 blend SrcAlpha OneMinusSrcAlpha이 부분입니다.

 

 

-블랜드 옵션 설정-

 

블랜드 옵션은 쉽게 말해서 포토샵에서 이미지를 Additive, Multiply 등 효과처럼 블랜딩을 어떻게 할 것인지 설정하는 것입니다.

 

blend 명령어를 통해서 설정이 가능합니다.

 

Blend Properties(블랜드 옵션 값)

One 1의 값 : Source또는 Destination 색상이 온전히 받는다.
Zero 0의 값 : Source또는 Destination 값을 삭제한다.
SrcColor 이 스테이지의 값은 Source 색상 값에 의 해 곱해진다.
SrcAlpha 이 스테이지의 값은 Source 알파 값에 의해 곱해진다.
DstColor 이 스테이지의 값은 프레임 버퍼 Source 색상 값에 의해 곱해진다.
DstAlpha 이 스테이지의 값은 프레임 버퍼 Source 알파 값에 의해 곱해진다.
OneMinusSrcColor 이 스테이지의 값은 (1 - Source 색상값)에 의해 곱해진다.
OneMinusSrcAlpha 이 스테이지의 값은 (1 - Source알파값)에 의해 곱해진다.
OneMinusDstColor 이 스테이지의 값은 (1 - Destination 색상값)에 의해 곱해진다.
OneMinusDstAlpha 이 스테이지의 값은 (1 - Destination 알파값)에 의해 곱해진다.

 

대표적인 Blend 옵션 조합

blend SrcAlpha OneMinusSrcAlpha Alpha Blend
blend One One Additive(With out alpha, black is Transparent)
blend SrcAlpha One Additive(With Alpha)
blend One OneMinusDstColor Soft Additive
blend DstColor Zero Multiplicative
blend DstColor SrcColor 2x Multiplicative

 

옵션값들과 대표적인 조합공식을 표로 만들었지만 당장 단어의 의미를 모르면 해석이 불가능합니다.

단어의 의미를 해석해보겠습니다.

 

 

SrcColor, SrcAlpha에 있는 Src는 Source의 약자로 쉐이더에서 출력할 텍스처를 의미하고  마찬가지로 DstColor, DstAlpha의 Dst는 Destination의 약자로 쉐이더가 출력될 목적지, 즉 배경을 의미합니다.

 

 

blend SrcAlpha OneMinusSrcAlpha

그래서 위에서 작성한 알파블랜드 공식을 해석해보면 위 사진과 같이 볼 수 있습니다. 결론적으로 Src(소스)와 Dst(배경)을 어떻게 섞을 것인가를 정하는 blend공식입니다.

위와 같은 원리로 Additive, Multiply등 다양한 블랜드 효과를 구현해 낼 수 있습니다.

 

 

 

알파 블랜딩의 문제

알파 블랜딩 쉐이더가 적용된 오브젝트를 일렬로 배치하고 카메라를 좀 돌려보니 위와 같이 이상한 현상이 일어납니다.

 

위 현상의 문제점을 한마디로 말하면 Z-Buffer(깊이버퍼) 연산으로 인해 뒤에 있는 오브젝트가 그려지지 않는 현상입니다.

 

 

Z-Buffer(깊이버퍼)란?

Z-Buffer는 깊이버퍼라는 의미로 카메라 기준으로 오브젝트가 떨어져있는 값을 저장하는 버퍼입니다.

 

Forward렌더링에서는 오브젝트를 무작위(불투명한에서) 렌더링하게 되는데 만약 뒤에 있는 박스가 앞에 있는 캡슐보다 늦게 출력되게 된다면 실제로는 박스가 뒤에 존재하지만 보이는 것은 박스가 앞에 보이게 될것입니다.

 

http://chulin28ho.egloos.com/5284164

그래서 출력하기전에 Z-Buffer라는 데이터 공간에 출력될 픽셀의 깊이값을 먼저 저장하고 깊이 데이터를 근거로 해당 오브젝트가 가려졌으면 출력하지 않고 깊이가 가까워서 가려지지 않았다면 출력하는 시스템입니다.

간단히 말해서 출력되는 자신(픽셀)보다 카메라에 가까운 픽셀이 존재하면 자신(픽셀)은 출력하지 않습니다.

 

그런데

 

http://chulin28ho.egloos.com/5284164

만약 Z-Buffer가 앞에 있는 오브젝트가 만약 반투명이면 이론상 뒤에 있는 오브젝트가 출력되지 않아야합니다.

 

근데 잘 동작합니다, 이 이유는 이런 문제를 해결하기 위해 엔진에서 기본적으로 해결 시스템이 몇 가지 구현되어 있습니다.

 

Alpha Sorting(알파 소팅)

https://docs.unity3d.com/kr/current/Manual/SL-Blend.html

알파 블랜드 오브젝트는 따로 가장 마지막에 렌더링 처리합니다, 그 결과 반투명의 핵심인 뒤의 오브젝트와 섞인다라는 현상이 구현될 수 있습니다, 그리고 반투명 오브젝트 끼리 블랜딩이 되기 위해서 Z-Buffer기준으로 맨 뒤에서 부터 출력이 됩니다. 이 기술을 Alpha Sorting(알파 소팅)이라고 명칭합니다.

 

그 결과 위 처럼 불투명 오브젝트와 잘 섞이고 반투명끼리 배치했을때 정상적으로 출력됩니다.

 

Alpha Sorting을 했음에도 불구하고 위와 같은 현상이 일어나는건 알파소팅할때 맨뒤에서 부터 출력되는 기준은 오브젝트의 Pivot기준으로 출력됩니다.

 

하지만 위와 같이 보이는 것과 별개로 Pivot 순서가 엇갈리면 잘리는 현상이 일어나게 됩니다.

 

엔진에서는 기본적으로 Alpha Sorting을 지원하고 있지만 이것 만으로는 알파 텍스처를 다루는데 있어 모든 문제가 해결되지 않습니다. 이 다음 챕터부터는 이와 관련된 여러가지 해결방법을 다뤄보겠습니다.

 

 

알파 블랜딩 문제 해결

 

해결방법 1 : 오브젝트 잘게 나누기

 

Alpha Sorting에서 Pivot기준으로 출력순서를 판단하는 기능을 응용합니다, 물체를 잘게 쪼개서 Pivot을 알맞게 배치하면 될 것입니다.

가장 무식한 방법이지만 큰 건물이나 구름같은 오브젝트에 사용하면 어쩌면 합리적인 방법일 수 도 있습니다.

 

 

 

해결방법 2 : Zwrite Off(Z-Buffer사용하지 않기)

Z-Buffer의 핵심은 픽셀이 뒤에 있다고 판단하면 그리지 않는 것인데 Z-Buffer를 사용하지 않음으로서 그냥 덮어서 픽셀을 그리게 됩니다.

 

결과적으로 문제가 해결됩니다.

 

하지만 OverDraw 이슈가 발생합니다, OverDraw란 한 픽셀에 두 번 이상 그리게 되는 경우를 뜻합니다, 기존에는 Z-Buffer로 인해서 OverDraw를 방지 할 수 있었지만 Z-Write Off를 함으로 OverDraw가 발생하게 됩니다.

 

 

 

해결 방법 3 : Alpha Testing(Cutout)

블랜딩(섞기)가 아니라 완전히 알파채널을 잘라버리는 방법입니다, 알파채널을 1비트(0 or 1)로 만들어 반투명의 개념이 없고 투명 or 불투명으로 됩니다.

알파채널이 완전히 제거된 것만 제외하면 일반 불투명(Opaque)와 똑같은 픽셀처리라서 Z-Buffer를 사용하여 픽셀을 출력하면 되서 알파소팅에 의한 문제가 사라지게 됩니다.

 

Cutout 쉐이더

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Shader "Custom/Cutout"
{
    Properties
    {
        _Color ("Color"Color= (1,1,1,1)
        _MainTex ("Albedo (RGB)"2D= "white" {}
        _Cutoff ("Cutout"Range(0,1)) = 0.5        //! 알파를 제거할 임계점 변수
    }
    SubShader
    {
        Tags { "RenderType"="TransparentCutout" "Queue" = "AlphaTest" }
       
 
        CGPROGRAM
      
        #pragma surface surf Lambert alphatest:_Cutoff    //! _Cutoff는 Properties인터페이스에 선언한 변수와 동일하게 지어야함
 
        sampler2D _MainTex;
 
        struct Input
        {
            float2 uv_MainTex;
        };
 
    
        fixed4 _Color;
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
            
    FallBack "Diffuse"
}
 
cs

 

 

 

알파관련 문제 해결 응용

사람머리 모델을 반투명 쉐이더로 출력해보니 반대편귀, 입, 코 내부가 보이는 문제가 발생 했습니다, 이번 챕터에서는 이 문제를 해결해 보겠습니다.

 

위 문제의 핵심은 알파소팅에 의해 출력되는 픽셀이 앞뒤 구분없이 출력되어 일어나느 현상입니다, 만약 알파블랜딩에 Z-Buffer를 사용 할 수 있게 되어 뒷 부분의 픽셀이 출력되지 않는다면 문제가 해결 될 것입니다.

 

만약 2 Pass로 계획잡아 1Pass에서 Zwrite On상태로 한번 출력하고 다음 2Pass에서 반투명 출력을 한다면 

 

깔끔하게 뒷면이 출력되지 않는 상태에서 반투명 오브젝트가 출력되었습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Shader "Custom/dddddd"
{
    Properties
    {
        _Color ("Color"Color= (1,1,1,1)
        _MainTex ("Albedo (RGB)"2D= "white" {}
     
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        
        zwrite on    //! zwrite는 기본적으로 켜져있지만 확실하게 코드를 짜줍니다.
        ColorMask 0    //! 렌더하지 않습니다.
        CGPROGRAM
 
        #pragma surface surf _NoLit nolight keepalpha noambient noforwardadd nolightmap novertexlights noshadow        //! 최적화 코드
 
        struct Input
        {
            float2 color:COLOR;
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
        }
        float4 Lighting_NoLit(SurfaceOutput s, float3 lightDir, float atten)    //! 커스텀 라이트 함수를 만들어 아무런 연산을 하지 않습니다.
        {
            return 0.0f;
        }
        ENDCG
        
 
        zwrite off
        CGPROGRAM
 
        #pragma surface surf Lambert alpha:blend
 
        sampler2D _MainTex;
 
        struct Input
        {
            float2 uv_MainTex;
        };
 
        fixed4 _Color;
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
 
            o.Alpha = 0.7f;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs

 

 

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,