ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๋ฐ˜์‘ํ˜•

๐Ÿ“Œ Three.js์—์„œ GLTFLoader๋ฅผ ํ™œ์šฉํ•˜์—ฌ 3D ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

์ด์ „ ๊ธ€์—์„œ๋Š” Three.js + React ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•˜๊ณ , ๊ธฐ๋ณธ 3D ์”ฌ์„ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” GLTFLoader๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ 3D ๋ชจ๋ธ์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ , Three.js ์”ฌ์— ๋ฐฐ์น˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.


๐Ÿš€ 1. GLTFLoader๋ž€?

Three.js์—์„œ .glb ๋ฐ .gltf ํ˜•์‹์˜ 3D ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๋ ค๋ฉด GLTFLoader๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

โœ… GLTF vs GLB ์ฐจ์ด์ 

ํŒŒ์ผ ํ˜•์‹ ์„ค๋ช…

.glb ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท (์šฉ๋Ÿ‰์ด ์ž‘๊ณ  ์ตœ์ ํ™”๋จ)
.gltf JSON ํฌ๋งท (ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜, ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์‰ฌ์›€)

๐Ÿ“Œ Three.js์—์„œ๋Š” .glb ํŒŒ์ผ์„ ๋” ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

  • ์••์ถ•๋ฅ ์ด ๋†’๊ณ , ๋กœ๋”ฉ ์†๋„๊ฐ€ ๋น ๋ฅด๋ฉฐ, ๋„คํŠธ์›Œํฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ ์Šต๋‹ˆ๋‹ค.
  • WebGL์—์„œ ์ตœ์ ํ™”๋˜์–ด ๋น ๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.

๐Ÿ›  2. ํ”„๋กœ์ ํŠธ์— 3D ๋ชจ๋ธ ์ถ”๊ฐ€ํ•˜๊ธฐ

โœ… 1) @react-three/drei ์„ค์น˜ (GLTFLoader ํฌํ•จ)

GLTF ๋ชจ๋ธ์„ ์‰ฝ๊ฒŒ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•ด @react-three/drei ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

npm install @react-three/drei

๐Ÿ”น ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” Three.js๋ฅผ React์—์„œ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค์–‘ํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.


โœ… 2) 3D ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ ๋ฐ ํ”„๋กœ์ ํŠธ์— ์ถ”๊ฐ€

๋ฐ˜์‘ํ˜•
  1. ๋ฌด๋ฃŒ 3D ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ ์‚ฌ์ดํŠธ
  2. ๋ชจ๋ธ ํŒŒ์ผ ์ €์žฅ ๊ฒฝ๋กœ
    ํ”„๋กœ์ ํŠธ ๋‚ด public/models ํด๋”์— 3D ๋ชจ๋ธ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ ์˜ˆ์‹œ ํŒŒ์ผ ๊ตฌ์กฐ:

๐Ÿ“‚ public/models/
 โ”ฃ ๐Ÿ“œ chair.glb
 โ”ฃ ๐Ÿ“œ robot.glb
 โ”— ๐Ÿ“œ car.glb

๐Ÿ— 3. GLTFLoader๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ 3D ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

๐Ÿ“Œ src/components/ModelViewer.jsx

import { useRef } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, useGLTF } from "@react-three/drei";

const Model = ({ modelPath }) => {
  const { scene } = useGLTF(modelPath);
  return <primitive object={scene} scale={1.5} />;
};

const ModelViewer = () => {
  return (
    <Canvas camera={{ position: [0, 2, 5], fov: 50 }}>
      {/* ์กฐ๋ช… ์„ค์ • */}
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} intensity={1} />

      {/* 3D ๋ชจ๋ธ ๋กœ๋“œ */}
      <Model modelPath="/models/chair.glb" />

      {/* ์นด๋ฉ”๋ผ ์ปจํŠธ๋กค */}
      <OrbitControls />
    </Canvas>
  );
};

export default ModelViewer;

๐ŸŽฏ 4. App.jsx์—์„œ ModelViewer ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€

๐Ÿ“Œ src/App.jsx

import ModelViewer from "./components/ModelViewer";

function App() {
  return (
    <div>
      <h1 style={{ textAlign: "center", marginTop: "20px" }}>3D ๋ชจ๋ธ ๋ทฐ์–ด</h1>
      <ModelViewer />
    </div>
  );
}

export default App;

โœ… ์ด์ œ npm run dev๋กœ ์‹คํ–‰ํ•˜๋ฉด 3D ๋ชจ๋ธ์ด Three.js ์”ฌ์— ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค!


๐Ÿš€ 5. 3D ๋ชจ๋ธ ๋กœ๋”ฉ ์ตœ์ ํ™”ํ•˜๊ธฐ

โœ… 1) ๋ชจ๋ธ ํฌ๊ธฐ ์กฐ์ •

<primitive> ํƒœ๊ทธ์—์„œ scale ๊ฐ’์„ ์กฐ์ •ํ•˜์—ฌ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<primitive object={scene} scale={2} />

โœ… 2) ์—ฌ๋Ÿฌ ๋ชจ๋ธ ์„ ํƒ ๊ธฐ๋Šฅ ์ถ”๊ฐ€

์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์–‘ํ•œ ๋ชจ๋ธ์„ ์„ ํƒํ•˜์—ฌ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก UI๋ฅผ ๊ฐœ์„ ํ•ด๋ด…๋‹ˆ๋‹ค.

๐Ÿ“Œ src/components/ModelGallery.jsx (๋ชจ๋ธ ์„ ํƒ ๋ฒ„ํŠผ ์ถ”๊ฐ€)

import { useState } from "react";
import ModelViewer from "./ModelViewer";

const models = [
  { name: "Chair", path: "/models/chair.glb" },
  { name: "Car", path: "/models/car.glb" },
  { name: "Robot", path: "/models/robot.glb" }
];

const ModelGallery = () => {
  const [selectedModel, setSelectedModel] = useState(models[0].path);

  return (
    <div>
      {/* ๋ชจ๋ธ ์„ ํƒ ๋ฒ„ํŠผ */}
      <div style={{ textAlign: "center", marginBottom: "10px" }}>
        {models.map((model) => (
          <button
            key={model.name}
            onClick={() => setSelectedModel(model.path)}
            style={{
              margin: "5px",
              padding: "10px",
              border: "1px solid gray",
              borderRadius: "5px",
              cursor: "pointer"
            }}
          >
            {model.name}
          </button>
        ))}
      </div>

      {/* ์„ ํƒ๋œ ๋ชจ๋ธ์„ ModelViewer์— ์ „๋‹ฌ */}
      <ModelViewer modelPath={selectedModel} />
    </div>
  );
};

export default ModelGallery;

๐Ÿ“Œ src/components/ModelViewer.jsx (๋™์ ์œผ๋กœ ๋ชจ๋ธ ๋ณ€๊ฒฝ)

import { useGLTF } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";

const Model = ({ modelPath }) => {
  const { scene } = useGLTF(modelPath);
  return <primitive object={scene} scale={1.5} />;
};

const ModelViewer = ({ modelPath }) => {
  return (
    <Canvas camera={{ position: [0, 2, 5], fov: 50 }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} intensity={1} />
      <Model modelPath={modelPath} />
      <OrbitControls />
    </Canvas>
  );
};

export default ModelViewer;

๐Ÿ“Œ App.jsx์—์„œ ModelGallery ์‚ฌ์šฉ

import ModelGallery from "./components/ModelGallery";

function App() {
  return (
    <div>
      <h1 style={{ textAlign: "center", marginTop: "20px" }}>3D ๋ชจ๋ธ ๋ทฐ์–ด</h1>
      <ModelGallery />
    </div>
  );
}

export default App;

โœ… 6. ์™„์„ฑ๋œ ๊ธฐ๋Šฅ

โœ” GLTFLoader๋ฅผ ํ™œ์šฉํ•˜์—ฌ 3D ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
โœ” Canvas๋ฅผ ํ™œ์šฉํ•œ Three.js ์”ฌ ๋ Œ๋”๋ง
โœ” ์—ฌ๋Ÿฌ ๊ฐœ์˜ 3D ๋ชจ๋ธ์„ ์„ ํƒํ•˜์—ฌ ํ‘œ์‹œ ๊ฐ€๋Šฅ
โœ” OrbitControls๋กœ ๋งˆ์šฐ์Šค ์กฐ์ž‘ ๊ธฐ๋Šฅ ์ถ”๊ฐ€


๐Ÿ”ฅ 7. ๋‹ค์Œ ๋‹จ๊ณ„: 3D ๋ชจ๋ธ์— ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ ์šฉํ•˜๊ธฐ

์ง€๊ธˆ๊นŒ์ง€ ์šฐ๋ฆฌ๋Š” GLTFLoader๋ฅผ ํ™œ์šฉํ•˜์—ฌ 3D ๋ชจ๋ธ์„ ๋กœ๋“œํ•˜๊ณ , Three.js ์”ฌ์— ๋ฐฐ์น˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํฌํ•จ๋œ GLTF ๋ชจ๋ธ์„ ๋ถˆ๋Ÿฌ์™€์„œ ์žฌ์ƒํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃจ๊ฒ ์Šต๋‹ˆ๋‹ค.

โœ… ๋‹ค์Œ ํŽธ ์˜ˆ๊ณ :
"Three.js์—์„œ 3D ๋ชจ๋ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ & ์ œ์–ดํ•˜๊ธฐ" ๐Ÿš€

 

โ€ป ์ด ํฌ์ŠคํŒ…์€ ์ฟ ํŒก ํŒŒํŠธ๋„ˆ์Šค ํ™œ๋™์˜ ์ผํ™˜์œผ๋กœ, ์ด์— ๋”ฐ๋ฅธ ์ผ์ •์•ก์˜ ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ์ œ๊ณต๋ฐ›์Šต๋‹ˆ๋‹ค.
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
ยซ   2025/05   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ
๋ฐ˜์‘ํ˜•