見出し画像

十二、図形をアニメーションで回転させる。【キーワード→requestAnimationFrame】

以前、この記事は十一番でした。しかし、正しくは十二番であることに気付き書き直しました。「スキ」を付けてくださった皆様、申し訳ございませんでした。

.oOo..oOo..oOo.
今回は、初のアニメーションという事で、見出し画像にGIFアニメを貼れるかな?と、調べてみましたが、貼れないそうです。

.oOo..oOo..oOo.
■サンプルページはコチラ↓

.oOo..oOo..oOo.
■このプログラムの解説は、次のExcelファイルをご覧下さい。

■htmlのコードです。↓

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>図形をアニメーションで回転させる</title>
  </head>

  <body onload="main()">
    <canvas id="webgl" width="400" height="400">

    </canvas>

    <script src="webgl-utils.js"></script>
    <script src="webgl-debug.js"></script>
    <script src="sano-func.js"></script>
    <script src="gyouretsu.js"></script>
    <script src="kaiten-anime.js"></script>
  </body>
</html>


■JavaScriptのコードです。↓

// RotatingTriangle.js (c) 2012 matsuda
// 頂点シェーダのプログラム
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform mat4 u_ModelMatrix;\n' +
  'void main() {\n' +
  '  gl_Position = u_ModelMatrix * a_Position;\n' +
  '}\n';

// フラグメントシェーダのプログラム
var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(0.6, 0.0, 0.9, 1.0);\n' +
  '}\n';

// 回転角の変化量(度)
var ANGLE_STEP = 45.0;

function main() {
  // Canvas要素を取得する
  var canvas = document.getElementById('webgl');

  // WebGL描画用のコンテキストを取得する
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('WebGLコンテキストの取得に失敗');
    return;
  }

  // シェーダを初期化する
  if (!initShaders2(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('シェーダの初期化に失敗');
    return;
  }

  // 頂点座標を設定する
  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('頂点座標の設定に失敗');
    return;
  }

  // Canvasをクリアする色を設定する
  gl.clearColor(0.3, 0.7, 0.7, 1.0);

  // u_ModelMatrix変数の格納場所を取得する
  var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
  if (!u_ModelMatrix) { 
    console.log('u_ModelMatrixの格納場所の取得に失敗');
    return;
  }

  // 現在の回転角
  var currentAngle = 0.0;
  // モデル行列
  var modelMatrix = new Matrix4();

  // 描画を開始する
  var tick = function() {
    currentAngle = animate(currentAngle);  // 回転角の更新
    draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix);   // 三角形の描画
    requestAnimationFrame(tick, canvas); // / tickが再び呼び出されるようにブラウザに登録
  };
  tick();
}

function initVertexBuffers(gl) {
  var vertices = new Float32Array([
    -0.41, -0.26, 0, 0.67, 0, 0, 0.41, -0.26
  ]);
  var n = 4; // 頂点数

  // バッファオブジェクトを作成する
  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log('バッファオブジェクトの生成に失敗');
    return -1;
  }

  // バッファオブジェクトをバインドする
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  // バッファオブジェクトにデータを書き込む
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  // a_Position変数にバッファオブジェクトを割り当てる
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if(a_Position < 0) {
    console.log('a_Positionの格納場所の取得に失敗');
    return -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  // a_Position変数でのバッファオブジェクトの割り当てを有効にする
  gl.enableVertexAttribArray(a_Position);

  // バッファオブジェクトのバインドを解除する
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  return n;
}

function draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix) {
  // 回転行列を設定する
  modelMatrix.setRotate(currentAngle, 0, 0, 1); // 回転角、回転軸(0, 0, 1)
 
  // 回転行列を頂点シェーダに渡す
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

  // Canvasをクリアする
  gl.clear(gl.COLOR_BUFFER_BIT);

  // 図形を描画する
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}

// 最後に呼び出された時刻
var g_last = new Date().getTime();
function animate(angle) {
  // 前回呼び出されてからの経過時間を計算
  var now = new Date().getTime();
  var elapsed = now - g_last;
  g_last = now;
  // 回転角度を更新する(経過時間により調整)
  var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
  return newAngle %= 360;
}

■参考文献:WebGL+HTML5 3DCGプログラミング入門 (良い本ですが、絶版)

■以下は検索用です。
ここはベクトル。例 (x, y, z, w)
ここは行列。mat4は4×4行列。

GLSLでは、行列×ベクトルの計算が可能。
また、この部分のvoid main()はGLSLで書かれている。下に出てくる、JavaScript内のmainとは別物。GLSLのvoid main()は予約された関数名なので、名前を変えてはいけない。
gl_Positionは、GLSLの組み込み変数なので、名前を変えてはいけない。

gl.clearColor(※)は描画の前に持ってきた。
※gl.clearColor は WebGL(Web Graphics Library)における命令で、描画時に使用されるクリアカラーを設定するための関数です。クリアカラーは、WebGL コンテキストのカラーバッファをクリア(消去)する際に使用される色です。この命令を使用すると、指定された色でカラーバッファをクリアすることができます。

格納場所を取得するのは1回でいいらしい。

draw()の中で、毎回Matrix4を呼び出すのは大変なので、ここで呼び出しておく。

drawを繰り返し使うことで、アニメーションにしている。
tickをtick自身の中で呼び出すことによって、ループの様になっている。
このプログラムは、終了条件が無いので、ずっと動き続ける。

指定されたバッファオブジェクト(vertexBuffer)を、gl.ARRAY_BUFFERという名前のバインドポイントにバインドします。
バインドポイント → バッファオブジェクトを収納しておく場所の様なイメージ。
”バインドポイントにバインドします” → バッファオブジェクトを収納する為のスペースに、バッファオブジェクトを収納します、的な意味。

verticesのデータ(69行目付近)を、gl.ARRAY_BUFFERに書き込む。

gl.getAttribLocation → 直訳すると、attribute変数の位置(場所)をゲットする。
指定されたプログラムオブジェクト("gl.program")内の頂点属性の位置を取得します。
第二引数のa_Positionは、取得したい頂点属性の名前。(4行目で定義) 取得される側は、attributeで宣言されていなければならない。
一方、var a_Positionには、取得された頂点属性の位置が格納される。
この様な形にしているのは、頂点属性を繰り返し使用する為、gl.getAttribLocationを繰り返し使うのではなく、一旦var a_Positionに入れて使用し、負荷を軽くするためだと思われる。
混同を避けるために変数の方のa_Psitionは名前を変えた方がいいかもしれない。
というわけで、Web上に掲載しているプログラムでは、変数の方を、a_PositionLocという名前に変更しました。(Lの小文字は、1やIと見分けづらいので使わない方がいい)

gl.getAttribLocationは属性が見つからない場合、-1を返すので、a_Positionが-1の場合は、エラーメッセージがconole上に表示される。

WebGLの準備が出来てから、時刻の呼び出しをしていると思われる。

Date().getTime()はJavaScriptの組み込みメソッドです。
具体的には、Dateオブジェクトのメソッドで、1970年1月1日00:00:00 UTC(UNIXエポック)からの経過ミリ秒数を返します。
一つ前のDate().getTime()との差を求める。
次に使うので、nowをg_lastに入れておく。

angleは、function animate(angle) { なので、animateの引数。
ANGLE_STEPは45°で固定されている。

.oOo..oOo..oOo.
requestAnimationFrame について。
今回のプログラムの様な場合、以前は、setInterval というメソッドを用いていました。
setIntervalはブラウザのタブ機能が無い時代に出来た物だそうです。
非アクティブのタブのページの中に、setInterval があると、ページを見ていなくても、setIntervalは動き続けている、つまりPCの負荷がかかるという恐れがあったそうです。
requestAnimationFrame はこの点を気にせず使えるそうです。

ウェブブラウザ上でアニメーションを実行する際には、連続して画面を更新し続ける必要があります。
従来は、setTimeoutやsetIntervalといったタイマーを使って定期的にアニメーションの更新を行っていました。
しかし、これらの方法はブラウザの再描画タイミングとは同期していないため、滑らかさに欠けたり、不要なCPUリソースを消費したりすることがありました。

requestAnimationFrameメソッドは、ブラウザが再描画を行う前に実行するコールバック関数(*)を登録します。 (*今回はtick()という関数)

これにより、ブラウザのリフレッシュレートに合わせて効率的にアニメーションを更新でき、より滑らかな動きを実現します。

requestAnimationFrameは、一般的にブラウザのディスプレイリフレッシュレート(通常は1秒間に60回、つまり60fps)に同期します。
これにより、フレームごとの描画間隔がほぼ一定になり、滑らかなアニメーションが実現されます。
ただし、requestAnimationFrameでは描画間隔を長くしたり、短くしたりはできない。

requestAnimationFrameは、ブラウザが非アクティブなタブやウィンドウの場合、アニメーションの更新を停止または減少させることができます。
これにより、リソースの無駄遣いを防ぎます。

requestAnimationFrameをキャンセルするメソッドもある。→cancelAnimationFrame

以上です。


頂戴したサポートは、レンタルサーバーの費用に充てさせて頂きます🙇 心より感謝いたします❤️