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 }
完成したデモになります。仰角と方位角のラジアンや半径の値、マテリアルの透明度を更新してアニメーションさせました。