본문 바로가기
Shader

14주차, 알파를 알아보자.

by JDCM 2024. 6. 10.

쉐이더를 알아보자 시리즈는 이 14주차를 기반으로 마지막입니다.

물론, 더 많은 쉐이더 공식을 알게 된다면 추가로 올리지만 '알아보자' 시리즈는 여기서 종료입니다 👍

 

긴 여정 수고 많으셨습니다! 자 그럼!

 

 

호적을 파야만 했었던 알파, 도대체 어떤 것일까?

 

 

1. 알파의 베이스 알기

 

지금까지 배운대로 알파를 작동시키려 한다면…

 

Tags { “RenderType” = “Transparent” “Queue” = “Transparent” }

o.Alpha = (특정 값);

 

이 들어가야만 했다.


문제는 이렇게 하게 되면 알파가 들어갔음에도 불구하고 바닥에 그림자가 남았다.

 

이럴 때 해결 방법이 있는데, 바로 최하단에 있는

FallBack “Diffuse”값에

FallBack “Transparent”를 넣어주면 된다.

 

그럼, 그림자가 생기던 문제는 1차 해결이다.

더 보도록 하자!

1-1. Buffer?

엥? 버퍼링의 그 버퍼-요?

 

 

상단의 코드 그대로 Plane에 Alpha 값이 있는 것을 가져와보았다.

 

여러개를 겹쳐도 딱히 문제 없이 잘 작동 되고, FallBack도 “Transparent”이므로 문제 없이 그림자도 비추지 않는다. 그런데 여기서 큰 문제 하나가 생긴다…

 

 

옆으로 옮기는 순간 튀어나온다.

 

왜 그럴까?

이게 바로 Buffer(버퍼)의 문제이다.

 

버퍼라는 것은 …

[  어느 물체를 보이게 할지 말아야 할지에 대한 가시도 문제의 한 해결책으로 쓰인다  ] 

라고 위키백과에 적혀있는데, 쉽게 말하자면 ...

우리가 보고 있는 모니터 뒤에서,

아직은 보이지 않지만 어딘가 뒤에서 그려지고 있는 다른 데이터 요소들!

...이라고 보면 된다.

 

Buffer에서는 종류가 굉장히 많은데,

  1. Z Buffer(Depth Buffer)
    : 어떤 물체가 그려질 때 만들어진 픽셀의 깊이 정보의 버퍼
  2. Stencil Buffer
    : 특정 픽셀들이 후면버퍼에 기록되지 않도록 하는 버퍼
  3. G Buffer(Geometry Buffer)
    : 디퍼드 랜더링에서 사용되는 버퍼
  4. Back Buffer
    : 모니터 뒤에서 프레임 레이트를 보정해주는 버퍼

그러나 우리가 상단에서 맞닥뜨린 버퍼 문제는 Z Buffer(깊이 버퍼)의 문제이다.

오브젝트의 깊이 값이 어떻게 처리가 되는지, 왜 저런 문제가 생기는지 더 알아보자.

 

1-2. Z Buffer가 뭐지?

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

 

깊이 버퍼란 상단에서 설명했 듯이 각 오브젝트간의 깊이를 나타낸다.

이 깊이를 따지는 과정에서 Z 값을 사용하는데, Z값이란…

 

https://www.youtube.com/watch?v=TsjYPu7piY0

 

결국 모니터를 바라보는 우리의 눈을 대신해주는 것은 카메라다. 

비록 모니터는 평면이라 할지라도, 그래픽에서는 XYZ축을 가지고 있다.

(저번 ScreenPos에서도 Z는 존재했지만 우리가 사용하지 않았을 뿐이다.)

 

XY축이 좌표를 가진 것처럼, Z축도 좌표를 가진 것이다.

만약 Z 좌표에 순번이 있다고 생각해보자.

 

각각 한 픽셀당 순번을 매겼다고 추측, 회색 세모는 카메라 뷰다.

 

이제 저 상태에서 동그라미 A군세모 B군이 각각 2와 3에 그려졌다고 치자.

 

 

그럼 당연히 카메라 뷰와 가까운 A가 먼저 보이고, B가 그 뒤로 보여지게 된다.

왜냐면 2번에 파란색이 그려진 그 위치는 A가 이미 차지했기 때문에,

3번이 그려질 픽셀이 남지 않았기 때문이다.

 

다만, 그래픽에서는 A를 먼저 그리고 남는 자리에 B를 그린다.

즉! Z값이 카메라에서 가장 가까운 값인 오브젝트를 먼저 그리는 것이다.

 

아하, 즉 Z값에는 깊이를 구별하기 위한 숫자가 정해져 있고... 

...

어라? 숫자가 정해져 있다면, 그 숫자를 0~1 사이로도 가능하게 할지도 모른다.

그럼, 알파 처리처럼 흑백(0~1)처리로도 가능한 것이다!!!



또 또 문제는…

그렇게 되면 Z값의 위치가 0.50과 0.51과 같은

픽셀들이 … … ...정말 미세한 오차 범위 안에 있다면…?

모니터 오니쨩의 자리는 내 것이라구!!

 

그렇다, 각 픽셀마다 겹치면서 나타내지기 위해 마구 “싸운다.”

놀랍게도 정식 용어가 Z Fighting(제트-파이팅) 이다. (모에)

 

아무튼 …

 

이 값들을 나타내기 위해 Z값을 0~1 사이로 나타나게 된 것이 Z Buffer이다.

 

  1. Z 버퍼는 깊이 값을 나타내는 버퍼 이미지이다.
  2. 물체는 그려질 때 Z 버퍼에 자신의 픽셀 위치 값을 쓴다.
  3. 자신보다 앞 부분의 Z 버퍼에 값이 쓰여져 있으면 그 픽셀은 그리지 않는다.
    - 출처: 정교수님의 PPT (너무 좋은 정리라 가져왔습니다.)

 

1-3. Alpha sorting?

동그라미 A군세모 B군이 있었고, 각각 잘 나타나고 있었다.

그런데, 동그라미 A군의 오퍼시티가 50%가 되어버린다면…?



우선 뒤에 있던 B군과 함께 섞어 그리면...

 되겠지? 싶긴 합니다만…

 

        상단에서 이야기 했었 듯…

   2번 자리는 A군이 차지하고 있어서,

  그 뒤에 있는 B군이 그려지지가 않습니다.

 

     이게 무슨 소리에요!

 

반투명을 주었어도 A군이 2번에서의 구역을 차지하고 있으므로, 3번의 B군은…(RIP)

 

A군이 가지고 있는 Plane의 값이 픽셀들을 전부 차지하고 있기 때문에,

B군은 A군이 차지하고 있다고 생각하기 때문에 그려지지 않는 것이다.

 

그럼… 어떻게 해결해요?

 

일종의 투명 필름처럼 “차곡차곡 쌓기 방식(알파 소팅)”으로 하면 된다.



불투명을 먼저 다 그린다.

불투명이기 때문에 순서는 상관이 없다.

나머지 반투명인 친구들은 그 위로 차곡차곡 쌓아 올리는 것이다.

 

그럼 뒤에서 천천히 쌓아 올린 불투명들은 상단의 문제 없이 같이 겹치게 보이게 된다.




근데, 문제가 있다.

 

  1. 최적화 문제 / (오버드로우, Overdraw) 문제
    불투명인 친구들은 앞에 큰 불투명이 있으면 뒤에 것이 그려지지 않지만…
    반투명인 친구들은 100개가 겹쳐있다면 100번 다 그려야 하는 것이다.

    겹쳐 그려지게 되면 그럼 Z값을 연산하고
    순서대로 차곡차곡을 위해서 줄을 세워야(소팅) 할 것이다.
    … 무식하게 그리게 된다.

 

아 이제 해결인가...



어 ㅡ ?

 

1-4. Alpha sorting의 문제점

알파가 들어가게 되면 차곡차곡 줄을 세운다(소팅한다)고는 했다.

근데 모든 오브젝트들이 반투명이고 줄을 세웠을 때…? 

 

누가 뒤에 있는거지?

 

이게 무슨 소리냐면,

 

이 그림에서 모든 오브젝트들이 반투명이라 판단했을 때,

앞과 뒤를 구별할 때 무엇이 기준이 되는가? 이다.

 

정답을 먼저 얘기하자면, 물체의 위치는 피봇 점이 기준이 된다.

즉, 카메라와 피봇 사이간의 거리가 앞과 뒤를 구별하게 되는 것이다!



즉 이렇게 옆으로 툭 튀어나오게 되는 것도, 카메라와 피봇간의 거리의 차가 좁혀졌기 때문에 그렇다.

 

피봇 점이 기준이기 때문에 컴퓨터 그래픽스의 기준에서는 선택한 오브젝트가 더 앞인 줄 아는 것이다 (...) 

(쟤네의 잘못이 아니에요.)

 

이걸 해결할 수는 있나요?

 

없어요. 아직 찾지 못했습니다.

걱정마세요 … 우리는 눈속임 정도는 할 수 있습니다.

 

  1. 물체 잘게 쪼개기
    저번 시간에 물을 만들 때, Plane을 여러개로 쪼갠 것도 바로 그 이유!
    만약 피봇이 중앙에만 떡 하니 박혀있다면 … (어우우)

  2. 알파 부분 최소화 하기
    모델링을 최~소화 해서 알파가 잘 티나지 않게 겹치기.

  3. 알파 테스팅 사용하기
  4. Z Write 사용 안 하기
    (이 둘은 하단에 더 설명할게요.)

  5. 랜더링 레이어 만들기
  6. 여러 Pass 사용하기 … 등등

 

그럼 급하게 알파 소팅(Alpha Sorting)을 정리해보자면...

 

  1. 불투명을 먼저, 반투명을 나중에 그린다.
  2. 반투명은 따로 모아 뒤에서 차곡차곡 겹쳐 올려 그린다.
  3. 어떤 것이 뒤에있는지는 완벽하지 않다. (피봇 기준)
  4. 각종 눈속임이 존재한다.
    - 출처: 정교수님의 PPT (너무 좋은 정리라 가져왔습니다.)

하단에 더 설명하기로 한 알파 테스팅과 Z Write 사용 안하기를 조금씩 더 봐보자!




1-5. Alpha testing?

알파 소팅이랑은 무엇이 다른거지?

 

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

 

우선 알파 테스트의 코드 먼저 살펴보자.

 

Properties{

_Cutoff (“Alpha cutoff” , Range(0,1)) = 0.5
}

SubShader{
Tags { “RenderType” = “TransparentCutout” “Queue” = “AlphaTest” }

CGPROGRAM
#pragma surface surf Lambert alphatest:_Cutoff

…}
ENDCG
}
Fallback “Legacy Shaders/Transparent/Cutout/VertexLit”
}

 

뭔가 신기한 것들이 많지만,

  1. RenderType에 “Transparent”가 아닌 “TransparentCutout”을 썼다.
  2. Queue에 AlphaTest를 사용했다.
  3. alpha:blend 대신에 alphatest:_Cutoff 로, Properties의 값을 사용했다.
  4. Fallback에서 보지 못했던 것을 사용하기도 했다.

 

음… 역시 잘 모르겠으니, 알파 블랜드를 했던 코드와

테스트를 적용한 코드 두개를 한 번 비교해보자.

 

 

Alpha Blend를 사용한 좌측과 달리 Alpha Test를 사용한 우측의 외곽이 좀 더 뚜렷(픽셀의 기준에서)한 것을 볼 수 있다.

 

근데 하단에 Fallback에서 썼던  “Legacy Shaders/Transparent/Cutout/VertexLit”는…?

 

기존 Shader에 존재하고 있던 Legacy Shaders에서 존재하고 있던 Cutout용

그림자 연산을 불러오기 위해서 사용되는 것이다.

“Transparent/Cutout/Diffuse" 라고 사용해도 큰 문제는 없었다.

 

문제는 “Transparent/Cutout/Diffuse"를 써도 그림자가 작동이 되지 않을 것이다.

왜냐면 그림자 색을… _Color로 받아오니까.

 

 

그렇다, Properties에서 _Color("color", color) = (1,1,1,1) 를 만들어주면 된다.

참고로 뒤에 그림자를 지는 것이 싫다면

alphatest:_Cutoff를 alpha:blend로 바꿔주면 된다.

놀랍게도 그림자를 받지는 않지만, 그림자를 가지고 있는 상태가 될 것이다.



1-6. Alpha testing의 장단점

[ 장점 ] 
  1. 앞뒤 문제가 쉽게 해결 된다.
  2. 버퍼(Buffer)가 확실하다.
  3. 그림자도 나름 해결 된다.
  4. 오버드로우 문제도 없다. (불투명과 동일시 된다.)

[ 단점 ]
  1. 반투명이 어렵다.
  2. 픽셀식으로 쉽게 끊어진다.

PC에서 쓰기 좋고, 모바일은 조금 힘들다.


2.
이펙트 쉐이더 만들어보기

우선 알파를 다시 한번 만들어 보면서 보자구요.

 

 

괜찮아보이지만 고질적인 알파 겹침 문제가 생길 것이다.

암튼 알파 블랜드(Alpha blend)를 만들어보았다!

 

자, 이제 이펙트를 생각해보자.

이펙트는 조명 연산을 대다수 하지 않는다. (조명이 있을 때도 있지만.)

조명을 받지 않기 위해선…


#pragma surface surf nolight alpha:blend

void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Emission = c.rgb;
o.Alpha = c.a;
}
...
float4 Lightingnolight(SurfaceOutput s, float3 lightDir, float atten)
{
return float4(0,0,0,s.Alpha);
}

 

light를 nolight로 설정하고, Emission에 넣어주게 되면 조명 연산을 받지 않는다.

그리고 이펙트는 앞뒤가 보이므로…


LOD 200
cull off //Two Side
...

 

그리고 까먹지 말자. FallBack은 “Transparent”!

이제 Surface를 위한 쉐이더 코드는 준비되었다.

 

2-1. 수동으로 alpha:blend 하기

그런게 가능하단 말이에요?

 

alpha:blend는 실제로 자동으로 작동되는데, 수정으로 해줄 수 있다.


LOD 200
cull off
blend SrcAlpha OneMinusSrcAlpha
//SrcAlpha는 소스 알파란 뜻!

CGPROGRAM
#pragma surface surf Standard keepalpha
//알파 유지용

 

이렇게 해주면…

엥?

 

소팅 문제가 생긴다.

괜찮다, 이것도 해결 방법이 있다!


LOD 200
cull off
zwrite off
blend SrcAlpha OneMinusSrcAlpha
//SrcAlpha는 소스 알파란 뜻!

CGPROGRAM
#pragma surface surf Standard keepalpha
//알파 유지용

 

상단의 소팅 문제만 해결한다고 했어요 (^^) 

뭐가 달라졌나요? alpha:blend를 Custom 했을 뿐입니다.

왜 Custom 하셨나요?

바로 우리가 추가한 코드에 추가적으로 붙일 수 있는 코드가 있습니다.

[ Blending Factor (블렌딩 팩터) ]

  1. One : 숫자 1을 의미
  2. Zero : 숫자 0을 의미
  3. SrcColor : 소스의 칼라를 의미
  4. SrcAlpha : 소스의 알파를 의미
  5. DstColor : 목적지(배경)의 칼라를 의미
  6. DstAlpha : 목적지(배경)의 알파를 의미
  7. OneMinusSrcColor : 1 - 소스 칼라를 의미합니다.
  8. OneMinusSrcAlpha : 1 - 소스 알파를 의미합니다.
  9. OneMinusDstColor : 1 - 목적지(배경)의 칼라를 의미합니다.
  10. OneMinusDstAlpha : 1 - 목적지(배경)의 알파를 의미합니다.

  11. keepalpha : 5.0의 버전부터 서피스 셰이더에서는 기본적으로 모든 불투명 셰이더에 알파에 1.0을 입력하도록 되어있다고 한다. 이것을 써주면 그 설정을 막아준다!
    (알파 채널 보존용)
    - 출처: 정교수님의 PPT (너무 좋은 정리라 가져왔습니다.)

 

저도 몰라요

그래도 정리해봅시다.

 

[ Texture Blending 옵션 ]
( factor ) * 소스 + ( factor ) * 배경

 

블렌딩 공식이라고 불리는데, 대표적인 공식을 보자면…

 

blend SrcAlpha OneMinusSrcAlpha // 알파 블랜딩
blend SrcAlpha One // Additive(알파와 함께)
blend One One // Additive (알파 없이, 검정이 투명)
blend DstColor Zero // Multiplicative
blend DstColor SrcColor // 2x Multiplicative
- 출처: 정교수님의 PPT (너무 좋은 정리라 가져왔습니다.)

 

 

우선 알파 채널이 있는 텍스쳐(Source, 소스)와 뒷 배경(Destination)이 있다고 보자.

 

블렌딩 공식이라고 불리는데, 대표적인 공식을 보자면…

(1) Alpha Blending

( SrcAlpha ) * Source + ( OneMinusSrcAlpha ) * Destination 

 

  1. ( SrcAlpha ) * Source
    우선 소스의 알파와 소스를 곱하고…
  2.  ( OneMinusSrcAlpha ) * Destination
    소스의 알파를 뒤집은 것과 배경을 곱해버리면…
  3. 1 + 2
    그리고 그 둘을 더하게 되었을 때 정상적인 알파 블랜딩이 나온다!

    이건 일반적인 알파 블렌딩과 다를 것이 없어요!

 

(2) Additive (with Alpha)

( SrcAlpha ) * Source + ( One ) * Destination 

 

  • ( SrcAlpha ) * Source
    우선 소스의 알파와 소스를 곱하고… (상단과 동일)

  • ( One ) * Destination
    배경에다가 1을 곱해버리면 … (동일한 배경이 나오겠죠?)
  • 1 + 2
    둘을 더하게 되면, 소스가 있는 부분만 밝게 빛나게 된다.


    잠시만! 이걸 어따 쓰냐고요?
    바로 이펙트 중에서도 폭★발 효과가 하얗게 나는 것은
    겹쳐도 티가 나지 않기 때문에 씁니다.

    One One, 즉 Additive (no Alpha, Black Transparent)도 동일하겠죠?


(3) Multiplicative

( DstColor ) * Source + ( Zero ) * Destination 

 

  1. ( DstColor ) * Source
    배경과 소스(텍스쳐)를 곱하고…
  2. ( Zero ) * Destination
    배경에다가 검정을 곱한 다음…
  3. 1 + 2
    둘을 더해주면?


이건 바로 Multiply! 알파를 절대 안써요. (멀티 공식인 만큼!)

이렇게 다양한 방식으로 커스텀이 가능하다!
저 blend를 통해 조작하면서 이펙트를 다양하게 만들 수 있기도 하다.

오버레이나 어두울 땐 Multi, 밝을 땐 Additive 등 다양하게 이용되면서
기본 Texture Blending 옵션 기반으로 다양한 시도를 할 수 있다.

참고로 Struct에 float3 color COLOR; 와 함께 버텍스 컬러를 이용함으로써 파티클 컬러들을 마구 사용할 수 있다. (이펙트 신기하다…)

 

 

3. Z Write 알기

3-1. Z Write 개념부터 알기

아까 배웠던 Z값을 기억하는가?
이번엔 Z 위치에 설정하지 않는 방식을 알아보자!

이게 또 뭔 소리야 했겠지만, 아까 A군 B군처럼 2, 3과 같이

자리를 사용할 때 위치의 값을 넣는 것이 아니라…

자리를 사용하긴 하지만 위치의 값을 설정하지 않는 것을 뜻한다.

왜 쓰나요?

  1. 반투명을 제대로 나오게 하기 위해서
  2. 이펙트 쪽에서 자주 쓰므로
  3. 다른 아티스트가 직접적으로 쓰지 않기 때문에
  4. 개념을 알면 문제 대처 능력이 향상된다
  5. Shader로 반투명을 만드면 쓸 일 있다
    - 출처: 정교수님의 PPT (너무 좋은 정리라 가져왔습니다.)

 

Z Write에는 크게 두가지가 있습니다.

  1. Zwrite On
    그리면서 Z 버퍼에 써두겠습니다.
  2. Zwrite Off
    그리면서 Z 버퍼에 안 써두겠습니다.


3-2. Z Write On

그리면서 Z 버퍼에 써둡니다. 이게 디폴트.
상단에 언급 했었던 A군 B군과 동일합니다.
반투명이 아닌 경우에도 기본 설정으로 쓰이며,  앞뒤로 그릴 때 둘 다 사용됩니다.

문제는 소팅 과정에서 Plane의 문제를 겪겠죠?

3-3. Z Write Off

그리면서 Z 버퍼에 써두지 않습니다. 다른 것들이 마구 겹치게 됩니다.
문제가 될 것 같지만… 오히려 해당 방법이 솔루션이 될 때도 있습니다.



마구 겹쳐진 상황이지만 이펙트라니 볼만하다.
(이런 꼼수가!)

 

( 각 이미지들의 출처: https://catlikecoding.com/unity/tutorials/rendering/part-11/ )

 

3-4. 반투명 소팅의 해결법


어떻게 하면 해결할 수 있을까요?
업계에서도 사실 해결하지 못한 부분이기 때문에 어쩔 수 없습니다…

 

  1. 가능한 알파 테스팅 이용하기
    물론 캐릭터에겐 어렵다. 몸 끄트머리가 투명한 친구는 웬만하면 만들지 말아주세요… (귀신…)

  2. 어렵다면 적절하게 Z write Off 사용하기
    이펙트에게선 기본, 100% 해결책 아님!

  3. ...ETC.  힘내봅시다!


Shader, 자주 쓰는 설정 알기


사실 이건 제가 나중에 보고 싶어서 써둡니다 (ㅎㅎ)

Fragment Shader와 설정이 겹칩니다!

4-1. Cull

[ Cull Back / Front / Off ]
- Backface culling control. 앞뒷면 보이기 안보이기 씁니다.
  2 Pass 아웃라인에서 사용했던 방식입니다. 2 Side를 제어합니다.

4-2. Fog

[ Fog { Mode Off } / Fog { Color (0,0,0,0) } }
- Fog를 받지 않도록 합니다. 또는 Fog 컬러를 특징적으로 제어할 수 있습니다.

4-3. FallBack

전체 쉐이더를 복잡하게 써뒀을 때, 대용품으로 쓰일 쉐이더를 찾아주는 용도입니다.

- Legacy/Diffuse가 디폴트이고, 쉐이더가 돌아가지 않을 때 대신 쓰일 셰이더를 설정합니다.

4-4. Tag

Rendering 순서(Queue) 명령을 만들 수 있습니다. 

Background: 1000
Geometry: 2000 (Default) 
AlphaTest: 2450
Transparent: 3000
Overlay: 4000


Tags { “Queue” = “Geometry + 1” }
: 숫자를 더해주어서 순서를 강제 조절할 수 있다.

         더할 경우 모든 것들 중에 가장 늦게 그려지는 것
         뺄 경우 모든 것들 중에서 가장 빠르게 그려지는 것

4-5. Properties

각 Properties당 추가 기능을 사용할 수 있습니다.

  1. [HideInInspector]
    : 존재는 하지만 인스펙터에는 보이지 않는 경우
    C 언어에서 public 과 private 중 private와 같은 경우입니다.

  2. [NoScaleOffset]
    : 텍스쳐의 UV와 옵셋 메뉴가 없음
    Scale이나 Offset을 건드릴 수 없는 텍스쳐를 넣을 수 있습니다.

  3. [Normal]
    : 텍스쳐가 노말맵이 들어올 것을 예상

  4. [HDR]
    : Color 앞에 썼을 경우 해당 Color가 HDR 설정이 가능

  5. [Toggle]
    : Toggle 메뉴가 생김(체크박스), 0과 1의 값으로 처리

  6. [KeywordEnum(name1,name2,name3)]
    : 각 이름마다의 Enum(Menu)가 생김, 각각 0, 1, 2 값으로 처리

  7. [Space]
    : Properties의 사이 간격을 뗌

  8. [Space(number)]
    : 숫자 간격 만큼 많이 뗌

  9. [Header(A group of things)]
    : 상단에 볼드체 제목이 생김 (개인적으로 간지난다고 생각합니다.)