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の頂点アニメーションのデモなので、パソコンとスマホで見ることができるようにしました。