メインコンテンツへ移動

基本変換とアニメーション_WebGL ノート 10

無料2016-01-06#JS#WebGL#webgl transform#webgl translate#webgl rotate#webgl scale#webgl animation#webgl animate#webgl平移旋转缩放#webgl动画

canvas 描画には便利な変換 API があるが、WebGL では自分で実装する必要がある

前置き

[前回のノート](/articles/glsl-es(opengl-es 着色器语言)-webgl 笔记 9/) で、GLSL ES の構文を全面的に理解し、習得必須の WebGL 基礎知識はほぼ揃いました(つまり API はもうありません..)。これからの内容は基本的に数学で、変換はその中で最も浅い部分の一つです

一.変換の数学原理

一言二言では説明しきれません。例えば translate(平行移動)は画像の各点の座標の各成分に delta を追加することです。点 (0, 3, 2) を y 軸正方向に 2 単位長さ移動すると、結果は (0, 5, 2) になります。原理はこれほど簡単ですが、説明しきれないのは公式導出と行列表現に関わるためです。以下の通り:

x' = x + deltaX;      how? why?      |1 0 0 tx|
y' = y + deltaY;    ------------>    |0 1 0 ty|
z' = z + deltaZ;                     |0 0 1 tz|
                                     |0 0 0 1 |

三次元座標はなぜ 4x4 行列で translate 変換を表す必要があるのか?このようにするメリットは何か?rotate と scale はどうか?

興味があれば自行で摸索(検索)してください。完全なプロセスは公式導出->斉次座標->行列乗法->変換行列->CTM で、CTM を理解すれば、変換の数学原理を完全に理解できます

P.S.あまりに数学的なものは暫時書きたくありませんが、遅かれ早かれ書きます。したがってここでは変換行列を展開せず、後で再说します

二.WebGL 変換行列

変換行列はすべて同じ形をしているわけではありません。いいえいいえいいえ、前回のノートで言いましたが、GLSL ES 中の行列は列主序です。例えば translate 行列はこのようになります:

Matrix4.prototype.setTranslate = function(x, y, z) {
    var e = this.elements;
    e[0] = 1;  e[4] = 0;  e[8]  = 0;  e[12] = x;
    e[1] = 0;  e[5] = 1;  e[9]  = 0;  e[13] = y;
    e[2] = 0;  e[6] = 0;  e[10] = 1;  e[14] = z;
    e[3] = 0;  e[7] = 0;  e[11] = 0;  e[15] = 1;
    return this;
};

P.S.以上のコードは『WebGL プログラミングガイド』ソースコード cuon-matrix.js から引用しました。API スタイルが使い勝手が良いので..以後は直接使用します

注意:WebGl は OpenGL と同じく、行列要素は列主序で保存されます。実際 CSS 中の transform: matrix(a,b,c,d,e,f) も列主序ですが、線形代数の中では一般に行主序に慣れています

三.行列ライブラリ

シンプルな行列ライブラリは必須で、少なくとも 3 種類の基本変換と行列乗法をサポートする必要があります。もちろん一セットの友好的な(使い心地の良い)API があるのがベストです

『WebGL プログラミングガイド』が提供する行列ライブラリ cuon-matrix.js はなかなか良く、小巧で最も基本的な機能のみを提供します。もし使いにくいと感じるなら其它の行列ライブラリを探すのも良いですが、Three.js は使用しないことをお勧めします。機能が多すぎて完全すぎるため、WebGL を深く理解するのに不利だからです

これからの例では cuon-matrix.js を使用します。具体的な用法は以下の通り:

// 4x4 行列を作成し、デフォルトで単位行列に填充
var m4 = new Matrix4();
// m4.elements === [
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0, 0, 0, 1]

// 平行移動行列に填充し、パラメータは deltaX, deltaY, deltaZ
m4.setTranslate(0.25, 0.25, 0.0);
// m4.elements === [
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0.25, 0.25, 0, 1]

// 平行移動の基礎上で回転
m4.rotate(30, 0, 0, 1);    // 反時計回り 30 度、(0, 0, 1) は回転軸 z 軸
// m4.elements === [
// 0.8660253882408142, 0.5, 0, 0,
// -0.5, 0.8660253882408142, 0, 0,
// 0, 0, 1, 0,
// 0.25, 0.25, 0, 1]

// ...
// setXXX は填充
// xxx は CTM を求める、つまり m4 = m4 x new Matrix4().setXXX

非常に便利で実用的な機能もあります。例えば:

// コピー
var newM4 = new Matrix4(oldM4);
// 行列乗法、乗積を m4_1 中に配置
m4_1.multiply(m4_2);
// 行列転置
m4.transpose();

四.基本変換

変換は画像上の各点の座標に対して同じ演算を行うことです。フラグメント���頂点から生成されるため、頂点に対して演算を行えば十分です。つまり頂点シェーダーのみを修改すればよいです。以下の通り:

// 頂点シェーダーソースプログラム
var vsSrc = 'attribute vec4 a_Position;' +
    'uniform mat4 u_transformMatrix;' +
    'void main() {' +
    'gl_Position = u_transformMatrix * a_Position;' +   // 座標を設定
    // 一点小技巧、変換前の三角形を表示するため
    'if (u_transformMatrix == mat4(0.0)) {gl_Position = a_Position;}' +
'}';

行列タイプ uniform 変数 u_transformMatrix を宣言し、変換行列を受け取るために使用します。次に値を伝達する必要があります

まず変換行列が必要です。今一つ作ります:

// 変換
var transformMatrix = new Matrix4();
// まず平行移動
transformMatrix.setTranslate(0.25, 0.25, 0.0);  // 右上に平行移動
// 次に回転
transformMatrix.rotate(30, 0, 0, 1);    // 反時計回り 30 度、(0, 0, 1) は回転軸 z 軸
// 次にスケーリング
transformMatrix.scale(0.5, 0.5, 1);     // x, y を半分スケーリング、z は不変

これで非常に複雑な変換行列ができました。次に uniform 変数に値を代入する方法を考えます。以下の通り:

// 変換行列を頂点シェーダーに伝達
var u_transformMatrix = gl.getUniformLocation(glUtil.program, 'u_transformMatrix');
gl.uniformMatrix4fv(u_transformMatrix, false, transformMatrix.elements);

mat タイプシェーダー変数に値を代入するには gl.uniformMatrix4fv を使用します。パラメータの意味は以下の通り:

gl.uniformMatrix4fv(location, transpose, array)
---
transpose   WebGL 中では false のみ可能、行列が転置行列かどうかを表す。WebGL は行列転置の方法を提供していない
array       タイプ化配列、4x4 行列は列主序で保存

最後に draw して完了です

五.アニメーション

アニメーションは不断に消去して再描画することで生じる視覚効果です。例えば回転は回転角度を不断に増分して描画することで実現されます。流暢さを保証するには角度が均一に増分することを保証する必要があります。したがってアニメーション実現の鍵は均一な変化を保証することになり、単純な setInterval では不十分です。したがって時間制御を追加する必要があります。具体的には以下の通り:

// 変換
var angle = 0;
var ROTATE_SPEED = 60;  // 60 度/秒
var transformMatrix = new Matrix4();
var now;
var lastTime;
var delta = 0;
var u_transformMatrix = gl.getUniformLocation(glUtil.program, 'u_transformMatrix');
// draw
function draw() {
    now = Date.now();
    delta = (now - lastTime) * ROTATE_SPEED / 1000;
    // 角度増分
    angle += delta;
    angle %= 360;
    // console.log(angle);
    // 回転
    transformMatrix.setRotate(angle, 0.0, 0.0, 1.0);
    // 回転行列を頂点シェーダーに伝達
    gl.uniformMatrix4fv(u_transformMatrix, false, transformMatrix.elements);
    // clear
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 変換後の三角形を描画
    gl.drawArrays(gl.TRIANGLES, 0, arrVtx.length / 2);

    // 時間を記録
    lastTime = now;

    // 遅延再帰
    window.requestAnimationFrame(draw);
}
// アニメーション
window.requestAnimationFrame(draw);
lastTime = Date.now();

回転速度は 度/秒 で、次回の描画時に何度回転したかは時間に基づいて計算されます。これにより回転角度が均一に変化することが保証されます

注意requestAnimationFramesetTimeout/setInterval に類似していますが、違いは requestAnimationFrameタブページがアクティブ状態の時のみ有効 であることです。setTimeout/setInterval と異なり、パフォーマンスもより高いです

P.S.本例中ではもちろんタブページ非アクティブ状態のアニメーション停止は分かりません。時間はに基づいて角度を計算しているため、途中で一段階停止しても、アクティブを回復した時に大きな角度を回転して、途中で停止した部分を追いかけます。css3 アニメーションの内部原理もこのようになっています:

CSS3 アニメーションは Tab 切り替え戻り時、アニメーション表現は暂停しない;Chrome frames ツールでテストして発見したところ、Tab 切り替え後、計算レンダリング描画はすべて停止し、Tab 切り替え戻り時は内蔵 JS でアニメーション位置を計算して再描画を実現し、アニメーションが暂停しない感觉を造成する

P.S.CSS3 アニメーションそんなに強い、requestAnimationFrame はまだ何の役に立つ? « 張鑫旭 - 鑫空間 - 鑫生活 から引用。requestAnimationFrame に関する詳細情報はこの記事を参照してください。非常に価値があります

六.DEMO

上記のコードを含む完全な例は、以下を参照してください:

console で各ステップの変換後の行列の値を確認できます

七.まとめ

変換行列はモデル行列とも呼ばれ、3D 中で不可欠な mvp は(Model Matrix、View Matrix、Projection Matrix)です

至此、WebGL 2D は終了しました。よく考えてみると何も学んでいないように思えます。大部分の API は 3D 側にあるのか?いいえ、大部分の常用 API はすべて使用しました。残りは少し役に立つものと非常に高度な機能(例えば動的テクスチャ)のみです。3D シーンはほとんどすべて行列で「搓」出来的もので、使用可能な API はもうありません。Camera.setPosition(0, 0, 3) のような神奇な方法はありません

視点とは何か、行列です。照明とは何か、行列です。では影は?行列です。霧化は?すべて行列です。。。

参考資料

  • 『WebGL プログラミングガイド』

コメント

コメントはまだありません

コメントを書く