2020年12月25日 - WebVR・Three.js
Three.jsのPointsで頂点アニメーション

「three.js blending test」を参考に、Three.jsのPointsで頂点アニメーションを試しました。※Three.jsはr124を使用しています。
球体上の頂点座標

「Three.jsで螺旋アニメーション」での円周上の頂点座標を求めましたが、球体上の頂点座標を求めます。円周上の座標は、半径(r)と角度(θ)を使用して求めましたが、球体上の座標は、半径(r)と仰角(ぎょうかく)(θ)と方位角(θ')を使用して求めます。
// 仰角 const radian1 = degree * Math.PI / 180; // 方位角 const radian2 = degree * Math.PI / 180; //X座標 const x = Math.cos(radian1) * Math.cos(radian2) * r; //Y座標 const y = Math.sin(radian1) * r; //Z座標 const z = Math.cos(radian1) * Math.sin(radian2) * r;
Three.jsのPointsで頂点アニメーション
● 頂点を球体上に配置
頂点を球体上に配置します。
Geometryは形状が空なので、球体上に配置した頂点座標を設定し、THREE.Pointsでメッシュを生成して配置します。頂点座標は管理しやすいように、管理用のクラスを作成します。
const phisicsArr = [];
const positionArr = [];
for(let i = 0; i < 120; i++){
for(let j = 0; j < 120; j++){
//頂点座標のインスタンスを生成
const phisics = new Phisics();
//仰角と方位角のラジアンを設定
const rad1 = (i * 3) * Math.PI / 180;
const rad2 = (j * 3) * Math.PI / 180;
phisics.init(rad1,rad2);
phisicsArr.push(phisics);
//頂点座標管理用の配列に追加
positionArr.push(phisics.position);
}
}
const geometry = new THREE.BufferGeometry().setFromPoints(positionArr);
const material = new THREE.PointsMaterial({
color:0x47656d,
size:6,
transparent:true,
opacity:0.5,
blending:THREE.AdditiveBlending,
depthTest:false
});
//頂点の生成
const points = new THREE.Points(geometry,material);
scene.add(points);
//頂点座標の管理用クラス
class Phisics{
constructor(){
//頂点座標
this.position = new THREE.Vector3();
//半径
this.radius = 300;
//仰角
this.rad1 = 0;
//方位角
this.rad2 = 0;
}
init(rad1,rad2){
this.rad1 = rad1;
this.rad2 = rad2;
this.setPosition();
}
setPosition(rad1,rad2){
//頂点座標を球体上に設定
const x = Math.cos(this.rad1 ) * Math.cos(this.rad2) * this.radius;
const y = Math.sin(this.rad1 ) * this.radius;
const z = Math.cos(this.rad1 ) * Math.sin(this.rad2) * this.radius;
this.position.set(x,y,z);
}
}
● テクスチャの設定

THREE.PointsMaterialはテクスチャ画像を設定できます。テクスチャを設定しないと、正方形の頂点になるので、円を少しぼかして背景を透過したテクスチャ画像を設定します。
//テクスチャ画像の読み込み
const texture = new THREE.TextureLoader().load('./img/point.png');
const material = new THREE.PointsMaterial({
//テクスチャを設定
map:texture,
color:0x47656d,
size:6,
transparent:true,
opacity:0.5,
blending:THREE.AdditiveBlending,
depthTest:false
});
● 頂点のアニメーション
頂点をアニメーションさせます。方位角のラジアンの値を更新すると、頂点が球体にそってアニメーションします。
function rendering(){
requestAnimationFrame(rendering);
const positions = points.geometry.attributes.position.array;
for(let i = 0; i < phisicsArr.length; i++){
//頂点座標のインスタンスを取得
const phisics = phisicsArr[i];
positions[i * 3] = phisics.position.x;
positions[i * 3 + 1] = phisics.position.y;
positions[i * 3 + 2] = phisics.position.z;
//アニメーション
phisics.updata();
}
points.geometry.attributes.position.needsUpdate = true;
renderer.render(scene,camera);
}
class Phisics{
constructor(){
this.position = new THREE.Vector3();
this.radius = 300;
this.rad1 = 0;
this.rad2 = 0;
}
init(rad1,rad2){
this.rad1 = rad1;
this.rad2 = rad2;
this.setPosition();
}
setPosition(rad1,rad2){
const x = Math.cos(this.rad1 ) * Math.cos(this.rad2) * this.radius;
const y = Math.sin(this.rad1 ) * this.radius;
const z = Math.cos(this.rad1 ) * Math.sin(this.rad2) * this.radius;
this.position.set(x,y,z);
}
//アニメーション
updata(){
//方位角のラジアンの値を更新
this.rad2 += 0.1 * Math.PI / 180;
this.setPosition();
}
}
● script.js
必要なライブラリを読み込みます。
<script src="js/preloadjs.min.js"></script> <script src="js/TweenMax.min.js"></script> <script src="js/script.js" type="module"></script>
完成したscript.jsになります。
//===============================================================
// Import Library
//===============================================================
import * as THREE from './lib/three_jsm/three.module.js';
import { OrbitControls } from './lib/three_jsm/OrbitControls.js';
import { scene, camera, container, renderer } from './lib/basescene.js';
//===============================================================
// Init
//===============================================================
window.addEventListener('load',function(){
init();
});
let orbitControls;
let value = 0;
let points;
const phisicsArr = [];
function init(){
setLoading();
}
function setLoading(){
TweenMax.to('.loader',0.1,{opacity:1});
TweenMax.to('#loader_wrapper',1,{
opacity:0,
delay:0,
onComplete: function(){
document.getElementById('loader_wrapper').style.display = 'none';
TweenMax.to('.loader',0,{opacity:0});
}
});
threeWorld();
setLight();
setControll();
rendering();
}
//===============================================================
// Create World
//===============================================================
function threeWorld(){
renderer.outputEncoding = THREE.sRGBEncoding;
const axesHelper = new THREE.AxesHelper(500);
const gridHelper = new THREE.GridHelper(1000,50);
//scene.add(axesHelper);
//scene.add(gridHelper);
const positionArr = [];
for(let i = 0; i < 120; i++){
for(let j = 0; j < 120; j++){
const phisics = new Phisics();
const rad1 = (i * 3) * Math.PI / 180;
const rad2 = (j * 3) * Math.PI / 180;
phisics.init(rad1,rad2);
phisicsArr.push(phisics);
positionArr.push(phisics.position);
}
}
const geometry = new THREE.BufferGeometry().setFromPoints(positionArr);
const texture = new THREE.TextureLoader().load('./img/point.png');
const material = new THREE.PointsMaterial({
map:texture,
color:0x47656d,
size:6,
transparent:true,
opacity:0.5,
blending:THREE.AdditiveBlending,
depthTest:false
});
points = new THREE.Points(geometry,material);
scene.add(points);
}
function setLight(){
const ambientlight = new THREE.AmbientLight(0xFFFFFF,1);
scene.add(ambientlight);
}
function setControll(){
document.addEventListener('touchmove',function(e){e.preventDefault();},{passive:false});
orbitControls = new OrbitControls(camera,renderer.domElement);
orbitControls.enableDamping = true;
orbitControls.dampingFactor = 0.5;
}
function rendering(){
requestAnimationFrame(rendering);
const positions = points.geometry.attributes.position.array;
for(let i = 0; i < phisicsArr.length; i++){
const phisics = phisicsArr[i];
positions[i * 3] = phisics.position.x;
positions[i * 3 + 1] = phisics.position.y;
positions[i * 3 + 2] = phisics.position.z;
phisics.updata();
}
points.geometry.attributes.position.needsUpdate = true;
value += 0.0025;
points.material.opacity = Math.min(Math.max(Math.abs(Math.cos(value)),0.1),0.65);
if(orbitControls){
orbitControls.update();
}
renderer.render(scene,camera);
}
class Phisics{
constructor(){
this.position = new THREE.Vector3();
this.radius = 300;
this.rad1 = 0;
this.rad2 = 0;
}
init(rad1,rad2){
this.rad1 = rad1;
this.rad2 = rad2;
this.setPosition();
}
setPosition(rad1,rad2){
const x = Math.cos(this.rad1 ) * Math.cos(this.rad2) * this.radius;
const y = Math.sin(this.rad1 ) * this.radius;
const z = Math.cos(this.rad1 ) * Math.sin(this.rad2) * this.radius;
this.position.set(x,y,z);
}
updata(){
this.rad1 += 0.05 * Math.PI / 180;
this.rad2 += 0.1 * Math.PI / 180;
this.radius = Math.cos(this.rad1) * 300;
this.setPosition();
}
}
● basescene.js
sceneやcameraなど基本的な設定を管理するbasescene.jsです。
//===============================================================
// Import Library
//===============================================================
import * as THREE from './three_jsm/three.module.js';
//===============================================================
// Base scene
//===============================================================
let scene,camera,container,renderer;
init();
function init(){
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,1,5000);
camera.position.set(600,600,600);
scene.add(camera);
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setClearColor(new THREE.Color(0x000000));
container = document.querySelector('#canvas_vr');
container.appendChild(renderer.domElement);
window.addEventListener('resize',function(){
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
},false);
}
export { scene, camera, container, renderer }
完成したデモになります。仰角と方位角のラジアンや半径の値、マテリアルの透明度を更新してアニメーションさせました。

