水中の屈折(shader)
テクスチャの取得
描画されている画面をテクスチャで取得し、そのテクスチャを歪ませたのちに水面へ描画することで水中の屈折を表現します。そのためにシェーダ内で画面をテクスチャにしたものを取得する必要があります。以下のコードをサブシェーダ内に追加するだけでテクスチャを取得できます。
1 |
GrabPass{ "_GrabTex" } |
次に、このテクスチャを表示するための座標を以下のコードにより取得します。
1 |
o.grabPos = ComputeGrabScreenPos(o.vertex); |
あとは、取得したテクスチャと座標を用いてフラグメントシェーダ内に以下のコードを追加すると平面に画面をそのまま表示することができます。
1 2 |
fixed4 col = tex2D(_GrabTex, i.grabPos.xy / i.grabPos.w); return col; |
シェーダ
描画されている画面をオブジェクトへ描画するシェーダです。Shiftを調整することでテクスチャをずらすことができます。
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 |
Shader "Unlit/GrabTest" { Properties { _MainTex ("Texture", 2D) = "white" {} _Shift ("Shift", range (0,2)) = 0.1 _Color ("Color", Color) = (1, 1, 1, 1) } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent"} GrabPass{ "_GrabTex" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 grabPos : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _GrabTex; float4 _GrabTex_ST; float4 _Color; float _Shift; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.grabPos = ComputeGrabScreenPos(o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_GrabTex, i.grabPos.xy / i.grabPos.w * _Shift); col *= _Color; return col; } ENDCG } } } |
実行結果
青くなっている場所が上記シェーダを適用した平面です。描画された画面がそのまま表示されているため、平面が半透明な色をしているように見えます。
テクスチャをずらすと以下の画像のようになります。平面が半透明ではなく、描画された画面をそのまま表示していることがわかります。
歪みシェーダ
ノーマルマップをもとに先ほど得られたテクスチャを歪ませます。フラグメントシェーダを以下のように書き換えます。
1 2 3 4 5 6 7 8 9 10 |
fixed4 frag (v2f i) : SV_Target { float3 bump = UnpackNormal(tex2D(_BumpTex, i.uv)); float4 grabUV = i.grabPos; grabUV.xy = (i.grabPos.xy * _Shift + (bump.xy * _Distortion)) / i.grabPos.w; fixed4 col = tex2D(_GrabTex, grabUV.xy); col *= _Color; return col; } |
実行結果
ノーマルマップによって、テクスチャを歪ませることができました。しかし、オブジェクトが水面から出ている部分まで歪みます。そのため、オブジェクトの後ろからはみ出る部分が生じてしまいます。
深度差シェーダ
GrabPassによって取得したテクスチャにおいて、オブジェクトが水面より上に出ている部分を歪ませないように処理する必要があります。そのために、カメラの深度テクスチャと平面の深度の差とり、その結果から水面より下か上かを判定します。カメラの深度テクスチャはシェーダ内で_CameraDepthTextureを宣言することで取得できます。
1 |
sampler2D _CameraDepthTexture; |
次に平面の深度を取得します。バーテックスシェーダ内で
1 |
o.scrPos = ComputeScreenPos (o.vertex); |
上記コードより得られた座標をフラグメントシェーダー内で
1 |
float surfDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.scrPos.z); |
と処理することで、平面の深度を取得しています。
シェーダ
平面とその他オブジェクトの深度差を表示するシェーダです。ノーマルマップによってカメラの深度テクスチャを歪ませています。
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 |
Shader "Unlit/Depth" { Properties { _BumpTex ("BumpTexture", 2D) = "white" {} _Distortion ("Distortion", Range(-0.5, 0.5)) = 0 _Shift ("Shift", range (0,2)) = 0.1 } SubShader { Tags { "RenderType"="Opaque" } GrabPass{ "_GrabTex" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 scrPos : TEXCOORD1; }; sampler2D _BumpTex; float4 _BumpTex_ST; sampler2D _CameraDepthTexture; float4 _CameraDepthTexture_ST; float _Distortion; float _Shift; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _BumpTex); o.scrPos = ComputeScreenPos (o.vertex); //COMPUTE_EYEDEPTH(o.scrPos.z); return o; } fixed4 frag (v2f i) : SV_Target { float3 bump = UnpackNormal(tex2D(_BumpTex, i.uv)); float4 depthUV = i.scrPos; depthUV.xy = i.scrPos.xy * _Shift + bump.xy * _Distortion; float depth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(depthUV))); float surfDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.scrPos.z); float depthDiff = saturate (depth - surfDepth); fixed4 col = float4(depthDiff, depthDiff, depthDiff, 1); return col; } ENDCG } } } |
実行結果
上記シェーダの実行結果は以下の通りです。
あまり変化がないように見えますが、テクスチャをずらしてみると
となります。これより、オブジェクトが水面より出ている部分が黒くなっていることがわかります。また、ノーマルマップによって歪ませると
深度差が歪んだ状態で求まっていることがわかります。
屈折シェーダ
深度差を用いて水中部分のみ歪ませるようなシェーダを作成します。深度差が0となっている場所では、ノーマルマップによってuv座標を変えないように処理をすれば良いので
1 2 3 |
float2 uvoffset = bump.xy * _Distortion; float2 grabUV; grabUV = (i.grabPos.xy + uvoffset * depthDiff) / i.grabPos.w; |
とすれば、水面に出ている部分は歪まなくなります。
シェーダ
歪みシェーダに深度差を求める処理と上記コードを追加しました。
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 |
Shader "Unlit/Refraction" { Properties { _Color ("Color", Color) = (1, 1, 1, 1) _BumpTex ("BumpTexture", 2D) = "white" {} _Distortion ("Distortion", Range(-0.3, 0.3)) = 0 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent"} GrabPass{ "_GrabTex" } Pass { //ZTest Always CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 grabPos : TEXCOORD1; float4 scrPos : TEXCOORD2; }; sampler2D _BumpTex; float4 _BumpTex_ST; sampler2D _GrabTex; float4 _GrabTex_ST; float4 _GrabTex_TexelSize; sampler2D _CameraDepthTexture; float2 _CameraDepthTexture_ST; float4 _CameraDepthTexture_TexelSize; float4 _Color; float _Distortion; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _BumpTex); o.grabPos = ComputeGrabScreenPos(o.vertex); o.scrPos = ComputeScreenPos(o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { float3 bump = UnpackNormal(tex2D(_BumpTex, i.uv)); float4 depthUV = i.grabPos; depthUV.xy = i.grabPos.xy + (bump.xy * _Distortion); float surfDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.scrPos.z); float refFix = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(depthUV)))); float depthDiff = saturate(refFix - surfDepth); float2 uvoffset = bump.xy * _Distortion; float2 grabUV; grabUV = (i.grabPos.xy + uvoffset * depthDiff) / i.grabPos.w; float4 col = tex2D(_GrabTex, grabUV) * _Color; return col; } ENDCG } } } |
実行結果
歪みシェーダではみ出していた部分が消えていることがわかります。
しかし、拡大してみると
このように、オブジェクトの周りに何やら発生しています。
解決方法
原因
GrabPassによって得られるテクスチャは色が補間されていますが、深度テクスチャは補間されません。そのため、GrabPassによって得られたテクスチャが補間されたことによって、深度テクスチャよりも大きくなる部分ができます。その結果、補間によってはみ出した場所がそのまま出力され、上記のような結果となっているようです。ためしに、歪みシェーダの結果に深度差を乗算した結果を出力したところ
このような結果が得られました。これにより、色の補間によってはみ出でてしまっていることがわかります。
解決方法
GrabPassによって得られるテクスチャの色の補間をなくす処理(ポイントフィルタ)を追加します。下記に示すAlignWithGrabTexelという関数を作成し、その関数でuv座標を処理することでフィルタリングを行います。
1 2 3 4 5 6 |
float2 AlignWithGrabTexel (float2 uv) { return (floor(uv * _CameraDepthTexture_TexelSize.zw) + 0.5) * abs(_CameraDepthTexture_TexelSize.xy); } ..... grabUV = AlignWithGrabTexel((i.grabPos.xy + uvoffset * depthDiff) / i.grabPos.w); |
シェーダ
ポイントフィルターによる処理を追加したシェーダです。
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 |
Shader "Unlit/Refraction" { Properties { _Color ("Color", Color) = (1, 1, 1, 1) _BumpTex ("BumpTexture", 2D) = "white" {} _Distortion ("Distortion", Range(-0.3, 0.3)) = 0 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent"} GrabPass{ "_GrabTex" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 grabPos : TEXCOORD1; float4 scrPos : TEXCOORD2; }; sampler2D _BumpTex; float4 _BumpTex_ST; sampler2D _GrabTex; float4 _GrabTex_ST; float4 _GrabTex_TexelSize; sampler2D _CameraDepthTexture; float2 _CameraDepthTexture_ST; float4 _CameraDepthTexture_TexelSize; float4 _Color; float _Distortion; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _BumpTex); o.grabPos = ComputeGrabScreenPos(o.vertex); o.scrPos = ComputeScreenPos(o.vertex); return o; } float2 AlignWithGrabTexel (float2 uv) { return (floor(uv * _CameraDepthTexture_TexelSize.zw) + 0.5) * abs(_CameraDepthTexture_TexelSize.xy); } fixed4 frag (v2f i) : SV_Target { float3 bump = UnpackNormal(tex2D(_BumpTex, i.uv)); float4 depthUV = i.grabPos; depthUV.xy = i.grabPos.xy + (bump.xy * _Distortion); float surfDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.scrPos.z); float refFix = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(depthUV)))); float depthDiff = saturate(refFix - surfDepth); float2 uvoffset = bump.xy * _Distortion; float2 grabUV; grabUV = AlignWithGrabTexel((i.grabPos.xy + uvoffset * depthDiff) / i.grabPos.w); float4 col = tex2D(_GrabTex, grabUV) * _Color; //col = tex2D(_GrabTex, grabUV) * depthDiff * _Color; return col; } ENDCG } } } |
実行結果
オブジェクトの周りに発生していたドットを消すことができました。
参考サイト
-
前の記事
グリッドシェーダ 2018.11.18
-
次の記事
シェーダでテクスチャフィルタリング 2018.12.15
コメントを書く