icon 2012/09/03 [Three.js]入門してみる
3DやWebGLをネーティブでコーディイングするのはやっぱり大変!
Flashだと「Alternativa3D」「 Away3D」「Papervision3D」らが有名ですが、JavaScriptの場合は「Three.js」「Sprite3D.js」「jQuery 3D」とかでしょうか?

この中でも「Three.js」が一番強力そう(妄想)なので試してみる事に。

GitHub


GitHub mrdoob / three.js

この中に「example」があるので動作確認できます。

ドキュメント


three.js

THREE.js Doc
引数説明とサンプルがあるので、どういう動作するのかの材料になるかと。



使ってみる


ライブラリを読み込み


とりあえずメインの「three.js(three.min.js)」とパフォーマンス監視ツール「Stats.js」を読み込む
<script src="build/three.min.js"></script>
<script src="js/Stats.js"></script>

3D描画の土台


グラフィック描画するベースのhtml要素を追加し、JavaScriptで取得する。
ここまでは、何の知識もいらないかと。
<div id="container"></div>
var container;
container = document.getElementById('container');

Cameraの追加


3Dオブジェクトの視点であるCameraを追加するが、今回はOrthographicCamera()を利用する。
var left = window.innerWidth / - 2;
var right = window.innerWidth / 2;
var top = window.innerHeight / 2;
var bottom = window.innerHeight / - 2;
var camera = new THREE.OrthographicCamera(left, right, top, bottom, -2000, 1000);
camera.position.x = 200;
camera.position.y = 100;
camera.position.z = 200;
引数の内容は、
OrthographicCamera(left, right, top, bottom, near, far, projectionMatrix)
ちなみに、現在3種類のカメラがある。

・THREE.OrthographicCamera:平行投影(正投影)
・THREE.PerspectiveCamera:透視投影
・THREE.CombinedCamera:透視投影と平行投影の複合


透視投影と平行投影の図的表現の違い

Sceneの追加


Sceneはグラフィックオブジェクトを配置する元のオブジェクトです。
scene = new THREE.Scene();

3Dオブジェクトの追加


Three.jsの3Dオブジェクトは以下のものがあります。

・THREE.CubeGeometry
・THREE.CylinderGeometry
・THREE.ExtrudeGeometry
・THREE.IcosahedronGeometry
・THREE.LatheGeometry
・THREE.OctahedronGeometry
・THREE.PlaneGeometry
・THREE.SphereGeometry
・THREE.TorusGeometry
・THREE.TorusKnotGeometry
・THREE.BinaryLoader
・THREE.UTF8Loader


だいたい名前で想像がつくが、今回は直方体の CubeGeometry を利用。

MeshLambertMaterial() で光反射のマテリアルを作成。
元の3Dオブジェクトとマテリアルを引数に、Mesh()でCubeオブジェクトを作成し、Sceneに追加します。
(Three.jsの場合、Cubeオブジェクトというよりメッシュと言った方が通じるか?)
var geometry = new THREE.CubeGeometry(50, 50, 50);
var params =  {
    color: 0xffffff,
    shading: THREE.FlatShading,
    overdraw: true
};
var material = new THREE.MeshLambertMaterial(params);
var cube = new THREE.Mesh(geometry, material);
cube.scale.y = Math.floor(Math.random() * 2 + 1);
cube.position.x = Math.floor((Math.random() * 1000 - 500) / 50) * 50 + 25;
cube.position.y = (cube.scale.y * 50) / 2;
cube.position.z = Math.floor((Math.random() * 1000 - 500) / 50) * 50 + 25;
scene.add(cube);

SceneにLightを追加


Lightを追加する事で、3Dに影をつけて奥行き感を出します。
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.x = 0.5;
directionalLight.position.y = 1.5;
directionalLight.position.z = 3.5;
directionalLight.position.normalize();
scene.add(directionalLight);

var ambientLight = new THREE.AmbientLight(0x10);
scene.add(ambientLight);
Lightには以下のものがある。

・THREE.AmbientLight
・THREE.DirectionalLight
・THREE.PointLight
・THREE.SpotLight


どう違うかは、後に調べてみよう。。。

レンダリングする


Renderer系には以下の種類があるようだが、今回は無難に CanvasRenderer を利用する。

・CanvasRenderer
・WebGLRenderer
・DOMRenderer
・SVGRenderer



CanvasRenderer.domElement をcontainer(HTML要素)に追加後、Scene と Camera をレンダリングする。
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
renderer.render(scene, camera);

とりあえず3D静止オブジェクトが描画される。

アニメーションさせる


アニメーションさせる場合は requestAnimationFrame() で時間軸をつけてやる。
function animate() {
    requestAnimationFrame(animate);
    render();
}

function render() {
    var timer = Date.now() * 0.0001;
    camera.position.x = Math.cos(timer) * 200;
    camera.position.z = Math.sin(timer) * 200;
    camera.lookAt(scene.position);
    renderer.render(scene, camera);
}
animate() でrequestAnimationFrame()を連続で呼び、render()で状態に応じてレンダリング処理している。

Demo・サンプルソース


Demo

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Cube Sample</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body {
                font-family: Monospace;
                background-color: #f0f0f0;
                margin: 0px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>

        <div id="container"></div>

        <script src="build/three.min.js"></script>
        <script src="js/Stats.js"></script>

        <script>
            var container, stats;
            var camera, scene, renderer;

            init();
            animate();

            function init() {
                container = document.getElementById('container');

                addCamera();
                addSecne();
                addGrid();
                addMaterial();
                addLight();
                addRenderer();
                //addStatus();

                window.addEventListener('resize', onWindowResize, false);
            }

            function addCamera() {
                var left = window.innerWidth / -2;
                var right = window.innerWidth / 2;
                var top = window.innerHeight / 2;
                var bottom = window.innerHeight / -2;

                camera = new THREE.OrthographicCamera(left, right, top, bottom, -2000, 1000);
                camera.position.x = 200;
                camera.position.y = 100;
                camera.position.z = 200;
            }

            function addSecne() {
                scene = new THREE.Scene();
            }

             function addGrid () {
                var geometry = new THREE.Geometry();
                geometry.vertices.push( new THREE.Vector3(-500, 0, 0 ));
                geometry.vertices.push( new THREE.Vector3(500, 0, 0));

                for ( var i = 0; i <= 20; i ++ ) {
                    var parameters = { color: 0x000000, opacity: 0.2 };
                    var line = new THREE.Line(geometry, new THREE.LineBasicMaterial(parameters));
                    line.position.z = ( i * 50 ) - 500;
                    scene.add( line );

                    var line = new THREE.Line(geometry, new THREE.LineBasicMaterial(parameters));
                    line.position.x = ( i * 50 ) - 500;
                    line.rotation.y = 90 * Math.PI / 180;
                    scene.add( line );
                }
            }

            function addMaterial () {
                var geometry = new THREE.CubeGeometry( 50, 50, 50 );
                var material = new THREE.MeshLambertMaterial( { color: 0xffffff, shading: THREE.FlatShading, overdraw: true } );

                for ( var i = 0; i < 100; i ++ ) {
                    var cube = new THREE.Mesh( geometry, material );
                    cube.scale.y = Math.floor( Math.random() * 2 + 1 );
                    cube.position.x = Math.floor( ( Math.random() * 1000 - 500 ) / 50 ) * 50 + 25;
                    cube.position.y = (cube.scale.y * 50) / 2;
                    cube.position.z = Math.floor( ( Math.random() * 1000 - 500 ) / 50 ) * 50 + 25;
                    scene.add( cube );
                }
            }

            function addLight () {
                var directionalLight = new THREE.DirectionalLight(0xffffff);
                directionalLight.position.x = 0.5;
                directionalLight.position.y = 1.5;
                directionalLight.position.z = 3.5;
                directionalLight.position.normalize();
                scene.add(directionalLight);

                var directionalLight = new THREE.DirectionalLight(0xcccccc);
                directionalLight.position.x = 0.5;
                directionalLight.position.y = 1.5;
                directionalLight.position.z = 3.5;
                directionalLight.position.normalize();
                scene.add(directionalLight );

                var ambientLight = new THREE.AmbientLight(0x10);
                scene.add(ambientLight);
            }

            function addRenderer () {
                renderer = new THREE.CanvasRenderer();
                renderer.setSize( window.innerWidth, window.innerHeight );
                container.appendChild(renderer.domElement);
            }

            function addStatus() {
                stats = new Stats();
                stats.domElement.style.position = 'absolute';
                stats.domElement.style.top = '0px';
                container.appendChild(stats.domElement);
            }

            function onWindowResize() {
                camera.left = window.innerWidth / - 2;
                camera.right = window.innerWidth / 2;
                camera.top = window.innerHeight / 2;
                camera.bottom = window.innerHeight / - 2;
                camera.updateProjectionMatrix();
                renderer.setSize( window.innerWidth, window.innerHeight );
            }

            function animate() {
                requestAnimationFrame( animate );
                render();
                stats.update();
            }

            function render() {
                var timer = Date.now() * 0.0001;
                camera.position.x = Math.cos(timer) * 200;
                camera.position.y = Math.sin(timer) * 200;
                //camera.position.z = Math.sin(timer) * 200;
                camera.lookAt(scene.position);
                renderer.render(scene, camera);
            }

        </script>

    </body>
</html>

Chrome環境で、テクスチャーをつけてないので結構スムーズに動きます。
何となく、Papervision3Dに近い気がしました。
ただ、これだけのサンプルだと何のイベントもないので「で?」ですねw
次は、イベント使ったサンプルを調べてみよう。

メモ:モデル


iClone Real-time Filmmaking

このサイトについて

HTML5 & CSS3化しつつあるので、現在IEには対応してません。
できれば、Google Chromeやら Apple SafariのWebKit系をお勧めします。

DBからプログラムまで一応全て自作なので、バグってたらすいません。
実験でFlash版(Flex版)を先に作りましたが、ちょっと停止してます。

プロフィール

新宿近辺でSE & プログラマーしてます。
Webアプリの開発・設計とか、最近はiPhoneとか奮闘してます。
デザインはさっぱりです。

音楽は、昔からCubase打ち込み人間で、そっちの方が経歴は長いですが、最近はやる暇がないです。。。

今は、Gon's Privates ってバンドのキーボードやってます。
単発的に、なんちゃってジャズ系のライブもやってます。

名古屋生まれなのでドラゴンズ好きです。

Info && SNS

Gmail

 yohei.yoshikawa@gmail.com

Twitter

 http://twitter.com/yoo_yoo_yoo

あんまつぶやきませんが、一応技術系メインで使ってます。情報交換はこちらへ

FaceBook

 http://www.facebook.com/#!/profile.php?id=1439130626

海外の知り合いがいないので閑散としてます。

mixi

 http://mixi.jp/show_profile.pl?id=230072

音楽仲間とかはこっちメインでやってます。興味があればこちらへ