project

๐Ÿ“Œ Three.js์—์„œ ์กฐ๋ช…๊ณผ ๊ทธ๋ฆผ์ž ์ ์šฉํ•˜๊ธฐ – ๋” ํ˜„์‹ค์ ์ธ 3D ์”ฌ ๋งŒ๋“ค๊ธฐ

octo54 2025. 3. 29. 01:57
๋ฐ˜์‘ํ˜•

๐Ÿ“Œ Three.js์—์„œ ์กฐ๋ช…๊ณผ ๊ทธ๋ฆผ์ž ์ ์šฉํ•˜๊ธฐ – ๋” ํ˜„์‹ค์ ์ธ 3D ์”ฌ ๋งŒ๋“ค๊ธฐ

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


๐Ÿš€ 1. Three.js์—์„œ ์กฐ๋ช…์ด ์ค‘์š”ํ•œ ์ด์œ 

3D ์”ฌ์—์„œ ์กฐ๋ช…์€ ์ž…์ฒด๊ฐ๊ณผ ์‚ฌ์‹ค๊ฐ์„ ๊ฒฐ์ •ํ•˜๋Š” ํ•ต์‹ฌ ์š”์†Œ์ž…๋‹ˆ๋‹ค.
Three.js๋Š” ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์กฐ๋ช…์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ด๋ฅผ ์ ์ ˆํžˆ ์กฐํ•ฉํ•˜๋ฉด ๋”์šฑ ๋ฆฌ์–ผํ•œ 3D ํ™˜๊ฒฝ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โœ… Three.js์—์„œ ์ง€์›ํ•˜๋Š” ์กฐ๋ช… ์ข…๋ฅ˜

์กฐ๋ช… ์ข…๋ฅ˜ ์„ค๋ช…

AmbientLight ์ „์ฒด์ ์œผ๋กœ ๋ถ€๋“œ๋Ÿฌ์šด ๋น›์„ ์ถ”๊ฐ€
DirectionalLight ํƒœ์–‘๊ด‘์ฒ˜๋Ÿผ ์ง์„ ์œผ๋กœ ๋น›์„ ์จ
PointLight ์ „๊ตฌ์ฒ˜๋Ÿผ ๋ชจ๋“  ๋ฐฉํ–ฅ์œผ๋กœ ๋น›์„ ๋ฐฉ์ถœ
SpotLight ํŠน์ • ๋ฐฉํ–ฅ์œผ๋กœ ๋น›์„ ์ง‘์ค‘์‹œํ‚ด
HemisphereLight ํ•˜๋Š˜๊ณผ ๋•…์˜ ์ƒ‰์ƒ์„ ๋ฐ˜์˜ํ•˜๋Š” ์กฐ๋ช…

๐Ÿ›  2. Three.js ์”ฌ์— ์กฐ๋ช… ์ถ”๊ฐ€ํ•˜๊ธฐ

โœ… 1) ๊ธฐ๋ณธ ์กฐ๋ช… ์„ค์ •

๐Ÿ“Œ src/components/LightingScene.jsx

import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import { useRef } from "react";
import * as THREE from "three";

const LightingScene = () => {
  const pointLightRef = useRef();

  return (
    <Canvas camera={{ position: [0, 3, 5], fov: 50 }}>
      {/* ๊ธฐ๋ณธ ํ™˜๊ฒฝ๊ด‘ (Ambient Light) */}
      <ambientLight intensity={0.3} />

      {/* ํƒœ์–‘๊ด‘ ๊ฐ™์€ ์กฐ๋ช… (Directional Light) */}
      <directionalLight position={[5, 5, 5]} intensity={1} />

      {/* ์ „๊ตฌ ์กฐ๋ช… (Point Light) */}
      <pointLight ref={pointLightRef} position={[2, 2, 2]} intensity={1} />

      {/* ๊ทธ๋ฆผ์ž ํ™œ์„ฑํ™” */}
      <mesh receiveShadow castShadow>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="orange" />
      </mesh>

      {/* ๋ฐ”๋‹ฅ ์ถ”๊ฐ€ */}
      <mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial color="gray" />
      </mesh>

      <OrbitControls />
    </Canvas>
  );
};

export default LightingScene;

๐Ÿ“Œ ์„ค๋ช…

  • ambientLight๋กœ ์ „์ฒด์ ์ธ ์กฐ๋ช…์„ ์ถ”๊ฐ€
  • directionalLight๋ฅผ ์‚ฌ์šฉํ•ด ๊ทธ๋ฆผ์ž๊ฐ€ ์ƒ๊ธฐ๋„๋ก ์„ค์ •
  • pointLight๋กœ ์ „๊ตฌ ๊ฐ™์€ ํšจ๊ณผ ๊ตฌํ˜„
  • receiveShadow์™€ castShadow ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ทธ๋ฆผ์ž ํ™œ์„ฑํ™”

๐ŸŽฏ 3. Three.js์—์„œ ๊ทธ๋ฆผ์ž ์„ค์ •ํ•˜๊ธฐ

Three.js์—์„œ ๊ทธ๋ฆผ์ž๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋ ค๋ฉด ๋ช‡ ๊ฐ€์ง€ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

โœ… 1) ๊ทธ๋ฆผ์ž ํ™œ์„ฑํ™”

๋ฐ˜์‘ํ˜•

Three.js์—์„œ๋Š” Renderer์™€ Light์—์„œ ๊ทธ๋ฆผ์ž๋ฅผ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

<Canvas shadows camera={{ position: [0, 3, 5], fov: 50 }}>
<directionalLight castShadow position={[5, 5, 5]} intensity={1} />
<mesh receiveShadow castShadow>
  <boxGeometry args={[1, 1, 1]} />
  <meshStandardMaterial color="orange" />
</mesh>

โœ… 2) ๋ฐ”๋‹ฅ์— ๊ทธ๋ฆผ์ž ๋ฐ›๊ธฐ

๋ฐ”๋‹ฅ์ด ๊ทธ๋ฆผ์ž๋ฅผ ๋ฐ›์œผ๋ ค๋ฉด receiveShadow๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

<mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
  <planeGeometry args={[10, 10]} />
  <meshStandardMaterial color="gray" />
</mesh>

๐Ÿ— 4. ๊ทธ๋ฆผ์ž ํ’ˆ์งˆ ๊ฐœ์„ ํ•˜๊ธฐ

๊ธฐ๋ณธ ๊ทธ๋ฆผ์ž๋Š” ํ๋ฆฌ๊ฒŒ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋” ์„ ๋ช…ํ•˜๊ฒŒ ๋งŒ๋“ค๋ ค๋ฉด ๋‹ค์Œ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ DirectionalLight ๊ทธ๋ฆผ์ž ํ•ด์ƒ๋„ ๊ฐœ์„ 

const directionalLight = useRef();

useEffect(() => {
  if (directionalLight.current) {
    directionalLight.current.shadow.mapSize.width = 2048;
    directionalLight.current.shadow.mapSize.height = 2048;
    directionalLight.current.shadow.camera.near = 0.5;
    directionalLight.current.shadow.camera.far = 50;
  }
}, []);

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด ๊ทธ๋ฆผ์ž๊ฐ€ ๋” ์„ ๋ช…ํ•˜๊ณ  ์‚ฌ์‹ค์ ์œผ๋กœ ํ‘œํ˜„๋ฉ๋‹ˆ๋‹ค.


๐ŸŽ› 5. ์กฐ๋ช… ๋ฐ ๊ทธ๋ฆผ์ž ์‹ค์ „ ์ ์šฉ

โœ… 1) 3D ๋ชจ๋ธ๊ณผ ์กฐ๋ช… ๊ฒฐํ•ฉ

์ด์ œ ์กฐ๋ช…์„ ์ถ”๊ฐ€ํ•œ ์”ฌ์—์„œ 3D ๋ชจ๋ธ์„ ๋กœ๋”ฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ src/components/LightingModelViewer.jsx

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

const Model = () => {
  const { scene } = useGLTF("/models/robot.glb");
  return <primitive object={scene} scale={1.5} />;
};

const LightingModelViewer = () => {
  return (
    <Canvas shadows camera={{ position: [0, 3, 5], fov: 50 }}>
      <ambientLight intensity={0.3} />
      <directionalLight castShadow position={[5, 5, 5]} intensity={1} />
      <pointLight position={[2, 2, 2]} intensity={1} />

      {/* 3D ๋ชจ๋ธ */}
      <Model />

      {/* ๋ฐ”๋‹ฅ */}
      <mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial color="gray" />
      </mesh>

      <OrbitControls />
    </Canvas>
  );
};

export default LightingModelViewer;

โœ… 6. ์ตœ์ข… ๊ฒฐ๊ณผ

โœ” AmbientLight, DirectionalLight, PointLight ์ถ”๊ฐ€
โœ” 3D ์˜ค๋ธŒ์ ํŠธ ๋ฐ ๋ฐ”๋‹ฅ ๊ทธ๋ฆผ์ž ์„ค์ •
โœ” ๊ทธ๋ฆผ์ž ํ’ˆ์งˆ ํ–ฅ์ƒ (mapSize ์„ค์ •)
โœ” 3D ๋ชจ๋ธ์„ ์กฐ๋ช…๊ณผ ํ•จ๊ป˜ ๋ฐฐ์น˜

๐Ÿš€ ์ด์ œ npm run dev๋กœ ์‹คํ–‰ํ•˜๋ฉด ์กฐ๋ช…์ด ์ถ”๊ฐ€๋œ 3D ์”ฌ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!


๐Ÿ”ฅ 7. ๋‹ค์Œ ๋‹จ๊ณ„: ์„ฑ๋Šฅ ์ตœ์ ํ™” & ๋กœ๋”ฉ ์†๋„ ๊ฐœ์„ 

์ง€๊ธˆ๊นŒ์ง€ ์šฐ๋ฆฌ๋Š” Three.js์—์„œ ์กฐ๋ช…์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๊ทธ๋ฆผ์ž๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ธฐ๋ฒ• (Lazy Loading, Draco ์••์ถ•, PostProcessing) ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ๋”์šฑ ๋ถ€๋“œ๋Ÿฝ๊ณ  ๋น ๋ฅด๊ฒŒ ๋กœ๋”ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃฐ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

โœ… ๋‹ค์Œ ํŽธ ์˜ˆ๊ณ :
"Three.js์—์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™” & ๋กœ๋”ฉ ์†๋„ ๊ฐœ์„ ํ•˜๊ธฐ" ๐Ÿš€