다른 많은 구현 사항이 있었지만, 오늘은 바다 속에서 수면을 바라봤을 때 볼 수 있는 빛의 산란 효과를 만드는 Shader의 구현에 대해서 작성한다.
해외 프로그래머의 튜토리얼을 참고하여 Shader를 작성하였다. 해당 튜토리얼을 작성한 프로그래머는 'Surf Shader' 형태로 구현하였지만, 1장에 서술한 Transparent Window에서는 원하는 효과를 볼 수 없어 'Vert/ Frag Shader'로 번역하여 구현하였다.
'Surf Shader' 형태의 쉐이더는 Transparent Window 환경에서는 완전히 투명하지 않고, 하위에 있는 Solid Color가 같이 섞여 나온다.
- Surf 형태의 Shader는 원 저작권자의 권리를 위해서 게시하지 않는다.
Caustics 효과는 빛이 물체에 반사, 굴절되어서 나오는 반사광이 다른 물체에 맺히는 현상을 말한다. 실제로 빛이 반사, 굴절되는 것과 그 반사광이 물체에 맺히는 것을 표현하려면 굉장히 높은 코스트가 소요된다. Alan Zucconi는 Florian(트위터로 연결되는 것으로 보아 사람 이름으로 보인다.)이 사용한 방법을 사용하여 Caustics 효과를 구현한다.
- Florian의 방법
- 모델에 두 가지 Caustics 패턴을 다른 크기와 다른 변화 속도를 가지고 그린다.
- 두 패턴을
min
HLSL function을 이용하여 혼합한다.- 패턴을 샘플링하는 과정에서 RGB를 분리한다.
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 ... } ...
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; ... }
위와 같은 방법으로 두번째 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 ... } ...
빛은 물을 통과할 때, 산란하여 각각 다른 색의 빛을 낸다. 이 효과를 추가하면 물의 수면 효과를 더 자연스럽게 표현할 수 있다.
이 효과는 _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 } } }