Unity Shader - Water

Study/Shader 2019. 6. 15. 21:52

 

 

 

물을 표현하기 위해서는 많은 현상들을 구현해야 하지만 그중에서 구체적으로 몇 가지만 구현을 하여 표현이 가능합니다.

이번 포스팅에서는 Reflection(반사), Wave(파도), Fresnel(빛투과), Distortion(굴절) 효과를 Unity Surface Shader로 구현하여 물을 표현해 보겠습니다.

 

 

 

 

반사 효과 

Reflection Probe를 통해 주변 환경을 Bake하여 이 데이터를 이용해 반사하는 쉐이더를 만들어봅시다.

우선 위 사진과 같이 환경상에 Reflection Probe를 설치하여 Bake하였습니다.

 

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
Tags { "RenderType" = "Opaque" }
 
        CGPROGRAM
 
        #pragma surface surf _CatDarkLight    //! Custom Light 함수 설정
 
        struct Input
        {
            float3 worldRefl;        //! 월드 반사 벡터 Input 선언
            INTERNAL_DATA            //! 반사벡터 노멀처리, WorldReflectionVector 함수 사용을 위한 메크로 선언
        };
 
        fixed4 _Color;
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            float3 fWorldReflectionVector = WorldReflectionVector(IN, o.Normal).xyz;        
            o.Emission = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, fWorldReflectionVector).rgb * unity_SpecCube0_HDR.r;        //! Reflection Probe데이터 읽어서 Emission에 출력
        }
 
        float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float atten)        //! Custom Light함수는 당장 아무것도 안함
        {
            float4 fFinalColor = 0.0f;
 
            return fFinalColor;
        }
cs

기본 Surface Shader 파일을 생성 이후 위 처럼 CustomLight 세팅 이후 Reflection Probe데이터를 Emission으로 출력하는 쉐이더를 작성합니다.

 

이후 Plane을 설치하여 마테리얼을 설정하면 거울같은 것이 구현 됩니다.

 

 

파도 효과 - Normal Map

Plane Mesh의 표면에 위 처럼 파도역할을 할 수 있는 노이즈형태의 노멀맵을 사용해 UV Offset을 돌려보겠습니다.

 

 

흘러가는 물처럼 구현이 되었지만 어색합니다.

 

노멀맵의 Offset을 4방향으로 동시에 하여 합쳐보겠습니다.

 

코드는 다음과 같습니다.

 

 

노멀맵 Offset을 응용하게 간단한 Wave효과를 구현하였습니다.

 

 

 

파도 효과 - Vertex Wave

Normal을 이용하여 파도를 만들 었지만 근본적으로 Mesh는 평면인건 변함이 없습니다, 그래서 더욱 강한 파도를 만들 수 없습니다.

이번엔 Vertex Shader를 통해 Vertex를 조작하여 파도를 만들어보겠습니다.

 

vertex Shader 함수를 제작후 간단하게 sin으로 버텍를 위 아래로 조작해 봅니다.

 

버텍스를 조작하는데는 성공했지만 파도의 형태는 아닙니다, 파도의 형태를 표현하기 위해서는 

 

위 사진과 같이 버텍스의 y값을 조절해야합니다.

 

이를 구현하기 위해서는 먼저 vertex함수에서 받아오는 appdata_full구조체 안에 메쉬의 UV정보를 가져올 수 있는 float4 texcoord를 이용해야 합니다.

 

 

위와 같이 버텍스에 texcoord.x를 더 해보겠습니다.

메쉬가 옆으로 기울어진 것을 확인할 수 있으며 이것을 통해 UV가 1~0으로 흐르는걸 확인 할 수 있습니다.

 

 

좀더 응용해서 위 코드를 작성하면,

값이 1~0~1로 이동되는 형태가 완성됩니다, 이제 이 값을 sin함수와 함께 응용하면 

 

sin 함수와 _Time변수를 활용하고 파도의 강도를 조절하는 코드입니다.

 

실제 Mesh의 버텍스가 파도처럼 움직이게 되었습니다, 여기서 더 응용하여 Normal Wave를 4방향에서 동시에 합친것 과 같이 이번 Vertex Wave에도 똑같이 적용시켜 보겠습니다.

 

 

 

투과 효과

 

- GrabPass

Unity Shader에는 총 3가지 종류의 Pass가 있습니다.

-Normal Pass : Pass라고 작성된 코드이며 우리가 여태까지 사용한 일반적인 Pass입니다.

-Use Pass : 다른 쉐이더 파일에서 작성된 Pass를 가져올때 사용합니다.

-Grab Pass : 화면 오브젝트를 캡쳐할 때 사용됩니다.

 

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

물로 사용 하고 있는 Plane Mesh의 뒷 부분을 보기 위해서는 Grab Pass를 사용하여 Plane Mesh의 뒷 부분을 캡쳐하여 쉐이더 상에서 출력하면 됩니다.

 

 

마지막으로 lerp를 이용하여 GrapTexture와 반사텍스처를 적절히 섞었습니다.

 

 

- Fresnel 효과 추가

위 사진을 보면 카메라로부터 가까운 곳에 있는 물 밑은 투과가 잘되어 바닥이 보이는 반면 멀리 있는 곳은 투과가 되지 않아서 표면 그대로 환경을 반사하고 있습니다.

 

그 이유는 프레넬 현상때문에 그렇습니다, 더 자연스러운 물을 만들기 위해 프레넬 공식을 적용한 투과 효과를 구현해 보겠습니다.

 

 

Normal Dot ViewDir 공식을 이용하여 프레넬 공식을 만들었습니다.

의도한대로 카메라로부터 가까운 곳을 0의 값을 멀리 있는 곳을 1의 값을 가지고 있습니다.

 

fRim 값을 Lerp함수의 마지막 인자값에 넣어서 물의 Fresnel현상을 구현했습니다.

 

 

굴절 효과

https://www.deviantart.com/candid-ishida/art/Water-Distortion-28736273

굴절효과는 간단합니다, 이전에 가져온 GrabTexture의 UV를 조작하여 굴절효과를 구현할 수 있습니다.

https://darkcatgame.tistory.com/9?category=806332 <- 이전에 간단한 UV조작으로 불, 물 쉐이덜 제작을 하는 법을 포스팅했습니다.

 

위 노이즈 텍스처를 이용하겠습니다.

 

 

 

전체 코드

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Shader "Custom/Water"
{
    Properties
    {
        _WaveNormalMap("NormalMap"2D= "bump" {}
        _DistortionMap("DistortionMap"2D= "black" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
 
        GrabPass{}        
 
        CGPROGRAM
       
        #pragma surface surf _CatDarkLight  vertex:vert noshadow
 
 
        void vert(inout appdata_full v)
        {
            v.vertex.y += sin((abs(v.texcoord.x * 2.0f - 1.0f)*10.0f) + _Time.y*0.8f) * 0.12f +
                sin((abs(v.texcoord.y * 2.0f - 1.0f)*10.0f) + _Time.y*0.8f) * 0.12f;
        }
 
 
         sampler2D _WaveNormalMap;
        sampler2D _DistortionMap;
        sampler2D _GrabTexture;        
        
 
        struct Input
        {
            float2 uv_WaveNormalMap;
            float2 uv_DistortionMap;
            float3 viewDir;
 
            float4 screenPos;    
 
            float3 worldRefl;
            INTERNAL_DATA
        };
 
        fixed4 _Color;
 
        void surf (Input IN, inout SurfaceOutput o)
        {  
            //o.Albedo = 1.0f;
 
            float fNormalWaveSpeed = 0.025f;
            float3 fNormal_L = UnpackNormal(tex2D(_WaveNormalMap, IN.uv_WaveNormalMap - float2(0, _Time.y*fNormalWaveSpeed)));
            float3 fNormal_R = UnpackNormal(tex2D(_WaveNormalMap, IN.uv_WaveNormalMap + float2(0, _Time.y*fNormalWaveSpeed)));
            float3 fNormal_T = UnpackNormal(tex2D(_WaveNormalMap, IN.uv_WaveNormalMap - float2(_Time.y*fNormalWaveSpeed, 0)));
            float3 fNormal_B = UnpackNormal(tex2D(_WaveNormalMap, IN.uv_WaveNormalMap + float2(_Time.y*fNormalWaveSpeed, 0)));
            
            o.Normal = (fNormal_L + fNormal_R + fNormal_T + fNormal_B) / 4.0f;
 
 
            float3 fWorldReflectionVector = WorldReflectionVector(IN, o.Normal).xyz;
            float3 fReflection = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, fWorldReflectionVector).rgb * unity_SpecCube0_HDR.r;
 
 
            float4 fDistortion = tex2D(_DistortionMap, IN.uv_DistortionMap + _Time.y*0.05f);    
 
            float3 scrPos = IN.screenPos.xyz / (IN.screenPos.w + 0.00001f);    
            float4 fGrab = tex2D(_GrabTexture, scrPos.xy + (fDistortion.r * 0.2f));        
 
            float fNDotV = dot(o.Normal, IN.viewDir);
            float fRim = saturate(pow(1 - fNDotV + 0.1f, 1));
 
            o.Emission = lerp(fGrab.rgb, fReflection, fRim);
        }
 
        float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float atten)
        {
            float4 fSpec = float4(0.0f, 0.0f, 0.0f, 0.0f);
            float3 fHalfVector = normalize(lightDir + viewDir);
            float fSpecHDotN = saturate(dot(s.Normal, fHalfVector));
            fSpecHDotN = pow(fSpecHDotN, 100.0f);
 
            float4 fFinalColor = 0.0f;
            fFinalColor = fSpecHDotN;
 
            return fFinalColor;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs

 

 

 

 

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,