WebGL」タグアーカイブ

JavaScriptで物理ベースレンダリングを実装してみる

3D触ってるのに物理ベースレンダリングについて全然知らないのはいかんよなー、と考えていたところ、@mebiusboxさんが以下の素晴らしい記事を公開しているのを見つけたので実装にチャレンジしてみる。
基礎からはじめる物理ベースレンダリング
他3DCG系ドキュメント

基本的には基礎からはじめる物理ベースレンダリングの実装編を参考にし、補足的に以下の記事なども見ながら実装してみた。
超雑訳 Real Shading in Unreal Engine 4
脱・完全鏡面反射~GGXについて調べてみた~

実装結果 :
動作サンプルは下記URLを参照
http://nktk-tech.com/example/pbr-test/main.html

割とそれっぽく動いていると思う。
standard_materialのチェックボックスをonにすると、Three.jsが用意したシェーダーと切り替えられる。
自分で実装した方が若干暗いけど、これはたぶんUnreal Engineの式を参考にした個所があるからだと思う。

鏡面反射が難しくて理解が大分怪しい……
雰囲気だけで理解して、式の導出とかはついていけてない感じが。
IBLとか実装すると、レンダリング結果に凄い説得力が増すから実装してみたいんだけど、鏡面反射以上に数式が難しいんだよなぁ……
今までごまかしごまかしやってきたけど、そろそろ本腰いれて数学学ばないといけんかもな。

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

Three.jsでカスタムシェーダーを扱う際の自分用メモ。
下の記事とか非常に参考になる 。
https://qiita.com/mebiusbox2/items/8a4734ab5b0854528789

Three.jsのライトとかそのヘルパー凄い使いやすいんだけど、その情報を自分のシェーダーに組み込む方法が分からなかったんで、そこらへん含めて調べてみた。
結論を言うと、以下のようなコードを基本にするとかなり楽に色々実験できそう。

ファイル構成main.html
main.js
js/
 └─three.js
   OrbitControls.js
   LoaderSupport.js
   OBJLoader2.js

サンプル : http://nktk-tech.com/example/three-template1/main.html

ソースコード

main.html<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8"/>
    <script src="js/three.js"></script>
    <script src="js/LoaderSupport.js"></script>
    <script src="js/OBJLoader2.js"></script>
    <script src="js/OrbitControls.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>
main.jswindow.addEventListener('load', init);

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 );

    // 点光源追加
    const light = new THREE.PointLight( 0xffffee );
    light.position.set( 5, 3, 5 );
    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 );

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

}
続きを読む