投稿日:2022-07-21
#Three.js
#Basic
最近 THREE.js を勉強しており、アウトプットがてらブログにしていこうと思います。跳ねながら球体が成長していくようなアニメーションを作成していこうと思います。ただ、あくまで Three.js 初学者ですので、コードは参考程度にしていただければと思います。きっとより効率のよい実装方法があるかと思います。
でソースコードと完成品を公開しておりますので、是非のぞいてみてください。Youtube にも完成版をアップロードしております。以下のリンクから完成形を確認できます。
index.html
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <link rel="stylesheet" href="style.css"> 8 <title>Document</title> 9</head> 10<body> 11 <script 12 src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js" 13 integrity="sha512-dLxUelApnYxpLt6K2iomGngnHO83iUvZytA3YjDUCjT0HDOHKXnVYdf3hU4JjM8uEhxf9nD1/ey98U3t2vZ0qQ==" 14 crossorigin="anonymous" 15 referrerpolicy="no-referrer" 16 ></script> 17 <script src="main.js"></script> 18</body> 19</html>
style.css
1* { 2 margin: 0; 3 padding: 0; 4}
main.js
1window.addEventListener("load", init); 2 3function init() { 4 let width = window.innerWidth; 5 let height = window.innerHeight; 6 7 const scene = new THREE.Scene(); 8 scene.background = new THREE.Color(0x050505); 9 10 const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); 11 cameraSetBackDist = 7; 12 camera.position.z = cameraSetBackDist; 13 14 const light = new THREE.PointLight(0xffffff, 2); 15 light.position.set(10, 10, 10); 16 scene.add(light); 17 18 const renderer = new THREE.WebGLRenderer(); 19 renderer.setSize(width, height); 20 document.body.appendChild(renderer.domElement); 21 22 const geometry = new THREE.IcosahedronGeometry(1, 10); 23 24 //↓この範囲は後で消す 25 const material = new THREE.MeshBasicMaterial({ 26 wireframe: true, 27 color: 0xc100eb 28 }); 29 const sphere = new THREE.Mesh(geometry, material); 30 scene.add(sphere) 31 scene.add(camera) 32 renderer.render(scene, camera); 33 //↑この範囲は後で消す 34}
main.js
1window.addEventListener("load", init); 2 3function init() { 4 let width = window.innerWidth; 5 let height = window.innerHeight; 6 7 const scene = new THREE.Scene(); 8 scene.background = new THREE.Color(0x050505); 9 10 const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); 11 cameraSetBackDist = 7; 12 camera.position.z = cameraSetBackDist; 13 14 const light = new THREE.PointLight(0xffffff, 2); 15 light.position.set(10, 10, 10); 16 scene.add(light); 17 18 const renderer = new THREE.WebGLRenderer(); 19 renderer.setSize(width, height); 20 document.body.appendChild(renderer.domElement); 21 22 const geometry = new THREE.IcosahedronGeometry(1, 10); 23 const geometryPos = geometry.getAttribute("position").array; 24 25 const mesh = []; 26 const normalDirection = []; 27 28 for (let i = 0; i < geometryPos.length; i += 9) { 29 const geometry2 = new THREE.BufferGeometry(); 30 31 const vertices = new Float32Array([ 32 geometryPos[i], 33 geometryPos[i + 1], 34 geometryPos[i + 2], 35 geometryPos[i + 3], 36 geometryPos[i + 4], 37 geometryPos[i + 5], 38 geometryPos[i + 6], 39 geometryPos[i + 7], 40 geometryPos[i + 8] 41 ]); 42 43 geometry2.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); 44 geometry2.setAttribute("normal", new THREE.BufferAttribute(vertices, 3)); 45 46 const normal = new THREE.Vector3( 47 (geometryPos[i] + geometryPos[i + 3] + geometryPos[i + 6]) / 3, 48 (geometryPos[i + 1] + geometryPos[i + 4] + geometryPos[i + 7]) / 3, 49 (geometryPos[i + 2] + geometryPos[i + 5] + geometryPos[i + 8]) / 3 50 ); 51 52 normal.normalize(); 53 const icoSphereGeometry = new THREE.IcosahedronGeometry(0.1, 1); 54 const material = new THREE.MeshBasicMaterial({ 55 wireframe: false, 56 color: 0xc100eb 57 }); 58 59 const sphere = new THREE.Mesh(icoSphereGeometry, material); 60 mesh.push(sphere); 61 62 normalDirection.push(normal); 63 64 } 65}
main.js
1window.addEventListener("load", init); 2 3function init() { 4 let width = window.innerWidth; 5 let height = window.innerHeight; 6 7//////さきほどの続き、省略///// 8 9 let loopSpeed = 0; 10 let rot = 0; 11 const clock = new THREE.Clock(); 12 13 const tick = () => { 14 rot += 0.3; 15 const cameraAngle = (rot * Math.PI) / 180; 16 let z = cameraSetBackDist * Math.cos(cameraAngle); 17 let x = cameraSetBackDist * Math.sin(cameraAngle); 18 camera.position.set(x, 0, z); 19 camera.lookAt(0, 0, 0); 20 21 const elapsedTime = clock.getElapsedTime(); 22 23 mesh.map((spheremesh, index) => { 24 const coordinateAverageValue = 25 (normalDirection[index].x + 26 normalDirection[index].y + 27 normalDirection[index].z) / 3; 28 const addAngle = coordinateAverageValue * elapsedTime ; 29 const distance = 1; 30 loopSpeed += 0.002; 31 32 const radians = (loopSpeed * Math.PI) / 180; 33 const angle = radians + addAngle; 34 const loop = (Math.sin(angle) + 1) * distance; 35 const scale = (Math.sin(angle) + 1.1) * 0.3; 36 37 spheremesh.position.set( 38 normalDirection[index].x * loop, 39 normalDirection[index].y * loop, 40 normalDirection[index].z * loop 41 ); 42 spheremesh.scale.set(scale, scale, scale); 43 44 const h = Math.abs(Math.sin(angle)) * 360; 45 const s = 100; 46 const l = 70; 47 const color = new THREE.Color(`hsl(${h},${s}%,${l}%)`); 48 spheremesh.material.color = color; 49 50 scene.add(spheremesh); 51 }); 52 requestAnimationFrame(tick); 53 }; 54 tick(); 55 56}
main.js
1window.addEventListener("load", init); 2 3function init() { 4 let width = window.innerWidth; 5 let height = window.innerHeight; 6 7 //////さきほどの続き、省略///// 8 9 window.addEventListener("resize", () => { 10 width = window.innerWidth; 11 height = window.innerHeight; 12 camera.aspect = width / height; 13 camera.updateProjectionMatrix(); 14 renderer.setSize(width, height); 15 }); 16 17 function animate() { 18 requestAnimationFrame(animate); 19 renderer.render(scene, camera); 20 } 21 22 animate(); 23}
index.html
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <link rel="stylesheet" href="style.css"> 8 <title>Document</title> 9</head> 10<body> 11 <script 12 src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js" 13 integrity="sha512-dLxUelApnYxpLt6K2iomGngnHO83iUvZytA3YjDUCjT0HDOHKXnVYdf3hU4JjM8uEhxf9nD1/ey98U3t2vZ0qQ==" 14 crossorigin="anonymous" 15 referrerpolicy="no-referrer" 16 ></script> 17 <script src="main.js"></script> 18</body> 19</html>
1* { 2 margin: 0; 3 padding: 0; 4}
main.js
1window.addEventListener("load", init); 2 3function init() { 4 let width = window.innerWidth; 5 let height = window.innerHeight; 6 7 const scene = new THREE.Scene(); 8 scene.background = new THREE.Color(0x050505); 9 10 const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); 11 cameraSetBackDist = 7; 12 camera.position.z = cameraSetBackDist; 13 14 const light = new THREE.PointLight(0xffffff, 2); 15 light.position.set(10, 10, 10); 16 scene.add(light); 17 18 const renderer = new THREE.WebGLRenderer(); 19 renderer.setSize(width, height); 20 document.body.appendChild(renderer.domElement); 21 22 const geometry = new THREE.IcosahedronGeometry(1, 10); 23 const geometryPos = geometry.getAttribute("position").array; 24 25 const mesh = []; 26 const normalDirection = []; 27 28 for (let i = 0; i < geometryPos.length; i += 9) { 29 const geometry2 = new THREE.BufferGeometry(); 30 31 const vertices = new Float32Array([ 32 geometryPos[i], 33 geometryPos[i + 1], 34 geometryPos[i + 2], 35 geometryPos[i + 3], 36 geometryPos[i + 4], 37 geometryPos[i + 5], 38 geometryPos[i + 6], 39 geometryPos[i + 7], 40 geometryPos[i + 8] 41 ]); 42 43 geometry2.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); 44 geometry2.setAttribute("normal", new THREE.BufferAttribute(vertices, 3)); 45 46 const normal = new THREE.Vector3( 47 (geometryPos[i] + geometryPos[i + 3] + geometryPos[i + 6]) / 3, 48 (geometryPos[i + 1] + geometryPos[i + 4] + geometryPos[i + 7]) / 3, 49 (geometryPos[i + 2] + geometryPos[i + 5] + geometryPos[i + 8]) / 3 50 ); 51 52 normal.normalize(); 53 const icoSphereGeometry = new THREE.IcosahedronGeometry(0.1, 1); 54 const material = new THREE.MeshBasicMaterial({ 55 wireframe: false, 56 color: 0xc100eb 57 }); 58 59 const sphere = new THREE.Mesh(icoSphereGeometry, material); 60 mesh.push(sphere); 61 62 normalDirection.push(normal); 63 64 } 65 66 let loopSpeed = 0; 67 let rot = 0; 68 const clock = new THREE.Clock(); 69 70 const tick = () => { 71 rot += 0.3; 72 const cameraAngle = (rot * Math.PI) / 180; 73 let z = cameraSetBackDist * Math.cos(cameraAngle); 74 let x = cameraSetBackDist * Math.sin(cameraAngle); 75 camera.position.set(x, 0, z); 76 camera.lookAt(0, 0, 0); 77 78 const elapsedTime = clock.getElapsedTime(); 79 80 mesh.map((spheremesh, index) => { 81 const coordinateAverageValue = 82 (normalDirection[index].x + 83 normalDirection[index].y + 84 normalDirection[index].z) / 3; 85 const addAngle = coordinateAverageValue * elapsedTime * 1; 86 const distance = 1; 87 loopSpeed += 0.002; 88 const radians = (loopSpeed * Math.PI) / 180; 89 const angle = radians + addAngle; 90 const loop = (Math.sin(angle) + 1) * distance; 91 const scale = (Math.sin(angle) + 1.1) * 0.3; 92 93 spheremesh.position.set( 94 normalDirection[index].x * loop, 95 normalDirection[index].y * loop, 96 normalDirection[index].z * loop 97 ); 98 spheremesh.scale.set(scale, scale, scale); 99 100 const h = Math.abs(Math.sin(angle)) * 360; 101 102 const s = 100; 103 const l = 70; 104 const color = new THREE.Color(`hsl(${h},${s}%,${l}%)`); 105 spheremesh.material.color = color; 106 107 scene.add(spheremesh); 108 109 }); 110 requestAnimationFrame(tick); 111 }; 112 tick(); 113 114 window.addEventListener("resize", () => { 115 width = window.innerWidth; 116 height = window.innerHeight; 117 camera.aspect = width / height; 118 camera.updateProjectionMatrix(); 119 renderer.setSize(width, height); 120}); 121 122 123 function animate() { 124 requestAnimationFrame(animate); 125 renderer.render(scene, camera); 126 } 127 128 animate(); 129}
以上になります。ソースコードは CodePen でも公開してますので、是非参考にしてみてください。