Skip to content
Published on

웹의 3D 개발 2026 — Three.js·R3F·WebGPU·Gaussian Splatting까지 (현대 3D 웹 스택 심층 가이드)

Authors

프롤로그 — WebGL은 어른이 되었고, WebGPU는 어제 일이 되었다

2010년대 후반, 웹에서 3D를 한다는 건 "WebGL"을 한다는 뜻이었다. Three.js가 그 위에 사람답게 쓸 수 있는 추상을 얹었고, 우리는 GLSL 셰이더를 두 벌(WebGL용·WebGPU용)로 유지하며 살았다.

2026년 1월, 그 풍경이 완전히 바뀌었다. Safari 26이 macOS Tahoe와 iOS에서 WebGPU를 정식 출시하면서, WebGPU는 Baseline이 되었다. Chrome·Edge·Firefox·Safari 모두 기본으로 켜져 있고, 전역 커버리지는 약 95%다. 나머지 5%는 Three.js의 자동 폴백이 WebGL 2로 처리한다.

이 한 줄로 끝나는 변화가 아니다.

  • Three.js r182(2025년 12월 릴리스)가 WebGPURenderer권장 렌더러로 올렸다.
  • TSL(Three Shading Language) — 한 번 짠 셰이더가 WGSL과 GLSL로 동시에 컴파일된다. 두 벌 유지가 끝났다.
  • React Three Fiber v9이 비동기 gl prop을 받아 WebGPU 초기화를 자연스럽게 처리한다.
  • Gaussian Splatting이 폴리곤 메시와 나란히 — 폴리곤 없이 — 사진실적 씬을 실시간으로 그리는 새로운 표현이 되었다.
  • Meshy·Tripo·Rodin은 텍스트 한 줄로 PBR 텍스처 입은 메시를 뽑는다.

이 글은 2026년의 웹 3D 스택을 한 호흡으로 정리한다. 처음 씬을 띄우는 코드부터 R3F·WebGPU·gsplat·AI 3D까지, 그리고 그 사이에서 "어디서 어떤 도구를 쓸지" 결정하는 매트릭스까지.


1장 · 렌더링 파이프라인 — 3D는 어떻게 그려지는가

먼저 그림 한 장. 도구를 알기 전에, 도구가 무엇을 하는지부터 봐야 한다.

[Scene Graph]
   |  (mesh / light / camera 트리)
   v
[CPU: JS] -- 매트릭스·컬링·정렬 ----+
                                    |
                                    v
                              [Draw Call]
                                    |
   GPU 파이프라인 ──────────────────┴────────────────
   |                                                |
   v                                                v
 Vertex Shader     ->     Rasterizer    ->    Fragment Shader
 (정점 변환)              (픽셀로 자르기)        (픽셀 색)
   |                                                |
   v                                                v
                Z-buffer / Blend / Output
                            |
                            v
                       [Framebuffer]
                            |
                            v
                       <canvas>

이 파이프라인의 어디를 누가 책임지는가가 곧 스택 선택의 기준이 된다.

  • CPU 쪽 — 씬 그래프, 변환 행렬, 컬링, 정렬. 여기서 끝까지 책임지는 건 Three.js다. R3F는 그 위에 React식 선언으로 얹는다.
  • 드로우 콜 — GPU로 던지는 명령 단위. 이게 적을수록 빠르다. 인스턴싱·머지·아틀라스가 다 이걸 줄이는 기술이다.
  • 셰이더(Vertex·Fragment) — GPU에서 도는 작은 프로그램. WebGL은 GLSL, WebGPU는 WGSL. TSL이 둘을 하나로 묶는다.
  • 출력 합성 — Z-buffer·블렌딩·후처리(post-processing).

기억할 한 줄: "드로우 콜이 모든 것의 절반이고, 셰이더가 나머지 절반이다."


2장 · Three.js — 웹 3D의 사실상 표준

숫자부터. Three.js는 npm 주간 다운로드 270만으로 Babylon.js의 약 270배, PlayCanvas의 약 337배다. "사실상 표준"이 아니라 그냥 표준이다.

가장 작은 씬을 띄우는 코드. 세 가지가 필요하다: 씬·카메라·렌더러, 그리고 안에 들어갈 메시.

import * as THREE from 'three'

// 1. 씬 — 모든 것이 들어가는 컨테이너
const scene = new THREE.Scene()

// 2. 카메라 — 어디서 보는가
const camera = new THREE.PerspectiveCamera(
  75,                                  // fov(시야각, degree)
  window.innerWidth / window.innerHeight,
  0.1,                                 // near clip
  1000                                 // far clip
)
camera.position.z = 5

// 3. 렌더러 — 2026년 기본은 WebGPURenderer
import { WebGPURenderer } from 'three/webgpu'
const renderer = new WebGPURenderer({ antialias: true })
await renderer.init()                 // 비동기 초기화 — 중요
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

// 4. 메시 = Geometry + Material
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshStandardMaterial({ color: 0x44aa88 })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)

// 5. 라이트 — 표준 머티리얼은 빛이 없으면 검정으로 보인다
scene.add(new THREE.AmbientLight(0xffffff, 0.4))
const dir = new THREE.DirectionalLight(0xffffff, 1.0)
dir.position.set(5, 10, 7.5)
scene.add(dir)

// 6. 루프 — requestAnimationFrame이 아니라 setAnimationLoop
renderer.setAnimationLoop(() => {
  cube.rotation.x += 0.01
  cube.rotation.y += 0.01
  renderer.render(scene, camera)
})

세 가지를 짚는다.

  1. await renderer.init() — WebGPU는 비동기다. 이 한 줄을 잊으면 첫 프레임이 검정이다.
  2. MeshStandardMaterial은 라이트가 필요하다 — 화면이 검정이면 라이트부터 의심한다.
  3. setAnimationLooprequestAnimationFrame 대신. WebXR이 자동으로 잡힌다.

이게 모든 Three.js 코드의 뼈대다. 나머지는 이 위에 얹는 것이다.


3장 · React Three Fiber + drei — React식 3D

명령형 코드는 작을 때는 깔끔하지만, 씬이 50개 노드를 넘어가면 빠르게 누더기가 된다. React Three Fiber(R3F) 는 Three.js를 React 컴포넌트 트리로 표현한다.

같은 큐브를 R3F로 다시 쓰면:

import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment } from '@react-three/drei'

function Cube() {
  return (
    <mesh rotation={[0, 0.4, 0]}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="hotpink" />
    </mesh>
  )
}

export default function Scene() {
  return (
    <Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
      <ambientLight intensity={0.4} />
      <directionalLight position={[5, 10, 7.5]} intensity={1} />
      <Cube />
      <OrbitControls />
      <Environment preset="city" />
    </Canvas>
  )
}

같은 일을 한다. 그런데:

  • 씬 그래프가 React 트리다. 조건부 렌더링·상태 관리·Hooks가 그대로 통한다.
  • <Canvas> 는 리사이즈·렌더 루프·픽셀 비율을 알아서 잡는다.
  • drei — Poimandres 팀의 헬퍼 라이브러리. OrbitControls·Environment·useGLTF·Html·Text 같은 매일 쓰는 것들이 다 있다.

R3F v9의 WebGPU — 비동기 gl prop

R3F v9이 gl prop을 비동기 팩토리로 받을 수 있게 됐다. WebGPU 초기화가 자연스럽게 묶인다.

import { Canvas } from '@react-three/fiber'
import { WebGPURenderer } from 'three/webgpu'

<Canvas
  gl={async (props) => {
    const renderer = new WebGPURenderer(props)
    await renderer.init()
    return renderer
  }}
>
  {/* ...씬... */}
</Canvas>

2026년 5월 현재, R3F의 WebGPU 통합은 아직 완전히 매끄럽지는 않지만 — Poimandres 팀이 적극적으로 다듬는 중이고 — 위 패턴은 프로덕션에서 충분히 돈다. WebGL 2 폴백이 필요하면 WebGLRenderer를 던지면 된다.

useFrame — 매 프레임 훅

R3F의 가장 React스러운 부분. 컴포넌트가 매 프레임 호출되는 콜백을 등록한다.

import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'

function Spinner() {
  const ref = useRef(null)
  useFrame((state, delta) => {
    if (ref.current) ref.current.rotation.y += delta
  })
  return (
    <mesh ref={ref}>
      <torusKnotGeometry args={[1, 0.3, 128, 32]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  )
}

delta는 이전 프레임으로부터의 초 단위 경과. 프레임률에 안 흔들리는 애니메이션의 출발점.


4장 · WebGPU와 TSL — 셰이더 두 벌이 한 벌로

WebGL과 WebGPU의 차이를 한 문장: WebGL은 OpenGL ES 2.0의 웹 포팅, WebGPU는 Vulkan·Metal·DX12 시대의 현대적 GPU API.

실무적으로 무엇이 달라지는가:

  • 드로우 콜 비용이 낮다 — 드로우 콜이 많은 씬(파티클·인스턴스 다수)에서 2~10배 빨라진다.
  • 컴퓨트 셰이더가 1급 시민 — GPGPU(파티클·물리·시뮬레이션·포스트프로세싱)가 메인 파이프라인 안에서 자연스럽다.
  • WGSL — WebGL의 GLSL 대신 새로운 셰이더 언어.

마지막이 항상 골치였다. WebGL 시대에 GLSL을 짰는데, WebGPU로 가려면 WGSL로 다시 써야 했다. TSL이 이걸 끝낸다.

TSL = Three Shading Language. 노드 기반 셰이더 추상. 한 번 짜면 Three.js가 내부적으로 WGSL(WebGPU용)·GLSL(WebGL용) 양쪽으로 컴파일한다.

간단한 노이즈 셰이더(머티리얼 색을 노이즈로 흔드는 예):

import { MeshStandardNodeMaterial } from 'three/webgpu'
import { uniform, vec3, mix, sin, time, positionLocal } from 'three/tsl'

const speed = uniform(1.0)
const wave  = sin(positionLocal.y.mul(8.0).add(time.mul(speed)))
const color = mix(vec3(0.1, 0.4, 0.9), vec3(1.0, 0.4, 0.2), wave.mul(0.5).add(0.5))

const material = new MeshStandardNodeMaterial()
material.colorNode = color

positionLocal·time·mix·sin 다 노드다. JS로 셰이더를 조립한다. GLSL 텍스트도 WGSL 텍스트도 손으로 안 짠다.

작은 WGSL 한 조각이 어떻게 생겼는지만 참고로 (TSL이 내부에서 비슷한 걸 만든다):

@fragment
fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
  let c = mix(vec3(0.1, 0.4, 0.9), vec3(1.0, 0.4, 0.2), sin(uv.y * 8.0) * 0.5 + 0.5);
  return vec4(c, 1.0);
}

요는 — 2026년에는 직접 짤 일이 거의 없다. TSL이 처리한다. WGSL을 한 번 읽어두는 정도면 충분하다.


5장 · glTF — 3D의 JPEG

폴리곤 모델을 웹으로 옮기는 표준은 glTF 2.0이다. "3D의 JPEG"라고 부른다. PBR 머티리얼·애니메이션·스킨·드라코 압축까지 한 파일에 들어간다.

Three.js의 로더:

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'

const draco = new DRACOLoader()
draco.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')

const loader = new GLTFLoader()
loader.setDRACOLoader(draco)

loader.load('/models/robot.glb', (gltf) => {
  scene.add(gltf.scene)
  // gltf.animations 에는 AnimationClip 배열이 들어있다
})

R3F + drei로는 한 줄이다:

import { useGLTF } from '@react-three/drei'

function Robot() {
  const { scene } = useGLTF('/models/robot.glb')
  return <primitive object={scene} />
}
useGLTF.preload('/models/robot.glb')

useGLTF는 Suspense로 묶이고, 같은 모델을 여러 곳에서 쓰면 한 번만 로드한다. preload로 미리 받아둘 수도 있다.

glTF 최적화 — 3가지만 기억

  1. Draco 압축 — 정점 데이터를 압축. 파일 크기 5~10배 작아진다.
  2. KTX2 / Basis 텍스처 — JPG·PNG 대신 GPU가 바로 먹는 압축 텍스처. 메모리·로드 시간 절약.
  3. gltf-transform CLI — 위 둘을 한 번에 해주는 도구. CI에 박아두면 더 이상 신경 안 써도 된다.
npx @gltf-transform/cli optimize input.glb output.glb \
  --texture-compress webp --simplify 0.5

6장 · 애니메이션 — 클립부터 스프링까지

3D 애니메이션은 크게 세 갈래다.

  1. glTF에 들어있는 본/스킨 애니메이션 — Blender·Maya에서 만든 걸 그대로 재생.
  2. 수학으로 도는 애니메이션useFrame 안에서 회전·트랜슬레이션.
  3. 인터랙션 기반 — 호버·드래그·스크롤에 반응. 보통 스프링 물리로.

glTF 클립 재생

import { useAnimations, useGLTF } from '@react-three/drei'
import { useEffect } from 'react'

function Robot() {
  const { scene, animations } = useGLTF('/models/robot.glb')
  const { actions, names } = useAnimations(animations, scene)
  useEffect(() => {
    actions[names[0]]?.reset().fadeIn(0.3).play()
  }, [actions, names])
  return <primitive object={scene} />
}

스프링 애니메이션 — react-spring/three

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

function Box({ hovered }) {
  const { scale } = useSpring({ scale: hovered ? 1.3 : 1.0 })
  return (
    <animated.mesh scale={scale}>
      <boxGeometry />
      <meshStandardMaterial />
    </animated.mesh>
  )
}

값이 튀지 않고 물리적으로 자연스럽게 보간된다. 포트폴리오·랜딩 페이지의 미세한 디테일에 결정적이다.


7장 · 포스트프로세싱 — 한 끗 차이의 마감

같은 씬이라도 블룸·SSAO·필름 그레인 한 번 거치면 영상미가 다른 차원이 된다. 표준 라이브러리는 postprocessing (Vanruesc), R3F 래퍼는 @react-three/postprocessing.

import { EffectComposer, Bloom, DepthOfField, Vignette } from '@react-three/postprocessing'

<Canvas>
  {/* ...씬... */}
  <EffectComposer>
    <Bloom intensity={1.2} luminanceThreshold={0.6} mipmapBlur />
    <DepthOfField focusDistance={0} focalLength={0.02} bokehScale={2} />
    <Vignette eskil={false} offset={0.1} darkness={1.0} />
  </EffectComposer>
</Canvas>

조심할 점: 포스트프로세싱은 풀스크린 패스다 — 픽셀이 많을수록 비싸다. 모바일에서는 항상 pixelRatio를 캡(보통 1.5~2.0)하고, 효과 두세 개만 고른다.


8장 · 성능 — 드로우 콜이 모든 것의 절반

3D 웹 성능은 거의 항상 드로우 콜 수셰이더 비용 두 곳에서 갈린다. 2026년의 핵심 패턴 다섯.

1. 인스턴싱 — 같은 메시 1만 개를 한 번에

같은 지오메트리·머티리얼의 메시를 여러 개 그려야 할 때(나무·풀·박스 더미), 인스턴싱을 쓰면 드로우 콜이 1로 줄어든다.

import { Instances, Instance } from '@react-three/drei'

<Instances limit={10000}>
  <boxGeometry args={[1, 1, 1]} />
  <meshStandardMaterial color="white" />
  {positions.map((p, i) => (
    <Instance key={i} position={p} />
  ))}
</Instances>

WebGPU는 인스턴싱 비용이 더 낮다. WebGL에서 5천 인스턴스가 한계였다면, WebGPU에서는 5만이 종종 돈다.

2. 프러스텀 컬링·LOD

Three.js는 기본으로 카메라 시야 밖 객체를 그리지 않는다(프러스텀 컬링). 단, Mesh.frustumCulled는 기본 true니까 끄지 말 것. LOD(Level of Detail)는 카메라 거리에 따라 메시 해상도를 바꾼다.

import { Detailed } from '@react-three/drei'

<Detailed distances={[0, 10, 50]}>
  <HighPolyMesh />
  <MidPolyMesh />
  <LowPolyMesh />
</Detailed>

3. 머티리얼·지오메트리 공유

같은 머티리얼·지오메트리는 메모리에서 단 한 벌만. R3F에서는 컴포넌트 바깥에서 만들어 공유한다.

4. 텍스처 — KTX2와 mipmap

JPG·PNG는 CPU에서 디코드 후 GPU로 업로드. KTX2(Basis Universal)는 GPU가 압축 그대로 먹는다. 로드는 빠르고, VRAM도 적게 쓴다.

5. pixelRatio

레티나에서 devicePixelRatio가 3이면 화면 픽셀이 9배다. 항상 캡한다.

<Canvas dpr={[1, 2]}>  {/* 최소 1, 최대 2 */}

9장 · WebXR — VR·AR을 웹으로

setAnimationLoop 한 줄과 WebXRManager 덕에 Three.js의 WebXR은 거의 공짜다. R3F에는 @react-three/xr이 있다.

import { XR, createXRStore, XROrigin } from '@react-three/xr'

const store = createXRStore()

<button onClick={() => store.enterVR()}>VR 진입</button>
<Canvas>
  <XR store={store}>
    <XROrigin />
    {/* ...씬... */}
  </XR>
</Canvas>

WebGPU + WebXR 조합은 2026년에 의외로 무겁지 않다 — Apple Vision Pro·Quest 3·Quest 3S 모두 WebGPU 기반 WebXR을 안정적으로 돌린다. 마케팅·교육·헬스케어 쪽에서 빠르게 채택 중.


10장 · Three.js vs Babylon.js vs PlayCanvas

엔진 비교는 짧게.

항목Three.jsBabylon.jsPlayCanvas
라이선스MITApache 2.0MIT (엔진)
강점거대한 생태계·예제·커뮤니티게임 기능 풍부(피직스·오디오·머티리얼 에디터)비주얼 에디터·클라우드 IDE
약점게임용 기능은 직접 조립생태계 작음코드 우선은 약함
WebGPUr182 기본 권장Babylon 7+에서 안정엔진 차원에서 지원
주간 다운로드(npm)~2.7M~10K~8K
대표 영역포트폴리오·제품·아트·시각화브라우저 게임·시뮬레이션광고·게임·구성 도구

한 줄 추천:

  • 창작·아트·포트폴리오·제품 시각화 → Three.js (+ R3F).
  • 게임형 인터랙션·물리 비중 큰 앱 → Babylon.js.
  • 에디터로 시각적 협업이 중요 → PlayCanvas.

생태계 크기가 깡패다. 모르면 Three.js다.


11장 · Gaussian Splatting — 폴리곤 없는 사진실적 3D

여기서부터 새로운 페이지다.

Gaussian Splatting(이하 gsplat) 은 폴리곤 메시가 아니다. 씬을 수백만 개의 작은 3D 가우시안 점들(각자 위치·색·투명도·방향성을 가진 타원체)로 표현한다. 카메라가 그 점들을 화면에 "스플랫"(투영해서 펴)으로써 이미지를 만든다.

폴리곤 메시:                     Gaussian Splatting:
  ┌── 정점·면 데이터              ┌── 수백만 개 가우시안
  ├── UV·텍스처                   │    (위치·SH 색·스케일·회전·α)
  ├── 노멀·머티리얼               ├── 텍스처 없음
  └── 라이트로 셰이딩             └── 캡처 당시의 라이팅이 굽혀짐

왜 흥분되는가?

  1. 사진실적 — 30장~수백 장의 사진/영상에서 학습. 출력이 사진과 거의 같다.
  2. 실시간 — GPU 친화적. 웹에서 60fps 가능.
  3. 메시 모델링 0 — Blender도, UV도, 텍스처도, 노멀도 필요 없다. 카메라만 있으면 된다.
  4. NeRF의 후계 — NeRF가 학술적 마일스톤이었다면, gsplat은 실무적 도구다(빠른 학습·실시간 렌더링).

한계도 분명하다

  • 라이팅이 "구워져" 있다 — 씬을 동적으로 비추기 어렵다.
  • 충돌·물리 시뮬레이션이 어렵다 — 메시가 아니니까.
  • 편집이 까다롭다 — SuperSplat 같은 전용 에디터가 필요.
  • 파일이 크다 — 수십~수백 MB.

요는 — 존재하는 공간을 통째로 캡처해서 보여주는 것에는 무적이다. 부동산·문화재·박물관·콘서트·이벤트·전시.

2026년의 도구 풍경

  • Polycam — 모바일 캡처의 시장 선도자. iOS LiDAR + 포토그래메트리 + gsplat. 평균 평점 4.7. iOS 리뷰 54만 개. 가장 쉬운 진입.
  • Luma AI — 클라우드 처리로 시각 품질이 가장 좋다고 평가되는 무료 gsplat 플랫폼. 임베드 가능.
  • SuperSplat — PlayCanvas 엔진 위에 만든 무료 오픈소스 브라우저 기반 gsplat 에디터. 라이브 어노테이션·핫스팟·후처리(블룸·비네트)·카메라 애니메이션·WebXR까지. HTML 뷰어로 내보내 GitHub Pages·Netlify·Vercel에 그대로 호스팅.
  • NeRF Studio — 연구 지향. 로컬 학습·실험에 강함.

웹에 띄우기 — @mkkellogg/gaussian-splats-3d

Three.js 호환의 가벼운 gsplat 뷰어. R3F 환경에서:

import { GaussianSplats3D } from '@mkkellogg/gaussian-splats-3d'
import { useThree } from '@react-three/fiber'
import { useEffect } from 'react'

function Splat({ url }) {
  const { scene, camera, gl } = useThree()
  useEffect(() => {
    const viewer = new GaussianSplats3D.Viewer({
      threeScene: scene,
      camera,
      renderer: gl,
      selfDrivenMode: false,
    })
    viewer.addSplatScene(url)
    return () => viewer.dispose()
  }, [url, scene, camera, gl])
  return null
}

브라우저에서 수백만 가우시안을 60fps로 그린다. 5년 전에는 SF였다.


12장 · AI로 3D를 만든다 — Meshy · Tripo · Rodin

마지막 한 갈래. 텍스트나 사진에서 3D 메시를 뽑는 AI. 2026년에는 더 이상 실험이 아니라 워크플로의 일부다.

세 강자.

  • Meshy 6 — 가장 균형 잡힌 제품. 텍스트→3D, 이미지→3D, PBR 텍스처, 토폴로지 제어, 폭넓은 익스포트. 생성 40~60초. "기본값 추천."
  • Tripo AI — 가장 빠른 생성(20~30초). 기본값이 영리해서 초기 진입 마찰이 가장 낮다. 텍스트·이미지 둘 다.
  • Rodin AI(Gen-2) — 100억 파라미터. 가장 높은 품질. 캐릭터·구조화된 자산에 강함. 생성 60~180초.

워크플로 예:

  1. 컨셉 단계 — Tripo에서 빠른 변형 30초.
  2. 마음에 드는 안 — Meshy에서 PBR 텍스처 깔끔하게 다시.
  3. 최종 캐릭터 — Rodin Gen-2로 고품질 메시.
  4. glTF로 내보내 Three.js/R3F 씬에 박는다.
Idea ─▶ Tripo (탐색)
         └─▶ Meshy (정제·PBR)
                  └─▶ Rodin (피니시·캐릭터)
                          └─▶ glTF
                               └─▶ R3F 씬에 useGLTF

중요한 현실: AI 생성 메시는 토폴로지가 깔끔하지 않다. 포트폴리오·시각화·게임 백그라운드에는 충분하지만, 리깅·애니메이션이 중요한 캐릭터는 보통 Blender에서 리토폴로지가 필요하다.


13장 · "무엇으로 무엇을 만들까" — 유스케이스 매트릭스

유스케이스추천 스택비고
개발자 포트폴리오R3F + drei + Bloomdrei의 Float/Text 한 줌으로 충분
제품 컨피규레이터R3F + glTF + KTX2색·텍스처 옵션은 머티리얼 교체
부동산 가상 투어gsplat (Luma·Polycam) + SuperSplat사진실적·실측 공간
박물관·전시gsplat + WebXR핫스팟·어노테이션
브라우저 게임Babylon.js 또는 Three.js + Rapier물리·충돌 필요
데이터 시각화R3F + 카메라 워크인스턴싱 적극 활용
AR 마케팅R3F + @react-three/xr + WebXRiOS Quick Look 병행
인터랙티브 아트Three.js + TSL (셰이더 직접)노드 셰이더 자유도
캐릭터 중심 인터랙션R3F + AI 생성(Rodin) + Mixamo 리타게팅토폴로지 정리 한 번
LiDAR 캡처 자산Polycam → glTF or gsplat모바일만으로 종결

룰 오브 섬:

  • "공간을 통째로 보여줘야 한다" → gsplat.
  • "조작할 수 있어야 한다(색·옵션·물리)" → 폴리곤(glTF) + R3F.
  • "조금 만들어 빨리 띄워야 한다" → R3F + AI 생성.

14장 · 포트폴리오 사이트 만들기 — 30분 레시피

가장 흔한 첫 프로젝트. 골격을 한 번에 본다.

import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment, Float, Text3D, useGLTF, ContactShadows } from '@react-three/drei'
import { EffectComposer, Bloom } from '@react-three/postprocessing'
import { Suspense } from 'react'

function Hero() {
  const { scene } = useGLTF('/hero.glb')
  return <primitive object={scene} scale={1.4} />
}

export default function Portfolio() {
  return (
    <Canvas camera={{ position: [0, 0, 6], fov: 50 }} dpr={[1, 2]}>
      <color attach="background" args={['#0a0a0a']} />
      <Suspense fallback={null}>
        <Environment preset="studio" />
        <Float speed={1.5} rotationIntensity={0.4} floatIntensity={0.8}>
          <Hero />
        </Float>
        <ContactShadows position={[0, -1.6, 0]} opacity={0.6} blur={2.4} />
      </Suspense>
      <OrbitControls enableZoom={false} />
      <EffectComposer>
        <Bloom intensity={0.8} mipmapBlur />
      </EffectComposer>
    </Canvas>
  )
}

체크리스트:

  • 모델은 Meshy/Tripo로 뽑거나, sketchfab CC0에서 받아 gltf-transform optimize 한 번.
  • 배경은 단색 + Environment HDR 한 장(스튜디오/시티).
  • Float·ContactShadows·Bloom으로 "AI 생성스러움"을 가린다.
  • dpr={[1, 2]} 로 레티나 폭주 막기.
  • 모바일에서는 Bloom을 끈다(미디어 쿼리 + 조건부 렌더).

에필로그 — 웹은 진짜로 3D가 되었다

2026년의 웹 3D는 더 이상 "한번 보고 마는 데모"의 자리가 아니다. 제품 페이지, 부동산, 박물관, 광고, 학습 도구가 일상적으로 3D를 깔고 있다. 그리고 그 일상의 도구는:

  • Three.js + R3F — 폴리곤 기반 표준 스택.
  • WebGPU + TSL — 셰이더 두 벌이 한 벌로.
  • Gaussian Splatting — 카메라만 있으면 사진실적 공간.
  • AI 3D 생성 — 텍스트 한 줄로 메시.

마지막으로 두 가지를 남긴다.

14개 항목 체크리스트

  1. WebGPURenderer를 기본으로 두고 WebGL 2 폴백을 확인했는가?
  2. renderer.init() 의 비동기 처리를 빠뜨리지 않았는가?
  3. glTF가 Draco·KTX2로 압축됐는가?
  4. 인스턴싱이 필요한 대량 메시를 그냥 그리고 있지 않은가?
  5. pixelRatio를 모바일에서 캡했는가?
  6. 프러스텀 컬링을 끄지 않았는가?
  7. 같은 머티리얼/지오메트리를 중복 생성하고 있지 않은가?
  8. 포스트프로세싱 효과 수를 모바일에서 줄였는가?
  9. Suspense로 로딩 UX를 잡았는가?
  10. WebXR 진입은 사용자 제스처(클릭) 안에서 호출하는가?
  11. gsplat 자산은 압축 포맷(SPZ·KSPLAT)으로 내보냈는가?
  12. AI 생성 메시의 토폴로지를 (필요하면) 한 번 정리했는가?
  13. setAnimationLoop 한 곳에서만 도는가(중복 루프 없음)?
  14. 첫 프레임 검정이 라이트 없음/init 누락은 아닌가?

안티패턴 10가지

  1. WebGPU 코드에 await renderer.init()을 빼고 첫 프레임이 검정.
  2. 라이트 없이 MeshStandardMaterial을 쓰고 검정.
  3. 같은 모델 인스턴스를 매 프레임 다시 로드.
  4. frustumCulled = false 를 그냥 켜둠.
  5. JPG·PNG를 압축 없이 그대로 GPU에.
  6. 데스크톱·모바일 같은 dpr 설정.
  7. 포스트프로세싱 5개를 모바일에서도 돌림.
  8. AI 생성 메시를 토폴로지 정리 없이 캐릭터 리깅에 쓰기.
  9. gsplat을 폴리곤 워크플로(편집·물리)로 다루려 함.
  10. useGLTF 대신 매 컴포넌트에서 GLTFLoader.load를 직접 호출.

다음 글 예고

다음 글 후보: WebGPU 컴퓨트 셰이더 실전 — GPGPU로 파티클 100만 개 돌리기, Gaussian Splatting 워크플로 — 캡처부터 웹 임베드까지, R3F + Rapier 물리 엔진 — 인터랙티브 3D 게임 한 시간 만에.

"웹은 진짜로 3D가 되었다. 폴리곤은 메시로, 사진은 가우시안으로, 텍스트는 AI로. 그 사이를 잇는 건 여전히 Three.js다."

— 웹의 3D 개발 2026, 끝.


참고 / References