9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swizzle演算子をT4Templateで作る

Last updated at Posted at 2018-05-29

さてみなさま

Shader Forgeの無料化や、Shader Graphの標準Component入りなど、UnityもShader周りが手厚くなってきましたね。
どうですか?今まで以上にUnityでShaderを触る事が多くなってきてるんじゃないですか?

ところで

shaderで使われるcg言語では float3 とか float4 てのが使われますよね。
これらって面白くて、

float4 a = float4(1,2,3,4);

float2 b = a.xy; //b = (1,2)
float2 c = a.ww; //c = (4,4)

float4 z = b.xxyy; //z = (1,1,2,2)

このように、各要素を .**** で入れ替えたり、増やして変換しつつ参照したりできるんですね。

これをswizzle(スウィズル)演算子って言うそうです。

Unityでは

Vector2からVector4まで用途としては同じようなのものがありますが、残念ながらswizzle演算子は使えません。 使えたら良いですよね。 とりあえず使い道が思いつかなくてもなんか良さそうですよね。

では、作りましょう
まず、Vector2 から、Vector2 へのswizzle演算子っぽいものを拡張メソッドで用意してみます。

using UnityEngine;
namespace VectorExtensions
{
	public static class Vector2SwizzleExtension
	{
		public static Vector2 xx(this Vector2 v)
		{
			return new Vector2(v.x,v.x);
		}
		public static Vector2 xy(this Vector2 v)
		{
			return new Vector2(v.x,v.y);
		}
		public static Vector2 yx(this Vector2 v)
		{
			return new Vector2(v.y,v.x);
		}
		public static Vector2 yy(this Vector2 v)
		{
			return new Vector2(v.y,v.y);
		}
	}
}

これで、
using VectorExtendions
の一文を入れてあげれば、swizzle演算子(っぽいもの)が使えるって寸法ですよ!

以下、動作確認

SwizzleTest.cs
using UnityEngine;
using VectorExtensions;

public class SwizzleTest : MonoBehaviour
{
	void Start ()
	{
		var v = new Vector2(1,2);
	    var v2 = v.yx();//拡張メソッドが呼ばれる

        Debug.Log("v2="+v2);
	}
}
Console出力
v2=(2.0, 1.0)
UnityEngine.Debug:Log(Object)
SwizzleTest:Start() (at Assets/SwizzleTest.cs:11)

おっちゃんとxとyの1と2が入れ替わってます。良い感じですね。

じゃぁ、次にVector2からVector3! レッツゴー!

		public static Vector3 xxx(this Vector2 v)
		{
			return new Vector3(v.x,v.x,v.x);
		}
		public static Vector3 xxy(this Vector2 v)
		{
			return new Vector3(v.x,v.x,v.y);
		}
		public static Vector3 xyx(this Vector2 v)
		{
			return new Vector3(v.x,v.y,v.x);
		}
		public static Vector3 xyy(this Vector2 v)
		{
			return new Vector3(v.x,v.y,v.y);
		}
		public static Vector3 yxx(this Vector2 v)
		{
			return new Vector3(v.y,v.x,v.x);
		}
		public static Vector3 yxy(this Vector2 v)
		{
			return new Vector3(v.y,v.x,v.y);
		}
		public static Vector3 yyx(this Vector2 v)
		{
			return new Vector3(v.y,v.y,v.x);
		}
		public static Vector3 yyy(this Vector2 v)
		{
			return new Vector3(v.y,v.y,v.y);
		}

で…できた。 うん。動いてる。

using UnityEngine;
using VectorExtensions;

public class SwizzleTest : MonoBehaviour
{
	void Start ()
	{
		var v = new Vector2(1,2);
	    var v2 = v.yx();
	    var v3 = v.yxy();
	    Debug.Log("v2=" + v2);
	    Debug.Log("v3=" + v3);
	}
}
出力
v2=(2.0, 1.0)
UnityEngine.Debug:Log(Object)
SwizzleTest:Start() (at Assets/SwizzleTest.cs:11)
v3=(2.0, 1.0, 2.0)
UnityEngine.Debug:Log(Object)
SwizzleTest:Start() (at Assets/SwizzleTest.cs:12)

つ、つぎ・・・。 Vector2からVector4… う・・・うわぁぁぁああああーー!

はい。地獄です。
この調子で作っていくとVector4からVector4のswizzleメソッドを作ると256メソッド
最終的には全部で484の拡張メソッド作ることになります。
仮に手作業で全部作ったとして、言うても人力。抜けが無いか心配ですよね。

そこでT4Template

T4Templateというのは、ちょっと特殊な記法で書かれたソースコードによってテキストやソースコードを生成する仕組みの事です。

VisualStudioならソリューションエクスプローラーで、Assembly-CSharpを右クリックして「追加」→「新しい項目」→「全般」→「テキストテンプレート」で追加できます(拡張子は*.tt)
image.png

そして、以下のように、t4templateの書式に従って、ソースコードを生成するコードを書きます。
(T4Templateについて詳しく知りたい方は
https://msdn.microsoft.com/ja-jp/library/bb126445.aspx
などを参照してください。)

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>

using UnityEngine;
namespace VectorExtensions
{
<#
	var target = new[]{"","x","y","z","w"};
	var min = 2;
	var max = 4;
	var classExtension = "Vector";

	for(var baseNum = min;baseNum <= max;baseNum++)
	{
		var alreadyUsedMethod = new List<string>();

# >
	public static class Vector<#=baseNum#>SwizzleExtension
	{
<#
		for(var targetNum = min;targetNum <= max;targetNum++)
		{
			for(var a = 0;a < 5;++a)
			{
				for(var b = 0;b < 5;++b)
				{
					for(var c = 0;c < 5;++c)
					{
						for(var d = 0;d < 5;++d)
						{
							var methodName = target[a]+target[b]+target[c]+target[d];
							if(methodName.Length < 2)continue;
							if(methodName.Length > targetNum)continue;
							if(a > baseNum || b > baseNum || c > baseNum || d > baseNum)continue;
							if(alreadyUsedMethod.Contains(methodName))continue;
							alreadyUsedMethod.Add(methodName);
# >
		public static <#=classExtension+targetNum#> <#=methodName#>(this <#=classExtension+baseNum#> v)
		{
			return new <#=classExtension+targetNum#>(<#=string.Join(",",methodName.Select(field => "v." + field))#>);
		}
<#
							}
						}
					}
				}
			}
# >
	}
<#
		}
# >
}

この<# から #> に挟まれている所にはC#などでScriptを書くことが出来ます。(逆にそれ以外の箇所は、テキストとして生成されます)
この<#~#>に着目すると、T4Templateを知らない人でもわかる、恐怖の6重forループが確認できると思います。これだけ見ても人の手には余る事がわかりますね。
(組み合わせ作成にあたっては、本当は再起とかを使えば良いんですが、今回は最大でも4要素(xyzw)と分かっているので、多重for文になってます)

T4Templateのビルド

T4Tempolateファイルは保存をした瞬間に変換・生成処理が走りるようになっているので、
保存すると、以下のように作ったttファイルの子要素的にcsファイルが生成されます。

image.png

(または、ツールバーの「ビルド」→「すべてのT4テンプレートの変換」でも明示的に変換・生成処理が走ります)
image.png

image.png

完成品(VectorNSwizzleExtension)

全部で1939行、484メソッドが一瞬にして生成されました。やったね!

大事なことを言い忘れてました

このT4Templateの仕組みはうまく使うと色んな作業が自動化出来て素晴らしいんですが、Unityでサポートされてません
なので、一度Unity側のウインドウを開いて再コンパイルなどが走ると、Unityがcsprojを把握しているcsファイルなどで再構築するため、ttファイルがプロジェクトから除外されてしまいます(ファイルとしては残る)
なので、もう一度変換したい場合はVisualStudioなどのIDE上でまたttファイルをプロジェクトに追加してから変換実行する必要があったりします(今回のは1回作れば要件を満たすので良いと言えば良いんですが)

これ、どうにかならないんですかね。
https://issuetracker.unity3d.com/issues/t4-templates-disappear-from-monodevelop-project-after-refreshing-solution
みたらもう2013年にはまったく同じような事がissueに上がってるので、まぁ、望み薄ですね・・・。

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?