デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す。https://qiita.com/yaju

テクスチャマッピングを理解してみる

ひさしぶりの投稿である。
1年前にやり始めたのにいつのまには遠のいてしまっていたが、ふとテクスチャマッピングってどういう仕組みなのか知りたくなったので再開する。

どんな単純な多角形も三角形に分割できるので、三角形が描ければテクスチャマッピングを実現できる。
FlashのActionScript3.0には、Graphics.drawTriangles()があるが、HTML5CanvasAPIには残念ながら存在しない。
「テスクチャ Javascript drawTriangle」で検索して、下記サイトが分かりやすいと思ったので理解してみようと思いたった。
Canvasによる3Dテクスチャマッピングとパフォーマンスチューニング(仮題)

アフィン変換行列には「変形」と「移動」が同時に含まれているので、まずは「変形」の方を考える。

Figure 3-3 に示すように、三角形のポリゴンを描画するとき、3つの頂点のうち1つを基点にして、のこりの頂点にベクトルを2本伸ばすことができる。貼り付け元のテクスチャ上でのベクトルを A および B 、描画先でのベクトルを A' および B' としよう。

ベクトル A および B に(移動を除く)アフィン変換をかけた結果、ベクトルを A' および B' になればよいのだから、

A'x = a*Ax + c*Ay (1)
A'y = b*Ax + d*Ay (2)
B'x = a*Bx + c*By (3)
B'y = b*Bx + d*By (4)
(1)と(3)を行列で書くと

(1) and (3) in matrices:

|A'x| = |Ax Ay| |a|
|B'x| |Bx By| |c|
だから、

|Ax Ay|^-1 |A'x| = |a|
|Bx By| |B'x| |c|
という操作で a と c を求めることができる。b と d についても、

|Ax Ay|^-1 |A'y| = |b|
|Bx By| |B'y| |d|
で求めることができる。

2009-02-11 - 最速チュパカブラ研究会

行列によるアフィン変換もすっかり忘れていたが、1年前に書いた自分の記事で思い出した。
前半の「(1)と(3)を行列で書くと」まではなんとなく理解できたが、「だから、」以降からつまずいた。

|Ax Ay|^-1 |A'x| = |a|
|Bx By|    |B'x|   |c|

これは何の式なのかと調べていくと、逆行列(^-1は逆行列の意味)であることが分かった。
現在位置(Ax,Ay)と移動先(A'x,A'y)は求まっているので、transformメソッドの引数で使う変換マトリックス(a,b,c,d)を求める必要がある。

↓a, cについて求めたいのだから、左に掛けているものを「1」にする必要がある。
行列を1にするには、逆行列を左から掛ければいいので、両辺に逆行列を掛ける。
|Ax Ay|^-1 |_Ax| = |a|
|Bx By|    |_Bx| = |c|

2×2行列の逆行列の公式は、下記の通りである。

A = |a b|
    |c d|

        1   |d -b|
A^-1= ----- |    |
      ad-bc |-c a|

上記のことが分かれば、下記のプログラムが何をしているかが理解できた。

// |a,b| |_11,_12|
// |c,d| |_21,_22|

function M22()
{
	this._11 = 1;  //a
	this._12 = 0;  //b
	this._21 = 0;  //c
	this._22 = 1;  //d
}

M22.prototype.getInvert = function()
{
	var out = new M22();
	//逆行列の公式 ad - bc の部分
	var det = this._11 * this._22 - this._12 * this._21;
	if (det > -0.0001 && det < 0.0001)
		return null;

	//逆行列の公式 det=(ad - bc) で各値(a,b,c,d)を割る
	out._11 = this._22 / det;  // a = d / det
	out._22 = this._11 / det;  // d = a / det

	out._12 = -this._12 / det; // b = -b / det
	out._21 = -this._21 / det; // c = -c / det

	return out;
}

ここからdrawTriangle()の中身を見てみる。
function drawTriangle(g, img, vertex_list, uv_list)
vertex_listは、描画する三角形の頂点座標3つである。
uv_listは、テクスチャの頂点座標3つである。UV座標は0~1の範囲で指定する。
テクスチャはサンプルから切り取った128x128の下記画像を使う。
f:id:Yaju3D:20130217230513j:plain

drawTriangle(g, img, 
[
 100, 100,
 150, 110,
 60, 160
],
[
 0.75, 0,
 1   , 0,
 0.75, 0.25
]
);

// Figure 3-6のソースリスト(平行移動とclipメソッドを含んでいる状態)
function drawTriangle(g, img, vertex_list, uv_list)
{
	var _Ax = vertex_list[2] - vertex_list[0];
	var _Ay = vertex_list[3] - vertex_list[1];
	var _Bx = vertex_list[4] - vertex_list[0];
	var _By = vertex_list[5] - vertex_list[1];

	var Ax = (uv_list[2] - uv_list[0]) * img.width;
	var Ay = (uv_list[3] - uv_list[1]) * img.height;
	var Bx = (uv_list[4] - uv_list[0]) * img.width;
	var By = (uv_list[5] - uv_list[1]) * img.height;

	//逆行列を求める
	var m = new M22();
	m._11 = Ax;
	m._12 = Ay;
	m._21 = Bx;
	m._22 = By;
	var mi = m.getInvert();
	if (!mi) return;

	//マトリックス変換値を求める
	var a, b, c, d;
	a = mi._11 * _Ax + mi._12 * _Bx;
	c = mi._21 * _Ax + mi._22 * _Bx;

	b = mi._11 * _Ay + mi._12 * _By;
	d = mi._21 * _Ay + mi._22 * _By;

	g.save();
	g.beginPath();
	g.moveTo(vertex_list[0], vertex_list[1]);
	g.lineTo(vertex_list[2], vertex_list[3]);
	g.lineTo(vertex_list[4], vertex_list[5]);
	g.clip(); //三角形に切り取る

	//平行移動を追加済み        
	g.transform(a, b, c, d,
		vertex_list[0] - (a * uv_list[0] * img.width + c * uv_list[1] * img.height),
		vertex_list[1] - (b * uv_list[0] * img.width + d * uv_list[1] * img.height));
	g.drawImage(img, 0, 0);
	g.restore();
}

これでテクスチャのルーチンの中身は把握できた。この三角形ルーチンを組み合わせれば多角形を描くことが出来るようになる。
次回はもう少しつっこんだツールっぽいのを作成してみる。