ポリゴンとテクスチャ

THREE.jsにはデフォルトのジオメトリが豊富で、いろいろなモデラーからのインポートも容易なようですが、プログラマ的にはポチポチと座標を指定した図形も表示してみたい。ということでやってみます。

HTML

前回と一緒。

<!doctype html>
<html lang="en">
  <head>
    <title>Polygon and Texture</title>
    <meta charset="utf-8">
    <style>body { background-color:black; padding:0; margin:0; }</style>
    <script src="js/Three.js"></script>
    <script src="js/RequestAnimationFrame.js"></script>
    <script src="js/jquery-1.7.1.min.js"></script>
    <script>
    // ...snip...
    </script>
  </head>
  <body>
  </body>
</html>

JSの大枠

前回とおおむね一緒。

$(function() {
  var container;
  var camera, scene, renderer;

  initScene();
  animate();


  function initPyramid() {
    // ...snip...
  }

  function initScene() {
    container = document.createElement('div');
    document.body.appendChild(container);

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
    camera.position.set(0, 0, 5);
    scene.add(camera);

    var light = new THREE.DirectionalLight(0xffffff, 2);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);

    light = new THREE.DirectionalLight(0xffffff);
    light.position.set(-1, -1, -1).normalize();
    scene.add(light);

    initPyramid();

    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);
  }

  function animate() {
    requestAnimationFrame(animate);
    render();
  }

  var time = 0;
  function render() {
    var angle = (time++) / 360 * Math.PI;
    camera.position.set(Math.sin(angle), Math.cos(angle), 2);
    camera.lookAt(new THREE.Vector3(0, 0, 0));
    renderer.render(scene, camera);
  }
});

ポリゴン

ポリゴンは以下の手順で描写できます。

  1. Geometryに座標(geometry.vertices)を追加
  2. Geometryに面(geometry.faces)を追加
    • THREE.Face3コンストラクタの引数は面を構成する座標のインデックス3つと、面の法線ベクトルです。
    • 座標のインデックスというのが少しわかりづらいですが、例えば new THREE.Face3(0, 1, 2) となっていたら geometry.vertices[0], geometry.vertices[1], geometry.vertices[2] の3頂点からなる面ということです。ポリゴンは他のポリゴンと頂点を共有することが多いのでメモリ節約のためにこういう指定方法になっています。たぶん。
    • 面の法線ベクトルは普通は面の垂直方向の単位ベクトルです。ライトの反射なんかに使われますが、今回の例だと正確に垂直じゃなくてもなんとかなるので、大体の方向だけを合わせています。ホントはちゃんと計算しましょう。
  3. Materialを定義して、Geometryと合わせて、Meshを作成して、Sceneに追加します。いつもの手順。
function initPyramid() {
  var geometry = new THREE.Geometry();
  geometry.vertices.push(new THREE.Vertex(new THREE.Vector3( 1,  0, 0)));
  geometry.vertices.push(new THREE.Vertex(new THREE.Vector3( 0,  1, 0)));
  geometry.vertices.push(new THREE.Vertex(new THREE.Vector3(-1,  0, 0)));
  geometry.vertices.push(new THREE.Vertex(new THREE.Vector3( 0, -1, 0)));
  geometry.vertices.push(new THREE.Vertex(new THREE.Vector3( 0,  0, 1/Math.sqrt(2))));

  geometry.faces.push(new THREE.Face3(0, 1, 4, new THREE.Vector3( 1,  1, 1).normalize()));
  geometry.faces.push(new THREE.Face3(1, 2, 4, new THREE.Vector3(-1,  1, 1).normalize()));
  geometry.faces.push(new THREE.Face3(2, 3, 4, new THREE.Vector3(-1, -1, 1).normalize()));
  geometry.faces.push(new THREE.Face3(3, 0, 4, new THREE.Vector3( 1, -1, 1).normalize()));
  geometry.faces.push(new THREE.Face3(2, 1, 0, new THREE.Vector3(0, 0, -1).normalize()));
  geometry.faces.push(new THREE.Face3(3, 2, 0, new THREE.Vector3(0, 0, -1).normalize()));

  var material = new THREE.MeshLambertMaterial({color:0xff0000});
  var mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);
}

この時点でこんな感じ。


テクスチャ

テクスチャを利用するには、面(faces)に対してテクスチャのどの部分を割り当てるか事前に定義しておく必要があります。文章で書くのが面倒なのと、実際はよく分かっていないので絵にするとこういう感じ。

テクスチャのどの部分を割り当てるかは geometry.faceVertexUvs[0] に設定します。なんで配列になってるのかは謎。あとUとかVとかの由来も謎。今回の場合は geometry.faces を設定した後に次のようにしました。

for (var i = 0; i < 6; i++) {
  geometry.faceVertexUvs[0].push([
    new THREE.UV(0.0, 0.0),
    new THREE.UV(1.0, 0.0),
    new THREE.UV(0.5, 1.0)
  ]);
}

faceが6つあるのでfaceVertexUvs[0]の要素も6つ。UVはテクスチャのサイズに依存しないように0..1の値で指定します。ホントはうまく重ならないようにUVを選ぶのかも知れないけど、面倒くさいので今回は全部テクスチャの同じ部分を切り出してます。

で Material をテクスチャを使ったものに変更。

var texture = THREE.ImageUtils.loadTexture('brick.jpg');
var material = new THREE.MeshLambertMaterial({map:texture});

画像からテクスチャを作るには THREE.ImageUtils.loadTexture 関数が利用できます。なお、ローカルのhtmlだとセキュリティ例外で画像を読めません。ウェブサーバーを立ててhtmlファイルと画像ファイルを同じサーバーから読むようにしてください。

ちなみに今回は使っていませんが canvas に表示されている内容をテクスチャにしたい場合はこんな風にします。

var texture = new THREE.Texture(canvas);

最終的にこんなふうになりました。夜明けのピラミッドを上空から眺めてる感じ。

砂漠はおまけで。