Three.jsにて自作シェーダーを試す際のサンプル Part.2

前回の続き。
GUIにてパラメータの調整ができると楽なので、今回はdat.guiによるパラメータの調整と、stats.jsによるリソースの状態の確認について書く。
サンプルは以下のURLを参照。
http://nktk-tech.com/example/three-template2/main.html

解説

dat.guiは以下のように用いる。

dat.gui周りの処理抜粋// dat.guiにて操作したいパラメータを持つオブジェクト
const lightProperty = {
    "color" : "#ffffff",
    "pos_x" : 3.0,
    "pos_y" : 4.0,
    "pos_z" : 0.0
};

// dat.guiの項目を初期化
const gui = new dat.GUI({name: 'light_property'});
gui.addColor(lightProperty, 'color').onChange( () => light.color.set(lightProperty.color) );
gui.add(lightProperty, 'pos_x', -5.0, 5.0, 0.1).onChange( 
    () => light.position.setX(lightProperty.pos_x ));
gui.add(lightProperty, 'pos_y', -5.0, 5.0, 0.1).onChange( 
    () => light.position.setY(lightProperty.pos_y ))
gui.add(lightProperty, 'pos_z', -5.0, 5.0, 0.1).onChange( 
    () => light.position.setZ(lightProperty.pos_z ));

まず、dat.guiにて変更したいパラメータを持つオブジェクトを用意する。
色に関してはThree.jsもdat.guiもCSSの書き方に対応しているのでそれで初期化すると楽かも。
そしてdat.guiのクラスに対して、パラメータのオブジェクトを登録していくと、パラメータをGUIで操作できるようになる。

色については、dat.GUI.addColor, それ以外は dat.GUI.addにて項目を追加できる。
第一引数にパラメータのオブジェクト 、第二引数は操作したいパラメータを文字列を指定する。
パラメータが数値の場合は、第三引数以降に最小値、最大値、刻み幅を指定できる。
詳しくは下記の公式ドキュメントを参照。
https://github.com/dataarts/dat.gui/blob/master/API.md#GUI+add

追加したパラメータの型によってdat.guiがよしなにgui作成してくれる。
詳しくは下記チュートリアル参照。これ見とけば大体のこと解決できる気がする。
http://workshop.chromeexperiments.com/examples/gui

.addにてdat.guiの項目を追加すると、dat.guiのControllerクラスが返される。
https://github.com/dataarts/dat.gui/blob/master/API.md#Controller
これの.onChangeにて、値が変化したときのコールバック関数を設定できる。
アニメーションループ内でフレームごとにプロパティ再設定するなら、.onChangeに関数設定する必要ないかも。

前回の記事のにそのままdat.guiを加えると、dat.guiの操作と同時に表示している3Dオブジェクトが動いちゃうので注意する。
OrbitControlsの第二引数に、表示用のcanvasのdomを渡してやれば、canvas範囲外でのマウスドラッグは無視できるようになる。

OrbitControlsの設定例const controls = new THREE.OrbitControls( camera, document.querySelector('#myCanvas') );

stats.jsによるfpsの表示は以下のコードで可能。

stats.js周りの処理を抜粋// stats.jsによりfpsを表示
const stats = new Stats();
stats.dom.style.left = "10px";
stats.dom.style.top = "10px";
document.body.appendChild( stats.dom );

// 毎フレーム時に実行されるループイベント
function tick() {
    controls.update();
    renderer.render(scene, camera);
    stats.update(); // 毎フレームごとにstats.update()を呼ぶ必要がある。
    requestAnimationFrame(tick);
}

クラスを作成して、適当なdomの配下に加えて、毎フレームupdateを呼び出すだけなので簡単。
他のリソースを表示する拡張なんかもあるみたい。
参考 : https://qiita.com/dockurage/items/13b71f86c7ac92dfb4c

最終的に、全体のコードは以下のようになった。


main.jswindow.addEventListener('load', init);

const lightProperty = {
    "color" : "#ffffff",
    "pos_x" : 3.0,
    "pos_y" : 4.0,
    "pos_z" : 0.0
};

function init() {
    // サイズを指定
    const width = 960;
    const height = 540;

    // レンダラーを作成
    const renderer = new THREE.WebGLRenderer({
        canvas: document.querySelector('#myCanvas')
    });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(width, height);
    renderer.setClearColor(new THREE.Color( 0.5, 0.5, 0.5 ))

    // シーンを作成
    const scene = new THREE.Scene();

    // カメラを作成
    const camera = new THREE.PerspectiveCamera(45, width / height);
    camera.position.set(0, 5, +10);

    // マウスドラッグによるコントーロールを有効化
    const controls = new THREE.OrbitControls( camera, document.querySelector('#myCanvas') );

    // 点光源追加
    const light = new THREE.PointLight( lightProperty.color );
    light.position.set( lightProperty.pos_x, lightProperty.pos_y, lightProperty.pos_z );
    scene.add( light );

    const sphereSize = 1;
    const pointLightHelper = new THREE.PointLightHelper( light, sphereSize );
    scene.add( pointLightHelper );

    // モデルのロードが終わった際のコールバック
    const callbackOnLoad = (event) => {
        let rootNode = event.detail.loaderRootNode;
        rootNode.children[0].material = new THREE.ShaderMaterial({
            uniforms: THREE.UniformsLib['lights'],
            vertexShader: document.getElementById('vs').textContent,
            fragmentShader: document.getElementById('fs').textContent,
            lights: true,
        });
        scene.add(rootNode);
        renderer.render(scene, camera);
        tick();
    };

    // .obj形式のモデルをロード
    const objLoader = new THREE.OBJLoader2();
    objLoader.setUseIndices(true);
    objLoader.load( 'model/teapot.obj', callbackOnLoad, null, null, null, false );

    // dat.guiの項目を初期化
    const gui = new dat.GUI({name: 'light_property'});
    gui.addColor(lightProperty, 'color').onChange( () => light.color.set(lightProperty.color) );
    gui.add(lightProperty, 'pos_x', -5.0, 5.0, 0.1).onChange( 
        () => light.position.setX(lightProperty.pos_x ));
    gui.add(lightProperty, 'pos_y', -5.0, 5.0, 0.1).onChange( 
        () => light.position.setY(lightProperty.pos_y ))
    gui.add(lightProperty, 'pos_z', -5.0, 5.0, 0.1).onChange( 
        () => light.position.setZ(lightProperty.pos_z ));

    // stats.jsによりfps, 他リソースを表示
    const stats = new Stats();
    stats.dom.style.left = "10px";
    stats.dom.style.top = "10px";
    document.body.appendChild( stats.dom );

    // 毎フレーム時に実行されるループイベント
    function tick() {
        controls.update();
        renderer.render(scene, camera); // レンダリング
        stats.update();
        requestAnimationFrame(tick);
    }
}
main.html<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8"/>
    <!-- r99 -->
    <script src="js/three.js"></script>
    <script src="js/LoaderSupport.js"></script>
    <script src="js/OBJLoader2.js"></script>
    <script src="js/OrbitControls.js"></script>
    <!-- ver0.7.5 -->
    <script src="js/dat.gui.min.js"></script>
    <!-- r17 -->
    <script src="js/stats.min.js"></script>
    <script src="main.js"></script>

    <script id="vs" type="x-shader/x-vertex">
        // PIの定義などがあり、ライト情報を使うのに必要
        #include <common>
        // punctualLightIntensityToIrradianceFactor関数がライトの定義の読み込みに必要
        #include <bsdfs>
        // ライトの構造体、uniform変数などが定義されている
        #include <lights_pars_begin>

        varying vec4 fragColor;

        void main() {
            vec4 tempFragColor = vec4(0.0, 0.0, 0.0, 0.0);
            for (int i = 0; i < NUM_POINT_LIGHTS; i++) {
                vec4 vertexToLight = normalize(vec4(pointLights[i].position, 1.0) - modelViewMatrix * vec4(position, 1.0));
                tempFragColor += vec4(pointLights[i].color, 1.0) * max(dot(vertexToLight.xyz, normalMatrix * normal), 0.0);
            }
            fragColor = tempFragColor;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
         }
    </script>
    <script id="fs" type="x-shader/x-fragment">
        varying vec4 fragColor;
        void main() {
            gl_FragColor = fragColor;
        }
    </script>
</head>

<body>
    <canvas id="myCanvas"></canvas>
</body>

</html>

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です