Three JS:Geometryでオリジナルの形状を作ってみる
今まで当ブログでthree.jsの基本的な扱い方を勉強してきましたが、もっと基礎的な部分を固める為に自作でGeometry(形状)を作ってみたいと思います。まずは簡単な所で、今回は下のようなただの八面体の作り方を説明します。
Geometryとは
3D空間の中の物体は基本的に頂点(vertice)とその頂点を繋げた三角形の面(face)の集合で成り立っています。three.jsのプリセットに含まれているTorusKnotGeometry(輪環結び目)のような複雑な形状のものも同じく、ワイヤーフレームを表示させてみると三角形の集合だと言う事が分かります。
その頂点と三角形の面を一つ一つ指定して一つのGeometryが形成されます。
Geometryを作ってみる
それでは早速、自作でのGeometryの形成の仕方を説明します。
まずはnew THREE.Geometry()によりGeometryのインスタンスを生成します。
1 | var geometry = new THREE.Geometry(); |
生成したインスタンスのverticesプロパティ(配列)に頂点座標を追加していきます。座標はnew THREE.Vector3(x, y, z)で指定します。
1 2 3 4 5 6 | geometry.vertices.push(new THREE.Vector3(0, 0, 1)); geometry.vertices.push(new THREE.Vector3(1, 0, 0)); geometry.vertices.push(new THREE.Vector3(0, -1, 0)); geometry.vertices.push(new THREE.Vector3(-1, 0, 0)); geometry.vertices.push(new THREE.Vector3(0, 1, 0)); geometry.vertices.push(new THREE.Vector3(0, 0, -1)); |
facesプロパティ(配列)に先ほど追加した頂点座標を3つ指定して、三角形の面を一つずつ作ります。八面体なので八つの面が必要です。指定の仕方はnew THREE.Face3()の引数にvertices配列に入れた頂点のインデックス値を渡します。この時の引数の順番が右回りか左回りかで面の表裏が決まるため統一して指定しましょう。
1 2 3 4 5 6 7 8 | geometry.faces.push(new THREE.Face3( 0, 2, 1)); geometry.faces.push(new THREE.Face3( 0, 3, 2)); geometry.faces.push(new THREE.Face3( 0, 4, 3)); geometry.faces.push(new THREE.Face3( 0, 1, 4)); geometry.faces.push(new THREE.Face3( 5, 1, 2)); geometry.faces.push(new THREE.Face3( 5, 2, 3)); geometry.faces.push(new THREE.Face3( 5, 3, 4)); geometry.faces.push(new THREE.Face3( 5, 4, 1)); |
少し分かりにくいかと思うので下の図を参考にしてください。最初にfaces配列に追加しているのが、0と2と1の頂点を繋げた右下の面です。
最後に面の向きを指定する法線ベクトルを指定するのですが、three.jsでは自動計算してくれるメソッドが用意されているのでそれを使いましょう。これにより光の反射・拡散の方向などが決まります。
1 | geometry.computeFaceNormals(); |
メッシュを作成して表示させてみる
形成したGeometryをメッシュに入れ込み表示させてみましょう。ここからは特に変わったことはなく、プリセットのGeometryを表示させるときと同じです。形状を分かりやすくするためワイヤーフレームのみのメッシュも一緒に表示させます。
1 2 3 4 5 6 7 8 9 10 | //八面体のメッシュ作成 var material = new THREE.MeshNormalMaterial(); //var material = new THREE.MeshPhongMaterial({color: 0x88FFFF}); var mesh = new THREE.Mesh(geometry, material); scene.add(mesh); //ワイヤーフレームのメッシュ作成 var wire = new THREE.MeshBasicMaterial({color: 0xffffff, wireframe: true}); var wireMesh = new THREE.Mesh(geometry, wire); scene.add(wireMesh); |
マテリアルにMeshNormalMaterialを使うことにより各面の法線ベクトルの向きが分かりやすくなります。テスト用に使うと便利です。実際はコメントアウトしてあるMeshPhongMaterialなどを使うことになるでしょう。
さらに下記のメソッドを使うとシェーディングが滑らかになるようです。
1 | geometry.computeVertexNormals(); |
念の為、コード全体を掲載しておきます。下記コードをこのまま使う場合はthree.jsの他にDetector.jsとOrbitControls.jsを読み込んでおく必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | (function(){ var canW = window.innerWidth; //canvas横 var canH = window.innerHeight; //canvas縦 //WebGL環境確認(Detector.js必要) if(!Detector.webgl)Detector.addGetWebGLMessage(); var scene = new THREE.Scene(); // カメラ:透視投影 var camera = new THREE.PerspectiveCamera( 60, canW / canH, 0.01, 100); scene.add(camera); camera.position.set( 1, 1, 5); // レンダラー var renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize( canW, canH); renderer.setPixelRatio( window.devicePixelRatio); document.body.appendChild( renderer.domElement); // ジオメトリ生成 var geometry = new THREE.Geometry(); // 八面体の頂点セット geometry.vertices.push(new THREE.Vector3(0, 0, 1)); geometry.vertices.push(new THREE.Vector3(1, 0, 0)); geometry.vertices.push(new THREE.Vector3(0, -1, 0)); geometry.vertices.push(new THREE.Vector3(-1, 0, 0)); geometry.vertices.push(new THREE.Vector3(0, 1, 0)); geometry.vertices.push(new THREE.Vector3(0, 0, -1)); // 八面体の面セット geometry.faces.push(new THREE.Face3( 0, 2, 1)); geometry.faces.push(new THREE.Face3( 0, 3, 2)); geometry.faces.push(new THREE.Face3( 0, 4, 3)); geometry.faces.push(new THREE.Face3( 0, 1, 4)); geometry.faces.push(new THREE.Face3( 5, 1, 2)); geometry.faces.push(new THREE.Face3( 5, 2, 3)); geometry.faces.push(new THREE.Face3( 5, 3, 4)); geometry.faces.push(new THREE.Face3( 5, 4, 1)); // 法線ベクトルの自動計算 geometry.computeFaceNormals(); geometry.computeVertexNormals(); // 八面体のメッシュ作成 var material = new THREE.MeshNormalMaterial(); //var material = new THREE.MeshPhongMaterial({color: 0x88FFFF}); var mesh = new THREE.Mesh(geometry, material); scene.add(mesh); // ワイヤーフレームのメッシュ作成 var wire = new THREE.MeshBasicMaterial({color: 0xffffff, wireframe: true}); var wireMesh = new THREE.Mesh(geometry, wire); scene.add(wireMesh); // 自然光 var ambientLight = new THREE.AmbientLight( 0x999999); // スポットライト var directionalLight = new THREE.DirectionalLight(0xaaaaaa,0.8); directionalLight.position.set(1,1,1); scene.add( ambientLight, directionalLight); // コントローラー(OrbitControls.js) var controls = new THREE.OrbitControls(camera, renderer.domElement); rendering(); function rendering(){ requestAnimationFrame(rendering); controls.update(); renderer.render( scene, camera); } }()); |
まとめ
今回は基礎の基礎と言う事でただの八面体でしたが、この程度でしたらプリセットのOctahedronGeometryを使えば事足ります。ただ今後プログラミングにて様々な形状を作っていく上で必要な知識との事でおさらいしました。今後複雑な形状の作り方や動的な形状の変化、ジェネラティブアートのような事もしていきたいと思います!