のぐそんブログ

暗いおじさんがシコシコ書くブログです。

THREE.jsでオフスクリーンレンダリングを試してみる為のメモ

オフスクリーンレンダリングに挑戦してみたいと思います。 オフスクリーンレンダリングとは、画面には表示せずにメモリ空間上に描画することです。

この一時的に描画されるメモリ空間のことを「フレームバッファ」と言います。

今回は以下の図のように、オフスクリーンレンダリングした情報をテクスチャ画像として、画面上に表示してみたいと思います。

手順1: ベースシーンを描画

まずはベースとなるシーンを描画してみる。 基本的には普通の描画方法と同じ。

window.onload = function () {

    var renderer;
    var baseCamera, baseScene;

    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;

    // rendererの作成
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColor('#CCC');

    // canvasをbodyに追加
    document.body.appendChild(renderer.domElement);

    // canvasをリサイズ
    renderer.setSize(windowWidth, windowHeight);

    // ベースの描画処理(renderTarget への描画用)
    baseScene = new THREE.Scene();

    //ベースの描画処理用カメラ
    baseCamera = new THREE.PerspectiveCamera(50, windowWidth / windowHeight, 0.1, 1000);
    baseCamera.position.z = 20;

    //ライトを追加
    var light = new THREE.DirectionalLight(new THREE.Color(0xffffff), 1);
    light.position.set(0, 10, 20);
    baseScene.add(light);

    //ベース用のマテリアルとジオメトリ
    var texture = THREE.ImageUtils.loadTexture('images/img.jpg');
    var baseGeometry = new THREE.BoxGeometry(10, 10, 10);
    var baseMaterial = new THREE.MeshBasicMaterial({
        map: texture,
        wireframe: false
    });
    var baseMesh = new THREE.Mesh(baseGeometry, baseMaterial);
    baseScene.add(baseMesh);

    render();

    function render() {

        renderer.setClearColor(new THREE.Color(0xffffff), 1.0);

        //レンダリング
        renderer.render(baseScene, baseCamera);

        requestAnimationFrame(render);
    }
};

こんな感じで描画される。

手順2: ベースシーンをオフスクリーンレンダリングしてみる

オフスクリーンレンダリングは、new THREE.WebGLRenderTargetを利用することで実現できる。 THREE.WebGLRenderTargetを使うことで、手順1で作成したsceneをテクスチャとして扱うことができる。

まずはTHREE.WebGLRenderTargetのインスタンスを作成。
    var renderTarget = new THREE.WebGLRenderTarget(256, 256, {
        magFilter: THREE.NearestFilter,
        minFilter: THREE.NearestFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping
    });
renderTargetにレンダリングする
 renderer.render(baseScene, baseCamera, renderTarget);

ここまでの、全体のコードは以下になります。 これを画面見た場合、メモリ空間上には描画されていますが、画面には描画されれていない為、何も表示されません。

window.onload = function () {

    var renderer;
    var postCamera, postScene;
    var baseCamera, baseScene;
    var renderTarget;
    var theta = 0;

    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;

    // rendererの作成
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColor('#CCC');

    // canvasをbodyに追加
    document.body.appendChild(renderer.domElement);

    // canvasをリサイズ
    renderer.setSize(windowWidth, windowHeight);

    // ベースの描画処理(renderTarget への描画用)
    baseScene = new THREE.Scene();

    //ベースの描画処理用カメラ
    baseCamera = new THREE.PerspectiveCamera(50, windowWidth / windowHeight, 0.1, 1000);
    baseCamera.position.z = 20;

    //ライトを追加
    var light = new THREE.DirectionalLight(new THREE.Color(0xffffff), 1);
    light.position.set(0, 10, 20);
    baseScene.add(light);

    //ベース用のマテリアルとジオメトリ
    var texture = THREE.ImageUtils.loadTexture('images/img.jpg');
    var baseGeometry = new THREE.BoxGeometry(10, 10, 10);
    var baseMaterial = new THREE.MeshBasicMaterial({
        map: texture,
        wireframe: false
    });
    var baseMesh = new THREE.Mesh(baseGeometry, baseMaterial);
    baseScene.add(baseMesh);

    //オフスクリーンレンダリング用
    renderTarget = new THREE.WebGLRenderTarget(256, 256, {
        magFilter: THREE.NearestFilter,
        minFilter: THREE.NearestFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping
    });

    render();

    function render() {

        //オフスクリーンレンダリング
        renderer.setClearColor(new THREE.Color(0xffffff), 1.0);
        renderer.render(baseScene, baseCamera, renderTarget);
        
        requestAnimationFrame(render);
    }
};

画面に描画する。

ポストプロセス用のシーンを用意する。 ポイントはmap(テクスチャ)に手順2で用意したrenderTargetをテクスチャとしてわたします。

    //ポストプロセス用
    postScene = new THREE.Scene();
    postCamera = new THREE.PerspectiveCamera(60, windowWidth / windowHeight, 0.1, 1000);
    postCamera.position.z = 20;

    //ポストプロセス用ライト
    var postLight = new THREE.DirectionalLight(new THREE.Color(0xffffff), 1);
    postLight.position.set(0, 10, 20);
    postScene.add(postLight);    

    //ポストプロセス用ジオメトリ、マテリアル
    var postGeometry = new THREE.BoxGeometry(5, 5,5);
    var postMaterial = new THREE.MeshPhongMaterial({
        map: renderTarget,//renderTargetをテクスチャとして渡す
        wireframe: false
    });

    var postMesh = new THREE.Mesh(postGeometry, postMaterial);
    postScene.add(postMesh);

全体のコードはこんな感じ。

window.onload = function () {

    var renderer;
    var postCamera, postScene;
    var baseCamera, baseScene;
    var renderTarget;
    var theta = 0;

    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;

    // rendererの作成
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColor('#CCC');

    // canvasをbodyに追加
    document.body.appendChild(renderer.domElement);

    // canvasをリサイズ
    renderer.setSize(windowWidth, windowHeight);

    // ベースの描画処理(renderTarget への描画用)
    baseScene = new THREE.Scene();

    //ベースの描画処理用カメラ
    baseCamera = new THREE.PerspectiveCamera(50, windowWidth / windowHeight, 0.1, 1000);
    baseCamera.position.z = 20;

    //ライトを追加
    var baseLight = new THREE.DirectionalLight(new THREE.Color(0xffffff), 1);
    baseLight.position.set(0, 10, 20);
    baseScene.add(baseLight);

    //ベース用のマテリアルとジオメトリ
    var texture = THREE.ImageUtils.loadTexture('images/img.jpg');
    var baseGeometry = new THREE.BoxGeometry(10, 10, 10);
    var baseMaterial = new THREE.MeshBasicMaterial({
        map: texture,
        wireframe: false
    });
    var baseMesh = new THREE.Mesh(baseGeometry, baseMaterial);
    baseScene.add(baseMesh);

    //オフスクリーンレンダリング用
    renderTarget = new THREE.WebGLRenderTarget(256, 256, {
        magFilter: THREE.NearestFilter,
        minFilter: THREE.NearestFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping
    });

    //ポストプロセス用
    postScene = new THREE.Scene();
    postCamera = new THREE.PerspectiveCamera(60, windowWidth / windowHeight, 0.1, 1000);
    postCamera.position.z = 20;

    //ポストプロセス用ライト
    var postLight = new THREE.DirectionalLight(new THREE.Color(0xffffff), 1);
    postLight.position.set(0, 10, 20);
    postScene.add(postLight);    

    //ポストプロセス用ジオメトリ、マテリアル
    var postGeometry = new THREE.BoxGeometry(5, 5, 5);
    var postMaterial = new THREE.MeshPhongMaterial({
        map: renderTarget,//renderTargetをテクスチャとして渡す
        wireframe: false
    });

    var postMesh = new THREE.Mesh(postGeometry, postMaterial);
    postScene.add(postMesh);

    render();

    function render() {

        //オフスクリーンレンダリング
        renderer.setClearColor(new THREE.Color(0xffffff), 1.0);
        renderer.render(baseScene, baseCamera, renderTarget);

        //画面にレンダリング
        renderer.setClearColor(new THREE.Color(0x000000), 1.0);
        renderer.render(postScene, postCamera);

        requestAnimationFrame(render);
    }
};

ベースのシーンがテクスチャとして表示される。

テクスチャをアニメーションさせてみる

Boxのジオメトリをテクスチャに設定してみます。 また、メッシュは回転させるようにしてみました。

See the Pen YYyyqj by nogson (@satofaction) on CodePen.

まとめ

メモリ空間上に描画するイメージがもてるようになりました。 シェーダーと組み合わせて使ってみたいです。

参考

[Three.js] WebGLRenderTargetを使ってオフスクリーンレンダリング