Three.jsベースのVJ用シーンマネージャーを作った話

  • 20
    いいね
  • 0
    コメント

作ったもの

vthreetest02.gif
はじめて作った、ブラウザとキーボードのみのVJアプリの一部抜粋です。

対象者

  • Three.jsの初心者
  • 入門用のサンプル見たけど、どうやってコンテンツ作っていいか未だピンときてない人。
  • TypeScriptが何となく分かる人

Three.jsでVJをやりたい

オーディオビジュアルパフォーマンスをやることになり、せっかく勉強してるThree.jsを使いたいということで、簡単なシーンマネージャーを作ることにしました。

構想

  • 左右のキーで複数のシーンを切り替え
  • 入力イベントなどを後から追加したりできるよう、拡張性は最低限ほしい

後は作りながら固める

どのように作ったか

そもそもThree.jsでレンダリングするまでのプロセスを把握する

だいたい以下のような仕組みになる。

  1. まずはSceneを作る
  2. Sceneの中にオブジェクトを入れる
  3. Rendererを作る
  4. Cameraを作る
  5. RendererにCameraとSceneを受け渡す

これらを踏まえて以下のようなステップでシーンをマネージメントしたい

  • Scene,Camera,オブジェクトを内包した独自のオブジェクトを作る
  • 例えば3つのシーンを切り替えたいなら、上記を3つ作り、配列におさめる
  • Rendererは一つだけ用意し、配列の中身をローテーションして渡す

以上の仕組みでなんとかなりそう。
オブジェクト思考だと実装が楽なので、TypeScriptを今回は採用した。
Three.jsはTypeScriptだと比較的ラクです。

子Sceneクラス

/// <reference path="typings/index.d.ts" />


class SceneBoxA {

    public scene: THREE.Scene;
    public camera: THREE.Camera;
    private Box:THREE.Mesh;
    private timer:number = 0;


    constructor() {

        this.createScene();

    }

    // シーンを作る。ここでオブジェクトを格納していく。
    private createScene(){

        // シーンを作る
        this.scene = new THREE.Scene();

        // カメラを作成
        this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 10000 );
        this.camera.position.z = 1000;

        this.Box = new THREE.Mesh(
            new THREE.BoxGeometry(50,50,50),
            new THREE.MeshBasicMaterial(0xffffff)
        );

        this.scene.add(this.Box);

    }

    // ワンフレームごとの処理
    public update() {

        // ❑の横運動
        this.timer += 0.1;
        this.Box.position.x = 50 * Math.sin(this.timer);
    }


}


// *********** ふたつめのシーン *********** //

class SceneBoxB {

    public scene: THREE.Scene;
    public camera: THREE.Camera;
    private Box:THREE.Mesh;
    private timer:number = 0;


    constructor() {

        this.createScene();

    }

    private createScene(){

        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 10000 );
        this.camera.position.z = 1000;


        this.Box = new THREE.Mesh(
            new THREE.BoxGeometry(50,50,50),
            new THREE.MeshBasicMaterial(0x888888)
        );

        this.scene.add(this.Box);

    }

    public update() {

        // ❑の縦運動
        this.timer += 0.1;
        this.Box.position.y = 50 * Math.sin(this.timer);
    }


}


今回は極力シンプルなものを用意しました。
それぞれのオブジェクトの中に入ってるのは

  • シーン
  • カメラ
  • ボックスのオブジェクト

のみです。
Meshオブジェクトは自身が持っているシーンに格納し、
あとはアニメーションの処理をupdate()内に書きます。
シーンAはボックスが横にゆらゆら、シーンBは縦にゆらゆらします。

親シーンマネージャークラス

class VThree
{
    // 現在のシーンの番号
    public NUM:number = 0;
    // シーンを格納する配列
    public scenes:any[] = [];
    // Renderer
    public renderer:THREE.Renderer;

    constructor()
    {
        // 初期化処理後、イベント登録
        this.init();

        document.addEventListener( 'resize', this.onWindowResize, false );
        document.addEventListener("keydown", this.onKeyDown, true);

    }


    public init()
    {

        // Rendererを作る

        this.renderer = new THREE.WebGLRenderer({antialias: true});
        this.renderer.setPixelRatio( window.devicePixelRatio );
        this.renderer.setSize( window.innerWidth, window.innerHeight );
        this.renderer.sortObjects = false;
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFShadowMap;
        this.renderer.domElement.id = "main";
        document.body.appendChild( this.renderer.domElement );

    }

    // 管理したいシーンを格納する関数

    public addScene(scene:Object)
    {

        this.scenes.push(scene);

    }




    // ウィンドウの幅が変わったときの処理
    public onWindowResize = () =>
    {
        this.renderer.setSize( window.innerWidth, window.innerHeight );
    }

    // 現在のシーン番号が、不適切な値にならないようにチェック
    public checkNum = () =>
    {
        if(this.NUM <0)
        {
            this.NUM = this.scenes.length-1;
        }

        if(this.NUM >= this.scenes.length)
        {
            this.NUM = 0;
        }

    }

    // ←→キーでシーン番号を足し引き

    public onKeyDown = (e:KeyboardEvent) => {

        console.log(e);
        // console.log(this.NUM);
        if(e.key == "ArrowRight")
        {
            this.NUM++;
            this.checkNum();
        }
        if( e.key == "ArrowLeft")
        {

            this.NUM--;
            this.checkNum();
        }

        console.log(this.NUM);

    }

    // 最終的な描写処理と、アニメーション関数をワンフレームごとに実行
    public draw() {

        this.scenes[this.NUM].update();
        this.renderer.render(this.scenes[this.NUM].scene, this.scenes[this.NUM].camera);
        requestAnimationFrame(this.draw.bind(this));

    }
}


Three.jsベースのVJ用のマネージャーということでVThreeというクラスを作ります。
基本的にはこの関数のインスタンスにシーンを突っ込んで、最終的にdraw()関数を呼ぶだけです。

最終的なHTML

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<style>
    body {
        width: 100%;
        height:100%;
        margin: 0;
        background-color: #000
    }
    canvas {
        width: 100%;
        height: 100%;
        position: fixed;
        top: 0;
        left: 0;
    }

</style>
<body>
<script type="text/javascript" src="js/three.min.js"></script>
<script type="text/javascript" src="js/VThree.js"></script>
<script type="text/javascript" src="js/template.js"></script>

<script>
    (function () {

        // VThreeのインスタンスを作成
        var vthree = new VThree();
        // 用意したシーンのインスタンスを作成
        var sceneBoxA = new SceneBoxA();
        var sceneBoxB = new SceneBoxB();
        // vthreeオブジェクトに突っ込む
        vthree.addScene(sceneBoxA);
        vthree.addScene(sceneBoxB);

        // ループ実行
        vthree.draw();

    })();
</script>
</body>
</html>



スッキリしましたね。

実行結果

vthreetest.gif
http://localhost:63342/vj_project/VThreeTest.html

わかりにくいですが、左右のキーボタンを押してます。

おまけ:最初のサンプルのやつについて

Screen Shot 2016-12-15 at 03.52.29.png
https://murasaki-uma.github.io/webgl_vj/

最後のサンプルは複数シーンに加えて、

  • 2つの映像を重ねて描写する
  • 数字キーを叩いて直接その番号のシーンを呼ぶ
  • シーンが変わるときはふわっとフェードイン・アウトさせる

といった細かい演出が入ってます。
しかしこのサンプルを作ってるときは、まだ上記のように完成されたスクリプトで無いのに加え、VJしながら直接コードを書き換えたりしてたのでかなりコードが汚い…。
いずれ作り直します!

ざっくり説明なので、気になったことがあればコメントやツイッターで直接リプもらえると助かります!