SVGでのアフィン変換の活用

Canvasによる3Dテクスチャマッピングとパフォーマンスチューニング|最速チュパカブラ研究会
では、HTML5Canvasの上で、3Dモデルのテクスチャマッピングを行う方法を紹介している。

内容は「アフィン変換とクリッピングを使って、画像上の三角形領域を、別の三角形にマッピングする」というもの。

図を使って説明する方がわかりやすい。

↑画像の上に青い三角形がある。この領域を緑色の三角形にマッピングしたい。

↑青い三角形の中身を緑色の三角形の内部にマッピングした結果。

結果の図から、何をしたいか理解できるだろう。
このような画像の切り取りと変形ができると、3DCGで使われるテクスチャマッピングが2Dグラフィックスで実現できる。

でも、青い三角形と緑の三角形は、大きさ・形が違うので、単純なコピーではうまくいかない。
青い三角形の各頂点が緑の三角形の頂点位置に来て、なおかつ内部の画像が適切に補間されて欲しい。

これは、青の三角形に、適切なアフィン変換を施すことで実現できる。

Canvasによる3Dテクスチャマッピングとパフォーマンスチューニング|最速チュパカブラ研究会
では、このアフィン変換に用いる行列を求める方法が説明されている。

アフィン変換は、回転・スケール変換・スキューを行う2x2の行列(4変数)と平行移動ベクトル(2変数)の組み合わせの演算で行われる。
つまり、6つの変数によって1つのアフィン変換が定まる。

入力として2つの三角形が与えられるので、2次元座標を持つ3つの点の位置関係が分かる。
その結果2x3=6つの式が立つので、ある三角形を別の三角形に移すようなアフィン変換は1つに定まる。

上記リンク先の記事では、これをHTML5Canvasで実現する方法を紹介してあって、興味深く思ったので、勉強を兼ねて同じことをSVGで行ってみた。

以下、その記録。


■ 画像の配置

画像の配置は<image>タグを使う。

<image x='0' y='0' width='256' height='256' xlink:href='checkerboard.png' />

SVGファイル全体

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' >
	<image x='0' y='0' width='256' height='256' xlink:href='checkerboard.png' />
</svg>

実行結果


■ 三角形の描画

三角形(多角形)の描画には<polygon>タグを使う。

<polygon points='150, 150 150, 220 220, 200' stroke='blue' fill='none' stroke-width='2' />
<polygon points='300, 80 360,160 410, 120' stroke='green' fill='none' stroke-width='2' />

SVGファイル全体

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' >
	<image x='0' y='0' width='256' height='256' xlink:href="checkerboard.png" />
	<polygon points='150, 150 150, 220 220, 200' stroke='blue' fill='none' stroke-width='2' />
	<polygon points='300, 80 360,160 410, 120' stroke='green' fill='none' stroke-width='2' />
</svg>

実行結果


■ symbolとuseを使った複製

同じ画像を複数回使う場合は、その都度ファイルを読み込むのではなく、<polygon>タグを使ってシンボルとして登録し、
<use>タグで再利用するのがよい

image オブジェクトの symbol 登録は次のように行う。 symbol は後から参照できるように id を設定しておく。

<symbol id='img01' viewBox='0 0 256 256'>	
	<image x='0' y='0' width='256' height='256' xlink:href='checkerboard.png' />
</symbol>

use タグを使った symbol の参照は次のように行う。xlink:href で、symbolのidを指定する

<use x='0' y='0' width='256' height='256' xlink:href='#img01' />

異なる場所に配置するには、transform の指定を行う。translate(300,100) で、横に300, 下に100だけ平行移動できる。

<use x='0' y='0' width='256' height='256' xlink:href='#img01' transform='translate(300,100)' />


SVGファイル全体

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' >
	<symbol id='img01' viewBox='0 0 256 256'>	
		<image x='0' y='0' width='256' height='256' xlink:href='checkerboard.png' />
	</symbol>
	<use x='0' y='0' width='256' height='256' xlink:href='#img01' />
	<use x='0' y='0' width='256' height='256' xlink:href='#img01' transform='translate(300,100)' />
</svg>

実行結果

■ アフィン変換の適用

画像のアフィン変換は、transform 要素に matrix(a, b, c, d, e, f) を指定して実現する。
Canvasによる3Dテクスチャマッピングとパフォーマンスチューニング|最速チュパカブラ研究会」で紹介されている方法で、アフィン変換の6変数を計算する。

<use x='0' y='0' width='256' height='256' xlink:href='#img01' transform='matrix(0.959184 -0.244898 0.857143 1.142857 27.551020 -54.693878)' />

SVGファイル全体

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' >
	<symbol id='img01' viewBox='0 0 256 256'>	
		<image x='0' y='0' width='256' height='256' xlink:href='checkerboard.png' />
	</symbol>
	<use x='0' y='0' width='256' height='256' xlink:href='#img01' />
	<use x='0' y='0' width='256' height='256' xlink:href='#img01' transform='matrix(0.959184 -0.244898 0.857143 1.142857 27.551020 -54.693878)' />
	<polygon points='150, 150 150, 220 220, 200' stroke='blue' fill='none' stroke-width='2' />
	<polygon points='300, 80 360,160 410, 120' stroke='green' fill='none' stroke-width='2' />
</svg>

実行結果


クリッピング

画像の一部分だけを表示するにはクリッピングを行う。
クリッピング領域の定義は<clipPath>タグを使う。今回は三角形なので、clipPathにpolygonを使う。
後で参照できるように、このクリッピングにidを付けて、<defs>タグで囲む。

<defs>
	<clipPath id='clip1'>
		<polygon points='150, 150 150, 220 220, 200' />
	</clipPath>
</defs>

ここで定義したクリッピングを画像に適用するには、clip-path 要素を追加し、先ほど定義したidを指定する。

<use x='0' y='0' width='256' height='256' xlink:href='#img01' transform='matrix(0.959184 -0.244898 0.857143 1.142857 27.551020 -54.693878)' clip-path='url(#clip1)' />

SVGファイル全体

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' >
	<symbol id='img01' viewBox='0 0 256 256'>	
		<image x='0' y='0' width='256' height='256' xlink:href='checkerboard.png' />
	</symbol>
	<use x='0' y='0' width='256' height='256' xlink:href='#img01' />
	<defs>
		<clipPath id='clip1'>
			<polygon points='150, 150 150, 220 220, 200' />
		</clipPath>
	</defs>
	<use x='0' y='0' width='256' height='256' xlink:href='#img01' transform='matrix(0.959184 -0.244898 0.857143 1.142857 27.551020 -54.693878)' clip-path='url(#clip1)' />
	<polygon points='150, 150 150, 220 220, 200' stroke='blue' fill='none' stroke-width='2' />
	<polygon points='300, 80 360,160 410, 120' stroke='green' fill='none' stroke-width='2' />
</svg>

実行結果

関連エントリ
・アフィン変換とは - 大人になってからの再学習

ゲームプログラミングのための3Dグラフィックス数学

ゲームプログラミングのための3Dグラフィックス数学

プログラミングのための線形代数

プログラミングのための線形代数