Files
2nd/10_Wiki/Topics/Coding/Frontend_Three_R3F.md
T
2026-05-09 21:08:02 +09:00

6.9 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
frontend-three-r3f Three.js / React Three Fiber — 3D 웹 Coding draft B conceptual 2026-05-09 2026-05-09
frontend
3d
threejs
r3f
vibe-coding
language applicable_to
TS / React
Frontend
Three.js
React Three Fiber
drei
Rapier
Spline
WebGL

Three.js / R3F (React Three Fiber)

Web 3D 표준. Three.js (vanilla) / React Three Fiber (React) + drei (helpers) + Rapier (physics). WebGL → WebGPU 미래.

📖 핵심 개념

  • Scene: 3D 공간.
  • Mesh: geometry + material.
  • Camera: 시점.
  • Light: 조명.
  • Renderer: WebGL / WebGPU.

💻 코드 패턴

기본 R3F

import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Box } from '@react-three/drei';
import { useRef } from 'react';

function RotatingBox() {
  const ref = useRef<THREE.Mesh>(null);
  useFrame((_, delta) => {
    ref.current!.rotation.y += delta;
  });
  return (
    <Box ref={ref} args={[1, 1, 1]}>
      <meshStandardMaterial color="orange" />
    </Box>
  );
}

export function Scene() {
  return (
    <Canvas camera={{ position: [3, 3, 5] }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} intensity={1} />
      <RotatingBox />
      <OrbitControls />
    </Canvas>
  );
}

drei (helpers)

import {
  OrbitControls, Environment, ContactShadows, Float,
  Text, Html, useGLTF, useTexture, PerspectiveCamera, Stage,
} from '@react-three/drei';

<Canvas>
  <Stage>
    <Float speed={2} rotationIntensity={0.5}>
      <mesh>...</mesh>
    </Float>
  </Stage>
  
  <Text fontSize={1} color="white">Hello 3D</Text>
  
  <Html position={[0, 2, 0]}>
    <div>HTML overlay</div>
  </Html>
</Canvas>

GLTF 모델 로드

function Model() {
  const { scene, animations } = useGLTF('/model.glb');
  return <primitive object={scene} />;
}

// Optimization — useGLTF preload
useGLTF.preload('/model.glb');

→ DRACO compression 자동 (drei 가).

Animation (모델)

import { useAnimations } from '@react-three/drei';

function AnimatedModel() {
  const ref = useRef<THREE.Group>(null);
  const { scene, animations } = useGLTF('/dancer.glb');
  const { actions } = useAnimations(animations, ref);
  
  useEffect(() => {
    actions.idle?.play();
  }, []);
  
  return <primitive ref={ref} object={scene} />;
}

Physics (Rapier)

import { Physics, RigidBody } from '@react-three/rapier';

<Canvas>
  <Physics gravity={[0, -9.81, 0]}>
    <RigidBody>
      <Box position={[0, 5, 0]} />
    </RigidBody>
    <RigidBody type="fixed">
      <Plane args={[10, 10]} rotation={[-Math.PI / 2, 0, 0]} />
    </RigidBody>
  </Physics>
</Canvas>

Material

<mesh>
  <boxGeometry args={[1, 1, 1]} />
  <meshStandardMaterial
    color="hotpink"
    metalness={0.5}
    roughness={0.3}
    map={texture}
  />
</mesh>

// MeshPhysicalMaterial — 더 사실적
<meshPhysicalMaterial
  clearcoat={1}
  transmission={0.5}    // 반투명 / glass
  thickness={0.5}
  ior={1.5}
/>

Shader (custom GLSL)

// vertex.glsl
varying vec2 vUv;
void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

// fragment.glsl
varying vec2 vUv;
uniform float uTime;
void main() {
    vec3 color = vec3(sin(vUv.x + uTime), sin(vUv.y + uTime), 1.0);
    gl_FragColor = vec4(color, 1.0);
}
<mesh>
  <planeGeometry args={[2, 2]} />
  <shaderMaterial
    vertexShader={vertexShader}
    fragmentShader={fragmentShader}
    uniforms={{ uTime: { value: 0 } }}
  />
</mesh>

Post-processing

import { EffectComposer, Bloom, ChromaticAberration } from '@react-three/postprocessing';

<Canvas>
  <Scene />
  <EffectComposer>
    <Bloom intensity={1.5} luminanceThreshold={0.5} />
    <ChromaticAberration offset={[0.001, 0.001]} />
  </EffectComposer>
</Canvas>

Performance

// Instances (수천 mesh)
import { Instances, Instance } from '@react-three/drei';

<Instances limit={1000}>
  <boxGeometry />
  <meshStandardMaterial />
  {data.map((d, i) => (
    <Instance key={i} position={d.pos} color={d.color} />
  ))}
</Instances>
// LOD (Level of Detail)
import { Detailed } from '@react-three/drei';

<Detailed distances={[0, 10, 20]}>
  <HighDetailMesh />
  <MidDetailMesh />
  <LowDetailMesh />
</Detailed>
// Frustum culling 자동
// Manual: ref.current.frustumCulled = true (default)

// Shadows — 비싸. 작은 set 만
<directionalLight castShadow shadow-mapSize={[1024, 1024]} />
<mesh receiveShadow castShadow>

Mobile / WebGL fallback

// 작은 화면 / GPU = quality down
const isMobile = useMediaQuery('(max-width: 768px)');
<Canvas dpr={isMobile ? 1 : 2} shadows={!isMobile}>

WebGPU (미래)

import { WebGPURenderer } from 'three/examples/jsm/renderers/webgpu/WebGPURenderer.js';

<Canvas
  gl={(canvas) => new WebGPURenderer({ canvas })}
>

→ WebGL 보다 빠름. 2025+ stable.

React-spring + R3F

import { useSpring, animated } from '@react-spring/three';

const { position } = useSpring({ position: hovered ? [0, 1, 0] : [0, 0, 0] });
<animated.mesh position={position} ... />

화면 → 3D coordinates

import { useThree } from '@react-three/fiber';

function ClickToWorld() {
  const { camera, raycaster, mouse } = useThree();
  
  const handle = (e: MouseEvent) => {
    raycaster.setFromCamera(mouse, camera);
    // raycaster.ray 로 intersection
  };
}

Spline (no-code 3D)

import Spline from '@splinetool/react-spline';

<Spline scene="https://prod.spline.design/xxx/scene.splinecode" />

→ Designer 가 Spline app 에서 만들고 R3F 가 import.

Bundle size 주의

Three.js: ~600KB (큰)
+ drei, postprocessing, rapier ...

→ Dynamic import + code split
const Scene = lazy(() => import('./Scene'));
<Suspense fallback={<Loader />}>
  <Scene />
</Suspense>

🤔 의사결정 기준

상황 추천
Product showcase R3F + drei + GLTF
Game R3F + Rapier physics
Data viz R3F + custom shader
Designer 만든 scene Spline
매우 simple (1-2 model)
강력 / vanilla Three.js direct

안티패턴

  • 모든 frame setState: re-render. useFrame 안 ref.
  • Shadow 모든 light + 큰 mapSize: 60fps 깨짐.
  • 너무 많은 mesh (1000+): Instances.
  • 모델 압축 안 함 (GLB 100MB): load 느림. DRACO + Meshopt.
  • WebGL fallback 없음 + GPU 약: blank 화면.
  • Mobile 무 dpr 2: 발열.
  • Memory leak (useEffect cleanup 없음): GPU resources 안 release.

🤖 LLM 활용 힌트

  • R3F + drei + Rapier 가 표준 stack.
  • GLTF + DRACO compression.
  • Instances / LOD / shadow 절제.
  • Suspense + lazy load.

🔗 관련 문서