티스토리 뷰

강의 필기/3-1

<14> 엔진 활용

YujinK 2020. 7. 13. 10:47

버퍼 (Buffer)

버퍼는 '미리 받아와 저장해 둔 데이터 메모리'를 뜻합니다. 

데이터를 전송하는 과정에서, 데이터를 받는 속도가 느린 탓에 중간에 출력이 끊기는 일을 방지하는데요, 그럼에도 불구하고 버퍼를 채우는 속도가 데이터 출력 속도를 못 따라갈 때 발생하는 현상이 우리가 흔히 말하는 '버퍼링'입니다. 평소 영상을 볼 때 "아 버퍼링 걸렸어.." 라고 말하던 그것 입니다. 


하여튼 버퍼는 데이터를 미리 킵 해두는 것이다, 아직 출력되지 않은(=앞으로 출력할) 데이터를 저장하는 것이다, 정도로 이해합시다. 오늘 중요한 것은 다양한 '버퍼' 중에서도 'Z버퍼'라는 것입니다.


Z버퍼 (Z Buffer = Depth Buffer)


(https://ko.wikipedia.org/wiki/Z_%EB%B2%84%ED%8D%BC%EB%A7%81)


Z버퍼는 깊이 정보를 나타내는 버퍼입니다. Z축, 즉 깊이에 따라 가깝고 먼 것을 판단하고 물체를 그려냅니다. Z버퍼를 통해 '가까운 것이 먼 것을 가리는 (먼 것을 덮어쓰는)' 현상을 구현합니다! 


Z버퍼에서 각 픽셀은 깊이에 따라 flaot값을 가지는데요, 여기서 float의 정밀도에 따라 문제가 하나 발생합니다.


Z-Fighting 은 거의 같은(정밀도가 높은) Z값을 가진 픽셀들이 서로 먼저 그려지겠다고 싸우는 것입니다...예시를 바로 봅시다!



이것이 Z-Fighting입니다. (난리 난 두 개의 플랜..)


이 외에도 Z버퍼에서 발생하는 문제점이 한 가지 더 있었는데요, 바로 알파에 의한 것입니다.

불투명한(Opaque) 오브젝트의 앞에 투명한(Transparent) 오브젝트가 있을 때,




투명해서 사라진 부분에 그려져야 할 불투명 오트젝트가 그려지지 않고, 뚫려버리는 현상입니다. Z버퍼는 '가까이 있는 것이 먼 것을 가리는' 현상에서 알파까지 고려하지 못한 것이죠. 


이를 해결하기 위해 나온 것이 alpha sorting 입니다.



Alpha sorting


불투명/반투명을 나누어 따로 그립니다. 불투명한 것이 먼저, 반투명한 것이 나중에 그려집니다. 또한, 반투명한 것은 뒤에서부터 그립니다. (알파를 정렬, sorting 합니다) 

그렇게 하면 위 그림처럼 앞에 있는 반투명 물체를 먼저 그렸다가 뒤에 있는 불투명 물체가 가려지는 일은 없겠죠? 


그럼 전부 해결되었을까요? 알파 소팅은 완벽한가요? 아닙니다..

그러면 알파 소팅의 문제점과 단점을 살펴봅시다.


(1) 씬에 반투명이 들어가는 순간 반투명을 정렬하고, 뒤에서부터 그리는 계산 = 알파 소팅이 진행되기 때문에, 연산이 무거워 집니다. 최적화의 문제가 있습니다.


(2) 


분명히 Z축에 차이가 있는 반투명 오브젝트를 나란히 두었는데,



뒤 쪽에 배치한 오브젝트를 x축으로 이동시키자, 뜬금없이 앞으로 툭 튀어나와 버립니다.

이는 알파 소팅에서 오브젝트의 멀고 가까움을 판단하는 기준이 '피봇' 이기 때문인데요.



(2)를 x축으로 옮기면서, 카메라를 기준으로 (2)의 피봇이 더 가까워진 것입니다(ㅠㅠ)

따라서 (2)를 (1)보다 나중에 그리고, 결과적으로 (2)가 (1)의 앞에 그려지는....난장판이 벌어집니다....


이 문제를 해결할 근본적인 방법은 없습니다. 

어떻게든 완화할 방법은 몇 가지 있는데요, 그럼 이제 그 방법을 살펴봅시다.



알파 소팅의 문제를 완화해보자 : Alpha Test (cutout)




알파 테스트는 알파가 있고 없고를 0 혹은 1로 판단한 뒤에 알파 부분을 아예 잘라버리는 방식입니다. 



알파 채널에서 경계 부분의 블렌딩 묘사를 해도 소용 없습니다. 알파 테스트는 '이 이상의 값은 알파로 인식하고 아예 잘라버릴거야' 하고 가차없이 굴어버리기 때문입니다.


그러니 사실상 반투명이 아닙니다. 투명한 부분은 전부 잘라버렸으니까요. 



알파 테스트를 사용하면?


기존 반투명(알파 블렌딩)이 Z버퍼에서 일으킨 문제: 앞뒤 기준이 모호해져, 오브젝트를 그리는 순서가 뒤죽박죽이 되던 문제를 해결할 수 있겠죠. 


다만 '반투명'이 아닌 점, 알파 부분이 뚝 잘려나가 경계가 딱딱하고 퀄리티가 떨어져 보이는 문제가 발생합니다.



(출처: https://medium.com/@bgolus/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f)


경계가 단단하니 부자연스러워 보입니다. 멀리서 보면 더 끔찍하네요..



유니티 셰이더에서 알파 테스트 가동


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
Shader "Custom/Cutout"
{
    Properties
    {
        _Color("color",color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Cutoff("cutoff",Range(0,1)) = 0.5 
    }
    SubShader
    {
        Tags { "RenderType"="TransparentCutout" "Queue" = "AlphaTest" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf Standard alphatest:_Cutoff
        #pragma target 3.0
 
        sampler2D _MainTex;
 
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Transparent/Cutout/Diffuse" 
//_Color변수와 "Transparent/Cutout/Diffuse"은 그림자 연산을 위한 것
}
cs


이렇게 작성한 셰이더를 오브젝트에 적용해보면, 



앞뒤 문제는 확실히 해결되었네요. 추가로 빛의 영향을 받고 그림자도 생깁니다. 근데 경계가..? 너무 구립니다. 불 텍스쳐에 쓰면 안되겠습니다. 텍스쳐 선택에서 에러가..



풀로 바꿔주었습니다. 한결 낫네요 



커스텀 알파블렌드


다시 알파 블렌드로 돌아옵시다. 이제부턴 코딩을 통해 커스텀 알파블렌드를 구현하는 것에 대해 알아볼 것입니다.



지금까지 alpha:blend 로 구현했던 알파 블렌드를 커스텀할 수 있도록 해봅시다.


1
2
3
4
5
6
7
8
9
 Tags { "RenderType"="Transparent" "Queue"="Transparent" }
       
        zwrite off 
        blend SrcAlpha OneMinusSrcAlpha 
 
 
        CGPROGRAM
        #pragma surface surf standard keepalpha
cs

 

이렇게 작성하면 alpha:blend와 같은 모양새가 됩니다.

zwrite, blend 어쩌구, keepalpha는 도대체 뭘까요? 하나씩 봅시다.


(1) keepalpha 는 SurfaceShader에서 알파에 1.0값을 주는 것을 막아줍니다. 알파를 구현하기 위해선 꼭 써줘야겠죠


(2) ★ blend 


알파 블렌드의 커스텀 옵션을 여기서 적용할 수 있습니다. 포토샵 레이어 옵션에서 Multiply,Additive 등을 적용하듯이요. 가장 중요한 부분이겠네요! 이 blend에 무엇이 쓰일 수 있는지 살펴봅시다.



(https://docs.unity3d.com/kr/530/Manual/SL-Blend.html)


여기서 저는 SrcAlpha(*SourceAlpha의 준말) 와 OneMinusSrcAlpha를 사용해 전통적인 반투명 셰이더를 만들었네요. 이것들이 구체적으로 어떤 연산 과정을 거쳐 적용되어 결과물을 그리는지 더 알아봅시다.




(1) 첫 번째 인자와(SrcAlpha) Source를 곱한 것

(2) 두 번째 인자와(OneMinusSrcAlpha) 배경,Destination을 곱한 것


(1)과 (2)를 구한 후 둘을 더한 결과물을 그린 것을 출력합니다. 


이미지로 다시 봅시다.



이렇게 첫번째 인자와 두번째 인자에서 연산한 결과물을 더해 나온 것을



최종적으로 화면에 그려줍니다!



엔진 내에선 위와 같이 그려집니다.


알파가 어떤 식으로 연산되어 출력되는지 살펴보았습니다. 

blend의 첫번째 인자, 두번째 인자에 무엇을 넣든 같은 방식으로 연산됩니다. 당연히 결과물은 다르겠죠! 입력한 인자에 따라 다양한 효과를 줄 수 있을테고요. 



blend ~ (인자) (인자) 에 대해서도 어느정도 알게된 것 같습니다. 그러면 마지막 하나가 남았네요. 



(3) Z Write 


이름 그대로 살피자면 'Z를 쓴다, 작성한다.' 인데요. 


위에서 Z Buffer에 대해 이야기 할 때 Z Buffer 에서는 "깊이에 따라 가깝고 먼 것을 판단하고","가까이 있는 것이 먼 것을 가린다" 즉, 가까이 있는 것이 먼 것을 덮어 그린다. 라고 이야기했지요.


여기서 '가까이 있는 것이 자신의 Z위치를 덮어 기록하는 것' 을 Z Write라고 부릅니다.



상단의 커스텀 알파 블렌드 셰이더 코드에서 Zwrite Off를 써주었던 것을 기억하시나요?

Zwrite는 On/Off 해줄 수 있습니다.


Zwrite On은 Zwrite를 해준다는 뜻이겠지요? 기본적으로 ON이 되어있을테고요. 대부분의 오브젝트, 불투명인 것들은 모두 Zwrite를 거쳐야 할 테니까요.





z write on을 해보았습니다. 모든 픽셀이 충실하게 자신의 Z값을 기록합니다. 그리고 Z버퍼에 대해 이야기 할 때 언급한 문제점...오브젝트가 뚫려 보이는 현상이 나타나네요. 앞에 위치한 녀석들이 쿼드 크기 만큼의 Z값을 차지하고 값을 덮어버리니까요. 

실제로 보니 더 끔찍합니다.




그러면 z write off 를 해봅니다. 각 픽셀은 Z값을 가지지 않은 채로 그려집니다. 피봇 문제가 보이고...앞뒤 구분이 엉망진창이긴 하지만 잘 그려지긴 합니다. 


반투명인 오브젝트는 적절히 z write off를 사용해주어야 겠습니다.



응용: 반투명 캐릭터


캐릭터에 반투명 셰이더를 적용해야 할 때가 있습니다. 은신 스킬을  사용한다거나, 시점 변화 때문에 필요하다거나.... 한 번 해볼까요?



안 쪽이 뚫려보여서 무섭습니다. 제가 바란 건 이런게 아닌데요...아무도 이런 걸 바라진 않겠죠...

깔끔한 반투명을 만들고 싶습니다...


2pass를 활용해 도전해봅시다.


1pass에는 아무것도 그리지 않고, Z버퍼(깊이 값)만 가지도록 합니다.

2pass에서 일반적인 반투명을 구현합니다. 



zwrite on을 하면 z버퍼값을 그리겠지요. 

여기서 ColorMask 0 을 사용해주는데, 이는 해당 패스에서 모든 채널의 렌더링을 끄는 역할을 합니다. 그러니까..아무것도 그리지 않습니다. 

1pass가 하는 기능은 딱 하나입니다: z버퍼값을 씁니다. 다른 모든 일반적인 불투명 오브젝트 처럼요.




2pass에선 평범한 반투명 셰이더를 구현합니다. float 변수를 하나 선언하여 Alpha값에 넣어주었습니다.



_Blending 변수 값을 통해 투명도를 제어할 수 있게 되었습니다. 게다가 아주 깔끔합니다! 1pass에서 그린 Z버퍼 값 덕분에 뚫림 현상이 사라졌네요. 


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
60
61
Shader "Custom/Alpha"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Blending ("alpha",Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        
        //1pass : 버퍼만 그려준다
 
        zwrite on
        ColorMask 0 //모든 채널의 랜더링 무효화 - 안그린다.
 
        CGPROGRAM
        #pragma surface surf cus 
 
        struct Input
        {
            float2 uv_MainTex;
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            
        }
 
        float4 Lightingcus(SurfaceOutput s, float3 lightDir, float atten)
        {
            return float4 (0, 0, 0, 0);
        }
 
        ENDCG
        
        //2pass : 알파 블렌딩 구현
        zwrite off
        
        CGPROGRAM
        #pragma surface surf Lambert alpha:blend
 
        sampler2D _MainTex;
        float _Blending;
 
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = _Blending;
        }
        ENDCG 
    }
    FallBack "Diffuse"
}
cs


전체 코드입니다.




'강의 필기 > 3-1' 카테고리의 다른 글

<13> 엔진 활용  (0) 2020.07.07
<11> 엔진 활용  (0) 2020.06.23
<10> 엔진 활용  (0) 2020.06.16
<9> 엔진 활용  (0) 2020.06.07
<8> 엔진 활용  (0) 2020.05.31
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
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
글 보관함