強火で進め

このブログではプログラム関連の記事を中心に書いてます。

PhiloGLでのカメラの移動処理

JavaScript Advent Calendar 2011 (WebGLコース) : ATND
http://atnd.org/events/21978

3日目担当のにゃまだんさんに続いてのJavaScript Advent Calendar 2011 (WebGLコース)、4日目担当のnakamura001です。

自分の記事はPhiloGLというライブラリを使ってカメラの移動処理を行う方法の解説です。

下準備

プログラムを1から書いているとライブラリを使っているとはいえ結構な量になり、その解説だけでこの記事が終わってしまうので今回はカメラの処理以外の処理が記述済みのこちらのファイルを使って始めることにします。

こちらのプログラムが正しく動作するか解凍後のフォルダに有る index.html というHTMLファイルをWebGL対応のブラウザで開いてみて下さい。

カメラの移動

まずはカメラの移動をしてみます。 index.js というJSファイルを開き、以下のコメントの部分を変更して下さい。

        // ここに描画前に行う処理を記述

    ↓

        camera.position.z += 0.1;
        camera.update();

その後、 index.html をブラウザで開き直すとカメラがオブジェクトから徐々に遠ざかる様になります。

camera.position.z += 0.1; を camera.position.z -= 0.1; にすると徐々に近づいて行き、zの値が0を越えた時点で画像が左右反転します。

camera.position.x の値を変える様に記述すると画面から見て左右方向。 camera.position.y の値を変える様に記述すると画面から見て上下方向に移動するのが確認出来るます。

WebGLOpenGL ESと同じく右手座標系が使われており、X、Y、Zそれぞれこの様な向きがプラスの向きとなっています。

右手を出して指をこの様な向きに向けた時、親指から中指に向かってそれぞれの指が順番にX、Y、Zの座標のプラス方向を表していると覚えると覚えやすいです。

【Tips】
なお、ここで camera.position.x や camera.position.y の値を変更した時にカメラの向きは自動的に原点である(0, 0, 0)の位置を向いていたかと思いますがこの動作はPhiloGLが内部で行なっている処理であり、自分で一からWebGLのプログラムを書いた時や一般的なライブラリを使用した場合はカメラ向きは正面を向いたまま並行にX軸の方向やY軸の方向にカメラが移動する事になります。この事を覚えておくとその様な環境でプログラムをした時にも戸惑わないで済むでしょう。

キーボードでカメラを制御する

次はキーボードで操作してみましょう。

カメラの移動処理を記述していたここの部分を変更します。

        camera.position.z += 0.1;
        camera.update();

    ↓

        camera.position = camPos;
        camera.update();

続いてキーボード入力のイベント発生時の処理を記述します。
イベントの処理をしている以下の部分を変更します。

    events: {
    },

    ↓

    events: {
      onKeyDown : function(e) {
        switch(e.key) {
          case 'up':
            camPos.y += 1;
            break;
          case 'down':
            camPos.y -= 1;
            break;
          case 'left':
            camPos.x -= 1;
            break;
          case 'right':
            camPos.x += 1;
            break;
        }
      },
    },

PhiloGLではキーボードやマウスの入力などのイベントは events に onKeyDown などの記述を追加しておくことでJavaScriptのaddEventListenerと同様の事が出来ます。

今回はキーボード入力時に処理を行いたかったので onKeyDown を追加してイベント発生時にカメラの位置を移動する処理を記述しています。

ブラウザをリロードして試してみて下さい。カーソルキーの入力方向に合わせてカメラが移動する事が出来ます。上手く行かない場合はフォーカスが当たって無い可能性が有ります。一度、画像部分をマウスでクリックした後にカーソルキーを入力してみて下さい。

マウスでカメラを制御する

index.js の先頭部分に var befMousePos; を追加します。

function webGLStart() {
  var box = new PhiloGL.O3D.Cube({

    ↓

function webGLStart() {
  var befMousePos;
  var box = new PhiloGL.O3D.Cube({

events: の onKeyDown の後に以下の記述を追加します。

      onDragStart  : function(e) {
        befMousePos = {
          x: e.x,
          y: e.y
        };
      },
      onDragMove  : function(e) {
        var subX = e.x - befMousePos.x;
        var subY = e.y - befMousePos.y;
        camPos.x += subX * 0.3;
        camPos.y += subY * 0.3;
        
        befMousePos.x = e.x;
        befMousePos.y = e.y;
      },

キーボードの時と同様に onDragStart と onDragMove の時に処理を記述しています。

処理としてはマウスの移動量を元にカメラの移動を行いました。
ドラッグ開始時(onDragStart)に最初のマウス位置を保存、ドラッグ中のイベント(onDragMove)で前回の位置との差分をカメラの移動量として設定しています。

ブラウザをリロードしてマウスのドラッグでカメラの移動が行われるのを確認してみて下さい。

まとめ

さて、簡単ですが「自動的(毎フレームごと)に移動させる」「キーボードで移動させる」「マウスで移動させる」の3つの方法の説明をしましたがどうでしたでしょうか?

意外にそんなに難しく無かったのでは無いでしょうか。WebGLの勉強をする場合、一から全部自分で作り始めようとするとかなり記述するべきものが多かったり、理解すべきものが多かったりします。

WebGLや3Dプログラムに慣れるまではライブラリを使ってプログラムを始めると簡単に始める事が出来てお勧めです。

なお、ライブラリを使っている場合でもまだまだ記述量は多いので今回行った事と同様に何らかのサンプルのソースなどをベースにそこへ自分の作りたい機能を追加して行くという方法を取るとつまずきづらく成ると思います(結構、ドキュメント通りにやっても何故か動かないとかよく有るものですw)。

ライブラリについては PhiloGL 以外にも Three.js を始め、数多くのものがリリースされています。実際に試したり、ドキュメントを眺めたり、サンプルをみたりしてみて下さい。そして、その中から自分に合ってそうだったり、やりたいと思っている事がすぐに出来そうなものを選択するのが良いでしょう。

それではみなさんもWebGLプログラミング、楽しんで下さい。

5日目は 3DCG Arts の角(@santarh)さんの「THREE.js での輪郭線表示」です。

最終的なプログラムの全ソース

今回解説したプログラムの全ソースを記述しておきます。途中でプログラムを追加する場所などが分からなくなった場合はこちらを参照して下さい。

function webGLStart() {
  var befMousePos;
  var box = new PhiloGL.O3D.Cube({
    colors: [1, 0, 0, 1]
  });
  box.position.set(-3.5, -3.5, 0);
  box.update();

  var sphere = new PhiloGL.O3D.Sphere({
    colors: [0, 1, 0, 1]
  });
  var s = 1.5;
  sphere.scale.set(s, s, s);
  sphere.update();

  var box2 = new PhiloGL.O3D.Cube({
    colors: [0, 0, 1, 1]
  });
  box2.position.set(3.5, 3.5, 0);
  box2.update();

  var camPos = new PhiloGL.Vec3(0, 0, 15);
  
  //Create application
  PhiloGL('camera_control-canvas', {
    events: {
      onKeyDown : function(e) {
        switch(e.key) {
          case 'up':
            camPos.y += 1;
            break;
          case 'down':
            camPos.y -= 1;
            break;
          case 'left':
            camPos.x -= 1;
            break;
          case 'right':
            camPos.x += 1;
            break;
        }
      },
      onKeyUp  : function(e) {
      },
      onClick  : function(e) {
      },
      onRightClick  : function(e) {
      },
      onDragStart  : function(e) {
        befMousePos = {
          x: e.x,
          y: e.y
        };
      },
      onDragMove  : function(e) {
        var subX = e.x - befMousePos.x;
        var subY = e.y - befMousePos.y;
        camPos.x += subX * 0.3;
        camPos.y += subY * 0.3;
        
        befMousePos.x = e.x;
        befMousePos.y = e.y;
      },
      onDragEnd  : function(e) {
      }
    },
    onError: function() {
      alert("There was an error creating the app.");
    },
    onLoad: function(app) {
      //Unpack app properties
      var gl = app.gl,
          program = app.program,
          scene = app.scene,
          canvas = app.canvas,
          camera = app.camera;
      
      //Basic gl setup
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clearDepth(1.0);
      gl.enable(gl.DEPTH_TEST);
      gl.depthFunc(gl.LEQUAL);
      gl.viewport(0, 0, +canvas.width, +canvas.height);

      var lights = scene.config.lights;
      lights.enable = true;
      lights.ambient = {
        r: .2,
        g: .2,
        b: .2
      };
      lights.points = {
        color: {
          r: .8,
          g: .8,
          b: .8
        },
        position: {
          x: 0,
          y: 0,
          z: 10
        }
      };

      scene.add(box, sphere, box2);
      draw();

      function draw() {
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
                
        camera.position = camPos;
        camera.update();

        scene.render();

        PhiloGL.Fx.requestAnimationFrame(draw);
      }
    }
  });
}