이제 물도 만들 때가 온 것 같다.
근데 그 전에 리플렉션도 알고...프레넬 반사도 알고...
1. 반사부터 시작하기
실제 반사는 아니지만 반사와 같은 효과를 주기 위해서
우리는 저번 시간에 Cubemap을 사용하여 Texture를 입히듯 했다.
Cubemap을 통한 반사를 만들고 노말맵을 넣을 때 유의할 점은 “INTERNAL_DATA”
즉, 탄젠트 값을 가진 노말맵을 월드 포지션으로 변경해줄 필요가 있었다.
왜냐면, Cubemap을 불러올 때 worldrefl라는 World 반사 값을 사용했기 때문이다!
우선 반사를 가진 텍스쳐를 기본으로 만들어보자.
그럼 물이라면 … 물이 흐르는 노말도 있어야 할테니 노말도 추가해주면…
분명히 노말이 들어갔음에도 불구하고 이상하기만 하다…
노말이 들어가긴 한 것 같다. (_BumpPower로 확인해봤는데, 움직이기는 한다.)
왜 그럴까? 하고 보면… 바로 코드의 순서다!
ref에서 o.Normal을 WorldReflectionVector 에서 사용하고 있는데,
해당 값에는 이미 Normal 값 전부가 들어가 있어야 하기 때문이다.
프로그래밍, 쉐이더 등 코드를 사용하는 것은 상단에서 불러와진 값을 하단에서 사용하는 방식이므로 o.Normal을 먼저 불러와야 할 필요가 있다.
됐다! 문제는 물이라고 보기에는 그냥… 파란 쿠킹호일 같다.
현실의 물과 비교해보자면...
|
외에도 굉장히 고려할 것이 많지만 우리가 시도할 수 있는 부분을 추리자면 이렇다!
우선 쉽게 접근이 가능한 노말의 이동부터 살펴보자.
1-1. 노말 흐르게 하기
어렵지 않다. 지금까지 배운대로 UV에 _Time.y 값만 넣어주기만 하면…
흐른다고 전부 해결되는 것은 아니었던 모양이다.
그러나, 여기서 굉장히 좋은 방법이 있다…! 바로 노말 두개 쓰기다!
노말 두개를 겹치고, 각 노말을 다른 방향으로 흐르게 하는 것이다!
그리고 도중 노말이 겹치는 경우가 있어서
나는 normal2에 타일링을 각각 2값씩 주었다.
흐르긴 하는 것 같은데 잘 모르겠다.
투명화(프레넬)을 진행해보자!
1-2. 투명하게 하기
물은 기본적으로 가까운 곳은 투명하고, 먼 곳은 환경 반사가 일어난다.
우선 투명하게 만들어보고 더 생각해보자!
SubShader { Tags { "RenderType"="Transparent" } LOD 200 CGPROGRAM #pragma surface surf Standard alpha:blend … void surf (Input IN, inout SurfaceOutputStandard o) ... o.Alpha = 0.5; } |
되기는 했는데 뭔가 묘하다. 반사도 똑같이 투명해진 것이다.
그리고 가까운 것이 투명, 먼 것이 불투명 해야 하는데…
문득 떠오르는 공식이 있을 것이다!
바로 림라이트다.
viewDir, 즉 보는 시선에 따라서 어둡고, 시선에서 멀어질 수록 밝은 부분이 되는데
상단의 검고 흰 부분이 알파, 즉 0~1 사이값으로 본다면 충분히 가능하다.
와, 그럼 가까운 곳은 투명, 먼 곳은 불투명하고 반사가 되고 있다!
얼추 물에 가까워지기 시작한 느낌이지만… 이제 남은 것은...
1-3. 웨이브 주기
버텍스를 건드리긴 해야 하는데… 어떻게 특정 버텍스만 건드릴 수 있지?
우선 버텍스를 건드리겠다는 선언을 해주자.
SubShader { Tags { "RenderType"="Transparent" } ... CGPROGRAM #pragma surface surf Standard alpha:blend vertex:vert ... void vert(inout appdata_full v) { } |
근데 진짜 어떻게 하지…?
하고 고민할 때 void vert에 해당 코드를 넣어보자.
엥, 옆으로 기울어졌다!
바로 texcoord로 불러온 UV값에서 Z축만 올렸기 때문이다.
문제는, 이렇게 되어버리면 각각 Plane이 맞지 않는 오류가 생긴다.
그럼 만약에 0~1 사이로 나타나고 있는 texcoord를 -1~1 사이로 만들어준다면…?
-1~1로 어떻게 만들 수 있을까? 바로 하프램버트 역공식이다.
* 2 - 1 |
-1을 0으로 올려주는 하프 램버트 공식에 반면, 0을 -1로 만들어주는 공식이다!
좌측의 이미지처럼 더 편차가 커 보일 수 있지만, abs를 쓰면…?
abs란, 절대값(absolute value)로써 음수를 양수로 만들어주는 함수이기 때문에
-1로 떨어진 값을 올려주면서 우측 하단과 같은 형태를 만들어준다!
똑딱똑딱 꺾인 버텍스들을 볼 수 있다.
물론 이 버텍스들을 cos 함수 식으로 부드럽게 만들어주면 된다.
동시에 시간 값을 주어 흐르게 해보자!
v.vertex.z = cos(abs(v.texcoord.x * 2 - 1) * _WaveHeight + _Time.y * _WaveTime); |
미묘하긴 하지만 움직이기는 한다.
물론 파도도 노말처럼 두 개로 겹칠 수 있다!
void vert(inout appdata_full v) { v.vertex.z = cos(abs(v.texcoord.x * 2 - 1) * _WaveHeight + _Time.y * _WaveTime); v.vertex.z = cos(abs(v.texcoord.y * 2 - 1) * _WaveHeight + _Time.y * _WaveTime); } |
좀 더 자연스럽기도 하다.
여러 요소들을 추가해보자!
결과물이 이 GIF보다 훨씬 이쁜데
압축하느라… 흑흑 아쉽습니다.
2. Triplanar 쉐이더
겁나 급한 모델러들을 위한 꿀 쉐이더를 알아보자.
Terrain 텍스쳐를 사용하게 되면 경사로가 높을 경우, UV가 늘어나고 해당 경우를 수정할 수 없어 좌측의 Std diffuse 상태가 된다.
그러나 Triplanar를 통해 UV를 피지 않아도!
편하게 모델링 위에 Terrain보다 훨 좋은 텍스쳐를 넣는 법을 소개한다.
문제는 모델링을 UV를 안 폈는데 그럼 UV값은 무엇으로…? 란 얘기가 나올 수 있겠지만… 놀랍게도 저번 시간에 사용한 World Normal을 사용할 것이다.
계속 봐보자!
worldPos, 월드 포지션의 경우 float2가 아닌 float3, 즉 UVW를 사용한다.
그럼 UV를 넣을 때 XY가 아닌 XYZ, 즉 사방에서 보일 수 있는 텍스쳐를
총 세 방면(XY, YZ, XZ)으로 넣을 수 있다는 것이다.
그리고 세 방면으로 넣은 Texture를 lerp해주면 되는 것이다.
우선 worldPos를 보자면…
0~1 사이의 부분은 텍스쳐가 정상적으로 출력될 수 있지만,
마이너스 값이 들어가는 부분부터는 텍스쳐가 출력되기 어렵다.
괜찮다! -1 값을 1, 즉 정수로 만들어주는 abs 함수를 쓰면 된다.
이제 둘 다 같은 흰 면을 얻게 되었으므로,
우리 기준 왼쪽 오른쪽 모두 동일한 텍스쳐를 사용할 수 있다.
o.Emission = lerp(front, side, abs(o.Normal.x)); |
좌우는 정상적으로 커버된 모습이지만, 상단의 경우 텍스쳐가 겹쳐보인다.
lerp를 한 번 더 사용해서, Top을 커버할 수도 있다.
그럼 이제 타코야끼도 만들 수 있겠다! (ㅎㅎ)
다음 회차에 물을 한 번 더 다뤄볼 예정입니다!
'Shader' 카테고리의 다른 글
14주차, 알파를 알아보자. (4) | 2024.06.10 |
---|---|
13주차, 물을 더 만들어보자. (0) | 2024.06.10 |
11주차, 툰 쉐이더를 더 알아보자. (0) | 2023.06.19 |
10주차, 툰 쉐이더를 만들어보자. (2) | 2023.06.19 |
9주차, 스펙큘러를 만들어보자. (1) | 2023.06.09 |