Three.jsで三角ポリゴンを扱う機会があり、BufferGeometryで頂点を指定して作ろうと思いました。
光源(ライト)がない場合にはしっかり描画されるのですが、光源がある場合にはうまく描画されずかなり悩んだので記事にして残しておこうと思います。
検証コード
今回は以下のコードで試していきます。
<!DOCTYPE html>
<html>
<head>
<!-- three.js -->
<script src="https://unpkg.com/three@0.140.2/build/three.min.js"></script>
<script>
window.addEventListener('load', () => {
const w = 300;
const h = 300;
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#app')
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
renderer.shadowMap.enabled = true;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, w / h);
camera.position.set(0, 0, 20);
const light = new THREE.DirectionalLight(0xFFFFFF, 1.0);
light.position.set(0, 0, 50);
scene.add(light);
const material = new THREE.MeshStandardMaterial({ color: 0x336699 });
//本題のGeometry
const geometryA = new THREE.BufferGeometry();
const verticesA = new Float32Array([
0.0, 0.0, 0.0,
4.0, 0.0, 0.0,
0.0, 3.0, 0.0
]);
geometryA.setAttribute('position', new THREE.BufferAttribute(verticesA, 3));
const meshA = new THREE.Mesh(geometryA, material);
meshA.position.set(-5, -1.5, 0);
scene.add(meshA);
//比較用のGeometry
const cubeSize = 2.5;
const geometryB = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
const meshB = new THREE.Mesh(geometryB, material);
meshB.position.set(5 - cubeSize / 2, 0, 0);
scene.add(meshB);
renderer.render(scene, camera);
});
</script>
</head>
<body>
<canvas id="app"></canvas>
</body>
</html>
問題となっているBufferGeometryを用いた三角ポリゴンのメッシュを左側に、また、比較用としてBoxBufferGeometryを用いたメッシュを右側に表示します。
この状態での結果は以下の通りです。
確かに、右側はしっかり光源が当たっているのに対して、左側は一切表示されていません。
解決方法
法線ベクトルが正規化されていないなどの理由で光源が正しく反映されないようです(詳しくは知らんけど……)。
Geometry.computeVertexNormals()で法線ベクトルを正規化するとうまくいきました。
...
//本題のGeometry
const geometryA = new THREE.BufferGeometry();
const verticesA = new Float32Array([
0.0, 0.0, 0.0,
4.0, 0.0, 0.0,
0.0, 3.0, 0.0
]);
geometryA.setAttribute('position', new THREE.BufferAttribute(verticesA, 3));
//これを追加
geometryA.computeVertexNormals();
const meshA = new THREE.Mesh(geometryA, material);
meshA.position.set(-5, -1.5, 0);
scene.add(meshA);
...
これでうまくいかない場合は、Float32Arrayの順番が良くなくてメッシュが裏返っている可能性があります。
実は、裏面からだと光源とか関係なしに透明に表示されます。
どこか2組(例えば、(0.0, 0.0, 0.0)と(4.0, 0.0, 0.0))を入れ替えれば、裏表を反転させて修正できます。
それでもうまくいかない場合はタイピングミスなどを探しながらおとなしく質問するのがいいでしょう。
まとめ
- Geometry.computeVertexNormals()で法線ベクトルを正規化する。
- メッシュが裏返っているかどうか確かめて裏返っている場合には直す。
以上です。
良いThree.jsライフを!