Textureを合成するShader
Textureを任意の位置に好きな大きさで合成することができるShaderです。
図の青色が元のTexture、赤色が合成するTextureです。
外積と内積を使用する方法
・Textureを合成する領域かの判別
外積と内積を用いて点Qが合成する赤色の領域(点Pを中心とした大きさ2hの四角形abcd)にあるかどうかを判別します。
\(\vec{ab}\)と\(\vec{aQ}\)の外積\(\vec{an}=\vec{ab}\times\vec{aQ}\)で得られるベクトルの方向は下図の紫色の範囲では手前方向、橙色の範囲では奥の方向になります。
また、\(\vec{bc}\)と\(\vec{bQ}\)の外積\(\vec{bn}=\vec{bc}\times\vec{bQ}\)で得られるベクトルの方向は下図で色分けされた通りです。
次に、\(\vec{an}\)と\(\vec{bn}\)の内積を計算します。下図の青色の領域は\(\vec{an}\)と\(\vec{bn}\)が同じ方向を、赤色の領域は反対の方向を向いています。それぞれを単位ベクトルにすることで、ベクトルがなす角度を計算することができます。角度が\(0^\circ\)、二つベクトルが同じ方向の場合は内積値は1となります。また、角度が\(180^\circ\)、二つベクトルが反対方向の場合は-1となります。そのため、下図に示すように青色の領域の内積値は1に、赤色の領域では-1となります。
以上より、\(\vec{an}\cdot\vec{bn}\)、\(\vec{bn}\cdot\vec{cn}\)、\(\vec{cn}\cdot\vec{dn}\)及び\(\vec{dn}\cdot\vec{an}\)の全ての内積値が1となる座標がabcdで囲まれた四角形内の座標となります。
・合成するテクスチャの座標変換
下図のように、\((P_{x}-h,P_{y}-h)\)を原点とする大きさが2h×2hの座標を、\((0,0)\)を原点とする大きさが1×1のUV座標へ変換する必要があります。式は以下の通りです。
$$
Q^{\prime}=\frac{Q-(P-h)}{2h}=\frac{Q-P}{2h}+\frac{1}{2}
$$
・shader
shaderでは内積値をsaturateを使用して-1~1を0~1へ変換しています。内積値に2をかけることにより、誤差により1未満の値になったときでも1となるようにしています。内積値を掛け算することで、領域内は1をそれ以外は0となるようにしています。最後に色の合成をしますが、合成するTextureのalpha値を用いて計算しています。これにより、合成するTextureに透明もしくは半透明な部分があっても、問題なく表示されるようになっています。
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 90 91 |
Shader "Unlit/Paint" { Properties { _MainTex ("Texture", 2D) = "white" {} _PaintTex ("BlushTexture", 2D) = "white" {}//合成するTexture _UVPosition ("UV Position", VECTOR) = (0.5, 0.5, 0, 0)//合成するTextureの中心座標 _Size ("PaintSize", Range(0.001, 0.5)) = 0.1//合成するTextureの大きさ } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha 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; }; sampler2D _MainTex; sampler2D _PaintTex; float4 _MainTex_ST; float4 _PaintTex_ST; float4 _UVPosition; float _Size; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { float3 ab = float3(_Size, 0, 0); float3 bc = float3(0, _Size, 0); float3 cd = float3( -_Size, 0, 0); float3 da = float3(0, -_Size, 0); float3 aq = float3(i.uv, 0) - float3(_UVPosition.x - _Size, _UVPosition.y - _Size, 0); float3 bq = float3(i.uv, 0) - float3(_UVPosition.x + _Size, _UVPosition.y - _Size, 0); float3 cq = float3(i.uv, 0) - float3(_UVPosition.x + _Size, _UVPosition.y + _Size, 0); float3 dq = float3(i.uv, 0) - float3(_UVPosition.x - _Size, _UVPosition.y + _Size, 0); float3 an = normalize(cross(ab, aq)); float3 bn = normalize(cross(bc, bq)); float3 cn = normalize(cross(cd, cq)); float3 dn = normalize(cross(da, dq)); float com; com = saturate(dot(an, bn) * 2); com = saturate(dot(bn, cn) * 2) * com; com = saturate(dot(cn, dn) * 2) * com; com = saturate(dot(dn, an) * 2) * com; com = saturate(com); fixed4 col_1 = tex2D(_MainTex, i.uv); fixed4 col_2 = tex2D(_PaintTex, (i.uv - _UVPosition) * 0.5 / _Size + 0.5); fixed4 col = col_1 * (1 - com * col_2.a) + col_2 * (com * col_2.a); return col; } ENDCG } } } |
・stepを使用する方法
外積と内積を使用して領域内に点Qがあるかどうかを判別していましたが、stepを用いることで簡潔に同じことができます。
\(u\)が\((P_{x}-h)\)より大きく、\((P_{x}+h)\)より小さい。また、\(v\)が\((P_{y}-h)\)より大きく、\((P_{y}+h)\)より小さい。これを満たすとき、点Qは領域内にあるといえます。領域内では1を、それ以外は0を返すコードをif文で記述すると
1 2 3 4 5 |
if(Px - h < u && Px + h > u && Py - h < v && Py + h > v){ com = 1; }else{ com = 0; } |
となります。これをstepを用いて書き直します。step(a, b)はbがa以上となった場合1を、それ以外は0を返します。よって上記のif文は
1 2 3 4 5 |
float com; com = step(Px - h, u); com = step(u, Px + h) * com; com = step(Py - h, v) * com; com = step(v, Py + h) * com; |
とstepで書き換えることができます。
・shder
stepを用いてTextureを合成するshaderは以下の通りです。外積と内積を使用した場合に比べて、コードが簡潔になりました。
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 |
Shader "Unlit/Paint" { Properties { _MainTex ("Texture", 2D) = "white" {} _PaintTex ("BlushTexture", 2D) = "white" {}//合成するTexture _UVPosition ("UV Position", VECTOR) = (0.5, 0.5, 0, 0)//合成するTextureの中心座標 _Size ("PaintSize", Range(0.001, 0.5)) = 0.1//合成するTextureの大きさ } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha 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; }; sampler2D _MainTex; sampler2D _PaintTex; float4 _MainTex_ST; float4 _PaintTex_ST; float4 _UVPosition; float _Size; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { float com; com = step(_UVPosition.x - _Size, i.uv.x); com = step(i.uv.x, _UVPosition.x + _Size) * com; com = step(_UVPosition.y - _Size, i.uv.y) * com; com = step( i.uv.y, _UVPosition.y + _Size) * com; fixed4 col_1 = tex2D(_MainTex, i.uv); fixed4 col_2 = tex2D(_PaintTex, (i.uv - _UVPosition) * 0.5 / _Size + 0.5); fixed4 col = col_1 * (1 - com * col_2.a) + col_2 * (com * col_2.a); return col; } ENDCG } } } |
alpha値を考慮した色の合成
以上のshaderではメインのテクスチャのalpha値は考慮されていないため、メインのテクスチャに透明、もしくは半透明な部分があった場合、正しく表示されません。そこで、メインのテクスチャのalpha値を考慮して、合成できるようにshaderを変更しました。
1 |
fixed4 col = col_1 * (1 - com * col_2.a) + col_2 * (com * col_2.a); |
上記部分を
1 2 3 |
col_2.a = col_2.a * com; fixed alpha = col_2.a + (1 - col_2.a) * col_1.a; fixed4 col = fixed4((col_2.rgb * col_2.a + (col_1.rgb * col_1.a * (1 - col_2.a))) / alpha, alpha); |
と書き換えることで両方のテクスチャのalpha値を考慮した合成ができます。
実行結果
実行結果はこちらです。スライダーを動かすことで、合成するTextureの位置と大きさを変更できます。
参考サイト
-
前の記事
端が直線にならないHPゲージの作り方(Shader) 2018.09.18
-
次の記事
平面の鏡面反射 2018.10.17
コメントを書く