이것저것

[UNITY] 물, 굴절 쉐이더 본문

[유니티] Unity3D/Unity Shader

[UNITY] 물, 굴절 쉐이더

Patch_JA 2019. 6. 16. 21:27
728x90

 

 

오늘 포스팅에서는 물 쉐이더와 저번 포스팅에서 활용했던 것들을 사용하여 굴절까지 구현해보려고 한다.

 

 

 

뜬금없지만 물하면 갑자기 수영장이 떠올라서 수영장에서 물을 만들어보려고 한다.

모델은 구글에서 무료라서 가져왔고 물 노말 맵 또한 구글에서 가져왔다.

 

첫 번째로 할 일은 위 우측 사진처럼 쉐이더로 물을 표현하기 위해서는 물이 있어야 할 곳에

유니티 기본 플랜(Plane)을 설치해준다.

 

유니티로 뭔가를 배치를 많이해보지 않아서 몰랐는데 표면이랑 정점으로 해서 스내핑이 된다.

원하는 부분에 V키를 누르고 오브젝트를 움직이면 스내핑 된다.

( 자세한 내용은 유니티 사이트 : https://docs.unity3d.com/kr/530/Manual/PositioningGameObjects.html )

 

 

물 쉐이더를 만들기 위해서는 버텍스가 많을수록 출렁일 때도 자연스러워 보여서

유니티 기본보다는 맥스에서 따로 버텍스양을 늘려서 사용하는 듯하다..

그러나 일단 맥스 도움 없이 기본 플랜을 이용해보겠다. 

플랜을 하나를 통으로 하는 것보다 여러 개로 나눠 쓰는 것이 좋다고 하는데 프로 스텀 컬링 때문이다.

( 컬링 기술 관련된 문서는.. https://docs.unity3d.com/kr/530/Manual/OcclusionCulling.html )

 

 

물 쉐이딩 시작

본격적으로 물 쉐이딩을 시작하기 전에 너무나도 익숙한 Lambert 코드를 준비하고

전 포스팅에서 사용했던 큐브 맵과 노말 맵이 들어가는 코드를 작성해보겠다.

시작에 앞서 수영장이라고는 했지만 파도가 치는 걸 볼 수 있으니 그래픽 전공자 분들은 눈 조심 바란다..

 

더보기

이제는 너무나 익숙한 기본 Lambert 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Shader "Custom/Water"
{
    Properties
    {
        _MainTex ("Albedo (RGB)"2D= "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf Lambert
        #pragma target 3.0
 
        sampler2D _MainTex;
 
        struct Input
        {
            float2 uv_MainTex;
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Shader "Custom/Water"
{
    Properties
    {
        _CubeMap("CubeMap"cube= "" {}
        _BumpMap ("Water Bump"2D= "bump" {}
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf Lambert
        #pragma target 3.0
 
        samplerCUBE _CubeMap;
        sampler2D _BumpMap;
 
        struct Input
        {
            float2 uv_BumpMap;
            float3 worldRefl;
            INTERNAL_DATA
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Albedo = texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal));
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 

 

여기까지는 전 포스팅인 큐브 맵과 다를 것이 없다. 단순히 물이 노말 맵으로 들어간 것뿐..

그러나 이렇게 해서는 뭔가 전혀 물 같지 않다. 물같이 보이게 하기 위해서는 많은 요소가 들어가지만

일단은 움직이는 것처럼 구현해보자.

 

 

1
2
3
4
5
 void surf (Input IN, inout SurfaceOutput o)
{
    o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap + float2(_Time.y * 0.20.0f)));
    o.Albedo = texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal));
}
 

노말 맵에 _Time 변수를 사용하여 X축만 움직이도록 했더니 조금이나마 나아진 듯하다.

여기서 조금 더 그럴듯하게 하려면 노말 맵을 하나 더 추가시켜주면 된다고 한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Shader "Custom/Water"
{
    Properties
    {
        _CubeMap("CubeMap"cube= "" {}
        _BumpMap ("Water Bump"2D= "bump" {}
        _BumpMap2("Water Bump2"2D= "bump" {}
 
    }
    SubShader
    {    
        ...
        samplerCUBE _CubeMap;
        sampler2D _BumpMap, _BumpMap2;
        ...
        void surf (Input IN, inout SurfaceOutput o)
        {
            float3 fNormal1 = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap + float2(_Time.y * 0.070.0f)));
            float3 fNormal2 = UnpackNormal(tex2D(_BumpMap2, IN.uv_BumpMap2 - float2(_Time.y * 0.050.0f)));
 
            o.Normal = fNormal1 + fNormal2;
            o.Albedo = texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal));
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
 

같은 노말 맵을 해도 되긴하지만 필자는 그냥 다른 물 노말맵을 넣기 위해 텍스쳐를 한 장 더 받았다.

비록 파도가 치고 있지만 생각보다 그럴듯하게 보인다.

이번에는 물에 투명함과 프리넬 반사를 줘보도록 하겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Shader "Custom/Water"
{
    Properties
    {
        ...
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent"}
        LOD 200
 
        CGPROGRAM
        #pragma surface surf Lambert alpha:fade
        #pragma target 3.0
 
        ...
 
        struct Input
        {
            float2 uv_BumpMap, uv_BumpMap2;
            float3 worldRefl;
            float3 viewDir;
            INTERNAL_DATA
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            ..
 
            float fRim = dot(IN.viewDir, o.Normal);
            o.Alpha = saturate(pow(1-fRim, 3));
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
 

물을 수직에 가까울수록 투명하고 그게 아니면 환경 반사가 되도록 하였다.

그런데 뭔가 이상하다. 자세히 보면 투명도 그렇고 안 보이는 부분들이 있다.

렌더 큐 문제인데 이것에 대해서는 다음 포스팅때 자세히 설명하겠다.

일단은 물 머티리얼로 가서 렌더큐 값을 수정해준다.

 

기본 2,000 값을 3,000으로 변경해주면 일단 해결은 된다.

다음으로는 물을 조금 일렁이도록 하려고 한다.

저번 툰 쉐이딩 포스팅에서 다룬 버텍스 값을 조절해서 움직이게 할 예정이다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SubShader
{
    ...
 
    CGPROGRAM
    #pragma surface surf Lambert alpha:fade vertex:vert
    #pragma target 3.0
    
    ...
 
    void vert(inout appdata_full v)
    {
        v.vertex.y += v.texcoord.x;
    }
 
    ...
}
 

vertex.y에 uv의 x축 크기만큼 더했더니 기울어졌다.

여기에 공식 하나를 넣을 건데 *2 -1 값을 넣어서 2배로 키운 다음 -1~+1까지 확장시켜준다음

abs함수로 감싸주면 -1~0~1이 1~0~0로 바뀌면서 지그재그로 나오게 된다 

 

1
2
3
4
void vert(inout appdata_full v)
{
    v.vertex.y += abs(v.texcoord.x * 2 - 1);
}
 

수영장에 있는 물치곤 많이 괴랄하긴 하지만 일단은 예상대로 지그재그로 출력되었다.

여기에 삼각함수 sin값 까지 얹어버리면..

 

1
2
3
4
void vert(inout appdata_full v)
{
    v.vertex.y += sin(abs(v.texcoord.x * 2 - 1)*2);
}
 

좀 더 둥글둥글해졌다 여기에 마무리로 출렁이며 파장도 조절해보겠다.

 

 

1
2
3
4
void vert(inout appdata_full v)
{
    v.vertex.y += sin((abs(v.texcoord.x * 2 - 1* 3+ sin(_Time.y * 1)) * 0.1;
}
 

값 조절을 잘해야겠지만 출렁이는 것처럼 출력되었다..

마지막으로 좀 더 물처럼 보이기 위해 굴절을 넣어보겠다.

굴절을 구현하기 위해서는 물아래의 화면을 캡처해서 구기는 것인데

ScreenPos 변수가 새로 등장하게 된다. 

일단은 시작하기 전에 코드를 한번 정리해보도록 하겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Shader "Custom/Water"
{
    Properties
    {        
        _CubeMap("CubeMap"cube= "" {}
        _MainTex("MainTex"2D= "white" {}
        _BumpMap ("Water Bump"2D= "bump" {}
        _BumpMap2("Water Bump2"2D= "bump" {}
 
        _SpecColor("Spec", color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque"}
        LOD 200
 
        CGPROGRAM
        #pragma surface surf BlinnPhong vertex:vert
        #pragma target 3.0
 
        samplerCUBE _CubeMap;
        sampler2D _BumpMap, _BumpMap2;
        sampler2D _MainTex;
 
        float4 _SepcColor;
 
        struct Input
        {
            float2 uv_MainTex, uv_BumpMap, uv_BumpMap2;
            float3 worldRefl;
            float3 viewDir;
            float4 screenPos;
 
            INTERNAL_DATA
        };
 
        ...
}
 

Tags에 Transparent 했던 것을 Opaque로 다시 변경하여 #pragma에서 alpha:fade 도 지우고,

Lambert를 BlinnPhong으로 변경하고 그에 따라 SpecColor가 추가되고 텍스쳐 한 장과

Input 구조체에 float4 screenPos를 추가하였다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SubShader
{
    Tags { "RenderType" = "Opaque"}
    LOD 200
 
    GrabPass{}
 
    CGPROGRAM
    #pragma surface surf BlinnPhong vertex:vert
    #pragma target 3.0
 
    samplerCUBE _CubeMap;
    sampler2D _BumpMap, _BumpMap2;
    sampler2D _MainTex, _GrabTexture;
 
    ...
}
 

여기에 GrabPass {}를 추가시켜 캡처를 하고 _GrabTexture 내장 변수에 담기게 된다.

 

1
2
3
4
5
6
7
8
9
10
11
void surf (Input IN, inout SurfaceOutput o)
{
    float4 fNoise = tex2D(_MainTex, IN.uv_MainTex + _Time.x);    
    float3 scrPos = IN.screenPos.xyz / (IN.screenPos.w + 0.0000001);
    float3 fGrab = tex2D(_GrabTexture, scrPos.xy + fNoise.r * 0.05);
 
    o.Gloss = 1;
    o.Specular = 1;
    o.Alpha = 1;
    o.Emission = fGrab;
}

노이즈 텍스쳐를 받아와 흐르게 만들고 fGrab 변수에서 노이즈 텍스쳐의 채널 1개를 가져와

_GrabTexture의 Offset을 조절하여 굴절 효과를 구현했다.

필요한 것들은 모두 준비했으니 이번에는 모두 혼합해서 결과물을 만들어 보겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
Shader "Custom/Water"
{
    Properties
    {        
        _CubeMap("CubeMap"cube= "" {}
        _MainTex("MainTex"2D= "white" {}
        _BumpMap ("Water Bump"2D= "bump" {}
        _BumpMap2("Water Bump2"2D= "bump" {}
 
        _SpecColor("Spec", color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque"}
        LOD 200
 
        GrabPass{}
 
        CGPROGRAM
        #pragma surface surf BlinnPhong vertex:vert
        #pragma target 3.0
 
        samplerCUBE _CubeMap;
        sampler2D _BumpMap, _BumpMap2;
        sampler2D _MainTex, _GrabTexture;
 
        float4 _SepcColor;
 
        struct Input
        {
            float2 uv_MainTex, uv_BumpMap, uv_BumpMap2;
            float3 worldRefl;
            float3 viewDir;
            float4 screenPos;
 
            INTERNAL_DATA
        };
 
        void vert(inout appdata_full v)
        {
            v.vertex.y += sin((abs(v.texcoord.x * 2 - 1* 3+ sin(_Time.y * 1)) * 0.1;
        }
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            // Normal
            float3 fNormal1 = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap + float2(_Time.y * 0.070.0f)));
            float3 fNormal2 = UnpackNormal(tex2D(_BumpMap2, IN.uv_BumpMap2 - float2(_Time.y * 0.050.0f)));
            o.Normal = (fNormal1 + fNormal2)/2;
 
            // reflection
            float3 fRefl = texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal));
 
            // rim
            float fRim = dot(o.Normal, IN.viewDir);
            fRim = saturate(pow(1 - fRim, 3));
 
            //grab
            float4 fNoise = tex2D(_MainTex, IN.uv_MainTex + _Time.x);
            float3 scrPos = IN.screenPos.xyz / (IN.screenPos.w + 0.0000001);
            float3 fGrab = tex2D(_GrabTexture, scrPos.xy + fNoise.r * 0.05);
 
            o.Gloss = 1;
            o.Specular = 1;
            o.Alpha = 1;
 
            o.Emission =  lerp(fGrab, fRefl, fRim);
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 

 

 

 

Comments