【Unity】アフィン変換+テクスチャ合成をやる2Dシェーダー
前置き
シェーダーの勉強をしてみよう!と思い立ち、テクスチャに別のテクスチャをシールのように貼れるシェーダーが作れたらいいんじゃないかな、もとい勉強になるんじゃないかなと思い立ち、色々勉強して書いてみました。
なかなか難産でした……。
参考文献はこちら↓
amagamina.jp
qiita.com
qiita.com
アフィン変換とは?
まずアフィン変換とは?です。
やってることとしては単なる行列計算です。
そもそも画像とは概して色情報の二次元配列(行列)ですが、それに特定の行列をかけることで、その色の位置を自在に並べ替えることが出来ます。
平行移動をする行列、拡大縮小をする行列など様々なものがありますが、それらを纏めて実行できるのがアフィン変換です。
ようするに、画像を回転、縮小、移動などの方式で変形できる式を、画像のピクセル座標それぞれにかけることですね。
シェーダー本文
Shader "AddAfin" { Properties { //インスペクターに表示する内容 [PerRendererData] _MainTex("Texture", 2D) = "white" {} //重ねるテクスチャ _SubTex("Texture plus", 2D) = "white" {} //サブテクスチャの変形パラメータ _Rotate("Rotate",Range(-6.28,6.28)) = 0 _ScaleX("ScaleX",Range(0,2)) = 1 _ScaleY("ScaleY",Range(0,2)) = 1 _MoveX("MoveX",Range(-0.5,0.5)) = 0 _MoveY("MoveY",Range(-0.5,0.5)) = 0 //回転中心 _PivotX("PivotX",Range(0,1)) = 0.5 _PivotY("PivotY",Range(0,1)) = 0.5 //透明度 _Blend("Blend",Range(0,1)) = 0.5 } SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv0 : TEXCOORD0; }; struct v2f { float2 uv0 : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; sampler2D _SubTex; float _Rotate; float _MoveX; float _MoveY; float _ScaleX; float _ScaleY; float _PivotX; float _PivotY; float _Blend; // TRANSFORM_TEXを使う時に各テクスチャについて _ST を付けたfloat4が必要 float4 _MainTex_ST; float4 _SubTex_ST; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv0 = TRANSFORM_TEX(v.uv0, _MainTex); return o; } fixed4 frag(v2f i) : SV_Target { // 平行移動 float3x3 positionMatrix = { 1, 0, 0, 0, 1, 0, _MoveX, _MoveY, 1 }; // 回転 float3x3 rotateMatrix = { cos(_Rotate), sin(_Rotate), 0, -sin(_Rotate), cos(_Rotate), 0, 0, 0, 1 }; // 拡大縮小 float3x3 scaleMatrix = { 1/_ScaleX, 0, 0, 0, 1/_ScaleY, 0, 0, 0, 1 }; //変形をかけるためにサブテクスチャの三次元行列をつくる float3 mtx = float3(i.uv0.x, i.uv0.y, 1); //ピボットの行列を作る float3 pv = float3(_PivotX, _PivotY, 0); //変形 順番大事! //ピボット分ずらす mtx = mtx - pv; mtx = mul(mtx, positionMatrix); mtx = mul(mtx, scaleMatrix); mtx = mul(mtx,rotateMatrix); //ピボット分戻す mtx = mtx + pv; // サブテクスチャを二次元に戻す float2 u = mtx; // テクスチャ生成 fixed4 main = tex2D(_MainTex, i.uv0); fixed4 sub = tex2D(_SubTex, u); //透明度に合わせてブレンドパラメータの調整 //step関数で、sub.aが0より大きい時には0を返し、f=1となるようにする float f = 1.0 - step(sub.a, 0); _Blend = _Blend * f; //メインテクスチャとサブテクスチャのブレンドパラメータに合わせた合成 fixed4 col = main * (1 - _Blend) + sub * _Blend; // 全体として透明な部分の描写をしない clip(col.a - 0.1); return col; } ENDCG } } }
ポイントはピボットの足し引き含む変形の順番です。
間違えると変形結果がおかしくなるかもしれないので注意してください。
また、サブテクスチャの透明度をブレンドパラメータで調整していますが、サブテクスチャが透明な部分を合成する際にはブレンドパラメータを0にしています。
詳細は各ドキュメント等などを確認してもらいたいのですが、簡単に言えば、シェーダーとはピクセルのそれぞれについて呼ばれ、実行されるプログラムです。
そして、メインテクスチャとサブテクスチャを合成する時には、ブレンドパラメータを用いてメインテクスチャとサブテクスチャのそれぞれの色の割合を調整し、掛け合わせています。
サブテクスチャで透明なピクセルでシェーダーが呼ばれたときにもブレンドパラメータが0より大きい値のままで二つのテクスチャを掛け合わそうとすると、サブテクスチャが重ならない部分でもメインテクスチャが不本意に薄い色で描写されてしまいます。
なのでこの処理が必要です。
後書き
応用すればマスクや逆マスクも実装できそうです。
それにしても難しかったです。数学音痴には厳しい…。
おすすめの本です↓
無料相談アリ!プロから学びたい方はこちら↓
TechAcademy [テックアカデミー]