Three.jsのGeometry(ジオメトリ)は、3Dオブジェクトの頂点・面・法線・UVなど「形」を表すデータです。見た目を決めるMaterialと組み合わせ、MeshにしてSceneへ追加します。
最初に要点を整理します。
- 箱は
BoxGeometry、球はSphereGeometry、床はPlaneGeometryから始める - 現在の組み込みGeometryは共通して
BufferGeometryを基盤にする - 頂点を変更したら
position.needsUpdate = trueを設定する - 幅やsegment数を変える場合は、新しいGeometryを作って古いものを
dispose()する - 輪郭線は
EdgesGeometryとLineSegmentsを組み合わせる
この記事では、種類の丸暗記ではなく「どのGeometryを選ぶか」「生成後にどう変更するか」を、現行Three.jsのAPIで順番に解説します。
Geometry・Material・Meshの違い
Three.jsで物体を表示する基本単位がMeshです。MeshはGeometryとMaterialを組み合わせたObject3Dです。
| 要素 | 役割 | 例 |
|---|---|---|
| Geometry | 頂点、三角形、法線、UVなどの形状データ | BoxGeometry |
| Material | 色、光沢、透明度、textureなどの描画方法 | MeshStandardMaterial |
| Mesh | GeometryとMaterialを組み合わせた表示object | new THREE.Mesh(geometry, material) |
const geometry = new THREE.BoxGeometry(2, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x38bdf8 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
MeshStandardMaterialは光源を必要とします。Geometryだけをまず表示したい場合は、光源なしで見えるMeshBasicMaterialを使うと切り分けやすくなります。Materialの選び方はThree.js Materialの種類で詳しく解説しています。
Scene、Camera、Rendererを含む最小構成から確認したい場合は、先にThree.jsの始め方を参照してください。
よく使うGeometryの種類と選び方
Three.jsには実行時に形を生成するprimitive Geometryが用意されています。まずは用途から候補を絞ります。
| 作りたい形 | Geometry | 主な引数 |
|---|---|---|
| 箱・直方体 | BoxGeometry |
width, height, depth, 各segment数 |
| 球 | SphereGeometry |
radius, widthSegments, heightSegments |
| 平面・床 | PlaneGeometry |
width, height, widthSegments, heightSegments |
| 円・円環 | CircleGeometry / RingGeometry |
radius、内外半径、segment数 |
| 円錐・円柱・カプセル | ConeGeometry / CylinderGeometry / CapsuleGeometry |
radius, height, segment数 |
| ドーナツ・結び目 | TorusGeometry / TorusKnotGeometry |
radius, tube, segment数 |
| 正多面体 | TetrahedronGeometryなど |
radius, detail |
| 2D輪郭 | ShapeGeometry |
Shape, curveSegments |
| 2D輪郭を押し出す | ExtrudeGeometry |
Shape, depth・bevelなどのoptions |
| 回転体・曲線に沿う管 | LatheGeometry / TubeGeometry |
Vector2の列 / Curve |
Box・Sphere・Planeの基本例
よく使う3種類は次のように生成できます。
const box = new THREE.BoxGeometry(2, 1, 1);
const sphere = new THREE.SphereGeometry(1, 32, 16);
const plane = new THREE.PlaneGeometry(10, 10, 1, 1);
PlaneGeometryは初期状態でXY平面上にあり、正面は+Z方向です。床としてXZ平面へ置くならMeshを回転します。
const floor = new THREE.Mesh(plane, floorMaterial);
floor.rotation.x = -Math.PI / 2;
位置・回転・拡大縮小はGeometryの頂点を書き換えなくても、Meshのtransformで変更できます。用途の違いはThree.jsで移動・回転・scaleを設定する方法も参照してください。
Shape・Extrude・Lathe・Tubeには元データが必要
一部のGeometryは、constructorを引数なしで呼ぶだけでは目的の形を作れません。
ShapeGeometryとExtrudeGeometryにはTHREE.Shapeが必要LatheGeometryには輪郭を表すVector2の配列が必要TubeGeometryにはCurveを継承したpathが必要
たとえばTubeGeometryは、3D点の配列をCatmullRomCurve3へ渡してpathを作ります。
const path = new THREE.CatmullRomCurve3([
new THREE.Vector3(-2, 0, 0),
new THREE.Vector3(-1, 1, 0),
new THREE.Vector3(1, -1, 0),
new THREE.Vector3(2, 0, 0),
]);
const geometry = new THREE.TubeGeometry(path, 64, 0.15, 8, false);
種類を切り替えるUIを作る場合も、引数なしのconstructor名だけをswitchするのではなく、各形状に必要なinputを用意したfactory関数へ分けます。
GeometryはBufferGeometryでできている
現在のThree.jsでは、組み込みのBoxGeometryやSphereGeometryもBufferGeometryを継承しています。BufferGeometryは、GPUへ渡しやすいTypedArrayをBufferAttributeとして保持します。
代表的なattributeは次のとおりです。
| attribute | 内容 | 1頂点あたりの値 |
|---|---|---|
position |
頂点の座標 | x, y, zの3個 |
normal |
面が向く方向。lightingに使用 | x, y, zの3個 |
uv |
texture上の位置 | u, vの2個 |
color |
頂点ごとの色 | r, g, bなど |
index |
どの頂点を使って三角形を作るか | 三角形ごとに3 index |
実際のGeometryをconsoleで確認できます。
const geometry = new THREE.BoxGeometry(1, 1, 1);
const position = geometry.getAttribute("position");
console.log(position.count);
console.log(position.itemSize); // 3
console.log(geometry.getAttribute("normal"));
console.log(geometry.getAttribute("uv"));
console.log(geometry.getIndex());
textureを正しく貼るにはuvが関係します。画像textureの読み込みとMaterialへの設定はThree.jsでtextureを設定する方法で分離して解説しています。
BufferGeometryで三角形を自作する
Meshの面は最終的に三角形として描かれます。3頂点のpositionを用意すれば、最小の独自Geometryを作れます。
const positions = new Float32Array([
-1, -1, 0,
1, -1, 0,
0, 1, 0,
]);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3),
);
geometry.computeVertexNormals();
const material = new THREE.MeshStandardMaterial({
color: 0x38bdf8,
side: THREE.DoubleSide,
});
const triangle = new THREE.Mesh(geometry, material);
scene.add(triangle);
BufferAttribute(positions, 3)の3は、1頂点をx・y・zの3値で表すという意味です。表裏を確認しやすい例としてDoubleSideを使っていますが、閉じた立体で常に両面描画すると描画costが増えます。必要な面方向を揃えてFrontSideを使うのが基本です。
indexで頂点を共有する
正方形は2つの三角形で構成できます。indexを使うと4頂点を再利用できます。
const positions = new Float32Array([
-1, -1, 0, // 0
1, -1, 0, // 1
1, 1, 0, // 2
-1, 1, 0, // 3
]);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
geometry.setIndex([
0, 1, 2,
2, 3, 0,
]);
geometry.computeVertexNormals();
ただし、同じ位置でも面ごとにnormalやuvが異なる頂点は共有できません。「座標が同じなら必ず同じ頂点」ではなく、position・normal・uvなどの組み合わせが1頂点です。
生成済みGeometryの頂点を変更する
頂点座標はposition attributeから変更します。変更後はneedsUpdateをtrueにして、GPU側のbufferを更新する必要があります。
const geometry = new THREE.PlaneGeometry(4, 4, 20, 20);
const position = geometry.getAttribute("position");
position.setUsage(THREE.DynamicDrawUsage);
for (let i = 0; i < position.count; i += 1) {
const x = position.getX(i);
const y = position.getY(i);
const z = Math.sin(x * 2) * Math.cos(y * 2) * 0.2;
position.setZ(i, z);
}
position.needsUpdate = true;
geometry.computeVertexNormals();
geometry.computeBoundingBox();
geometry.computeBoundingSphere();
DynamicDrawUsageは頻繁に更新する予定であることをrendererへ伝えるhintです。最初のrenderより前に設定します。
法線とbounding volumeを再計算する
頂点の形が変わると、lightingに使うnormalも古くなる場合があります。陰影が不自然ならcomputeVertexNormals()を実行します。
bounding boxやbounding sphereを利用する処理がある場合も、頂点変更後に再計算します。これらはfrustum culling、raycast、helperなどの判定に影響します。
毎frame大きなGeometryの法線や境界を再計算すると重くなります。変形頻度・頂点数・必要な機能を見て、shaderでの変形や事前計算も検討します。
bufferの頂点数は途中で増減できない
TypedArrayは作成時に長さが決まります。既存BufferAttributeの内容は更新できますが、同じbufferを無制限に拡張することはできません。
頂点数が増える可能性があるlineなどでは、最大数を先に確保し、setDrawRange()で描画範囲を調整します。
const maxPoints = 500;
const positions = new Float32Array(maxPoints * 3);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3),
);
geometry.setDrawRange(0, 2);
// 点を追加したあと、描画する頂点数だけ増やす
geometry.setDrawRange(0, currentPointCount);
最大数を予測できない、attribute構成自体が変わる場合は、新しいBufferGeometryを作るほうが明確です。
幅・高さ・segment数を変更する
組み込みGeometryのparametersは、constructorへ渡した値の記録です。次のように変更しても、すでに作られた頂点は更新されません。
// 形は変わらない
mesh.geometry.parameters.width = 5;
幅・半径・segment数を変える場合は、新しいGeometryへ交換し、不要になった古いGeometryを破棄します。
function resizeBox(mesh, width, height, depth) {
const oldGeometry = mesh.geometry;
mesh.geometry = new THREE.BoxGeometry(width, height, depth);
oldGeometry.dispose();
}
resizeBox(boxMesh, 3, 1.5, 1);
単に全体を2倍に見せたいだけなら、Geometryを再生成せずmesh.scale.set(2, 2, 2)を使えます。Geometry自体の寸法を変えたいか、Object3Dのtransformで見た目を変えたいかを分けて考えます。
Geometryを移動・回転・中央揃えする
BufferGeometryにはtranslate()、rotateX()、scale()など、頂点へ変換を焼き込むmethodがあります。
geometry.translate(0, 1, 0);
geometry.rotateX(-Math.PI / 2);
geometry.scale(2, 1, 1);
geometry.center();
一方、通常のanimationや配置ではMeshのtransformを使います。
| 変更先 | 適する用途 |
|---|---|
mesh.position / rotation / scale |
Scene内での配置、animation、同じGeometryの共有 |
geometry.translate() / rotateX() / scale() |
pivot調整、import後の軸補正、頂点へ固定変換を適用 |
同じGeometryを複数Meshが共有している場合、Geometry側を変更するとすべてに影響します。1つだけ変形したいならMeshのtransformを使うか、Geometryをclone()してから変更します。
segment数とperformanceの考え方
SphereGeometryやPlaneGeometryのsegment数を増やすと、頂点と三角形が増えます。球は滑らかになりますが、GPUへ渡すdata量、頂点shader・fragment処理、memory使用量も増えます。
const low = new THREE.SphereGeometry(1, 12, 8);
const medium = new THREE.SphereGeometry(1, 32, 16);
const high = new THREE.SphereGeometry(1, 96, 64);
1個の球だけを見る場合と、1,000個表示する場合では適切な値が異なります。画面上の大きさ、数、端末性能、Material、shadowの有無を含めて測定します。
Planeは変形しない床や1枚画像なら2三角形で十分です。displacementや頂点変形を行うときだけ必要な分割数を追加します。
wireframeとEdgesGeometryの違い
三角形の分割をすべて見たい場合はMaterialのwireframe: trueが簡単です。
const material = new THREE.MeshBasicMaterial({
color: 0x111827,
wireframe: true,
});
物体の輪郭に近い線だけを表示したい場合はEdgesGeometryを作り、LineSegmentsで描画します。
const sourceGeometry = new THREE.BoxGeometry(2, 2, 2);
const edgeGeometry = new THREE.EdgesGeometry(sourceGeometry, 15);
const edgeMaterial = new THREE.LineBasicMaterial({ color: 0xffffff });
const edges = new THREE.LineSegments(edgeGeometry, edgeMaterial);
scene.add(edges);
第2引数thresholdAngleは、隣接面の法線角度が何度を超えたedgeを残すか指定します。WireframeGeometryもline segment用のGeometryであり、通常のMeshではなくLineSegmentsと組み合わせます。
Geometryを切り替える安全な実装
selectやbuttonで形を切り替える場合は、constructorを返すfactory関数を用意すると管理しやすくなります。
function createGeometry(type) {
switch (type) {
case "box":
return new THREE.BoxGeometry(1.5, 1.5, 1.5);
case "sphere":
return new THREE.SphereGeometry(1, 32, 16);
case "torus":
return new THREE.TorusGeometry(0.8, 0.3, 16, 64);
default:
throw new Error(`Unknown geometry type: ${type}`);
}
}
function replaceGeometry(mesh, type) {
const nextGeometry = createGeometry(type);
const previousGeometry = mesh.geometry;
mesh.geometry = nextGeometry;
previousGeometry.dispose();
}
先に次のGeometryを正常に作ってから交換しているため、unknown typeやconstructor errorで現在の表示を失いにくい構造です。
Geometryを共有しているMeshがある場合は、1つのMeshが勝手にdispose()しないよう所有関係を決めます。参照中のGeometryを破棄すると、次のrenderで再uploadが必要になったり表示管理が崩れたりします。
不要なGeometryをdisposeする
SceneからMeshをremoveしただけでは、Geometryが内部で確保したGPU resourceは自動解放されません。今後使わないGeometryは明示的に破棄します。
scene.remove(mesh);
mesh.geometry.dispose();
mesh.material.dispose();
Materialが配列の場合、各Materialを破棄します。textureはMaterialと別resourceなので、ほかで共有していないことを確認してtexture.dispose()します。
EdgesGeometryのように元Geometryから別Geometryを作った場合は、元と派生後の両方が不要になった時点でそれぞれdisposeします。
古いTHREE.Geometryコードを移行する
古い記事やsampleには、次のAPIが出てくることがあります。
// 現行Three.jsでは使わない古い例
geometry.vertices[0].x = 1;
geometry.verticesNeedUpdate = true;
geometry.faces[0].color.set(0xff0000);
旧Geometry classとFace3中心のAPIはcoreから削除されています。現在はBufferGeometryのattributeを取得し、setterとneedsUpdateを使います。
const position = geometry.getAttribute("position");
position.setX(0, 1);
position.needsUpdate = true;
古いBoxBufferGeometryなどの別名ではなく、現在はBoxGeometryを使います。古いprojectを更新するときは、一度に大きくversionを飛ばさず、Three.js公式Migration Guideでrelease間の破壊的変更を確認してください。
Geometryが表示されないときの確認項目
Geometryを作れたのに見えない場合は、次の順番で確認します。
- MeshまたはLineSegmentsを
scene.add()したか - Cameraの前にあり、near・farの範囲内か
- Materialが透明、黒、裏面だけになっていないか
- 光を必要とするMaterialなのにLightがない状態ではないか
- Planeなど1面Geometryの表裏がCameraと合っているか
- position attributeに
NaNやInfinityが入っていないか - 頂点変更後に
needsUpdateを設定したか - indexの順序と頂点数が正しいか
- resize後にCameraとRendererのsizeを更新しているか
- すでに共有Geometryをdisposeしていないか
形が黒い・陰影がおかしい
MeshStandardMaterialなどではLightが必要です。Lightを追加しても不自然ならnormalを確認します。頂点を変えた場合はcomputeVertexNormals()を実行します。
Lightの種類と配置はAmbientLight・DirectionalLight・PointLightの使い方で解説しています。
一部の面が消える
面の頂点順序が逆、またはCameraが裏側を見ている可能性があります。診断時だけside: THREE.DoubleSideへ切り替え、原因が分かったらFrontSideへ戻せるか検討します。
頂点を変えたのに表示が変わらない
変更しているGeometryが実際にMeshへ設定されているか、共有Geometryではないか、positionのneedsUpdateを設定したかを確認します。幅・segment数はparametersを書き換えず、Geometryを再生成します。
Geometry選択の実践チェックリスト
- 基本形で足りるなら組み込みprimitiveを使う
- 複雑な製品・人物・背景はBlenderなどからglTFで読み込む選択も検討する
- 独自形状はBufferGeometryのposition・normal・uv・indexを設計する
- 同じ形を多数置く場合はGeometryを共有し、必要ならInstancedMeshを検討する
- segment数は画面上の品質と表示個数に合わせる
- 頻繁な頂点更新ではDynamicDrawUsage、needsUpdate、再計算costを確認する
- 形状交換時は古いGeometryの所有者を確認してdisposeする
まとめ
Three.jsのGeometryは、BoxやSphereなどの種類を選ぶだけでなく、その内部にあるBufferGeometryとattributeを理解すると扱いやすくなります。
基本形はconstructorの引数で生成し、Scene内の配置はMeshのposition・rotation・scaleで変更します。頂点を直接変える場合はposition attributeを更新してneedsUpdateを設定し、必要に応じてnormalとbounding volumeを再計算します。
幅・半径・segment数を変更するときは新しいGeometryへ交換し、不要なGPU resourceをdispose()します。この区別を押さえると、表示されない・変形が反映されない・切り替えるたびに重くなるといった問題を避けやすくなります。