본문 바로가기
Shader

13주차, 물을 더 만들어보자.

by JDCM 2024. 6. 10.

이전 블로그 게시글을 올린지 아슬아슬하게 1주년이 되기 전에...이제서야 13주차를 올립니다.

알아보자 시리즈는 14주차가 마지막인데...연달아 올려보겠습니다 ㅎㅎ


비루한 블로그에 자주 찾아와주셔서 감사드리고 쉐이더를 배우시는 모든 분들을 응원합니다!

 

 

1. 물의 굴절 알기 

https://www.hisuzanne.com/

 

물은 빛의 굴절이 생긴다! 흐르는 물은 그 굴절이 더 뚜렷하게 보일 것이다. 

빛의 굴절을 직접 나타낸다 한다면 무척 어려워지니, 속임수를 알아보도록 하자!

1-1. screenPos 알기

WorldPos는 배웠는데, ScreenPos라면 혹시…

 

 

예상하는 대로, Screen, 즉 바라보는 방면(화면)의 Position이다.

이것으로 무엇을 할 수 있을까? 우선 확인해보자!

옆에는 WorldPos의 Kyle

 

Struct에 screenPos를 불러와 Emission에 불러왔다.

만약 엔진 내에서 이리저리 돌려보면 UV의 형태가 화면의 방향에 따라가는 것을 확인할 수 있을 것이다!

 

문제는 화면을 따라서 Perspective 된 것처럼 따라가는데,

이는 화면의 원근감이 적용되고 있기 때문이다.

 

우리가 screenPos를 불러올 때, float4 형식으로 불러오는 것도 원근감을 위해서인데

단순화하여 UV만 가져올 필요가 있다. 이는 공식이 따로 있다!

 

 

rgb(xyz)값을 a(w)로 나누면 원근감(Perspective)이 사라지게 된다.

w(a, 즉 알파)에 거리값이 들어가있기 때문이다!

 

그치만 rg를 쓰면 되는 것 아니에요? 라고 말할 수 있지만, 공식이 따로 있는 것은 분명 이유가 있다.

(지금은 오류가 없지만 언젠가 나타날 수 있는 무시무시한 오류)

 

문제는 이 상황에서 어떻게 쓸 수 있을까?

바로 저 screenPos UV 위에 화면을 덮을 것입니다.

https://m.blog.naver.com/PostView.nhn?blogId=powernet_2&logNo=60180004444&proxyReferer=https:%2F%2Fwww.google.com%2F

바다 아래에 있는 소재의 굴절을 나타내기 위해 눈속임을 한다고 했다.

우리는 직접 아래 있는 오브젝트를 굴절시킬 수 없기 때문에, 

화면을 찍고, 그 화면을 일그러지도록 만들 것이다!

만약 screenPos의 UV를 일그러트린다면 … 

screenPos에 찍힌 화면의 모든 오브젝트들이 일그러지기 때문에,

오브젝트가 올라가더라도 찍힌 부분들이 일그러진다.

 

그럼 화면은 어떻게 넣을까? 

 

1-2. screenPos에 화면 넣기

https://answers.unity.com/questions/464746/grab-pass-alternative-frag-shader-that-applies-alp.html

 

문득 다시 기억해야 할 것은 툰 쉐이더에서 사용했던 2PASS 기법이다.

우리는 오브젝트를 두번 그리기 위해서

PASS 영역을 지정해서 PASS 항목 두 개를 사용했었다.

 

screenPos에 카메라(Main Camera)에 찍히는 이미지를 투영하려면 2PASS와 같이 특정 PASS를 만들어줘야 한다. 공식은 이 쪽이 더 쉽다!

 

 

그럼 투명해진 것을 확인할 수 있다.

당연하다, 보이는 것을 똑같이 찍고, 화면의 움직임에 따라 보여지고 있기 때문이다.

 

이제 이 상태에서 UV를 움직이고, 노말을 넣고, 바다에 넣어보면 된다!

 

 

잠시 장난치다가…

Z문제 없는 투명화도 가능하다. 

물 쉐이더 기본은 12주차를 보자: https://24dc-m.tistory.com/17

 

미리 보여주는 결과물! 만들어보자!

 

1) GrabPass{} 추가와  _GrabTexture; 추가

 

2) CubeMap과 _GrabTexture 가져오기


- refltex의 경우 : CubeMap 텍스쳐를 가져오는데, 프레넬 반사에서 “반사 될 부분” 에 들어가게 된다.
- grabtex의 경우 : 카메라에 찍히는 ScreenPos 텍스쳐를 가져오는데, 프레넬 반사에서 “반사되지 않는 투명한 부분”에 들어가게 된다.

GrabPass의 경우 o.Normal을 screenPos에 더해주었는데, 노말의 Time값에 따라 UV가 변화할 수 있도록 움직임을 넣어준 것이다.
다만 o.Normal 값과 같이 흘러갈 경우 난잡하기 때문에 0.2를 곱해 조정했다.

 

3) refltex와 grabtex를 LERP 하기



- 투명한 부분(0)은 grabtex가, 불투명한 경우(1)는 refltex가 나올 수 있도록 프레넬 반사(rim)에 lerp로 적용시켜준다.

 

4) 모든 쉐이더 코드 보기

 

여기서 하나 재미있는 점이 있다.



분명 물에 들어가지 않은 부분도 일그러지고 만다.

이건 ScreenPos에 찍힌 모든 오브젝트들이 처리가 되어서 나타나는 문제인데... 

이를 고치기엔 꽤 깊은 부분이 들어가다보니…

 

http://ff14.inven.co.kr/

 

대다수의 게임이 포기하고 만다...

 

2. LOD 알기

(http://practicalbim.blogspot.com/2013/03/what-is-this-thing-called-lod.html)

 

LOD, 즉 Level of Detail은 말 그대로 어느 단계에서

어떤 레벨의 단계를 보여줄까? 이다.

 

 

스피드 트리와 같은 원, 근경이 나누어질 오브젝트에서 자주 쓰는 방식인데 현 졸작에서는 디테일이 중요한 만큼 크게 쓰이지 않지만 현업에서는 자주 쓰는 모양이다. (점점 사라질 것 같기도 하지만 그래도 여전히 존재한다.)

 

https://www.hggaming.eu/news/detail/optimize-your-graphic-settings-in-r6-siege / 이 이미지는 사이트 들어가서 크게 보기를 추천합니다.

 

2-1. 노말은 왜 파래요?

그러게요? 가 아니라…

 

https://docs.unity3d.com/kr/530/Manual/LightPerformance.html

 

 

Light(빛)은 두 가지 방법 중 하나로 렌더링이 되는데,

총 두가지의 랜더링 파이프라인이 있다.

 

  • Pixel Lighting의 경우
    모든 스크린의 픽셀을 개별적으로 계산하는 방식이다.

  • Vertex Lighting의 경우
    메쉬의 정점(버텍스)만 조명을 계산하고 나머지는 보간하는 방식이다.

 

빛의 경우 위치가 고정되어 있다 하더라도 물체의 표면을 나타내는 것은

결국 물체의 굴곡, 방향성(Vector)이다.

 

만약 물체의 굴곡(방향성)이 없는 상태라면 2D와 다를바가 없다.

앞, 옆, 뒤, 전부 다 한 방향을 보는 것이기 때문이다.

이렇게 물체의 방향을 정해주기 위해서는 3차원의 축을 기준으로 방향을 정해놓은 노말 벡터(Normal Vector)가 필요하다.

 

그럼 표면의 굴곡을 나타내주는 이 방향성을 가진 노말 벡터(Normal Vector)를 어떻게 노말 맵으로 가져오는 것일까?

 

“ 로우 폴리곤 텍셀로부터 하이폴리곤 표면을 향해 반직선 투영 ” 을 한다.

(https://bobbybong.tistory.com/entry/%EB%85%B8%EB%A7%90%EB%A7%B5Normal-Map%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80)

(https://docs.unity3d.com/560/Documentation/Manual/StandardShaderMaterialParameterNormalMap.html)

 

뭐...암튼 어떻게든 하이 폴리곤의 XYZ 벡터를, RGB인 노말 맵으로 가져온다고 보면 되겠다. 문제는… Z축은 필요가 없다.

 

 

X(R)축과 Y(G)축은 노말의 방향을 표시하게 되지만,

결국 필요 없는 Z축(B값)은 1로 정규화(Normalize)된다. 노말의 기준축이 되는 것이다.

 

문제는 그렇게 되면 (0, 0, 1) 즉 생파랑인데 왜 노말맵은 (0.5, 0.5, 1)의 색을 보일까?

 

우린 오른쪽이 익숙한데, Unity에서는 막 왼쪽으로 바꿔버린다.

 

지금까지 배워왔던 바에 의하면,

0과 -1은 동일한 검은색으로 보여도 값이 다르다는 것을 알 수 있었다.

 

유니티에서는 0과 -1을 정규화 하거나 하프 램버트로 개선할 수 있다고 하지만…

그림 리소스. 말 그대로 “이미지”인 텍스쳐에 -1의 값을 넣을 수 있을리가 없다.

 

문제는 벡터는 방향, 즉 각도다.

0과 1 사이의 값으로 180도의 각도를 표현하자고 한다면 큰 한계가 생긴다.

(0~1 값으로 각도를 표현해 보아도 90도가 최대기 때문에.)

2-2. UnpackNormal의 정체 알기

https://www.patreon.com/posts/texture-part-2-37604751

 

UnpackNormal 공식을 살펴보면 ...

 

  1. XY(RG)를 WY(AG)로 변형

  2. * 2 - 1 (역 하프램버트) 계산
  3. Z에는 뭘 하는지 모르겠어요

 

2-3. XY(RG)를 왜 WY(AG)로 변형하는 것일까?

Texture 타입을 노말맵으로 바꾸면 텍스쳐가 DXTnm으로 바뀐다.

 

DXTmn 압축의 경우 DXT5와 동일한

 

DTX = R5 G6 B5 A8

 

G와 A가 더 많은 압축 형식이다.

그러다보니 좀 더 많은 단계를 위해서 비트가 더 많은 GA에 RG를 나타내기 위해 변형하는 것이다. 

https://forum.unity.com/threads/runtime-generated-bump-maps-are-not-marked-as-normal-maps.413778/

 

큰 차이는 느낄 수 없지만 아무튼 효율을 따진다!

 

문제는 이렇게 되면 R과 B가 남는다. 쓸 곳이 없어진다!

그래서… AG, 즉 RG만 필요하게 되는 것이다 … 그래서 ...

 

https://typhen.artstation.com/blog/GZdL/this-is-normal-3-types-of-normal-maps

 

실제론 BA값을 버린,

RG만 남긴 Yellow Normal Map(Channel Tangent Space Normal Map)이 있다.

이런 경우에는 UnpackNormal의 단계를 거칠 필요도 없는 것이다(!)

그래서 텍스쳐의 용량이 가벼워진다.

 

2-4. 역 하프 램버트 계산( * 2 - 1 )은 왜 하는 것일까?

문제는 벡터는 방향, 즉 각도다.
0과 1 사이의 값으로 180도의 각도를 표현하자고 한다면 큰 한계가 생긴다.
(0~1 값으로 각도를 표현해 보아도 90도가 최대기 때문에.)

 

그러다보니 텍스쳐 안에서 -1을 나타내려면 기준을 바꾸게 된 것이다!

말 그대로 기준점을 0.5에서 바꿔둔 상태에서(마치 하프 램버트처럼)

그 바꾼 점을 역 하프 램버트를 통해 0~1처럼 바꿔놓는 것이다.

와! 그럼 굳이 UnpackNormal을 사용하지 않아도 Custom 해서 만들 수있다!

 

말 그대로 기준점을 0.5에서 바꿔둔 상태에서(마치 하프 램버트처럼)

그 바꾼 점을 역 하프 램버트를 통해 0~1처럼 바꿔놓는 것이다.

 

 

 

마지막 14주차가 남았다.

이후 14주차에선 가장 많이 쓰이는 투명화, 알파(Alpha)를 알아볼 예정이다.