차비쓰리움(ChaBCrium) [2] - 수면의 빛 산란 효과 Shader

박창훈 (MERHENNE)·2020년 7월 15일
0

ChaBCrium

목록 보기
2/2

다른 많은 구현 사항이 있었지만, 오늘은 바다 속에서 수면을 바라봤을 때 볼 수 있는 빛의 산란 효과를 만드는 Shader의 구현에 대해서 작성한다.

해외 프로그래머의 튜토리얼을 참고하여 Shader를 작성하였다. 해당 튜토리얼을 작성한 프로그래머는 'Surf Shader' 형태로 구현하였지만, 1장에 서술한 Transparent Window에서는 원하는 효과를 볼 수 없어 'Vert/ Frag Shader'로 번역하여 구현하였다.

'Surf Shader' 형태의 쉐이더는 Transparent Window 환경에서는 완전히 투명하지 않고, 하위에 있는 Solid Color가 같이 섞여 나온다.

  • Surf 형태의 Shader는 원 저작권자의 권리를 위해서 게시하지 않는다.

Caustics Shader

Caustics 효과는 빛이 물체에 반사, 굴절되어서 나오는 반사광이 다른 물체에 맺히는 현상을 말한다. 실제로 빛이 반사, 굴절되는 것과 그 반사광이 물체에 맺히는 것을 표현하려면 굉장히 높은 코스트가 소요된다. Alan Zucconi는 Florian(트위터로 연결되는 것으로 보아 사람 이름으로 보인다.)이 사용한 방법을 사용하여 Caustics 효과를 구현한다.

  • Florian의 방법
  1. 모델에 두 가지 Caustics 패턴을 다른 크기와 다른 변화 속도를 가지고 그린다.
  2. 두 패턴을 min HLSL function을 이용하여 혼합한다.
  3. 패턴을 샘플링하는 과정에서 RGB를 분리한다.

1. Draw Caustics Texture

Single Caustics Texture Sampling

Unlit Shader를 하나 생성한 후, 아래와 같이 Texture를 사용하기 위한 변수를 선언한다. float4 _Caustics_ST; 는 x, y 성분에 Texture의 Scale과 z, w 성분에 Offset 정보를 포함하고 있다.

...
Properties{
    _SizeTex("Using For Size", 2D) = "white" {} 
    _CausticsTex("Frist Caustics Texture", 2D) = "white" {}
    _Caustics_ST("Caustics St", Vector) = (1, 1, 0, 0)
}
...
sampler2D _CausticsTex; // Caustics texture
float4 _Caustics_ST; // Caustics texture's scale and offset info
...
v2f vert(appdata v) {
    ...
    o.uv_effectSize = TRANSFORM_TEX(v.uv, _SizeTex);
    ...
}
...
fixed4 frag(v2f i) : SV_Target {
	...
    // Caustics texture sampling
    float2 first_caustics_uv = i.uv_effectSize * _Caustics_ST.xy + _Caustics_ST.zw;
    // A Part

    fixed4 caustics = tex2D(_CausticsTex, first_caustics_uv); // C Part
    ...
    col *= _Color * caustics; // B Part
    ...
}
...

Animated Caustics Texture

Shader에 기본 내장되어 있는 float4 _Time 변수를 이용하여 Caustics Texture를 움직이게 한다. _Time 내장 변수는 (t / 20, t, t 2, t 3) 값을 가지고 있다. 여기서는 그 중에서 y값인 t를 이용하였다.

...
Properties {
    ...
    _Caustics_Speed("Caustics Simulation Speed", Vector) = (1, 1, 0, 0)
}
...
float4 _Caustics_Speed;
...
fixed4 frag(v2f i) : SV_Target {
	// Below A Part
	first_caustics_uv += _Caustics_Speed * _Time.y;
	...
}

2. Multiple Caustics Texture Sampling And Blending

위와 같은 방법으로 두번째 Caustics Texture도 가져올 수 있다. 단순히 두 값의 평균으로 블렌딩하는 것보다 Shader에 내장되어 있는 min 함수를 이용하면 더 좋은 결과를 얻을 수 있다. 이와 함께 두 Caustics Texture의 ST 값 즉, Scale과 Translation을 다르게 설정하면 좀 더 자연스러운 효과를 볼 수 있다.

두 번째 Caustics Texture를 Sampling하는 방법은 여기서는 기술하지 않는다.

...
fixed4 flag(v2f i) : SV_Target {
	...
    // col *= _Color * caustics; // Old B Part
    col *= _Color * min(caustics, second_caustics); // New B Part
    ...
}
...

3. Split RGB wile sampling

빛은 물을 통과할 때, 산란하여 각각 다른 색의 빛을 낸다. 이 효과를 추가하면 물의 수면 효과를 더 자연스럽게 표현할 수 있다.

이 효과는 _SplitRGB의 영향을 받는다. 해당 값을 float2(_SplitRGB, _SplitRGB)의 형태로 각 uv에 더하여 R, G, B 값을 추출한다.

...
fixed4 flag(v2f i) : SV_Target {
    ...
    // fixed4 caustics = tex2D(_CausticsTex, first_caustics_uv); // Old C Part
   
    fixed s = _SplitRGB;
    fixed r_first = tex2D(_CausticsTex, uv_first + fixed2(+s, +s)).r;
    fixed g_first = tex2D(_CausticsTex, uv_first + fixed2(+s, -s)).g;
    fixed b_first = tex2D(_CausticsTex, uv_first + fixed2(-s, -s)).b;
    fixed a_first = tex2D(_CausticsTex, uv_first).a;

    fixed4 caustics = fixed4(r_first, g_first, b_first, a_first); // New C Part
    ...
}
...

두 번째 Texture의 RGB Split Sampling은 위 코드와 동일하다.

Shader "Unlit/WaterCausticsReflection_Unlit_Blend_Transparent"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _SizeTex ("For Size (Use Tilling Only)", 2D) = "white" {}
        _AlphaTex("Alpha Texture", 2D) = "white"{}

        _SplitRGB("Split Power", Float) = 0.0
        [Header(Caustics)]
        _CausticsTex("Caustics (RGB)", 2D) = "white" {}
        _Caustics_ST("Caustics St", Vector) = (1, 1, 0, 0)
        _CausticsSpeed("Caustics Speed", Vector) = (1, 1, 0, 0)

        [Header(Secondary Caustics)]
        _Secondary_CausticsTex("Caustics (RGB)", 2D) = "white" {}
        _Secondary_Caustics_ST("Caustics St", Vector) = (1, 1, 0, 0)
        _Secondary_CausticsSpeed("Caustics Speed", Vector) = (1, 1, 0, 0)
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent"}
        Blend SrcAlpha One
        Cull off
        ZWrite off
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;

                float2 uv_effectSize : TEXCOORD1;
                UNITY_FOG_COORDS(1)
            };

            fixed4 _Color;
            sampler2D _SizeTex;
            sampler2D _AlphaTex;

            float4 _SizeTex_ST;
            float4 _AlphaTex_ST;

            fixed _SplitRGB;

            sampler2D _CausticsTex;
            float4 _Caustics_ST;
            float4 _CausticsSpeed;

            sampler2D _Secondary_CausticsTex;
            float4 _Secondary_Caustics_ST;
            float4 _Secondary_CausticsSpeed;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                o.uv = TRANSFORM_TEX(v.uv, _AlphaTex);
                o.uv_effectSize = TRANSFORM_TEX(v.uv, _SizeTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_AlphaTex, i.uv);

                float2 uv_first = i.uv_effectSize * _Caustics_ST.xy + _Caustics_ST.zw;
                uv_first += _CausticsSpeed * _Time.y;

                fixed s = _SplitRGB;
                fixed r_first = tex2D(_CausticsTex, uv_first + fixed2(+s, +s)).r;
                fixed g_first = tex2D(_CausticsTex, uv_first + fixed2(+s, -s)).g;
                fixed b_first = tex2D(_CausticsTex, uv_first + fixed2(-s, -s)).b;
                fixed a_first = tex2D(_CausticsTex, uv_first).a;

                fixed4 caustics = fixed4(r_first, g_first, b_first, a_first);

                float2 uv_second = i.uv_effectSize * _Secondary_Caustics_ST.xy + _Secondary_Caustics_ST.zw;
                uv_second += _Secondary_CausticsSpeed * _Time.y;

                fixed r_second = tex2D(_Secondary_CausticsTex, uv_second + fixed2(+s, +s)).r;
                fixed g_second = tex2D(_Secondary_CausticsTex, uv_second + fixed2(+s, -s)).g;
                fixed b_second = tex2D(_Secondary_CausticsTex, uv_second + fixed2(-s, -s)).b;
                fixed a_second = tex2D(_Secondary_CausticsTex, uv_second).a;

                fixed4 caustics_second = fixed4(r_second, g_second, b_second, a_second);

                col *= _Color * min(caustics, caustics_second);

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);

                return col;
            }
            ENDCG
        }
    }
}

결과 화면

결과 화면 1

결과 화면 2


참고

  1. Alan Zucconi
  2. 남의 궁댕이 블로그

0개의 댓글