๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Develop ๐Ÿ’ป/JavaScript

[JavaScript] Three.js๋กœ ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ์šฐ์ฃผ !

by jungin 2021. 7. 25.

Intro


๋žœ๋”ฉ ํŽ˜์ด์ง€๋ฅผ 3D๋ฅผ ์ด์šฉํ•ด ์‹œ๊ฐ์ ์œผ๋กœ ๋”์šฑ ์˜ˆ์˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์‹ถ์–ด three.js๋ฅผ ๋ฐฐ์šฐ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ตœ๊ทผ ๊นƒํ—™ ๋žœ๋”ฉ ํŽ˜์ด์ง€์—์„œ ํ•œ๊ตญ์–ด ์ง€์›์„ ์‹œ์ž‘ํ•˜์˜€๋Š”๋ฐ , ์ €๊ธฐ ๋‚˜์˜ค๋Š” 3D ์ง€๊ตฌ๋ณธ๋„ ๋ฐ”๋กœ ์ด three.js๋ฅผ ์ด์šฉํ•˜์—ฌ ๋งŒ๋“  ๊ฒƒ์ž…๋‹ˆ๋‹ค..!!

GitHub: Where the world builds software

Three.js


What is Three.js


Three.js๋Š” WebGL์„ ์ด์šฉํ•˜์—ฌ ์›นํŽ˜์ด์ง€์— 3D ๊ฐ์ฒด๋ฅผ ์‰ฝ๊ฒŒ ๋ Œ๋”๋ง ํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ 3D ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ WebGL์€ html์˜ ์บ”๋ฒ„์Šค ์š”์†Œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ธํ„ฐ๋ ‰ํ‹ฐ๋ธŒ ํ•œ 3D ๊ทธ๋ž˜ํ”ฝ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ CPU๊ฐ€ ์•„๋‹Œ GPU๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ™”๋ฉด์„ ๋ Œ๋”๋ง ํ•˜์—ฌ ๊ต‰์žฅํžˆ ๋น ๋ฆ…๋‹ˆ๋‹ค! ๊ทธ๋ž˜์„œ ์ด WebGL์„ ๊ฐ€์ง€๊ณ  ์›น ๊ฒŒ์ž„ , ์ธํ„ฐ๋ ‰ํ‹ฐ๋ธŒ ํŽ˜์ด์ง€, VR ์ฝ˜ํ…์ธ  ๋“ฑ ์—ฌ๋Ÿฌ 3D ์ž‘์—…๋ฌผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"Hello Cube"


ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ฅผ ๋ฐฐ์šธ ๋•Œ ๊ฐ€์žฅ ๋จผ์ € "Hello World!"๋ฅผ ์ถœ๋ ฅํ–ˆ๋‹ค๋ฉด, Three.js์—์„œ๋Š” 3์ฐจ์› ์„ธ๊ณ„์—์„œ ์ •์œก๋ฉด์ฒด ํ๋ธŒ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์‹œ์ž‘์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋จผ์ € ์ด๋ ‡๊ฒŒ three.js๋ฅผ ์‚ฌ์šฉํ•  html ํŒŒ์ผ ํ•˜๋‚˜์™€ three.js ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐˆ script.js ํŒŒ์ผ ํ•˜๋‚˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ  <body> ํƒœ๊ทธ ์•ˆ์— ๋ถˆ๋Ÿฌ์™€์ค๋‹ˆ๋‹ค.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>my first three.js app</title>
		<style>
			body { margin: 0; }
			canvas { display: block; }
		</style>
	</head>
	<body>
		<script type ="module" src="./js/script.js"></script>
	</body>
</html>

์ด๊ฒƒ์€ Three.js ์•ฑ์˜ ๊ตฌ์กฐ๋ฅผ ๋„์‹ํ™”ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ ์ฒ˜์Œ ๋ณด๋ฉด ์ดํ•ด๊ฐ€ ์•ˆ ๋  ์ˆ˜๋„ ์žˆ์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์งœ ๋ณด๊ณ  ๋‹ค์‹œ ์ด ๊ตฌ์กฐ๋„๋ฅผ ๋ณด๋ฉด ๋ฌด์Šจ ์†Œ๋ฆฌ์ธ์ง€ ๋ฐ”๋กœ ์ดํ•ด๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

Three.js๋Š” npm์„ ํ†ตํ•˜์—ฌ ์„ค์น˜ํ•˜๊ฑฐ๋‚˜, CDN์ด๋‚˜ static ํ˜ธ์ŠคํŒ…์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. Three.js ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” npm์„ ํ†ตํ•œ ์„ค์น˜๋ฅผ ๊ถŒ์žฅํ•˜์ง€๋งŒ ์ €๋Š” UNPKG CDN์„ ์ด์šฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

npm install --save three

three.js๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ํ‘œํ˜„ํ•˜๋ ค๋ฉด scene, camera ๊ทธ๋ฆฌ๊ณ  renderer๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์นด๋ฉ”๋ผ๋กœ ์žฅ๋ฉด์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์นด๋ฉ”๋ผ์˜ ์ฒซ ๋ฒˆ์งธ ์†์„ฑ์€ **field of view(์‹œ์•ผ๊ฐ)**์ž…๋‹ˆ๋‹ค. FOV(์‹œ์•ผ๊ฐ)๋Š” ํ•ด๋‹น ์‹œ์ ์˜ ํ™”๋ฉด์ด ๋ณด์ด๋Š” ์ •๋„๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ๊ฐ’์€ ๊ฐ๋„ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ์†์„ฑ์€ **aspect ratio(์ข…ํšก๋น„)**์ž…๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์š”์†Œ์˜ ๋†’์ด์™€ ๋„ˆ๋น„์— ๋งž์ถ”์–ด ํ‘œ์‹œํ•˜๊ฒŒ ํ•  ํ…๋ฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์™€์ด๋“œ์Šคํฌ๋ฆฐ์— ์˜›๋‚  ์˜ํ™”๋ฅผ ํŠธ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ด๋ฏธ์ง€๊ฐ€ ํ‹€์–ด์ ธ ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ๋‘ ์†์„ฑ์€ near์™€ far ์ ˆ๋‹จ๋ฉด์ž…๋‹ˆ๋‹ค. ๋ฌด์Šจ ๋œป์ธ๊ฐ€ ํ•˜๋ฉด, far ๊ฐ’ ๋ณด๋‹ค ๋ฉ€๋ฆฌ ์žˆ๋Š” ์š”์†Œ๋‚˜ near ๊ฐ’๋ณด๋‹ค ๊ฐ€๊นŒ์ด ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ๋Š” ๋ Œ๋”๋ง ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ ์‹œ์ ์—์„œ ์ด๊ฒƒ๊นŒ์ง€ ๊ณ ๋ คํ•  ํ•„์š”๋Š” ์—†์ง€๋งŒ, ์•ฑ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.(์œ„ ๋‚ด์šฉ์€ three.js ๊ณต์‹๋ฌธ์„œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค)

import * as THREE from "https://unpkg.com/three@0.126.1/build/three.module.js";

const scene = new THREE.Scene();
//์žฅ๋ฉด ์ƒ์„ฑ
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
//์นด๋ฉ”๋ผ ์„ธ๋ถ€ ์„ค์ •
const renderer = new THREE.WebGLRenderer({ antialias: true });
//renderer ์ƒ์„ฑ
renderer.setSize(window.innerWidth, window.innerHeight);
//ํ™”๋ฉด ์‚ฌ์ด์ฆˆ ์„ค์ •
document.body.appendChild(renderer.domElement);
//html ํ™”๋ฉด์— ๊ตฌํ˜„

์ด์ œ ํ๋ธŒ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค.

const geometry = new THREE.BoxGeometry();
//BoxGeometry ์ƒ์„ฑ , ์ด ์•ˆ์— vertices(๊ผญ์ง“์ )๊ณผ faces(๋ฉด)์ด ํฌํ•จ
const material = new THREE.MeshBasicMaterial( { color: 0x00ff80 } );
// MeshBasicMatreial ์ƒ์„ฑ , color: ํ˜•๊ด‘์ƒ‰ 
const cube = new THREE.Mesh( geometry, material );
//Mesh๋ฅผ ์ด์šฉํ•˜์—ฌ geometry์— material์„ ์ ์šฉํ•˜๊ณ  ์ž์œ ๋กญ๊ฒŒ ์›€์ง์ผ ์ˆ˜ ์žˆ๋„๋ก ํ•จ
scene.add( cube );
// ์žฅ๋ฉด์— cube๋ฅผ ์ถ”๊ฐ€ํ•จ

camera.position.z = 5;
//๊ธฐ๋ณธ ์นด๋ฉ”๋ผ ์…‹ํŒ…์€ (0,0,0)์ด๋ฏ€๋กœ ์นด๋ฉ”๋ผ์˜ z ๊ฐ’์„ 5๋กœ ํ•จ

์ด์ œ ํŽ˜์ด์ง€๋ฅผ ์—ด์–ด๋ณด๋ฉด ๋†€๋ž๊ฒŒ๋„ ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์•„์ง ์•„๋ฌด๊ฒƒ๋„ ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ์ฝ”๋“œ๋กœ ์ธํ•ด 1์ดˆ์— 60๋ฒˆ์”ฉ ํ™”๋ฉด์ด ๋ Œ๋”๋ง ๋  ๊ฒƒ์ด๊ณ  , ํ๋ธŒ๋Š” 1์ดˆ์— 60๋ฒˆ์”ฉ ์• ๋‹ˆ๋ฉ”์ดํŒ… ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค! ์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ ๊ณต์‹๋ฌธ์„œ์— ๋‚˜์™€์žˆ๋Š” ํ—ฌ๋กœ ํ๋ธŒ์ด์ง€๋งŒ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ ๋ช‡ ๊ฐœ๋งŒ ๋” ์จ์„œ ์นด๋ฉ”๋ผ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ์š”๊ตฌ์— ๋งž๊ฒŒ ์›€์ง์ด๋„๋ก ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

function animate() {
	requestAnimationFrame( animate );
	cube.rotation.x += 0.01;
	cube.rotation.y += 0.01;
	renderer.render( scene, camera );
}
animate();

๋จผ์ € js ๋งจ ์œ—์ค„์— CDN์„ ์ด์šฉํ•œ OrbitControls ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์™€์ค๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ์— ์•„๋ž˜ 3์ค„์„ ์จ์ค๋‹ˆ๋‹ค.

function animate() {
	requestAnimationFrame( animate );
	cube.rotation.x += 0.01;
	cube.rotation.y += 0.01;
	renderer.render( scene, camera );
}
animate();

๊ทธ ํ›„ animate ํ•จ์ˆ˜์— controls.update();๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

const animate = function () {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  controls.update(); // ์ถ”๊ฐ€ !
  renderer.render(scene, camera);
};

์ด์ œ ์‹คํ–‰์„ ํ•ด๋ณด๋ฉด ๋Œ์•„๊ฐ€๋Š” ํ˜•๊ด‘์ƒ‰ ์ •์œก๋ฉด์ฒด ํ๋ธŒ์™€ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ๋Š” ์นด๋ฉ”๋ผ๊ฐ€ ๋นฐ!!

How to make my landing page


์ค‘์•™์— ๋„๋„› ๋ชจ์–‘์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” ๋„ํ˜•๊ณผ ๋žœ๋ค์œผ๋กœ ํผ์ ธ์žˆ๋Š” ์›๋“ค์ด ๋งˆ์น˜ ์šฐ์ฃผ ๊ฐ™์€ ๋Š๋‚Œ์„ ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. script.js ํŒŒ์ผ์— ์•„๊นŒ ํ๋ธŒ๋ฅผ ๋งŒ๋“ค ๋•Œ์™€ ๊ฐ™์ด scene, camera, renderer๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

// Scene
const scene = new THREE.Scene();
//size
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight,
		// ํ™”๋ฉด ๋งž์ถค์„ ์œ„ํ•ด sizes๋ผ๋Š” ์ƒ์ˆ˜ ์ •์˜
  };
//camera
const camera = new THREE.PerspectiveCamera(
    75,
    sizes.width / sizes.height,
    0.1,
    100
  );
  camera.position.z = 2;
  scene.add(camera);
//renderer
const renderer = new THREE.WebGLRenderer({
    antialias: true,
  });
  renderer.setSize(sizes.width, sizes.height);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
//canvas๊ฐ€ ํ๋ฆฟํ•˜๊ฒŒ ์ถœ๋ ฅํ•ด์ฃผ๋Š” ๊ฒƒ์„ ๋ง‰์Œ
  renderer.setClearColor(new THREE.Color("#21282a"), 1);
// ๋ฐฐ๊ฒฝ์ƒ‰์„ ์–ด๋‘์€ gray๋กœ ์„ค์ •
  document.body.appendChild(renderer.domElement);

๊ทธ๋ฆฌ๊ณ  ๋žœ๋”ฉํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฉด์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•˜๋Š” ๊ฒƒ์— ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ˜์‘ํ˜•์œผ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด resize event๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

window.addEventListener("resize", () => {
    // Update sizes
    sizes.width = window.innerWidth;
    sizes.height = window.innerHeight;
  
    // Update camera
    camera.aspect = sizes.width / sizes.height;
    camera.updateProjectionMatrix();
  
    // Update renderer
    renderer.setSize(sizes.width, sizes.height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  });

์ด์ œ ๋„๋„›ํ˜• ๋„ํ˜•*(๋งž๋Š” ํ‘œํ˜„์ธ์ง€ ๋ชจ๋ฅด๊ฒ ์Œ)*์„ ๋งŒ๋“ค ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค. TorusGeometry๋ฅผ ์ด์šฉํ•˜๋ฉด ๋„๋„›ํ˜• ๋„ํ˜•์ด ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค. TorusGeometry์˜ ์ฒซ ๋ฒˆ์งธ ์†์„ฑ์€ radius์ž…๋‹ˆ๋‹ค. ๋น„์–ด์žˆ๋Š” ์›์˜ ๋ฐ˜์ง€๋ฆ„์„ ๋œปํ•ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ์†์„ฑ์€ tube์ž…๋‹ˆ๋‹ค. ์ด๋Š” tube(๋น„์–ด์žˆ์ง€ ์•Š์€ ๋ถ€๋ถ„)์˜ ๋ฐ˜์ง€๋ฆ„์ž…๋‹ˆ๋‹ค. ์„ธ ๋ฒˆ์งธ ์†์„ฑ์€ radialSegments์ด๊ณ  ๋„ค ๋ฒˆ์งธ ์†์„ฑ์€ tubularSegments์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ง์ ‘ ์ˆ˜์น˜๋ฅผ ์กฐ์ ˆํ•ด๊ฐ€๋ฉด์„œ ๋Š๋‚Œ์„ ์žก์•„๊ฐ€๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. ์•„๊นŒ๋Š” Mesh๋ฅผ ์ด์šฉํ•˜์—ฌ ํ‘œํ˜„ํ•˜์˜€์ง€๋งŒ ์ด๋ฒˆ์—๋Š” ์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๊ธฐ์— Points๋ผ๋Š” ๊ฒƒ์„ ์ด์šฉํ•ด์ค๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๋ฉด ์ค‘์•™์— ์žˆ๋Š” ๋„๋„›ํ˜• ๋„ํ˜•์€ ๋‹ค ๋งŒ๋“  ๊ฒƒ์ž…๋‹ˆ๋‹ค! (animate์—์„œ ๋Œ์•„๊ฐ€๊ฒŒ๋งŒ ํ•˜๋ฉด)

const geometry = new THREE.TorusGeometry(0.7, 0.2, 16, 100);
const material = new THREE.PointsMaterial({
  size: 0.005,
  color: 0x87a7ca,
});
const sphere = new THREE.Points(geometry, material);

์ด์ œ ์šฐ์ฃผ ๊ฐ™์€ ๋Š๋‚Œ์„ ์ฃผ๊ธฐ ์œ„ํ•ด paticles์„ ๋งŒ๋“ค์–ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. paticlesGemotry๋Š” BufferGeometry๋ฅผ ์ด์šฉํ•ด์„œ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. BufferGeometry๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋„ํ˜•์ด๊ณ  , ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ vertices์˜ position, normals, colors, uv ๋“ค์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ three.js์—์„œ ๋ณ„ ๋ชจ์–‘ material์€ ์—†๊ธฐ์— ํฌํ† ์ƒต์„ ์ด์šฉํ•˜์—ฌ ์ง์ ‘ pngํŒŒ์ผ๋กœ ๋ณ„ ๋ชจ์–‘์„ ๋งŒ๋“ค์–ด ์ค€ ๋‹ค์Œ์— material์— maping ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

const particlesGeometry = new THREE.BufferGeometry();
const loader = new THREE.TextureLoader();
const star = loader.load("./star.png");
const particlesmaterial = new THREE.PointsMaterial({
  size: 0.005,
  map: star,
  transparent: true,
});

์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋ณ„๋“ค์„ ๋žœ๋ค์œผ๋กœ ๋ฟŒ๋ฆด ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค. ๋จผ์ € ๋ณ„ 700๊ฐœ์˜ xyz ๊ฐ’์„ ๋žœ๋ค ํ•˜๊ฒŒ ๋Œ๋ฆฌ๊ธฐ ์œ„ํ•ด posArray์— 2100๊ฐœ์งœ๋ฆฌ ๋ฐฐ์—ด์„ 32๋น„ํŠธ ๋ถ€๋™ ์†Œ์ˆ˜์  ๋ฐฐ์—ด์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ทธ๋‹ค์Œ ๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ๋žœ๋ค ํ•˜๊ฒŒ ์œ„์น˜๋ฅผ ๋Œ๋ ค์ค๋‹ˆ๋‹ค. posArray[i] = Math.random() ์ด๋Ÿฐ ์‹์œผ๋กœ ๋Œ๋ฆฌ๊ฒŒ ๋œ๋‹ค๋ฉด ๋ณ„๋“ค์ด ํ•œ ๊ณณ์— ๋ญ‰์น˜๊ณ  ๋‹ค๋ฅธ ์ด์ƒํ•œ ๊ณณ์— ์œ„์น˜ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. posArray[i] = Math.random() - 0.5 ์ด๋ ‡๊ฒŒ ํ•ด์ค€๋‹ค๋ฉด ๊ฐ€์šด๋ฐ๋กœ ๋ญ‰์น˜๊ธฐ๋Š” ํ•˜์ง€๋งŒ ๋ณ„๋“ค์ด ๋ถ„ํฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ์„ค๋ช…์„ ํ•˜์ž๋ฉด Math.random ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ ๋ฒ”์œ„๊ฐ€ 0 ์ด์ƒ 1 ๋ฏธ๋งŒ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ -0.5๋ฅผ ํ•จ์œผ๋กœ์จ ๋ฒ”์œ„๋Š” -0.5 ์ด์ƒ 0.5 ๋ฏธ๋งŒ์ด ๋ฉ๋‹ˆ๋‹ค. three.js์—์„œ ๊ธฐ๋ณธ position์€ (0,0,0)์ด ์ค‘์•™์ด๋ฏ€๋กœ ๋ณ„๋“ค์€ ์ค‘์•™์—์„œ ๋ญ‰์ณ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. posArray[i] = (Math.random() - 0.5) * 5 ์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๋ฉด ๋ญ‰์ณ์žˆ๋˜ ๊ฒƒ์ด 5๋ฐฐ ์ปค ๋ณด์—ฌ ๊ฑฐ์˜ ์ž์—ฐ์Šค๋Ÿฝ์ง€๋งŒ ์ด๊ฒƒ์€ ๊ทธ๋ƒฅ ๋‹จ์ˆœํžˆ ์•„๊นŒ ์ „ ์ฝ”๋“œ๋ฅผ ํฌ๊ฒŒ ํ•ด ์ค€ ๊ฒƒ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. posArray[i] = (Math.random() - 0.5) * (Math.random() * 5) ๋งˆ์นจ๋‚ด ์ด ์ฝ”๋“œ๋ฅผ ์จ์ฃผ๋ฉด ๋ณ„๋“ค์€ ์ค‘์•™์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํผ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค!

const particlesGeometry = new THREE.BufferGeometry();
const loader = new THREE.TextureLoader();
const star = loader.load("./star.png");
const particlesmaterial = new THREE.PointsMaterial({
  size: 0.005,
  map: star,
  transparent: true,
});

์‚ฌ์‹ค ์ด์ œ ๋ Œ๋”๋ง๋งŒ ํ•˜๋ฉด ์™„์„ฑ์ด์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด web์˜ ์ธํ„ฐ๋ ‰ํ‹ฐ๋ธŒ ํ•œ ๋ง›์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ €๋Š” ๋งˆ์šฐ์Šค๊ฐ€ ์›€์ง์ผ ๋•Œ๋งˆ๋‹ค ๋ณ„๋“ค์ด ์›€์ง์ด๋Š” event๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์„ค์ •์„ ํ•ด์ฃผ๊ณ  animate ํ•จ์ˆ˜์—์„œ ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค!

document.addEventListener("mousemove", animateParticles);

let mouseX = 0;
let mouseY = 0;

function animateParticles(event) {
  mouseY = event.clientY;
  mouseX = event.clientX;
}

์ด์ œ animate ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์—ฌ๊ธฐ์„œ ๋„ํ˜•๋“ค์„ ์• ๋‹ˆ๋ฉ”์ดํŒ… ํ•ด์ฃผ๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ๋!! ์ด ์™ธ ๋‹ค๋ฅธ ๋ถ€๋ถ„์€ three.js ๊ฐ€ ์•„๋‹Œ CSS ๋งŒ์„ ์ด์šฉํ•˜์—ฌ ๋งŒ๋“  ๊ฒƒ์ด๊ธฐ์— ์ด ํฌ์ŠคํŠธ ๋‚ด์šฉ์—์„œ๋Š” ์ œ์™ธํ–ˆ์Šต๋‹ˆ๋‹ค.

const clock = new THREE.Clock();

const animate = () => {
  window.requestAnimationFrame(animate);

  const elapsedTime = clock.getElapsedTime();
  // ๊ฒฝ๊ณผ์‹œ๊ฐ„

  sphere.rotation.y = 0.5 * elapsedTime;
	//๋„๋„›ํ˜• ๋„ํ˜•์˜ y์ถ•์„ ํšŒ์ „
  particlesMesh.rotation.y = -1 * (elapsedTime * 0.1);
	//๋ณ„๋“ค์ด ๊ฒฝ๊ณผ์‹œ๊ฐ„๋งˆ๋‹ค ์Œ์˜ ๋ฐฉํ–ฅ์œผ๋กœ ์ด๋™

  if (mouseX > 0) {
		//์‚ฌ์šฉ์ž๊ฐ€ ๋งˆ์šฐ์Šค๋ฅผ ์›€์ง์ด๋ฉด ๊ทธ์— ๋งž์ถฐ ์ธํ„ฐ๋ ‰ํ‹ฐ๋ธŒํ•˜๊ฒŒ ์›€์ง์ž„
    particlesMesh.rotation.x = -mouseY * (elapsedTime * 0.00008);
    particlesMesh.rotation.y = -mouseX * (elapsedTime * 0.00008);
  }

  renderer.render(scene, camera);
};

animate();

Reference


Three.js - JavaScript 3D library

Working with Three.js Particle Systems - They're AWESOME!

๋ณธ ํฌ์ŠคํŠธ๋Š” ์œ„ ์œ ํŠœ๋ธŒ ๋‚ด์šฉ์„ ์ œ๊ฐ€ ํ•ด์„ํ•œ ๋Œ€๋กœ ์“ด ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋Œ“๊ธ€