Kugelblitz

いつ何時誰の挑戦でも受ける!

樹木の表示

WebGLアプリですが、今週も色々機能の追加、問題点の解決を行いました。

樹木を表示するようにしました!

マップに現在位置を表示

canvas上に表示しているマップに、キャラクターの現在地に応じた位置に、赤い丸を表示するだけなので簡単なのですが、適当に実装したら、東西方向はOKなのですが、南北方向が逆になってしまいました(北に行くほど、南側に表示される)。

よくよく考えたら、canvasは左上原点なのに対し、3D空間は左下が原点(なんか変な言い方ですが)だからですね。

また、毎フレーム現在地を更新するようにしたら、結構パフォーマンスに影響があることがわかったので、マップを表示している場合のみ更新を行うようにしました。

水面がちらつく問題の修正

完全に解決したわけではないのですが、水面のdepthTestをfalseに、renderOrderを10にしたら、多少はましになりました。

星空がちらつく問題の修正

背景の空と、星空が近すぎるのが問題なんですね。距離を離してとりあえず解決です。
そこそこ離していたと思っていたのですが、もしかすると、深度バッファの精度が、距離が離れれば離れるほど低くなるのかもしれません。

DirectXでの話ですが「その71 深度バッファの精度って?」が参考になります。OpenGLでも同じなんでしょうか?

キャラクターの初期表示位置を、水面じゃない場所にする

これは単に、初期表示位置を決める際に、ランダムで決めた初期表示位置が水面だったら(高さが一定以下だったら)、再度初期表示位置を決め直すだけです。

樹木の表示

今週の大きな機能追加として、樹木を表示するようにしました。いろいろ検討しましたが、結局板ポリゴンを十字に配置することで、樹木とする方法を採用しました。PS、Saturnの頃の3Dゲームでよく見かけた手法です。正直なところ、角度によっては十字になっているのが丸わかりなのですが、大量に表示しても描画負荷が低いです。

今時のゲームだと、LODを使って近景の樹木はキチンしたモデルのものを表示しているのでしょうが、現時点でそれをまともに実装できる気がしません。樹木が近くにあるのか遠くにあるのか、全ての樹木に対して判定すると、パフォーマンスに影響がもろにでてしまいます。地形をいくつかのエリアに分割して、エリア外のものはそもそも判定しないような処理にすればいいのかな。

また、樹木のジオメトリはマージして、BufferGeometry化することで、描画負荷が高くならないようにしています。

具体的なコードは以下の様な感じです。

        
new THREE.TextureLoader().load('./img/tree.png', 
    function (tex) {
        var geometry = new THREE.Geometry;
        var meshItem1 = new THREE.Mesh(new THREE.PlaneGeometry(3000, 3000, 1, 1));
        var meshItem2 = new THREE.Mesh(new THREE.PlaneGeometry(3000, 3000, 1, 1));
        // posArr は、各樹木の座標情報
        for (var key in posArr) {
            meshItem1.position.x = treePoint[key].x;
            meshItem1.position.y = treePoint[key].y;
            meshItem1.position.z = treePoint[key].z;
            meshItem2.position.set(meshItem1.position.x, meshItem1.position.y, meshItem1.position.z);
            meshItem2.rotation.y = Math.PI / -2;
            // ジオメトリをマージ
            geometry.mergeMesh(meshItem1);
            geometry.mergeMesh(meshItem2);
         }
         // new THREE.BufferGeometry().fromGeometry(geometry) でBufferGeometry化
         var trees = new THREE.Mesh(
             new THREE.BufferGeometry().fromGeometry(geometry),
             new THREE.MeshLambertMaterial({map: tex, side: THREE.DoubleSide, alphaTest: 0.5})
         );
         // シーンに追加
         DEMO.ms_Scene.add(trees);
    },                
    function (xhr) {
        console.log((xhr.loaded / xhr.total * 100) + '% loaded');
    },
    function (xhr) {
        console.log('An error happened');
    }
);

また、各樹木の座標位置は、最初アプリ起動時に乱数で決めていたのですが、3万本もあると相当遅いので、事前に決定した情報を引っ張ってくるようにしています。

今後の課題

Profilerで負荷がかかる場所を確認すると、キャラクターとカメラのy座標を決める際に使っている(raycaster.intersectObjects)が重いですね。真上から真下にレイを飛ばして、地形のポリゴンとぶつかったところをy座標にしているのですが、真上から真下、とレイの方向が限定されているなら、もしかしたらもう少し高速な実装にできるかもしれません。あとは、カメラ側はある程度地面に潜り込むのを許容するかですね。

こういったWebGLアプリを作っていてわかったのですが、「処理」は、CPU側とGPU側の両方にあって、圧倒的にCPU側が遅いですね。できるだけCPU側の処理が少なくなるように作るのが、パフォーマンスのよいWebGLアプリを作るコツだと思いました。

Pocket

他の記事