WebGL (8)


アニメーション

アニメーションにするには、一定時間毎に画像を生成する必要があります。 javascriptでは、 一定時間ごとに処理を繰り返すsetInterval()と、 一定時間後に処理を行う setTimeout()があります。

    var timer = setInterval(func , t );
        t ミリ秒ごとに関数 func() が呼び出される。
    繰り返しを中止するには、setTimeintervalの返り値 timer を引数としてclearTiminterval(timer );を呼び出す。

    setTimeout(func , t );
        t ミリ秒後に関数 func() が1度だけ呼び出される。

また、最近では requestAnimationFrame()もよく使われるようになりました。 requestAnimationFrame()は呼び出す時間間隔を指定するのではなく、ブラウザの 再描画準備ができると直ちに呼び出されます。 再描画の頻度は、フォアグラウンドのタブでは 60 FPS (frame per second, 毎秒約60回)で、間に合わない場合は減らされます。 また、バックグラウンドのタブではもっと少ない頻度になります。

requestAnimationFrame()の呼び出しでは、コールバック関数を引数として渡します。 コールバック関数は再描画時刻を引数として呼び出されます。

構文
    requestID = window.mozRequestAnimationFrame(callback);    // Firefox
    requestID = window.requestAnimationFrame(callback);       // IE 10 / Chrome
    requestID = window.webkitRequestAnimationFrame(callback); // Safari
返り値は、window.cancelAnimationFrame(requestID)に渡して、以後のアニメーションの更新を中止することができます。

requestAnimationFrameの特徴
  • ブラウザの描画更新と同じタイミングで呼び出される
  • 次の再描画が行われる前に、アニメーションを更新する関数を呼び出す
  • タブが非アクティブの時はFPSを落とす。
setInteval、setTimeoutの特徴
  • ブラウザで準備ができていなくても必ず実行される。
  • タブが非アクティブの状態でも常に実行される。

アニメーションの例

次に setInterval() を使ってアニメーションにした例を示します。

ParallelLightObjectsAnim.htmlの変更点
*** ParallelLightObjects.html	Tue Jul  1 18:31:08 2014
--- ParallelLightObjectsAnim.html	Wed Jul  8 20:47:11 2015
***************
*** 83,124 ****
      gl.depthFunc(gl.LEQUAL);
  
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
-     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  
      viewMatrix.setLookAt(-30.0, 50.0, 50.0, 0, 0, 0, 0, 1, 0);
      projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
  
!     var oidx;
!     for (oidx = 0; oidx < obj.length; oidx ++) {
!         var vertices = obj[oidx].vertices;
!         var indices = obj[oidx].indices;
!         var normals = obj[oidx].normals;
!         var colorList = obj[oidx].colorList;
!     
!         if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;
!         if (! tsuda.initArrayBuffer('a_Color',colorList[0],3,gl.FLOAT)) return;
!         if (! tsuda.initArrayBuffer('a_Normal',normals,3,gl.FLOAT)) return;
!     
!         if (! tsuda.setElementArrayBuffer(indices)) return;
! 
!         for (i=0; i<30; i++) {
!             var colorIndex = Math.floor(Math.random()*colorList.length);
!             if (! tsuda.initArrayBuffer('a_Color',colorList[colorIndex],3,gl.FLOAT)) return;
!             modelMatrix.setTranslate(tsuda.tr(20),tsuda.tr(20),tsuda.tr(20));
!             modelMatrix.rotate(Math.random()*90, 1, 0, 0);
!             modelMatrix.rotate(Math.random()*90, 0, 1, 0);
!             modelMatrix.rotate(Math.random()*90, 0, 0, 1);
!             mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
!             gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);
!             normalMatrix.setInverseOf(modelMatrix);
!             normalMatrix.transpose();
!             gl.uniformMatrix4fv(u_NormalMatrix,false,normalMatrix.elements);
!     
!             gl.drawElements(gl.TRIANGLES, indices.length,gl.UNSIGNED_BYTE,0);
          }
!     }
  }
  
  tsuda.tr = function (d) {
      return Math.random() * 2 * d - d;
  };
--- 83,179 ----
      gl.depthFunc(gl.LEQUAL);
  
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // black
  
      viewMatrix.setLookAt(-30.0, 50.0, 50.0, 0, 0, 0, 0, 1, 0);
      projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
  
!     tsuda.initAnim = function() {
!         var oidx,i;
!         tsuda.objState = [];
!         for (oidx=0; oidx<obj.length; oidx++) {
!             tsuda.objState[oidx] = [];
!             for (i=0; i<30; i++) {
!                 tsuda.objState[oidx][i] = tsuda.randomObjState(10,90,obj[oidx].colorList.length);
!             }
          }
!     };
!     tsuda.stepAnim = function() {
!         gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
!         var oidx,i;
!         for (oidx = 0; oidx < tsuda.objState.length; oidx ++) {
!             var vertices = obj[oidx].vertices;
!             var indices = obj[oidx].indices;
!             var normals = obj[oidx].normals;
!             var colorList = obj[oidx].colorList;
!         
!             if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;
!             if (! tsuda.initArrayBuffer('a_Color',colorList[0],3,gl.FLOAT)) return;
!             if (! tsuda.initArrayBuffer('a_Normal',normals,3,gl.FLOAT)) return;
!         
!             if (! tsuda.setElementArrayBuffer(indices)) return;
! 
!             var states = tsuda.objState[oidx];
!             for (i=0; i<states.length; i++) {
!                 var state = states[i];
!                 if (! tsuda.initArrayBuffer('a_Color',colorList[state.c],3,gl.FLOAT)) return;
!                 state.setMatrix(modelMatrix);
!                 state.step();
!                 mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
!                 gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);
!                 normalMatrix.setInverseOf(modelMatrix);
!                 normalMatrix.transpose();
!                 gl.uniformMatrix4fv(u_NormalMatrix,false,normalMatrix.elements);
!         
!                 gl.drawElements(gl.TRIANGLES, indices.length,gl.UNSIGNED_BYTE,0);
!             }
!         }
!     };
  }
  
+ tsuda.randomObjState = function(L,T,C) {
+     var state = {};
+     state.x = (Math.random() - 0.5) * L;
+     state.y = (Math.random() - 0.5) * L;
+     state.z = (Math.random() - 0.5) * L;
+     state.vx = (Math.random() - 0.5) * L * 0.03;
+     state.vy = (Math.random() - 0.5) * L * 0.03;
+     state.vz = (Math.random() - 0.5) * L * 0.03;
+     state.rx = Math.random() * T;
+     state.ry = Math.random() * T;
+     state.rz = Math.random() * T;
+     state.drx = Math.random() * T * 0.03;
+     state.dry = Math.random() * T * 0.03;
+     state.drz = Math.random() * T * 0.03;
+     state.c = Math.floor(Math.random() * C)
+     state.step = function() {
+         state.x += state.vx; state.y += state.vy; state.z += state.vz;
+         state.rx += state.drx; state.ry += state.dry; state.rz += state.drz;
+     };
+     state.setMatrix = function(m) {
+         m.setIdentity();
+         m.translate(state.x,state.y,state.z);
+         m.rotate(state.rx,1,0,0); m.rotate(state.ry,0,1,0); m.rotate(state.rz,0,0,1);
+         return m;
+     };
+     return state;
+ };
+ 
+ tsuda.loop = false;
+ function flipstate() {
+     var button = document.getElementById("button");
+     if (tsuda.loop == false) {
+         tsuda.loop = true;
+         button.value = "stop";
+         tsuda.initAnim();
+         tsuda.stepAnim();
+         tsuda.timer = setInterval("tsuda.stepAnim()",33);
+     } else {
+         clearInterval(tsuda.timer);
+         tsuda.loop = false;
+         button.value = "start";
+     }
+ };
+ 
  tsuda.tr = function (d) {
      return Math.random() * 2 * d - d;
  };
***************
*** 127,133 ****
    </script>
  </head>
  <body onload="main()">
!   <canvas id="mycanvas" width="320" height="240">
      Your browser does not support Canvas TAB.
    </canvas>
  </body>
--- 182,189 ----
    </script>
  </head>
  <body onload="main()">
!   <input type="button" id="button" value="start" onclick="flipstate()" /><br />
!   <canvas id="mycanvas" width="640" height="480">
      Your browser does not support Canvas TAB.
    </canvas>
  </body>

実は、上のプログラム ParallelLightObjectsAnim.html は、各物体を描画する度に、 バッファオブジェクトを生成して頂点座標や色座標を WebGLに送りこんでいるので、 実行効率は悪くなっています。

アニメーション動作をスムーズにするために、実際には次のように記述するべきです。

ParallelLightObjectsAnim2.htmlの変更点
*** ParallelLightObjectsAnim.html	Wed Jul  8 20:47:11 2015
--- ParallelLightObjectsAnim2.html	Thu Jul  9 13:58:34 2015
***************
*** 43,59 ****
      var gl = app.gl;
  
      var obj = [ tsuda.cube, tsuda.pyramid ];
!     var i,j;
      for (i=0; i<obj.length; i++) {
!         obj[i].colorList = [];
!         obj[i].colorList[0] = tsuda.get3Float32Array(1.0, 0.0, 0.0, obj[i].vertices.length);
!         obj[i].colorList[1] = tsuda.get3Float32Array(0.0, 1.0, 0.0, obj[i].vertices.length);
!         obj[i].colorList[2] = tsuda.get3Float32Array(0.0, 0.0, 1.0, obj[i].vertices.length);
!         obj[i].colorList[3] = tsuda.get3Float32Array(1.0, 1.0, 0.0, obj[i].vertices.length);
!         obj[i].colorList[4] = tsuda.get3Float32Array(1.0, 0.0, 1.0, obj[i].vertices.length);
!         obj[i].colorList[5] = tsuda.get3Float32Array(0.0, 1.0, 1.0, obj[i].vertices.length);
!         obj[i].colorList[6] = tsuda.get3Float32Array(1.0, 1.0, 1.0, obj[i].vertices.length);
          obj[i].normals = tsuda.indexNormals(obj[i].vertices,obj[i].indices);
      }
  
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
--- 43,78 ----
      var gl = app.gl;
  
      var obj = [ tsuda.cube, tsuda.pyramid ];
!     var i,j,k;
      for (i=0; i<obj.length; i++) {
!         var clist = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 1.0, 0.0], 
!                      [1.0, 0.0, 1.0], [0.0, 1.0, 1.0],[1.0, 1.0, 1.0]];
!         obj[i].colorList = new Float32Array(obj[i].vertices.length * 3 * clist.length);
!         var idx = 0;
!         for (j=0; j<clist.length; j++) {
!             for (k=0; k<obj[i].vertices.length; k++) {
!               obj[i].colorList[idx++] = clist[j][0];
!               obj[i].colorList[idx++] = clist[j][1];
!               obj[i].colorList[idx++] = clist[j][2];
!             }
!         }
          obj[i].normals = tsuda.indexNormals(obj[i].vertices,obj[i].indices);
+ 
+         obj[i].a_Position = tsuda.setArrayBuffer(obj[i].vertices); // prepare buffers for later use
+         obj[i].a_Color = tsuda.setArrayBuffer(obj[i].colorList);
+         obj[i].a_Normal = tsuda.setArrayBuffer(obj[i].normals);
+         obj[i].e_indices = tsuda.setElementArrayBuffer(obj[i].indices);
+         if (obj[i].a_Position == null
+             || obj[i].a_Color == null
+             || obj[i].a_Normal == null
+             || obj[i].e_indices == null) {
+             console.log('cannot create array buffer or element array buffer');
+             console.log(obj[i].a_Position);
+             console.log(obj[i].a_Color);
+             console.log(obj[i].a_Normal);
+             console.log(obj[i].e_indices);
+             return null;
+         }
      }
  
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
***************
*** 93,99 ****
          for (oidx=0; oidx<obj.length; oidx++) {
              tsuda.objState[oidx] = [];
              for (i=0; i<30; i++) {
!                 tsuda.objState[oidx][i] = tsuda.randomObjState(10,90,obj[oidx].colorList.length);
              }
          }
      };
--- 112,118 ----
          for (oidx=0; oidx<obj.length; oidx++) {
              tsuda.objState[oidx] = [];
              for (i=0; i<30; i++) {
!                 tsuda.objState[oidx][i] = tsuda.randomObjState(10,90,clist.length);
              }
          }
      };
***************
*** 101,121 ****
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
          var oidx,i;
          for (oidx = 0; oidx < tsuda.objState.length; oidx ++) {
!             var vertices = obj[oidx].vertices;
!             var indices = obj[oidx].indices;
!             var normals = obj[oidx].normals;
!             var colorList = obj[oidx].colorList;
!         
!             if (! tsuda.initArrayBuffer('a_Position',vertices,3,gl.FLOAT)) return;
!             if (! tsuda.initArrayBuffer('a_Color',colorList[0],3,gl.FLOAT)) return;
!             if (! tsuda.initArrayBuffer('a_Normal',normals,3,gl.FLOAT)) return;
!         
!             if (! tsuda.setElementArrayBuffer(indices)) return;
  
              var states = tsuda.objState[oidx];
              for (i=0; i<states.length; i++) {
                  var state = states[i];
!                 if (! tsuda.initArrayBuffer('a_Color',colorList[state.c],3,gl.FLOAT)) return;
                  state.setMatrix(modelMatrix);
                  state.step();
                  mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
--- 120,137 ----
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
          var oidx,i;
          for (oidx = 0; oidx < tsuda.objState.length; oidx ++) {
!             gl.bindBuffer(gl.ARRAY_BUFFER,obj[oidx].a_Position); // use buffers prepared before
!             tsuda.setAttributeVariable('a_Position',3,gl.FLOAT,0,0);
!             gl.bindBuffer(gl.ARRAY_BUFFER,obj[oidx].a_Normal);
!             tsuda.setAttributeVariable('a_Normal',3,gl.FLOAT,0,0);
!             gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,obj[oidx].e_indices);
  
              var states = tsuda.objState[oidx];
              for (i=0; i<states.length; i++) {
                  var state = states[i];
!                 gl.bindBuffer(gl.ARRAY_BUFFER,obj[oidx].a_Color);
!                 tsuda.setAttributeVariable('a_Color',3,gl.FLOAT,0,
!                  obj[oidx].vertices.length*3*state.c*obj[oidx].colorList.BYTES_PER_ELEMENT);
                  state.setMatrix(modelMatrix);
                  state.step();
                  mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
***************
*** 124,130 ****
                  normalMatrix.transpose();
                  gl.uniformMatrix4fv(u_NormalMatrix,false,normalMatrix.elements);
          
!                 gl.drawElements(gl.TRIANGLES, indices.length,gl.UNSIGNED_BYTE,0);
              }
          }
      };
--- 140,146 ----
                  normalMatrix.transpose();
                  gl.uniformMatrix4fv(u_NormalMatrix,false,normalMatrix.elements);
          
!                 gl.drawElements(gl.TRIANGLES, obj[oidx].indices.length,gl.UNSIGNED_BYTE,0);
              }
          }
      };