LoginSignup
0
0

More than 5 years have passed since last update.

初めてのThree.js 1章 マテリアル ライト 影の追加

Last updated at Posted at 2017-11-27

はじめに

three.jsを学ぶため、
オライリー本「初めてのThree.js」をまとめることで内容を咀嚼していこう思う。
前回
今回は第一章の最後まで。

picture978-4-87311-770-6.gif

初めてのThree.js 第2版
――WebGLのためのJavaScript 3Dライブラリ

github learning-three-js-2e-ja-support

はじめのまとめ

  • code sample
  • マテリアル ライト 影の追加
  • code sample
  • アニメーションの追加
  • 統計情報の追加

code sample

script.js

    // once everything is loaded, we run our Three.js stuff.
    function init() {
        // create a scene, that will hold all our elements such as objects, cameras and lights.
        var scene = new THREE.Scene();
        // create a camera, which defines where we're looking at.
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        // create a render and set the size
        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        // create the ground plane
        var planeGeometry = new THREE.PlaneGeometry(60, 20);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.receiveShadow = true;
        // rotate and position the plane
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        // add the plane to the scene
        scene.add(plane);
        // create a cube
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.castShadow = true;
        // position the cube
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;
        // add the cube to the scene
        scene.add(cube);
        var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
        var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        // position the sphere
        sphere.position.x = 20;
        sphere.position.y = 4;
        sphere.position.z = 2;
        sphere.castShadow = true;
        // add the sphere to the scene
        scene.add(sphere);
        // position and point the camera to the center of the scene
        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        spotLight.castShadow = true;
        scene.add(spotLight);
        // add the output of the renderer to the html element
        document.getElementById("WebGL-output").appendChild(renderer.domElement);
        // call the render function
        renderer.render(scene, camera);
    }
    window.onload = init;

以下、部分解説

 マテリアル ライト 影の追加

script.js
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        scene.add(spotLight);

光源をシーンに追加します。
THREE.SpotLightは自分の位置(spotLight.position.set(-20, 30, -5);)から光を照らします。

script.js
        // create the ground plane
        var planeGeometry = new THREE.PlaneGeometry(60, 20);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);

        // create a cube
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

        // position the sphere
        sphere.position.x = 20;
        sphere.position.y = 4;
        sphere.position.z = 2;

マテリアルを前回のMeshBasicMaterialからMeshLambertMaterialに変更しています。
このマテリアルとMeshPhongMaterial MeshStandardMaterialが光源を計算に含めるものです。

次に影を追加します。
Three.jsでは影の描写はデフォルトでは無効化されています。
有効化のためには複数箇所コードを追加します。

script.js
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;

上記はrendererに影の使用を伝えています。
次に影を使用するオブジェクトのプロパティを設定します。

script.js
       plane.receiveShadow = true;
       ...
       cube.castShadow = true;
       ...
       sphere.castShadow = true;

最後にspotLightcastShadowプロパティを設定します。

script.js
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        spotLight.castShadow = true; // ←このコード
        scene.add(spotLight);

 統計情報・アニメーションの追加

code sample

script.js
    // once everything is loaded, we run our Three.js stuff.
    function init() {
        var stats = initStats();
        // create a scene, that will hold all our elements such as objects, cameras and lights.
        var scene = new THREE.Scene();
        // create a camera, which defines where we're looking at.
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        // create a render and set the size
        var renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xEEEEEE));
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        // create the ground plane
        var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);
        var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
        var plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.receiveShadow = true;
        // rotate and position the plane
        plane.rotation.x = -0.5 * Math.PI;
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        // add the plane to the scene
        scene.add(plane);
        // create a cube
        var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
        var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
        var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.castShadow = true;
        // position the cube
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;
        // add the cube to the scene
        scene.add(cube);
        var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
        var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        // position the sphere
        sphere.position.x = 20;
        sphere.position.y = 0;
        sphere.position.z = 2;
        sphere.castShadow = true;
        // add the sphere to the scene
        scene.add(sphere);
        // position and point the camera to the center of the scene
        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);
        // add subtle ambient lighting
        var ambientLight = new THREE.AmbientLight(0x0c0c0c);
        scene.add(ambientLight);
        // add spotlight for the shadows
        var spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-20, 30, -5);
        spotLight.castShadow = true;
        scene.add(spotLight);
        // add the output of the renderer to the html element
        document.getElementById("WebGL-output").appendChild(renderer.domElement);
        // call the render function
        var step = 0;
        renderScene();
        function renderScene() {
            stats.update();
            // rotate the cube around its axes
            cube.rotation.x += 0.02;
            cube.rotation.y += 0.02;
            cube.rotation.z += 0.02;
            // bounce the sphere up and down
            step += 0.04;
            sphere.position.x = 20 + ( 10 * (Math.cos(step)));
            sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }
        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            // Align top-left
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;

以下、部分解説

  • requestAnimationFrame関数
    ブラウザによって定義された感覚で呼び出される関数が設定でき、必要なものをその関数内で自由に描画できる。利用するにはレンダリングを処理する関数を作成して、引数として渡すだけ。
        function renderScene() {
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }

renderScene関数内で を再び呼び出してアニメーションが実行され続けるようにしています。
シーンを完全に作成し終わったあとで、renderer.renderを呼び出す代わりに一度renderScene関数を呼び出してアニメーションを開始します。

        // add the output of the renderer to the html element
        document.getElementById("WebGL-output").appendChild(renderer.domElement);

       // render the scene ←こっちではなくて
        renderer.render(scene, camera);

        // call the render function ←こっち
        var step = 0;
        renderScene();

 統計情報の追加

アニメーションが実行されるフレームフレートに関する情報を表示してくれるヘルパーライブラリーを追加します。

   <script type="text/javascript" src="../libs/stats.js"></script>

   <div id="Stats-output"></div>

統計情報を初期化して<div>に追加

        function initStats() {
            var stats = new Stats(); 
            stats.setMode(0); // 0: fps, 1: ms
            // Align top-left
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }

下記で呼び出し

var stats = initStats();

新しいレンダリングサイクルに入ったことをstatsオブジェクトに通知する。

      function renderScene() {
            stats.update();
            ...
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
      }

上記のようにstats.update関数renderScene関数内で呼び出すことで、新しいレンダリングサイクル入ったことをstatsオブジェクトに通知できるようになる。
これによって左上に統計情報が表示されるようになる。

 アニメーションの追加

  • 立方体を回転させる
function renderScene(){
            ...
            cube.rotation.x += 0.02;
            cube.rotation.y += 0.02;
            cube.rotation.z += 0.02;
            ...

            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);

renderScene関数が呼ばれるたびにrotationプロパティのそれぞれの軸の値を0.02ずつ増やすと、立方体がすべての軸に対してなめらかに回転します。

  • ボールを移動する
        function renderScene() {
            stats.update();
            ...
            // bounce the sphere up and down
            step += 0.04;
            sphere.position.x = 20 + ( 10 * (Math.cos(step)));
            sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
            // render using requestAnimationFrame
            requestAnimationFrame(renderScene);
            renderer.render(scene, camera);
        }

Math.cos関数Math.sin関数を使用すればstep変数を元に軌跡を作成できる。
step += 0.04 によって弾む球の速さを定義できる。

  • dat.GUIを導入する
index.html
      <!-- head 内で読み込む -->
   <script type="text/javascript" src="../libs/dat.gui.js"></script>
        //プロパティとデフォルト値を設定
        var controls = new function () {
            this.rotationSpeed = 0.02;
            this.bouncingSpeed = 0.03;
        };
                //オブジェクトを作成したdat.GUIに渡して2つのプロパティの範囲を設定する
        var gui = new dat.GUI();
        gui.add(controls, 'rotationSpeed', 0, 0.5);
        gui.add(controls, 'bouncingSpeed', 0, 0.5);
                
               //renderSceneループの中で2つのプロパティを直接参照して、datGUIのUIで値を変更したらすぐに反映される様にする
                ...
            // rotate the cube around its axes
            cube.rotation.x += controls.rotationSpeed;
            cube.rotation.y += controls.rotationSpeed;
            cube.rotation.z += controls.rotationSpeed;
            // bounce the sphere up and down
            step += controls.bouncingSpeed;
            sphere.position.x = 20 + ( 10 * (Math.cos(step)));
            sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(step)));
                ...

dat.GUIというライブラリを使用するとコード内の変数の値を変更するための単純なUIコンポーネントを簡単に作成できます。

  • ブラウザサイズが変更されたら出力を自動的にリサイズする
    function onResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }

    // イベントリスナーの登録
    window.addEventListener('resize', onResize, false);

最後にcamera scene rendererの変数定義をinit()関数の外に移動して別の関数からもアクセスできるようにする。

    var camera;
    var scene;
    var renderer;

function init(){
        ...
        // create a scene, that will hold all our elements such as objects, cameras and lights.
        scene = new THREE.Scene();
        // create a camera, which defines where we're looking at.
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        // create a render and set the size
        renderer = new THREE.WebGLRenderer();
        ...
}

まとめ  

Three.jsでは、はじめにThree.Sceneオブジェクトを作成する必要があり、それからシーンオブジェクトにカメラ、ライト、描画したいオブジェクトを追加します。
さらに基本的なシーンを拡張して影とアニメーションを追加します。必要があれば、ヘルパーライブラリを追加します。

以上第1章のまとめになります。長くなりましたね。
長文最後までありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0