안녕하세요, 흑기사입니다.

 이번 시간에는 오브젝트를 반투명으로 그려볼까 합니다. 지난 시간 마지막에 작성한 셰이더 코드를 약간 수정하여서 반투명 구(sphere)를 한번 그려보도록 하겠습니다.

 자, 우리가 오브젝트를 반투명으로 만들기 위해서 가장 먼저 해야 할 것은, 바로 블렌딩에 대한 디바이스의 렌더 상태를 변경해주는 것입니다. 그러니까 이 말은 앞으로 곧 렌더링할 오브젝트의 결과색상값과 이미 버퍼에 있는 색상값(배경색상)을 어떻게 섞을 지를 명시해줘야 할 필요가 있다는 뜻입니다. 만약 명시하지 않으면(지금까지와 같이), 블렌딩은 비활성화(디폴트)되어 있으므로 결국 오브젝트 렌더링 시에 결과 색상값을 그냥 버퍼에 덮어쓰므로 반투명을 표현할 수 없게 됩니다. 따라서 우리는 이것을 위해서 Blend 라는 ShaderLab 커맨드를 사용해서 렌더 상태를 블렌딩이 활성화되도록 지정해줘야 합니다:

 Blend SrcFactor DstFactor 

 위의 코드는 Blend 커맨드의 가장 기본적인 구문입니다. (다른  형식의 구문도 있지만, 여기서는 설명생략)
 이 구문을 해석하자면,
 (이제 나올 결과 색상값 x SrcFactor) + (이미 스크린(버퍼)에  존재하는 색상값 x DstFactor)
 이렇게 색상을 섞어서 최종 색상값을 결정하겠다는 뜻입니다. SrcFactor와 DstFactor 자리에는 우리가 직접 속성을 지정해주어야 하는데요. 사용할 수 있는 속성들은 아래와 같습니다:

 One, Zero,  
 SrcColor, SrcAlpha,  
 DstColor, DstAlpha,   
 OneMinusSrcColor, OneMinusSrcAlpha,  
 OneMinusDstColor, OneMinusDstAlpha 

 결국 우리는 이것들을 사용하여 블렌딩 연산을 지정하게 되는 것입니다. 뭔가 많아 보이고 복잡해 보이는데요... 사실 속성의 이름들을 하나하나 살펴보면 그 의미는 직관적으로 쉽게 알 수 있습니다. 어떤 세부 설명이 필요하다면, 유니티 메뉴얼을 통해 자세한 내용을 참조하실 수 있습니다. (일단 이번 글에서는 스킵해도 상관없습니다.)
 이제 당장 우리가 필요한 속성들만 취해서 구문을 작성해 보겠습니다. 우리는 아주 기본적인 알파 블렌딩을 하는 것이 목표이므로, SrcFactor에는 오브젝트 알파값을 지정하고 DstFactor에는 (1-오브젝트 알파값)을 지정하여야 합니다. 
 따라서 현재 오브젝트 알파값을 의미하는 SrcAlpha 와  (1-오브젝트 알파값)을 의미하는 OneMinusSrcAlpha를 속성 팩터로 사용하여 블렌드 구문을 작성합니다:

 Blend SrcAlpha OneMinusSrcAlpha 

 이 경우에 만약 오브젝트의 알파값이 0.6 이라고 한다면, SrcAlpha = 0.6 이고 OneMinusSrcAlpha = 0.4 이므로 오브젝트 색상과 배경 색상은 60% 대 40% 비율로 섞여서 결국 오브젝트가 반투명하게 보이는 효과를 나타내는 것입니다. 

 블렌딩 설정은 쉽게 끝났군요. 그 다음은... 
 아, 오브젝트 알파값을 지정해줘야 겠군요. 우리가 직접 오브젝트의 알파값을 정해주면 재밌을 것 같습니다.
 에디터에서 알파값을 지정할 수 있도록 하면 되겠군요. 이건 알파값을 지정할 수 있게 프로퍼티를 추가해주면 되겠죠. 
 이전에 Main Color 라는 프로퍼티를 추가했다가, 라이팅 연산을 없애면서 이 프로퍼티도 함께 삭제했었는데요. 알파값 지정을 위해서 다시 이 프로퍼티를 살리도록 합니다:

Shader "2Textures SemiTrans" { 
    Properties { 
        _Color ("Main Color", COLOR) = (1,1,1,1) 
        _MainTex("Texture"2D) = "white" {} 
        _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
        Pass { 
                 Blend SrcAlpha OneMinusSrcAlpha 
                
                 SetTexture [_MainTex] { 
                        Combine texture 
                 } 
                                                                       
                 SetTexture [_SubTex] { 
                        Combine texture lerp(texture) previous
                 } 
        } 
    } 
} 


자, 아직 코드가 완성되지 않았습니다. 
프로퍼티로 다시 추가한 _Color의 알파값을 사용하는 곳이 아직 없군요... 
이제 우리는 두번째 SetTexture 블럭을 다음과 같이 수정하려 합니다.

SetTexture [_SubTex] { 
          ConstantColor[_Color] 
          Combine texture lerp(texture) previous, constant 
}

 윽, 당황하지 마시기 바랍니다. 갑자기 뭔가 복잡한 코드가 추가되어 혼란스러울 수 있지만, 막상 내용은 별 것 아닙니다. 
 우선 Combine 커맨드를 주목하시기 바랍니다. 콤마(,)가 추가되었군요. 이건 대체 뭘까요? 
 Combine 커맨드에서 우리는 콤마(,)를 이용하여 색상값 연산과 알파값 연산을 분리할 수 있습니다. 분리한다는 말은 결국.. 콤마를 사용하지 않았던 이전에는 색상과 알파 연산이 동일하게 적용되었다는 얘기입니다. 그렇습니다... 만약 연산을 따로 콤마(,)를 사용하여 구분하지 않는다면, 색상(rgb)과 알파(a) 모두 하나의 Combine 연산을 통해서 계산합니다. (지금까지 그래왔겠군요.)
 그런데 우리는 임의로 직접 어떤 알파값을 지정해주길 필요가 있기 때문에, 일단 알파에 대한 연산을 따로 구분하기 위해 콤마(,)를 넣은 것입니다. 그 다음 알파연산 만을 위한 구문을 따로 작성했습니다. 위의 예제의 경우, 별다른 연산이 없이 constant 만 있는 걸 보니, 그냥 constant 라는 것을 알파값으로 사용한다는 의미겠군요. 여기서 그럼 constant는 또 뭘까요? constant는 단어 뜻 그대로 그냥 어떤 상수값을 말합니다. 그게 어떤 상수값인지는 ConstantColor 라는 커맨드를 사용하여 지정합니다. 그러니까 ConstantColor (1,1,1,1) 이런 식으로 지정해주면, constant 값은 결국 (1,1,1,1)이 되는 셈이지요. 그런데 우리는 하나 더 나아가서 프로퍼티 _Color값을 알파값으로 이용하려 하므로 ConstantColor [_Color] 라고 지정한 것입니다. 결과적으로 constant는 _Color값이 되는 것이고, 마침내 우리는 constant의 알파값을 최종 알파값으로 사용하도록 지정했습니다.

지금까지 내용을 정리한 코드는 다음과 같습니다:

Shader "2Textures SemiTrans" { 
    Properties { 
        _Color ("Main Color", COLOR) = (1,1,1,1) 
        _MainTex("Texture"2D) = "white" {} 
        _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
        Pass { 
                 Blend SrcAlpha OneMinusSrcAlpha 
                
                 SetTexture [_MainTex] { 
                          Combine texture 
                 } 
                                                                       
                 SetTexture [_SubTex] { 
                          ConstantColor[_Color] 
                          Combine texture lerp(texture) previous, constant 
                 }
        } 
    } 
} 

그 결과는 이렇습니다:


 반투명이 제대로 적용했는지를 확실히 구문하기 위해서 더 먼 뒷쪽에 다른 두 오브젝트를 추가했는데요. 반투명 처리가 잘 적용되어 보입니다. 인스펙터 창에서 Main Color의 알파값을 조절해보면 구(sphere)의 반투명 정도가 변하는 게 느껴지는군요... 잘 구현된 것 같습니다!

 어라.... 근데 좀 들여다 보니까 약간 이상한 부분이 바로 눈에 들어옵니다!
 뒤쪽에 배치된 노란색 큐브를 주목해보면, 초록색 캡슐 오브젝트와는 달리 구(sphere)와 시야에서 겹치는 부분의 색상이 제대로 나타나지 않는다는 것을 쉽게 알 수 있습니다. 왜 이런 일이 생겼을까요? 
 이미 어느 정도는 벌써 눈치채셨으리라 생각이 듭니다. 우리는 지금 반투명 오브젝트를 그리는 중이고, 반투명 오브젝트는 일반적인 불투명한 오브젝트와는 다르게 그리는 순서에 큰 영향을 받지요. 그런데 위의 경우에 반투명 구  (sphere)를 일반적인 불투명한 오브젝트와 함께 그렸기 때문에 문제가 발생하게 되었던 것입니다. 어떤 오브젝트가 먼저 그려질 지 우리가 알 수 없기 때문입니다. 위의 결과로 볼 때에 노란색 큐브가 구(sphere)보다 늦게 렌더링 되어버려서 이슈가 생겨 버렸군요.
 이걸 해결하기 위해서는 모든 불투명 오브젝트들이 렌더된 한 후에 반투명 오브젝트를 렌더링되도록 하면 깔끔하게 정리가 됩니다. 
 이를 위해서 우리는 렌더링 순서 지정을 위해서 Queue 태그라는 것을 사용할 것입니다. 


 
 Queue 태그는 렌더링 순서를 결정하는 데에 사용하는 태그입니다.
유니티에서는 렌더링 순서를 구분하는 가장 일반적인 케이스를 고려하여, 다음과 같이 5가지의 큐를 미리 정의해 놓았으며, 아래의 큐 순서대로 렌더링을 하게 됩니다.

1) Background 
2) Geometry (디폴트) 
3) AlphaTest
4) Transparent
5) Overlay

 우리가 셰이더에 아무런 Queue 태그를 지정하지 않으면, 유니티는 해당 셰이더를 디폴트로 Geometry 큐로 지정합니다. 그런데 만약 필요하다면, 우리가 해당 셰이더에 5개 중 하나의 큐를 직접 지정해 줄 수 있습니다. 그렇게 되면 결국 렌더링 순서를 제어할 수 있게 되겠죠.
 지금 우리가 작성하는 셰이더의 경우, 반투명 오브젝트를 렌더링해야 하기 때문에 Geometry 큐보다 늦게 렌더링되는 큐로 지정해줘야 할 필요가 있습니다. 마침 적당한 큐가 눈에 띄는군요. 바로 Transparent 큐입니다. 이름대로 이 큐(queue)는 반투명 렌더링되는 경우를 위해 미리 정의되어 있는 큐(queue)입니다. 따라서 우리는 다음과 같이 ShaderLab의 Tags 구문을 이용하여 Queue 태그를 새로 지정할 수 있습니다:

 Tags { "Queue" = "Transparent" } 

이렇게 추가하면 우리는 반투명 구(sphere)를 다른 불투명한 오브젝트보다 더 늦게 렌더링하게 됩니다. 또한 Transparent 큐 안에서도 오브젝트들이 거리에 따라 내부적으로 정렬되므로, 더 이상 렌더링 순서에 따른 문제는 발생하지 않게 됩니다. 이렇듯 우리는 큐를 지정하여 렌더링 순서를 원하는대로 제어할 수 있습니다!

자, 그럼 다시 우리가 작성하던 코드로 돌아오면:

Shader "2Textures SemiTrans" { 
    Properties { 
        _Color ("Main Color", COLOR) = (1,1,1,1) 
        _MainTex("Texture"2D) = "white" {} 
        _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
  Tags { "Queue" = "Transparent}
        Pass { 
                 Blend SrcAlpha OneMinusSrcAlpha 
                
                 SetTexture [_MainTex] { 
                          Combine texture 
                 } 
                                                                       
                 SetTexture [_SubTex] { 
                          ConstantColor[_Color] 
                          Combine texture lerp(texture) previous, constant 
                 }
        } 
    } 
} 

 여기서 잠깐 하나만 더~.
우리는 Queue 태그를 지정할 때에 SubShader의 Tags를 사용했다는 것을 주의하시기 바랍니다. 이 언급을 하는 이유는 바로 ShaderLab 구문의 Tags는 SubShader 뿐만 아니라 Pass의 Tags도 있기 때문입니다. 그러니까 ShaderLab에는 SubShader Tags와 Pass Tags가 각각 존재하기 때문에 이 둘을 혼동하지 않도록 합니다. (Pass 태그는 나중에 배울 기회가 있을 것입니다.)

 그럼 이제 결과를 한번 보겠습니다:


이제 제대로 그려지는군요. 보기 좋습니다!

끝으로 Queue 태그에 대해서 하나만 더 이야기하고 마치겠습니다. 
앞서  유니티에는 미리 정의된 렌더링 큐(queue)가 5개 있다고 설명드렸고, 필요하면 다른 큐로 지정할 수 있다고 하였습니다. 그런데 이 렌더링 큐는 5개만 존재하는 것은 아니고, 필요에 따라서 직접 정의할 수 있습니다. 이 큐들은 유니티 내부에서 정수 인덱스로 표현됩니다. Background 는 1000, Geometry 는 2000, AlphaTest 는 2450, Transparent 는 3000 그리고 Overlay 는 4000 입니다. 그리고 큐(queue) 인덱스값 순서가 곧 렌더링 순서와 동일합니다. 따라서 우리가 만약 필요하다면 적당한 큐 인덱스를 정의하여 어떤 필요한 타이밍에 렌더링이 가능하도록 할 수 있습니다.
 예를 들면:

 Tags { "Queue" = "Geometry+1" }

 이렇게 정의하면 오브젝트는 2001번 렌더 큐에 속하게 되는 것이고, Geometry 큐(2000)와 AlphaTest 큐(2450) 사이에서 렌더링되는 것입니다.

 오늘은 여기까지 입니다.
 감사합니다~

Posted by 흑 기사
,