본문 바로가기
Shader

12주차, 물을 일단 만들어보자.

by JDCM 2023. 6. 28.

이제 물도 만들 때가 온 것 같다.

근데 그 전에 리플렉션도 알고...프레넬 반사도 알고...

 

1. 반사부터 시작하기

 

실제 반사는 아니지만 반사와 같은 효과를 주기 위해서

우리는 저번 시간에 Cubemap을 사용하여 Texture를 입히듯 했다.

 

Cubemap을 통한 반사를 만들고 노말맵을 넣을 때 유의할 점은 “INTERNAL_DATA”

즉, 탄젠트 값을 가진 노말맵을 월드 포지션으로 변경해줄 필요가 있었다.

왜냐면, Cubemap을 불러올 때 worldrefl라는 World 반사 값을 사용했기 때문이다!

 

우선 반사를 가진 텍스쳐를 기본으로 만들어보자.

그럼 물이라면 … 물이 흐르는 노말도 있어야 할테니 노말도 추가해주면… 

 

분명히 노말이 들어갔음에도 불구하고 이상하기만 하다…

노말이 들어가긴 한 것 같다. (_BumpPower로 확인해봤는데, 움직이기는 한다.)

 

왜 그럴까? 하고 보면… 바로 코드의 순서다!

 

 

ref에서 o.Normal을 WorldReflectionVector 에서 사용하고 있는데,

해당 값에는 이미 Normal 값 전부가 들어가 있어야 하기 때문이다.

 

프로그래밍, 쉐이더 등 코드를 사용하는 것은 상단에서 불러와진 값을 하단에서 사용하는 방식이므로 o.Normal을 먼저 불러와야 할 필요가 있다.

 

 

됐다! 문제는 물이라고 보기에는 그냥… 파란 쿠킹호일 같다.

 

( https://shanesimmsart.wordpress.com/2018/03/29/fresnel-reflection/ )

 

현실의 물과 비교해보자면...

 

  1. 물 표면이 불규칙적으로 흐른다(흔들린다.)
    = 노말의 이동 (UV Time)

  2. 가까운 곳은 투명하고, 먼 곳은 반사한다.
    = 프레넬 효과(Fresnel Reflection)

  3. 파도와 같은 기본적인 웨이브가 있다.
 = 버텍스 접근

 

외에도 굉장히 고려할 것이 많지만 우리가 시도할 수 있는 부분을 추리자면 이렇다!

우선 쉽게 접근이 가능한 노말의 이동부터 살펴보자.

 

1-1. 노말 흐르게 하기

어렵지 않다. 지금까지 배운대로 UV에 _Time.y 값만 넣어주기만 하면…

 

흐른다고 전부 해결되는 것은 아니었던 모양이다.

그러나, 여기서 굉장히 좋은 방법이 있다…! 바로 노말 두개 쓰기다!

 

 

노말 두개를 겹치고, 각 노말을 다른 방향으로 흐르게 하는 것이다!

그리고 도중 노말이 겹치는 경우가 있어서

나는 normal2에 타일링을 각각 2값씩 주었다.

 




 

흐르긴 하는 것 같은데 잘 모르겠다.

투명화(프레넬)을 진행해보자!

 

1-2. 투명하게 하기

( https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/reflection-refraction-fresnel )

 

물은 기본적으로 가까운 곳은 투명하고, 먼 곳은 환경 반사가 일어난다.

우선 투명하게 만들어보고 더 생각해보자!

 

    SubShader
    {
        Tags { "RenderType"="Transparent" }
       LOD 200

        CGPROGRAM
        #pragma surface surf Standard alpha:blend


        void surf (Input IN, inout SurfaceOutputStandard o)
...
            o.Alpha = 0.5;
}

 

 

되기는 했는데 뭔가 묘하다. 반사도 똑같이 투명해진 것이다. 

그리고 가까운 것이 투명, 먼 것이 불투명 해야 하는데…

문득 떠오르는 공식이 있을 것이다!

 

https://24dc-m.tistory.com/11

 

바로 림라이트다.

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 쉐이더

겁나 급한 모델러들을 위한 꿀 쉐이더를 알아보자.

 

(  https://forum.unity.com/threads/free-triplanar-terrain-shaders.367992/  )

 

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을 커버할 수도 있다.

 

 

 

그럼 이제 타코야끼도 만들 수 있겠다! (ㅎㅎ)

 

다음 회차에 물을 한 번 더 다뤄볼 예정입니다!