【Three.js】球状にオブジェクトを配置する方法2つ
2025/12/14
球状にオブジェクトを配置したいときに使える方法を2つ挙げます。多分もっと色々方法はある。
以下に例として、パーティクルのFloat32Arrayを編集して、複数のパーティクルを均等に配置する。
単体のメッシュを球状に配置する場合、thetaとphiを任意の値に定義すればいい。
前提知識
球座標系(3次元極座標)は緯度・経度をtheta(θ)とphi(φ)で角度を表す。
なんかサイトとか分野によってどっちがthetaかphiかは変わる感じ?
計算方法に関しては、この動画が非常にわかりやすい。
https://www.youtube.com/watch?v=5qXMuaWe-HA
動画と違うところは、赤道が緯度の0度で、y軸が上であること。
今回は緯度をtheta、経度をphiとする。
縦方向がthetaのほうが馴染むしね。
角度 | 定義 | 範囲 |
|---|---|---|
theta(θ) | 緯度。極角。 | -π/2 ~ π/2 |
phi(φ) | 経度。方位学。 | 0 ~ 2π |
方法1(汎用)
赤道を緯度0度として、上をy軸に変換し、
上の添付動画のように計算すると、
const count = 100;
const radius = 2;
const positionArray = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const i3 = i * 3;
const theta = Math.asin(2 * Math.random() - 1) - Math.PI / 2;
const phi = Math.random() * 2 * Math.PI;
positionArray[i3 + 0] = radius * Math.cos(theta) * Math.sin(phi);
positionArray[i3 + 1] = radius * Math.sin(theta);
positionArray[i3 + 2] = radius * Math.cos(theta) * Math.cos(phi);
}thetaの計算について
thetaの計算は、範囲が-π/2~π/2でMath.random() * Math.PI - Math.PI / 2とするのが本能的かもしれないし、見た目も少ししか変わらないかもしれない。
ただ、Math.random() * Math.PIでthetaを計算すると以下の問題が発生する。
- 赤道付近は半径が大きく面積が広いため、密度が薄く
- 極付近は半径が小さく面積が狭いため、密度が濃く
結果、見た目に偏りが生じる。
ので、asinを用いてsin(theta)を-1から1の間で均等に分布させる。2 * Math.random() - 1 は -1 〜 1 の均一乱数
結果として、球面上の点の密度が均等になります。
asinとかがわからなかったらhttps://www.koh-fukuzawa.jp/hw1qqg8cssj参照
よりわかりやすく
もしかしたら、緯度にこだわらず、北極の角度を0度、南極の角度を180度として、その範囲で閉じたthetaを定義するほうが、
範囲が-π/2 ~ π/2から0 ~ πになるので計算簡単。
その時の計算方法は以下
x: radius * Math.sin(theta) * Math.sin(phi);
y: radius * Math.cos(theta);
z: radius * Math.sin(theta) * Math.cos(phi);方法2(ライブラリ)
three.jsにお力を借りる。
※Sphericalにカーソル当てると、Spherical(radius?: number, phi?: number, theta?: number)とでる。phiとthetaの定義が上と違うようなので注意。だって上の方がわかりやすかったんだもん
const count = 100;
const radius = 2;
const positionArray = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const i3 = i * 3;
const theta = Math.acos(2 * Math.random() - 1);
const phi = Math.random() * 2 * Math.PI;
const spherical = new THREE.Spherical(radius, theta, phi);
const position = new THREE.Vector3();
position.setFromSpherical(spherical);
positionArray[i3 + 0] = position.x;
positionArray[i3 + 1] = position.y;
positionArray[i3 + 2] = position.z;
}まあ、3次元の理解を深めるためにも方法1をおすすめする。