2020年07月04日 - WebVR・Three.js
BufferGeometryで頂点アニメーション

BufferGeometryとシェーダーを使用していろいろ制作したいと思い、Three.jsのサンプルを参考に、頂点アニメーションを試しました。※Three.jsはr117を使用しています。
BufferGeometryで頂点アニメーション
● BoxHelper
立方体のフレームを表示します。頂点アニメーションが綺麗に見えるように、各面の対角線が表示されないBoxHelperを使用します。
//立方体の辺の長さを設定 const r = 10; const geometry = new THREE.BoxGeometry(r,r,r); const material = new THREE.MeshBasicMaterial(); const box = new THREE.Mesh(geometry,material); //BoxHelperを生成 const frameBox = new THREE.BoxHelper(box,0x111111); scene.add(frameBox);
● 頂点の生成
Pointsを使用して頂点を生成します。
Geometryは毎フレームごとに頂点データをCPUからGPUに転送するのに対し、BufferGeometryは最初の1度だけGPUに転送しその後はGPUで処理するため、大量の頂点を操作する場合に効果を発揮します。
const r = 10;
//頂点数を設定
const particleNum = 2;
const pointMaterial = new THREE.PointsMaterial();
//型付配列で頂点座標を設定
const particlePositions = new Float32Array(particleNum * 3);
for(let i = 0; i < particleNum; i++){
//頂点座標(x、y、z)を設定
particlePositions[i*3] = Math.random() * r - r / 2.0;
particlePositions[i*3+1] = Math.random() * r - r / 2.0;
particlePositions[i*3+2] = Math.random() * r - r / 2.0;
}
//バッファーオブジェクトを生成
const particles = new THREE.BufferGeometry();
//バッファーオブジェクトのattributeに頂点座標を設定
particles.setAttribute('position',new THREE.BufferAttribute(particlePositions,3).setUsage(THREE.DynamicDrawUsage));
//頂点の生成
const pointCloud = new THREE.Points(particles,pointMaterial);
scene.add(pointCloud);
● 頂点のアニメーション
頂点座標に速度を加算して、頂点をアニメーションさせます。
const r = 10;
const particleNum = 2;
//最大速度の設定
const maxVelocity = 0.1;
let pointCloud;
const pointMaterial = new THREE.PointsMaterial();
//速度用の配列
const particleVelocity = [];
const particlePositions = new Float32Array(particleNum * 3);
for(let i = 0; i < particleNum; i++){
particlePositions[i*3] = Math.random() * r - r / 2.0;
particlePositions[i*3+1] = Math.random() * r - r / 2.0;
particlePositions[i*3+2] = Math.random() * r - r / 2.0;
//速度(x、y、z)を設定
particleVelocity[i] = new THREE.Vector3();
particleVelocity[i].x = -1 + Math.random() * 2.0;
particleVelocity[i].y = -1 + Math.random() * 2.0;
particleVelocity[i].z = -1 + Math.random() * 2.0;
//速度の調整
particleVelocity[i].multiplyScalar(maxVelocity / Math.sqrt(3.0));
}
const particles = new THREE.BufferGeometry();
particles.setAttribute('position',new THREE.BufferAttribute(particlePositions,3).setUsage(THREE.DynamicDrawUsage));
pointCloud = new THREE.Points(particles,pointMaterial);
scene.add(pointCloud);
function rendering(){
requestAnimationFrame(rendering);
const rHalf = r / 2.0;
//頂点座標を取得
const particlePositions = pointCloud.geometry.attributes.position.array;
for(let i = 0; i < particleNum; i++){
//頂点座標に速度を加算
particlePositions[i*3] += particleVelocity[i].x;
particlePositions[i*3+1] += particleVelocity[i].y;
particlePositions[i*3+2] += particleVelocity[i].z;
//立方体フレームの外側に移動した場合に速度を反転
if(particlePositions[i*3] < -rHalf || particlePositions[i*3] > rHalf){
particleVelocity[i].x *= -1;
}
if(particlePositions[i*3+1] < -rHalf || particlePositions[i*3+1] > rHalf){
particleVelocity[i].y *= -1;
}
if(particlePositions[i*3+2] < -rHalf || particlePositions[i*3+2] > rHalf){
particleVelocity[i].z *= -1;
}
}
//更新を通知するフラグ
pointCloud.geometry.attributes.position.needsUpdate = true;
renderer.render(scene,camera);
}
● 線のアニメーション
LineSegmentsを使用して線を生成し、アニメーションさせます。
const r = 10;
const particleNum = 2;
const maxVelocity = 0.1;
let pointCloud;
const pointMaterial = new THREE.PointsMaterial();
const particleVelocity = [];
const particlePositions = new Float32Array(particleNum * 3);
for(let i = 0; i < particleNum; i++){
particlePositions[i*3] = Math.random() * r - r / 2.0;
particlePositions[i*3+1] = Math.random() * r - r / 2.0;
particlePositions[i*3+2] = Math.random() * r - r / 2.0;
particleVelocity[i] = new THREE.Vector3();
particleVelocity[i].x = -1 + Math.random() * 2.0;
particleVelocity[i].y = -1 + Math.random() * 2.0;
particleVelocity[i].z = -1 + Math.random() * 2.0;
particleVelocity[i].multiplyScalar(maxVelocity / Math.sqrt(3.0));
}
const particles = new THREE.BufferGeometry();
particles.setAttribute('position',new THREE.BufferAttribute(particlePositions,3).setUsage(THREE.DynamicDrawUsage));
pointCloud = new THREE.Points(particles,pointMaterial);
scene.add(pointCloud);
//線の頂点数の設定
const segments = particleNum * particleNum;
//型付配列で線の頂点座標を設定
const positions = new Float32Array(segments * 3);
const lineGeometry = new THREE.BufferGeometry();
lineGeometry.setAttribute('position',new THREE.BufferAttribute(positions,3).setUsage(THREE.DynamicDrawUsage));
const lineMaterial = new THREE.LineBasicMaterial({
color:0xFFFFFF
});
//LineSegmentsで線を生成
lineMesh = new THREE.LineSegments(lineGeometry,lineMaterial);
scene.add(lineMesh);
function rendering(){
requestAnimationFrame(rendering);
const rHalf = r / 2.0;
let vertexpos = 0;
const particlePositions = pointCloud.geometry.attributes.position.array;
for(let i = 0; i < particleNum; i++){
particlePositions[i*3] += particleVelocity[i].x;
particlePositions[i*3+1] += particleVelocity[i].y;
particlePositions[i*3+2] += particleVelocity[i].z;
if(particlePositions[i*3] < -rHalf || particlePositions[i*3] > rHalf){
particleVelocity[i].x *= -1;
}
if(particlePositions[i*3+1] < -rHalf || particlePositions[i*3+1] > rHalf){
particleVelocity[i].y *= -1;
}
if(particlePositions[i*3+2] < -rHalf || particlePositions[i*3+2] > rHalf){
particleVelocity[i].z *= -1;
}
//線の頂点座標に速度を加算
for(let j = i+1; j < particleNum; j++){
let linePositions = lineMesh.geometry.attributes.position.array;
linePositions[vertexpos++] = particlePositions[i*3];
linePositions[vertexpos++] = particlePositions[i*3+1];
linePositions[vertexpos++] = particlePositions[i*3+2];
linePositions[vertexpos++] = particlePositions[j*3];
linePositions[vertexpos++] = particlePositions[j*3+1];
linePositions[vertexpos++] = particlePositions[j*3+2];
}
}
//更新を通知するフラグ
lineMesh.geometry.attributes.position.needsUpdate = true;
pointCloud.geometry.attributes.position.needsUpdate = true;
renderer.render(scene,camera);
}
完成したデモになります。Three.jsのサンプルを参考に、頂点数を増やし頂点間の距離によって線の描画色を調整しました。Three.jsの頂点アニメーションのデモなので、パソコンとスマホで見ることができるようにしました。

