Three.js๋กœ ์›นํŽ˜์ด์ง€์— 3D ๊ทธ๋ž˜ํ”ฝ ๋„ฃ๊ธฐ (feat.blender)

2026. 2. 2. 22:21ใ†Projects/WooniePangee

๋ฐ˜์‘ํ˜•

 

๋ธ”๋ Œ๋”๋กœ 3D ์˜ค๋ธŒ์ ํŠธ ๋งŒ๋“ค๊ณ  Three.js๋กœ ๋ฐ˜์‘ํ˜•์„ ๊ณ ๋ คํ•œ ์›น ๋ Œ๋”๋ง ํ•˜๊ธฐ

๋ชฉ์ฐจ

1. ์›น ๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ปค์„œ๋ฅผ ์ถ”์ ํ•˜๋Š” 3D ์˜ค๋ธŒ์ ํŠธ ๋„ฃ๊ธฐ

2. ์ค€๋น„๋ฌผ: Three.js์™€ 3D ์˜ค๋ธŒ์ ํŠธ(๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด ๋ดค๋‹ค)

3. blender๋กœ 3D ์˜ค๋ธŒ์ ํŠธ ๋งŒ๋“ค๊ธฐ

4. Three.js๋กœ ์›นํŽ˜์ด์ง€์— ๊ฐ์ฒด ๋„ฃ๊ธฐ


1. ์›น ๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ปค์„œ๋ฅผ ์ถ”์ ํ•˜๋Š” 3D ์˜ค๋ธŒ์ ํŠธ ๋„ฃ๊ธฐ

์ผ๋‹จ ๊ธฐ์กด ํ”ผ๊ทธ๋งˆ๋กœ ๋Œ€์ถฉ ๋””์ž์ธํ•œ ๋ฉ”์ธ ํ™”๋ฉด์€ ์•„๋ž˜์™€ ๊ฐ™์•˜๋‹ค.

์ด๋ ‡๊ฒŒ ๊ทธ๋ƒฅ ์ญ‰ ์•„๋ž˜๋กœ ์Šคํฌ๋กคํ•˜๋Š” ๋ฉ”์ธ ํ™”๋ฉด์ด๋‹ค.

 

๊ทธ๋Ÿฐ๋ฐ ๋ฆฌํŒฉํ† ๋ง์œผ๋กœ ๋‚จ๊ฒจ๋‘๋ คํ–ˆ์ง€๋งŒ ๋ฉ”์ธ ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๋ ค๋‹ˆ ๋„ˆ๋ฌด ๋ชป์ƒ๊ฒจ์„œ ํ•˜๊ธฐ๊ฐ€ ์‹ซ์€ ๊ฑฐ๋‹ค.

๊ทธ๋ž˜์„œ ์ผ๋‹จ ๊ณ ๋ ค๋งŒ ํ–ˆ๋˜ Three.js๋ฅผ ๋ฐ”๋กœ ํˆฌ์ž…ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. ์–ด์ฐจํ”ผ ๊ฐˆ์•„์—Ž๊ฒŒ ๋  ๋ฉ”์ธ์ด๋ผ๋ฉด.. ์ŠคํŠธ๋ ˆ์Šค ๋ฐ›์ง€ ๋ง๊ณ  ๊ฐˆ์•„์—Ž๊ธฐ๋กœ 

 

 


2. ์ค€๋น„๋ฌผ: Three.js์™€ 3D ์˜ค๋ธŒ์ ํŠธ(๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด ๋ดค๋‹ค)

1. Three.js

https://threejs.org/manual/#ko/fundamentals

Three.js๋Š” ์›นํŽ˜์ด์ง€์— 3D ๊ฐ์ฒด๋ฅผ ์‰ฝ๊ฒŒ ๋ Œ๋”๋งํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. WebGL์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

์ฝ”๋“œ ๋ณด๋Š”๋ฐ OpenGL ์ˆ˜์—… ๋“ค์—ˆ๋˜ ๊ฒŒ ๋– ์˜ฌ๋ผ์„œ ์กฐ๊ธˆ ์ต์ˆ™ํ•œ ๋А๋‚Œ์ด์—ˆ๋‹ค.

 

docs์— ํ•œ๊ตญ์–ด๋„ ์žˆ๊ณ  ์˜ˆ์‹œ์™€ ํ•จ๊ป˜ ์„ค๋ช…์ด ์ •๋ง ์ž˜ ๋˜์–ด์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

์‹œ๊ฐ„์ด ์—†์–ด์„œ ์ž์„ธํžˆ ์ฝ์–ด๋ณด๋ฉด์„œ ํ•˜์ง€๋Š” ๋ชปํ–ˆ์ง€๋งŒ ๋‚˜์ค‘์— ์ฐฌ์ฐฌํžˆ ์ฝ์–ด๋ณด๋ฉด (ํŠนํžˆ ํŒ) ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

 

2. 3D ์˜ค๋ธŒ์ ํŠธ

์›นํŽ˜์ด์ง€์— 3D ๊ฐ์ฒด๋ฅผ ๋ Œ๋”๋งํ•˜๋ ค๋ฉด ์•„๋ฌด๋ž˜๋„ 3D ๊ฐ์ฒด๊ฐ€ ์šฐ์„  ํ•„์š”ํ•˜๋‹ค.

์‚ฌ์‹ค 3D ๊ฐ์ฒด์•ผ ai๋กœ ๋š๋”ฑ ๋งŒ๋“ค์–ด์„œ ์“ฐ๋ฉด ๋˜๊ฒ ์ง€ ์ƒ๊ฐํ–ˆ์œผ๋‚˜..

https://www.meshy.ai/discover

https://hyper3d.ai/?lang=ko&gad_campaignid=23436059038&gbraid=0AAAABCNk7GYRlaWIKdh023Dat0yJDxD2t

 

 

 

์•ˆํƒ€๊น๊ฒŒ๋„ 3D ๋ชจ๋ธ์„ ๊ทธ๋ฆฌ๋Š” ai๋“ค์€ ๊ตฌ๋…์„ ํ•ด์•ผ ๋‹ค์šด ๋ฐ›๊ฑฐ๋‚˜, ์•„๋‹ˆ๋ฉด ์ •๋ง ์ด์ƒํ•˜๊ฒŒ ์ƒ๊ธด ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋‹ค์šด๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒƒ๋งŒ ์žˆ์—ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ์ž˜ ๋งŒ๋“  ๊ฒƒ๋„ ๋‚ด ๋งˆ์Œ์— ์™ ๋“ค์ง€๋Š” ์•Š์•˜๋‹ค. ๋‚ด๊ฐ€ ๊ทธ๋ฆฐ ์บ๋ฆญํ„ฐ์—ฌ์„œ ์ข€ ๋งˆ์Œ์— ์•ˆ๋“ค๊ฒŒ ์ƒ๊ธด ๊ฑธ ๋„˜์–ด๊ฐˆ ์ˆ˜๊ฐ€ ์—†๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋ธ”๋ Œ๋”๋กœ ์ง์ ‘ ๊ฐ์ฒด๊นŒ์ง€ ๋งŒ๋“ค์—ˆ๋‹ค.. ๊ทผ๋ฐ ์ € meshy๋Š” ์นœ๊ตฌ ์ดˆ๋Œ€ํ•˜๋ฉด ํ•œ๋‹ฌ๋™์•ˆ ํ”„๋กœ ํ”Œ๋žœ ๋ฌด๋ฃŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ˆ๊นŒ ์ด๋Ÿฐ ๊ณ ์ง‘์ด ์—†๋‹ค๋ฉด ai ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ํ›จ์”ฌ ๋‚ซ๋‹ค.

๋…ธ๋ฒ ์ด์Šค๋กœ 3D ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ ์‰ฝ์ง€ ์•Š๋‹ค.

 


3. blender๋กœ 3D ์˜ค๋ธŒ์ ํŠธ ๋งŒ๋“ค๊ธฐ

์ด๊ฑด ๊ฐœ๋ฐœ๊ณผ ์ƒ๊ด€ ์—†๋Š” ์–˜๊ธฐ๋‹ค. ์˜คํžˆ๋ ค ์ด๊ฑฐ ํ•˜๋Š”๋ฐ ์‹œ๊ฐ„ ๋„ˆ๋ฌด ๋งŽ์ด ์จ์„œ ์˜ค๋Š˜ ํ•ด์•ผํ•  ๋‹ค๋ฅธ ์ผ๋“ค์„ ๋ชปํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋‚˜๋ฆ„ ๋งŒ์กฑ์Šค๋Ÿฌ์šด ๊ฒฐ๊ณผ๋ฅผ ์–ป์€ ๊ฒƒ ๊ฐ™๋‹ค.

๋‚ด๊ฐ€ ์•Œ๊ธฐ๋กœ 3D ๋ Œ๋”๋ง ๋„๊ตฌ๋กœ ๋Œ€ํ‘œ์ ์ธ ๊ฒƒ์ด ๋งˆ์•ผ์™€ ๋ธ”๋ Œ๋”์ธ๋ฐ, ๋งˆ์•ผ๊ฐ€ ์ „๋ฌธ๊ฐ€ ๋А๋‚Œ์ด ์ข€ ๋” ์žˆ๋Œ€์„œ ์ „์— ๋ธ”๋ Œ๋”๋ฅผ ํ•œ ๋ฒˆ ์จ๋ณธ ์ ์ด ์žˆ์—ˆ๋‹ค. ๊ทธ ๋•Œ ๊ธˆ๋ฐฉ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด ๋ณธ ๊ฒŒ ์žˆ์–ด์„œ ๊ธˆ๋ฐฉ ํ•˜๊ฒ ๊ฑฐ๋‹ˆํ•˜๊ณ  ๋งŒ๋“ค์–ด๋ณด๋‹ค๊ฐ€ ํ™”๋ฅผ ๋ดค๋‹ค.

๋Œ€ํ•™๊ต 3ํ•™๋…„ ๋ธ”๋ Œ๋” 4์‹œ๊ฐ„์ฐจ / ์กธ์—…์ƒ 1๋…„ ๋ธ”๋ Œ๋” 6์‹œ๊ฐ„์ฐจ

https://www.youtube.com/watch?v=RedV1S8ssDA&list=PLAosp_j4QfQfErnJbpZ1PcHw1Z74k_Xfd&index=3

์œ ํŠœ๋ธŒ์— ๋ธ”๋ Œ๋” ๊ฒ€์ƒ‰ํ•˜๋‹ค๊ฐ€ ์šฐ๋‹ˆ๋ž‘ ์ข€ ๋น„์Šทํ•˜๊ฒŒ ์ƒ๊ธด ๊ฒƒ ๊ฐ™์•„์„œ ์ด๊ฑฐ ๋ณด๋ฉด์„œ ํ–ˆ๋‹ค. ๋งˆ์šฐ์Šค/ํ‚ค๋ณด๋“œ ๋ˆ„๋ฅด๋Š” ๊ฒƒ๋„ ๊ฐ™์ด ๋– ์„œ ๋” ๋ณด๊ธฐ ๊ดœ์ฐฎ์•˜๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

 

์™„์ „ ๋งค๋ˆ๋งค๋ˆํ•˜๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ๊ธดํ•œ๋ฐ ๊ฐ์ง„ ๊ฒŒ ๋ญ”๊ฐ€ ์ข€ ๋” 3D ๋А๋‚Œ ๋‚˜๋Š” ๊ฒƒ ๊ฐ™์•„์„œ ์ผ๋‹จ ๊ฐ์ง„ ๊ฑธ๋กœ ๋„ฃ์–ด๋ดค๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ € spheer ์•ž๋จธ๋ฆฌ๊ฐ€ ์ข€ ๊ทธ๋Ÿฐ๊ฐ€ ์‹ถ์–ด์„œ.. ๋‚˜์ค‘์— ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“คํ•œํ…Œ๋„ ๋ฌผ์–ด๋ด์•ผ์ง€..

 

 


4. Three.js๋กœ ์›นํŽ˜์ด์ง€์— ๊ฐ์ฒด ๋„ฃ๊ธฐ

6-7์‹œ๊ฐ„๋งŒ์— ์–ป์€ .glb ํŒŒ์ผ์„ ์ด์ œ ์›นํŽ˜์ด์ง€์— ๊ทธ๋ฆฌ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

  • ๋ธŒ๋ผ์šฐ์ €์— ์˜ค๋ธŒ์ ํŠธ ๋ Œ๋”๋งํ•˜๊ธฐ
  • ๋งˆ์šฐ์Šค/ํ„ฐ์น˜์— ๋”ฐ๋ผ ์˜ค๋ธŒ์ ํŠธ ํšŒ์ „์‹œํ‚ค๊ธฐ

ํ•  ์ผ์€ ํฌ๊ฒŒ ์ด๋ ‡๊ฒŒ ๋‘๊ฐ€์ง€์ด๋‹ค. ์—ฌ๊ธฐ์„œ ์‹ ๊ฒฝ์“ธ ์ ์€ ์•„๋ž˜ ์ •๋„๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค.

  • ๊ทธ๋ƒฅ ๊ทธ๋ฆฌ๋ฉด ์ฐฝ ํฌ๊ธฐ๊ฐ€ ๋ณ€ํ•  ๋•Œ ๋ Œ๋”๋ง ๊ณต๊ฐ„ ๋ฐ ์˜ค๋ธŒ์ ํŠธ ํฌ๊ธฐ๊ฐ€ ๊ทธ๋Œ€๋กœ์ด๊ธฐ ๋•Œ๋ฌธ์— innerWidth์— ๋”ฐ๋ผ ์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜์ด ํ•„์š”
  • ๋งˆ์šฐ์Šค์— ์‹œ์„ ์„ ์ž˜ ๊ฝ‚๋„๋ก ์กฐ์ •
  • ๋ Œ๋”๋ง ๋ฃจํ”„ ๊ด€๋ฆฌ ๋ฐ ์ •๋ฆฌ

 

1. import

typescript๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” THREE๋งŒ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ ํƒ€์ž…๋ฌธ์ œ์ธ์ง€ ๋ฒ„์ „๋ฌธ์ œ์ธ์ง€ ์—๋Ÿฌ๊ฐ€ ์ž๊พธ ๋‚˜์„œ GLTFLoader, GLTF๋Š” three-stdlib์„ ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ–ˆ๋‹ค.

'use client';

import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { GLTFLoader } from 'three-stdlib';
import type { GLTF } from 'three-stdlib';

 

 

2. ref ์ฐธ์กฐ

export default function ThreeHead() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    // ref ๊ฐ’์„ ๋ณ€์ˆ˜์— ์ €์žฅ
    const container = containerRef.current;

useRef๋กœ ๋ Œ๋”๋งํ•  div ์š”์†Œ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  container์— ์ €์žฅํ•œ๋‹ค.

๋‚˜์ค‘์— ํด๋ฆฐ์—…ํ•  ๋•Œ ๋ฐ”๋กœ containerRef.current๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฏธ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๊ฐ€๋Šฅ์„ฑ์œผ๋กœ ๋ฆฐํŠธ ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— container ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ด์„œ ๋‹ด์•˜๋‹ค.

  • useEffect ์‹คํ–‰ ์‹œ์ : containerRef.current๋Š” DOM ์š”์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚ด
  • ํด๋ฆฐ์—…(์–ธ๋งˆ์šดํŠธ) ๊ณผ์ • ์ค‘ React๊ฐ€ ref๋ฅผ ๋จผ์ € null๋กœ ๋งŒ๋“  ๋‹ค์Œ useEffect cleanup์ด ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Œ

 

 

3. Scene, Camera, Renderer, Light ์ƒ์„ฑ

    // --- ๋™์  ์‚ฌ์ด์ฆˆ ---
    const sizes = {
      width: window.innerWidth,
      height: window.innerHeight,
    };

    // --- ์”ฌ / ์นด๋ฉ”๋ผ / ๋ Œ๋”๋Ÿฌ ---
    const scene = new THREE.Scene();

    const camera = new THREE.PerspectiveCamera(45, sizes.width / sizes.height, 0.1, 100);
    camera.position.set(0.5, 0, 4);

    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    renderer.setSize(sizes.width, sizes.height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

    containerRef.current.appendChild(renderer.domElement);

    // --- ์กฐ๋ช… ---
    scene.add(new THREE.AmbientLight(0xffffff, 2));
    const dirLight = new THREE.DirectionalLight(0xffffff, 0.7);
    dirLight.position.set(0, 5, 5);
    scene.add(dirLight);

์šฐ์„  ์œˆ๋„์šฐ ํฌ๊ธฐ์— ๋”ฐ๋ผ ๋™์  ์‚ฌ์ด์ฆˆ๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•œ sizes๋ฅผ ์„ ์–ธํ•˜๊ณ ,

3D ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๊ทธ๋ฆด ๊ณต๊ฐ„ Scene, ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ณด๋Š” ์‹œ์ ์ธ Camera, 3D๋ฅผ ๊ทธ๋ฆฌ๋Š” Renderer, ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋น„์ถœ Light๋ฅผ ์ƒ์„ฑํ•˜์ž.

 

  • THREE.Scene : ๊ณต๊ฐ„์„ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ๋‹ค
  • HTREE.PerspectiveCamera : Camera๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
    45 : ์นด๋ฉ”๋ผ ๊ฐ๋„(degree)
    0.1 ~ 100 : ์ดฌ์˜์ด ๊ฐ€๋Šฅํ•œ ๊ณต๊ฐ„์œผ๋กœ, ์ด ๋ฐ–์— ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ์นด๋ฉ”๋ผ์— ์•ˆ์ฐํžŒ๋‹ค.
    camera.position : ์™ผ์ชฝ, ์œ„์•„๋ž˜, ์˜ค๋ฅธ์ชฝ์— ๋Œ€ํ•œ ์นด๋ฉ”๋ผ ์œ„์น˜
  • THREE.WebGLRenderer: Renderer๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
    alpha: true → ๋ฐฐ๊ฒฝ ํˆฌ๋ช…ํ•˜๊ฒŒ
    antialias : ๊ฒฝ๊ณ„์„  ๋ถ€๋“œ๋Ÿฝ๊ฒŒ
  • THREE.AmbientLight: ๊ธฐ๋ณธ์กฐ๋ช…์„ ์„ธํŒ…ํ•œ๋‹ค.
    THREE.DirectionalLight: ์ด๊ฒŒ ์—†์œผ๋ฉด ๋ชจ๋ธ์ด 3D์ธ๋ฐ 3D๊ฐ€ ์•„๋‹Œ ๋А๋‚Œ์ด ๋œ๋‹ค.

DirectionalLight 0, 0.3, 0.7

 

 

4. GLTF ๋ชจ๋ธ ๋กœ๋“œ

    // --- GLTF ๋กœ๋“œ ---
    const loader = new GLTFLoader();
    let head: InstanceType<typeof THREE.Object3D>;
    let baseScale = 1; // ๊ธฐ๋ณธ ์Šค์ผ€์ผ ์ €์žฅ

    // ํ™”๋ฉด ํฌ๊ธฐ์— ๋”ฐ๋ฅธ ์Šค์ผ€์ผ ๊ณ„์‚ฐ ํ•จ์ˆ˜
    const calculateScale = () => {
      // ๊ธฐ์ค€: 1920px ํ™”๋ฉด์—์„œ scale 1
      const scaleMultiplier = Math.min(sizes.width / 1920, 1.5); // ์ตœ๋Œ€ 1.5๋ฐฐ๊นŒ์ง€
      return baseScale * scaleMultiplier;
    };

    loader.load('/images/head.glb', (gltf: GLTF) => {
      const model = gltf.scene;
      const group = new THREE.Group();
      group.add(model);

      const box = new THREE.Box3().setFromObject(model);
      const center = new THREE.Vector3();
      box.getCenter(center);
      model.position.sub(center);

      const size = new THREE.Vector3();
      box.getSize(size);
      const maxDim = Math.max(size.x, size.y, size.z);
      baseScale = 2 / maxDim; // ๊ธฐ๋ณธ ์Šค์ผ€์ผ ์ €์žฅ

      const currentScale = calculateScale();
      group.scale.set(currentScale, currentScale, currentScale);

      group.rotation.y = Math.PI;

      scene.add(group);
      head = group;

      camera.lookAt(group.position);
    });

ํ™˜๊ฒฝ ์„ธํŒ…์ด ๋๋‚˜๋ฉด ์ด์ œ ๋ชจ๋ธ์„ ๋กœ๋“œํ•  ์ฐจ๋ก€์ด๋‹ค.

GLTFLoader๋กœ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋Š” loader๋ฅผ ์„ ์–ธํ•˜๊ณ , calculateScalesํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•ด์„œ ํ™”๋ฉด ํฌ๊ธฐ์— ๋”ฐ๋ผ ์˜ค๋ธŒ์ ํŠธ ํฌ๊ธฐ๊ฐ€ ๋‹ฌ๋ผ์ง€๋„๋ก ํ•˜์˜€๋‹ค.

 

loader.load('/imaes/head.glb')์™€ ๊ฐ™์ด 3D ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์ฃผ๊ณ  ์•ˆ์—์„œ ์„ธํŒ…์„ ํ•˜๋ฉด ๋œ๋‹ค.

์ฒ˜์Œ์— ์˜ค๋ธŒ์ ํŠธ์˜ 0,0,0์ด ์ค‘์‹ฌ์ถ•์ด ๋˜๋Š” ๋А๋‚Œ์œผ๋กœ ํšŒ์ „์„ ํ•ด์„œ ํšŒ์ „์˜ ์ค‘์‹ฌ์ถ•์ด ์˜ค๋ธŒ์ ํŠธ ๋‚ด๋ถ€์˜ ์ •์ค‘์•™์ด ๋˜๋„๋ก ํฌ์ง€์…”๋‹์„ ํ–ˆ๋‹ค.

๊ทธ ์•„๋ž˜๋Š” ์Šค์ผ€์ผ ๊ณ„์‚ฐ์œผ๋กœ, ๋งŒ์•ฝ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ €๋งŒํผ์ด ํ•„์š”๊ฐ€ ์—†๋‹ค.

 

5. ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์ด๋ฒคํŠธ

    // --- ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ์œ„์น˜ ---
    const mouse = { x: 0, y: 0 };

    // ์œ„์น˜ ์—…๋ฐ์ดํŠธ ๊ณตํ†ต ํ•จ์ˆ˜
    const updateMousePosition = (clientX: number, clientY: number) => {
      mouse.x = (clientX / sizes.width) * 2 - 0.8;
      mouse.y = -(clientY / sizes.height) * 2 + 1;
    };

    // ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ
    const onMouseMove = (e: MouseEvent) => {
      updateMousePosition(e.clientX, e.clientY);
    };

    // ํ„ฐ์น˜ ์ด๋ฒคํŠธ
    const onTouchMove = (e: TouchEvent) => {
      if (e.touches.length > 0) {
        const touch = e.touches[0];
        updateMousePosition(touch.clientX, touch.clientY);
      }
    };

    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('touchmove', onTouchMove, { passive: true });

์ฒ˜์Œ์— ๋งˆ์šฐ์Šค๋งŒ ๋งŒ๋“ค์—ˆ๋‹ค๊ฐ€ ์•„์ฐจ์ฐจํ•˜๊ณ  ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ๊ธ‰ํ•˜๊ฒŒ ๋„ฃ์—ˆ๋‹ค.

ํ™”๋ฉด ์ขŒํ‘œ๋ฅผ 3D ํšŒ์ „์— ์ ํ•ฉํ•œ ์ •๊ทœํ™” ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ณ„์‚ฐ์ด ์กฐ๊ธˆ ๋“ค์–ด๊ฐ„๋‹ค.

updateMousePosition์— ๋“ค์–ด๊ฐ„ ์ˆ˜์‹์˜ ๊ฒฝ์šฐ, ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

(clientX / sizes.width) * 2 - 0.8  โ–ถ๏ธŽโ–ถ๏ธŽ

ํ™”๋ฉด ๋‚ด ์ƒ๋Œ€์  ์œ„์น˜ ๋น„์œจ * ์ค‘์‹ฌ ๊ธฐ์ค€์œผ๋กœ ํ™•์žฅ - ๋ชจ๋ธ ์‹œ์„  ์ฒ˜๋ฆฌ(UX) ๋ณด์ •์šฉ ์ƒ์ˆ˜

 

y๋Š” ๋ธŒ๋ผ์šฐ์ €๋Š” ์•„๋ž˜๋กœ ๊ฐˆ์ˆ˜๋ก y๊ฐ€ ์ฆ๊ฐ€ํ•˜๋‚˜, Three.js ํšŒ์ „์€ ์œ„๋กœ ๊ฐˆ์ˆ˜๋ก ์ฆ๊ฐ€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋Œ€ ๋ถ€ํ˜ธ๊ฐ€ ๋œ๋‹ค.

 

 

6. ๋ฆฌ์‚ฌ์ด์ฆˆ ๋Œ€์‘

    // --- ๋ฆฌ์‚ฌ์ด์ฆˆ ํ•ธ๋“ค๋Ÿฌ ---
    const handleResize = () => {
      // sizes ๊ฐ์ฒด ์—…๋ฐ์ดํŠธ
      sizes.width = window.innerWidth;
      sizes.height = window.innerHeight;

      // ์นด๋ฉ”๋ผ ์—…๋ฐ์ดํŠธ
      camera.aspect = sizes.width / sizes.height;
      camera.updateProjectionMatrix();

      // ๋ Œ๋”๋Ÿฌ ์—…๋ฐ์ดํŠธ
      renderer.setSize(sizes.width, sizes.height);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

      // ์˜ค๋ธŒ์ ํŠธ ํฌ๊ธฐ ์—…๋ฐ์ดํŠธ
      if (head) {
        const newScale = calculateScale();
        head.scale.set(newScale, newScale, newScale);
      }
    };
    
    window.addEventListener('resize', handleResize);

์œˆ๋„์šฐ ํฌ๊ธฐ๊ฐ€ ๋ณ€ํ•˜๋ฉด sizes๋ฅผ ๋จผ์ € ์—…๋ฐ์ดํŠธํ•˜์—ฌ ์นด๋ฉ”๋ผ, ๋ Œ๋”๋Ÿฌ, ์˜ค๋ธŒ์ ํŠธ ํฌ๊ธฐ๊ฐ€ ๋ชจ๋‘ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค.

 

7. ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฃจํ”„

    // --- ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฃจํ”„ ---
    const animate = () => {
      requestAnimationFrame(animate);
      if (head) {
        head.rotation.y = Math.PI + mouse.x * 1;
        head.rotation.x = -mouse.y * 1;
      }
      renderer.render(scene, camera);
    };
    animate();

requestAnimationFrame์œผ๋กœ ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ฐฑ์‹ ํ•œ๋‹ค.

๋ Œ๋”๋งํ•œ ๊ฐ์ฒด head์— ๋งˆ์šฐ์Šค๋ฅผ ๋”ฐ๋ผ์„œ x์ถ•, y์ถ• rotation์„ ์ฃผ๊ณ , ํ•ด๋‹นํ•˜๋Š” scene๊ณผ camera ๋ Œ๋” ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ฆฐ๋‹ค.

 

8. ํด๋ฆฐ์—…

    // --- ํด๋ฆฐ์—… ---
    return () => {
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('touchmove', onTouchMove);
      window.removeEventListener('resize', handleResize);
      // ์ €์žฅ๋œ ๋ณ€์ˆ˜ ์‚ฌ์šฉ
      container.removeChild(renderer.domElement);
      renderer.dispose();
    };
  }, []);

์œ„์—์„œ ์„ ์–ธํ•œ ์ด๋ฒคํŠธ๊ฐ€ ๋งŽ์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์ด๋ฒคํŠธ ์ œ๊ฑฐ ๋ฐ ๋ Œ๋”๋Ÿฌ ํ•ด์ œ๋ฅผ ๋‘”๋‹ค.

 

 

9. ๋ฆฌํ„ด

  return (
    <div
      ref={containerRef}
      className="bg-blue-50 bg-[linear-gradient(to_right,#e5e7eb_1px,transparent_1px),linear-gradient(to_bottom,#e5e7eb_1px,transparent_1px)] bg-size-[20px_20px]"
    />
  );

์—ฌ๊ธฐ์„œ div๋Š” ๋ Œ๋”๋ง ์บ”๋ฒ„์Šค ์—ญํ• ์„ ํ•œ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๊ทธ๋ƒฅ ๋„ฃ์–ด๋ดค๋‹ค.

 

 

 

 

์ฐธ๊ณ 

๋”๋ณด๊ธฐ
๋ฐ˜์‘ํ˜•