three.js (1)

cleanยท2022๋…„ 11์›” 22์ผ
0

๐Ÿ‘ผ๋™๋ฃŒ๐Ÿ‘ผ๋ถ„์˜ ๋„์›€์œผ๋กœ inflearn three.js๋กœ ์‹œ์ž‘ํ•˜๋Š” 3D ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์›น ์„ ์ˆ˜๊ฐ•ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋˜์—ˆ๋‹ค.

Threejs ๋ž€?

๊ธฐ๋ณธ ๊ฐœ๋…

  • threejs ๊ณต์‹ ๋ฌธ์„œ https://threejs.org/
  • webGL ์ด๋ž€ js๋ฅผ ์ด์šฉํ•˜์—ฌ ์›น ์ƒ์—์„œ ๊ทธ๋ž˜ํ”ฝ ํ‘œํ˜„ ์‹œ ์‚ฌ์šฉ๋œ๋‹ค. 2D, 3D๋ฅผ GPU๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ทธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ด ์ข‹์ง€๋งŒ webGL ์ž์ฒด๊ฐ€ LOW LEVEL์˜ API์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹ค๋ฌด์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต๊ณ  ๋ณต์žกํ•จ
  • ์ด webGL์„ ์‰ฝ๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒํ•˜๋Š” Library๊ฐ€ threejs

์˜ˆ์ œ๋ฅผ ์œ„ํ•œ ํ™˜๊ฒฝ ๊ตฌ์„ฑ

  • ๊ฐ•์˜์—์„  ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ๊ทธ๋ƒฅ ๋‚ด๊ฐ€ ํŽธํ•ด์„œ eslint, prettier, typescript ํ™˜๊ฒฝ์œผ๋กœ ๊ตฌ์„ฑ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

๊ธฐ๋ณธ ๊ตฌ์„ฑ

์บ”๋ฒ„์Šค ๋ถ€์ฐฉํ•˜๊ธฐ

three.js ์—์„œ ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ทธ๋ฆฌ๋ ค๋ฉด scene, camera, renderer ๊ฐ€ ํ•„์š”ํ•จ

  • scene: ๋ฌด๋Œ€/์žฅ๋ฉด
  • camera: ๋ฌด๋Œ€๋ฅผ ์ฐ๋Š” ์นด๋ฉ”๋ผ
    • FOV(field of view): ์‹œ์•ผ๊ฐ, ํ™”๋ฉด์ด ๋ณด์—ฌ์ง€๋Š” ์ •๋„
    • aspect ratio: ์ข…ํšก๋น„
    • near: near ๋ณด๋‹ค ๊ฐ€๊นŒ์ด ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ๋Š” ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š์Œ
    • far: far ๋ณด๋‹ค ๋ฉ€๋ฆฌ ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ๋Š” ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ
  • renderer: ์ŠคํŠธ๋ฆฌ๋ฐ์˜ ์—ญํ• 
  • mesh: ์นด๋ฉ”๋ผ์— ์ฐํž ๋Œ€์ƒ, ๋ฌผ์ฒด
<body>
	<canvas id="three-canvas"></canvas>
</body>
import * as THREE from 'three';
const canvas = document.querySelector('#three-canvas');
if (canvas) {
  const renderer = new THREE.WebGLRenderer({
    canvas,
    antialias: true, // true: ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํ‘œํ˜„ (๊ณ„๋‹จ์‹์œผ๋กœ ํ…Œ๋‘๋ฆฌ๊ฐ€ ๋˜์–ด์žˆ๊ฑฐ๋‚˜ ํ•˜๋Š” ๋ถ€๋ถ„)
  });
}
// renderer ์‚ฌ์ด์ฆˆ ์„ค์ •
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
// PerspectiveCamera, ์›๊ทผ ์นด๋ฉ”๋ผ (๊ธฐ๋ณธ๊ฐ’ x=0, y=0, z=0)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1);
camera.position.y = 2;
camera.position.z = 8; // ๊ทธ๋ฆด ๋Œ€์ƒ์˜ ๊ฐœ๋…์ ์ธ ๊ฑฐ๋ฆฌ์˜ ๊ฐ’
scene.add(camera);

๋ฌผ์ฒด ๊ทธ๋ ค๋ณด๊ธฐ

// BoxGeometry(width, height, depth) - ํ˜•ํƒœ
const geometry = new THREE.BoxGeometry(1, 1, 1); 
// ์ƒ‰์น ํ•ด์ฃผ๊ธฐ (hex ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) - ์žฌ์งˆ
const material = new THREE.MeshBasicMaterial({color: 'red',});
// ํ˜•ํƒœ์™€ ์žฌ์งˆ์„ ๊ฐ€์ง„ ๋ฌผ์ฒด ์ƒ์„ฑ
const mesh = new THREE.Mesh(geometry, material);
// ๋ฌด๋Œ€์— ๋ฌผ์ฒด ๋ถ€์ฐฉ
scene.add(mesh);
renderer.render(scene, camera);
  • ์ถ”๊ฐ€๋œ Mesh๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ 0, 0, 0์— ์œ„์น˜ํ•จ
  • ์ด ๋•Œ ์นด๋ฉ”๋ผ๋ฅผ 0, 0, 0 (x, y, z)์œผ๋กœ ํ•˜๋ฉด ์นด๋ฉ”๋ผ์™€ ๋ฌผ์ฒด๊ฐ€ ๋™์ผํ•œ ์ขŒํ‘œ๊ฐ’์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๊ฒƒ๋„ ๋ณด์ด์ง€ ์•Š์Œ. ๋”ฐ๋ผ์„œ ์นด๋ฉ”๋ผ ์œ„์น˜๋ฅผ ์‚ด์ง ์›€์ง์—ฌ์ค˜์•ผํ•จ

๊ธฐ๋ณธ ์š”์†Œ ์•Œ์•„๋ณด๊ธฐ

๋ธŒ๋ผ์šฐ์ € ์‚ฌ์ด์ฆˆ์— ๋”ฐ๋ฅธ ํฌ๊ธฐ ๋ณ€๊ฒฝ

// window resize event ์— ์•„๋ž˜์ฝ”๋“œ ์ˆ˜ํ–‰๋˜๋„๋ก ์ฒ˜๋ฆฌ
// ๋ฐฐ๊ฒฝ ํฌ๊ธฐ์— ๋”ฐ๋ผ ๋ฌผ์ฒด ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก ํ–‰๋ ฌ ๊ตฌ์กฐ๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๊ฒŒ ํ•จ
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

// ์ฐฝ ํฌ๊ธฐ์— ๋”ฐ๋ผ ๋ฐฐ๊ฒฝ ์‚ฌ์ด์ฆˆ ๋ณ€๊ฒฝ
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);

๋ฐฐ๊ฒฝ์ƒ‰ ๋ณ€๊ฒฝํ•˜๊ธฐ

๋ฐฉ๋ฒ• 1. renderer ์ƒ‰์ƒ์„ ๋ณ€๊ฒฝ

const renderer = new THREE.WebGLRenderer({
      canvas,
      antialias: true,
      alpha: true, // ๋ฐฐ๊ฒฝ์„ ํˆฌ๋ช…ํ•˜๊ฒŒ
});	
renderer.setSize(window.innerWidth, window.innerHeight);
 // ํ‘œ์‹œํ•  ์‚ฌ์ด์ฆˆ์˜ ๋‘ ๋ฐฐ ๋งŒํผ์œผ๋กœ ํฌ๊ธฐ (width/height) ๋ฅผ ์„ค์ •ํ•˜์—ฌ ๊ณ ํ•ด์ƒ๋„๋กœ ํ‘œํ˜„
renderer.setPixelRatio(2);
// ๋ฐฐ๊ฒฝ์ƒ‰ ์„ค์ •
renderer.setClearColor('#00ff00');
// ๋ฐฐ๊ฒฝ ํˆฌ๋ช…๋„ ์กฐ์ ˆ
renderer.setClearAlpha(0.1);

๋ฐฉ๋ฒ• 2. scene ์ƒ‰์ƒ์„ ๋ณ€๊ฒฝ

  • THREE.Color(์ƒ‰์ƒ)
const scene = new THREE.Scene();
scene.background = new THREE.Color('blue')
  • renderer ๋ณด๋‹ค Scene ์ด ์œ„์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Scene ์— ๋ฐฐ๊ฒฝ์„ ์น ํ•˜๋ฉด Renderer ์ƒ‰์ƒ์€ ๋ฌด์‹œ๋œ๋‹ค.

์นด๋ฉ”๋ผ ์ข…๋ฅ˜

  • PerspectiveCamera
    • 3D ์žฅ๋ฉด์„ ๋ Œ๋”๋งํ•˜๋Š”๋ฐ ๊ฐ€์žฅ ๋„๋ฆฌ ์“ฐ์ž„
    • ์‚ฌ๋žŒ์˜ ๋ˆˆ์œผ๋กœ ๋ณด๋Š” ๋ฐฉ์‹์„ ๋ชจ๋ฐฉํ•˜์—ฌ ์„ค๊ณ„๋จ
  • OrthographicCamera
    • ๋ Œ๋”๋ง๋œ ์ด๋ฏธ์ง€์—์„œ ๊ฐ์ฒด์˜ ํฌ๊ธฐ๋Š” ์นด๋ฉ”๋ผ์˜ ๊ฑฐ๋ฆฌ์— ๊ด€๊ณ„์—†์ด ์ผ์ •ํ•˜๊ฒŒ ์œ ์ง€๋จ.
    • ๋”ฐ๋ผ์„œ zoom ์œผ๋กœ ํฌ๊ธฐ ์กฐ์ ˆํ•จ

๋น›์„ ์ถ”๊ฐ€ํ•˜๊ธฐ

  • THREE.DirectionalLight(๋น›์˜ ์ƒ‰์ƒ, ๋น›์˜ ๊ฐ•๋„)
const light = new THREE.DirectionalLight(0xffffff, 0.5);
// ๋น›์˜ ๋ฐฉํ–ฅ
light.position.x = 1;
light.position.z = 2;
scene.add(light);

์• ๋‹ˆ๋ฉ”์ด์…˜

1. requestAnimationFrame ๋งŒ ์‚ฌ์šฉ

function draw(): void {
   // ๊ฐ€๋กœ๋กœ ํšŒ์ „
  mesh.rotation.y += 3;
  renderer.render(scene, camera);
  requestAnimationFrame(draw);
  draw();
}
  • ๋Œ€๋ถ€๋ถ„ ์ดˆ ๋‹น 60 ํ”„๋ ˆ์ž„์œผ๋กœ ์‹คํ–‰๋จ
  • ๊ทธ๋Ÿฌ๋‚˜ ์‹คํ–‰ํ•˜๋Š” ์žฅ๋น„์— ๋”ฐ๋ผ ๋” ๋Š๋ฆฌ๊ฒŒ ํ˜น์€ ๋” ๋น ๋ฅด๊ฒŒ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ ์ผ์ • ์‹œ๊ฐ„์˜ ๊ฐ„๊ฒฉ์— ๋”ฐ๋ผ ์‹คํ–‰๋˜๋„๋ก ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค.

2. requestAnimationFrame + Three.js ์˜ Clock ์‚ฌ์šฉ

Clock (autoStart: Boolean)

  • autoStart- ์ž๋™์œผ๋กœ ์‹œ๊ณ„๋ฅผ ์‹œ์ž‘์‹œํ‚ฌ์ง€ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’ true)
  • property
    • startTime:Float - ์‹œ๊ณ„์˜ start ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์‹œ๊ฐ„์„ ๋ฉˆ์ถค
    • oldTime:Float - ์‹œ๊ณ„์˜ start/getElapsedTime/getDelta ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์‹œ๊ฐ„์„ ๋ฉˆ์ถค
    • startTime:Float - ์‹œ๊ณ„์˜ start ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์‹œ๊ฐ„์„ ๋ฉˆ์ถค
    • elapsedTime - ์‹œ๊ณ„๊ฐ€ ์ž‘๋™ํ•œ ์ด ์‹œ๊ฐ„
  • method
    • start(): void - ์‹œ๊ณ„๋ฅผ ์‹œ์ž‘, startTime & oldTime์„ ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ, elapsedTime ์„ 0ใ…‡์œผ๋กœ ์„ค์ •
    • stop(): void - ์‹œ๊ณ„๋ฅผ ๋ฉˆ์ถ”๊ณ  oldTime์„ ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ ์„ค์ •
    • getElapsedTime(): Float - ์‹œ๊ณ„๊ฐ€ ์‹œ์ž‘ํ•œ ์ดํ›„๋กœ๋ถ€ํ„ฐ์˜ ์ดˆ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉฐ oldTime์„ ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ ์„ค์ •
    • getDelta(): Float - oldTime์ด ์„ค์ •๋œ ์ดํ›„๋กœ๋ถ€ํ„ฐ ์ง€๋‚œ ์ดˆ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉฐ oldTime์„ ํ˜„์žฌ ์‹œ๊ฐ„์œผ๋กœ ์„ค์ •
  • getElapsedTime ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

    const clock = new THREE.Clock();
    function draw(): void {
      const time = clock.getElapsedTime();
      mesh.rotation.y = 2 * time; // ์†๋„๋ฅผ ๋” ๋น ๋ฅด๊ฒŒ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด 2๋ณด๋‹ค ํฐ ๊ฐ’์„ ์‚ฌ์šฉ
      renderer.render(scene, camera);
      requestAnimationFrame(draw);
    }
    draw();
  • getDelta ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

     const clock = new THREE.Clock();
     function draw(): void {
       const delta = clock.getDelta();
       mesh.rotation.y += 3 * delta;
       renderer.render(scene, camera);
       requestAnimationFrame(draw);
     }
    draw();
  • ๋งŒ์•ฝ draw ๋‚ด์—์„œ ๊ฐ™์€ clock ์˜ getDelta ์™€ getElapsedTime ๋ฅผ ๋‘˜ ๋‹ค ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋™์ž‘์ด ๊ผฌ์ผ ์ˆ˜ ์žˆ๋‹ค. (๊ฐ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ oldTime ์„ ์ดˆ๊ธฐํ™”ํ•˜๋Š”๋ฐ ์ด๋ถ€๋ถ„ ๋•Œ๋ฌธ์ผ ๋“ฏ?)

3. requestAnimationFrame + greenSock Library ์‚ฌ์šฉ

  • ์„ค์น˜
npm i gsap
  • ์‚ฌ์šฉ
import gsap from 'gsap';

//...
function draw(): void {
  renderer.render(scene, camera);
  renderer.requestAnimationFrame(draw);
}
// mesh์˜ y์œ„์น˜๋ฅผ 2๊นŒ์ง€ 1์ดˆ ๋™์•ˆ ์ด๋™ํ•œ๋‹ค.
gasp.to(mesh.position, {
  duration: 1, 
  y: 2,
});
draw();

์•ˆ๊ฐœ ํšจ๊ณผ

  • THREE.Fog(์•ˆ๊ฐœ ์ƒ‰์ƒ, ์•ˆ๊ฐœ ์ ์šฉ ์ตœ์†Œ ๊ฑฐ๋ฆฌ, ์•ˆ๊ฐœ ์ ์šฉ ์ตœ๋Œ€ ๊ฑฐ๋ฆฌ)
const scene = new THREE.Scene();
// ๋’ค์—์žˆ๋Š” ๋ฐ•์Šค๋Š” ์•ˆ๊ฐœ์— ๋ฎ์ธ ์ƒ‰์œผ๋กœ ๋ณด์ž„
scene.fog = new THREE.Fog('black', 3, 7);

0๊ฐœ์˜ ๋Œ“๊ธ€