본문 바로가기
Graphic/R3F

55 R3F Application 기초

by curious week 2025. 8. 15.

React Three Fiber(R3F)

1. 개요

Three.js를 다뤄보신 분들이라면 아시겠지만, 순수 Three.js로 프로젝트를 구성할 때는 다음과 같은 흐름을 직접 작성해야 합니다.

// Three.js 기본 구성 예시
import * as THREE from 'three'

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()

renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}
animate()

R3F는 이 모든 흐름을 React 컴포넌트 안에서 자동으로 처리해 줍니다.

비교 요약

기능 | Three.js | R3F

Scene 생성 new THREE.Scene() 직접 호출 <Canvas> 컴포넌트 자동 생성
Camera 설정 new THREE.PerspectiveCamera(...) <Canvas camera={{ ... }}> props
Renderer 생성 new THREE.WebGLRenderer() Canvas 내부에서 자동 생성 및 React 생명주기에 맞게 관리
렌더 루프 requestAnimationFrame 직접 작성 useFrame 훅 제공
이벤트 처리 Raycaster 직접 구현 onPointerDown, onClick 등 DOM 이벤트처럼 사용
상태 관리 전역 변수 또는 수동 관리 React state, props, context 사용 가능

2. 프로젝트 준비

2.1 React 프로젝트 생성

권장: Vite

npm create vite@latest my-r3f-app
cd my-r3f-app
npm install

2.2 R3F 설치

npm install three @react-three/fiber

3. R3F의 핵심 컴포넌트 — <Canvas>

R3F에서 <Canvas>는 Three.js의 Renderer + Scene + Camera 역할을 한 번에 담당합니다.

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

export default function App() {
  return (
    <Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
      {/* Scene 안에 들어갈 3D 객체 */}
    </Canvas>
  )
}

Canvas Props 예시

  • camera: 초기 카메라 설정 { position: [x,y,z], fov: number }
  • shadows: 그림자 활성화
  • gl: WebGLRenderer 옵션 { antialias: true, powerPreference: 'high-performance' }
  • frameloop: 'always' | 'demand' | 'never'

4. 첫 번째 Mesh 생성하기

4.1 Three.js에서 Mesh 생성

const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshStandardMaterial({ color: 'orange' })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

4.2 R3F에서 Mesh 생성

function Box() {
  return (
    <mesh>
      <boxGeometry args={[1, 1, 1]} /> {/* args는 생성자 매개변수 */}
      <meshStandardMaterial color="orange" />
    </mesh>
  )
}

export default function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 5, 5]} />
      <Box />
    </Canvas>
  )
}

차이점

  • Three.js: scene.add(mesh) 직접 호출
  • R3F: JSX 태그로 Scene 트리에 자동 추가
  • args: Three.js 생성자 인자를 배열 형태로 전달

5. 애니메이션 — useFrame

5.1 Three.js 방식

function animate() {
  requestAnimationFrame(animate)
  mesh.rotation.x += 0.01
  renderer.render(scene, camera)
}
animate()

5.2 R3F 방식

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

function RotatingBox() {
  const ref = useRef()

  useFrame((state, delta) => {
    ref.current.rotation.x += delta
    ref.current.rotation.y += delta
  })

  return (
    <mesh ref={ref}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="hotpink" />
    </mesh>
  )
}

차이점

  • Three.js: 렌더 루프 전체를 직접 제어
  • R3F: useFrame 훅으로 프레임별 업데이트만 제어, 렌더링 자체는 Canvas가 관리

6. 이벤트 처리 — Raycaster vs Pointer Events

6.1 Three.js

// 마우스 위치 계산 + Raycaster 사용

직접 Raycaster 인스턴스를 만들고, 마우스 좌표를 정규화해 객체 교차를 계산해야 함.

6.2 R3F

function InteractiveBox() {
  const [hovered, setHovered] = useState(false)

  return (
    <mesh
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
    >
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color={hovered ? 'skyblue' : 'orange'} />
    </mesh>
  )
}
  • DOM 이벤트처럼 사용 가능 (onPointerOver, onPointerOut, onClick 등)
  • 터치 디바이스까지 동일하게 처리됨

7. 카메라 컨트롤 — OrbitControls

Three.js에서는:

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const controls = new OrbitControls(camera, renderer.domElement)

R3F에서는:

import { useThree, extend, useFrame } from '@react-three/fiber';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

extend({ OrbitControls }); // extend는 three.js 클래스를 JSX에서 사용할 수 있도록 해줌.

export default function Experience() {
  const { camera, gl } = useThree();
  // const three = useThree();
  // three.camera: PerspectiveCamera
  // three.gl: WebGLRenderer, domElement 등 <canvas> 요소 
  // three.clock: 시계 인스턴스
  // 등 three js 요소를 가지고 있음.

  return (
    <>
      {/* orbitControls는 camera와 domElement 필요 */}
      <orbitControls args={[camera, gl.domElement]} />    
    </>
  );
}

또는:

npm install @react-three/drei
import { OrbitControls } from '@react-three/drei'

<Canvas>
  <OrbitControls enableZoom={true} />
  <mesh>...</mesh>
</Canvas>
  • Drei는 R3F 생태계에서 필수적인 유틸 라이브러리
  • 자동으로 camera와 renderer에 연결

8. 성능 최적화 팁

  • frameloop="demand": 장면이 바뀔 때만 렌더링
  • <mesh castShadow receiveShadow>: 그림자 최적화
  • useMemo: 지오메트리/머티리얼 캐싱
  • <Suspense>: 모델 로딩 시 비동기 처리

9. 확장 방향

  • Drei의 <Environment>로 HDRI 환경 조명 적용
  • <Html>로 3D 위치에 HTML 오버레이 배치
  • useGLTF로 glTF 모델 로딩
  • @react-three/postprocessing으로 후처리 효과 추가

10. 전체 템플릿 예시

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

function RotatingBox() {
  const ref = useRef()
  const [hovered, setHovered] = useState(false)

  useFrame((_, delta) => {
    ref.current.rotation.x += delta
    ref.current.rotation.y += delta
  })

  return (
    <mesh
      ref={ref}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
      castShadow
      receiveShadow
    >
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color={hovered ? 'skyblue' : 'orange'} />
    </mesh>
  )
}

export default function App() {
  return (
    <Canvas
      shadows
      camera={{ position: [3, 3, 3], fov: 60 }}
      style={{ height: '100vh', background: '#272730' }}
    >
      <ambientLight intensity={0.5} />
      <directionalLight
        position={[5, 5, 5]}
        castShadow
        shadow-mapSize-width={1024}
        shadow-mapSize-height={1024}
      />
      <OrbitControls />
      <RotatingBox />
    </Canvas>
  )
}

💡 정리

  • R3F는 Three.js의 기본 객체 생성, 렌더링 루프, 이벤트 처리를 React 컴포넌트로 추상화하여 생산성과 유지보수성을 크게 높여줍니다.
  • 기존 Three.js 지식을 그대로 활용하면서도, React의 장점(컴포넌트화, 상태 관리, props)을 결합할 수 있습니다.
  • 특히 Pointer 이벤트, useFrame 훅, Drei 유틸은 Three.js 순수 구현보다 훨씬 짧고 명확한 코드 작성이 가능하게 합니다.

 

1) index.jsx (엔트리 + <Canvas> 설정)

import './style.css';
import ReactDOM from 'react-dom/client';
import { Canvas } from '@react-three/fiber';
import Experience from './Experience';
import * as THREE from 'three';

const root = ReactDOM.createRoot(document.querySelector('#root'));

// ------------------------------
// 아래 블록은 "전통적인 three.js(명령형, imperative) 방식"을 JSX로 치환하기 전의 참고 예시입니다.
// R3F는 이 imperative 코드를 "선언형(declarative) JSX"로 바꿔주어, 상태/라이프사이클 관리를 React로 위임합니다.

// const mesh = new THREE.Mesh();
// mesh.position.set(1, 2, 3);
// mesh.rotation.x = 0.5;
// mesh.geometry = new THREE.BoxGeometry(1, 1, 1);
// mesh.material = new THREE.MeshBasicMaterial({ color: 'red' });

// scene.add(mesh);

// root.render(
//   <>
//     <mesh position={[1, 2, 3]} rotation-x={0.5}>
//       <boxGeometry />
//       <meshBasicMaterial color={'red'} />
//     </mesh>
//   </>,
// );

// -------------------------------
// 그룹도 동일한 개념으로, R3F에서는 <group> / <mesh>로 트리 구조를 "선언"하면
// 내부적으로 create + add + set 등을 자동으로 수행합니다.

// const group = new THREE.Group();
// scene.add(group);

// const mesh1 = new THREE.Mesh();
// mesh1.geometry = new THREE.BoxGeometry(1, 1, 1);
// mesh1.material = new THREE.MeshBasicMaterial({ color: 'red' });

// const mesh2 = new THREE.Mesh();
// mesh2.geometry = new THREE.SphereGeometry(0.5);
// mesh2.material = new THREE.MeshBasicMaterial({ color: 'orange' });

// group.add(mesh1, mesh2);

// root.render(
//   <>
//     <group>
//       <mesh>
//         {/* attach를 사용하여  부모의 특정 속성을 구성 요소에 할당할 수 있지만,
//          r3f가 이름으로 구분하므로 생략할 수 있음. */}
//         <boxGeometry attach="geometry" />
//         <meshBasicMaterial attach="material" color="red" />
//       </mesh>
//       <mesh>
//         <sphereGeometry attach="geometry" />
//         <meshBasicMaterial attach="material" color="orange" />
//       </mesh>
//     </group>
//   </>,
// );

// -------------------------------

const cameraSetting = {
  // OrthographicCamera에서는 fov 대신 zoom이 핵심
  // (type: number) 화면에서의 '픽셀/단위' 배율 → 값이 클수록 크게 보임
  // zoom: 100,

  // (type: number) 클리핑 평면 — 너무 작은 near는 z-fighting을 유발할 수 있음
  near: 0.1,
  far: 200,

  // (type: [number, number, number]) 카메라 위치; 정사영은 거리로 스케일이 변하지 않음
  position: [3, 2, 6],
};

root.render(
  <Canvas
    // dpr={[1, 2]} // default
    //  - (type: number | [min, max]) 렌더링 해상도를 장치 픽셀 비율에 맞춰 제한
    //  - 고해상도 디스플레이에서 성능/선명도 균형을 맞추기 좋음

    // flat // tone mapping 없이 "있는 그대로(flat)" 렌더링 = 색이 왜곡되면 안 될 때 사용
    //  - flat이 켜지면 toneMapping=NoToneMapping, outputColorSpace=Linear로 강제되어
    //    PBR 톤매핑/감마 보정을 끕니다. (UI/HUD, 포스트 프로세싱 직접 제어 시 유용)

    gl={{
      antialias: false, // 안티앨리어싱 - 기본값 true
      //  - 캔버스 컨텍스트 생성 옵션. WebGL의 MSAA는 컨텍스트 특성이라 post-생성 변경 불가.
      //  - post-processing(SMAA/TAA 등)으로 대체할 계획이면 false가 성능상 유리할 수 있음.

      toneMapping: THREE.ACESFilmicToneMapping, // toneMapping
      //  - flat을 사용하지 않는 경우에만 의미가 있습니다.
      //  - 물리기반 렌더링(PBR)에서 HDR 값을 SDR 디스플레이로 자연스럽게 압축.

      outputColorSpace: THREE.SRGBColorSpace, // outputEncoding에서 outputColorSpace로 변경됨.
      //  - 세 줄 요약: Linear(연산) → sRGB(출력)로 감마 보정 적용.
      //  - flat 모드에서는 Linear로 강제되므로 여기는 무시됩니다.
    }}
    // orthographic // (type: boolean) 정사영 카메라 사용 플래그, perspective가 기본값
    //  - 정사영은 거리와 무관하게 동일 크기로 보이며, 기본 zoom=1이면 "1월드단위 ≈ 1픽셀"로
    //    체감상 매우 작게(멀리) 보입니다. 이때 zoom을 올려 프레이밍합니다.

    camera={cameraSetting} // (type: object) 카메라 초기 파라미터
  >
    <Experience />
  </Canvas>,
);

✅ Canvas 주요 파라미터 정리 (역할/타입)

  • dpr: number | [number, number] — 디바이스 픽셀 비율 범위. 성능/선명도 절충.
  • flat: boolean — tone mapping/감마 보정 비활성화(Linear 출력).
  • gl: WebGLRenderer 생성 옵션 객체
    • antialias: boolean — MSAA 사용 여부(컨텍스트 생성 시 결정).
    • toneMapping: THREE.ToneMapping — flat이 꺼져 있을 때만 유효.
    • outputColorSpace: THREE.ColorSpace — 출력 색 공간(sRGB 권장).
  • orthographic: boolean — 정사영 카메라 사용 플래그.
  • camera: { fov?, near, far, position, zoom? } — 초기 카메라 설정.
    • PerspectiveCamera: fov 사용.
    • OrthographicCamera: zoom 사용(정사영에서 스케일 조절 핵심).

2) Experience.jsx (씬 구성 + 컨트롤 + 애니메이션)

import { useThree, extend, useFrame } from '@react-three/fiber';
import { useRef } from 'react';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import CustomObject from './CustomObject';

extend({ OrbitControls }); // extend는 three.js 클래스를 JSX에서 사용할 수 있도록 해줌.
// - 등록 후에는 <orbitControls />처럼 소문자 태그로 사용할 수 있습니다.
// - 대안: @react-three/drei의 <OrbitControls makeDefault /> 사용(자동 등록 + 편의 props)

export default function Experience() {
  const { camera, gl } = useThree();
  // const three = useThree();
  // three.camera: PerspectiveCamera
  // three.gl: WebGLRenderera를 포함하는 것 domElement, 즉<canvas>
  // three.clock: 시계 인스턴스
  // 등 three js 요소를 가지고 있음.
  // + 추가 설명:
  // - R3F의 커스텀 렌더러가 관리하는 씬/카메라/렌더러에 접근할 수 있어,
  //   컨트롤러 초기화나, 사이즈/FOV 동기화 같은 imperative 작업을 최소한으로 수행 가능.

  const cubeRef = useRef();  // (type: React.MutableRefObject<THREE.Mesh | undefined>)
  const groupRef = useRef(); // (type: React.MutableRefObject<THREE.Group | undefined>)

  useFrame((state, delta) => {
    // useFrame은 렌더 루프에 매 프레임 호출되며, (state, delta)를 제공합니다.
    // - state: clock, pointer, size, camera 등 R3F 상태 및 헬퍼 모음
    // - delta: 이전 프레임 대비 경과 시간(초) → 프레임레이트와 무관한 애니메이션에 사용

    // const angle = state.clock.elapsedTime;
    // state.camera.position.x = Math.sin(angle) * 8;
    // state.camera.position.z = Math.cos(angle) * 8;
    // state.camera.lookAt(0, 0, 0); // 중앙 지정
    // - 위 패턴은 "원형 궤도" 카메라를 직접 구현할 때 유용.
    // - OrbitControls를 쓰는 경우 충돌하므로 한쪽만 사용.

    cubeRef.current.rotation.y += delta; // 회전 속도는 delta(초) 기반이라 FPS와 무관(일관된 속도)
    // groupRef.current.rotation.y += delta; // 그룹 단위 회전도 가능(자식 전체가 같이 회전)
  });

  return (
    <>
      {/* camera와 domElement 필요 */}
      {/* args의 의미: new OrbitControls(camera, gl.domElement)와 동일 */}
      <orbitControls args={[camera, gl.domElement]} />

      {/* 라이트: MeshStandardMaterial(PBR)를 쓰므로 직접광 + 환경광 조합 */}
      <directionalLight position={[1, 2, 3]} intensity={4.5} />
      <ambientLight intensity={1.5} />

      <group ref={groupRef}>
        <mesh position-x={-2}>
          <sphereGeometry />
          <meshStandardMaterial color="orange" />
          {/* Standard는 PBR 재질. 라이트 영향 받음(메탈/러프니스 등 추가 가능). */}
        </mesh>

        {/* scale={[x, y, z]} == mesh.scale.set(x, y, z) */}
        <mesh
          ref={cubeRef}
          rotation-y={Math.PI * 0.23}
          // position={[2, 0, 0]}
          position-x={2}
          scale={1.5}>
          {/* args = {[radius, widthSegments, heightSegment]} */}
          {/* <sphereGeometry args={[1.5, 32, 32]} /> */}

          {/* ⚠️ 주의: <boxGeometry scale={1.5} /> 는 geometry의 "필드"가 아니라 "메서드(geometry.scale)"라
              R3F가 자동으로 호출해주지 않습니다. 실제 스케일링은 mesh의 scale={1.5}가 담당합니다.
              (geometry 자체를 스케일하고 싶다면 ref로 geometry.scale(x,y,z) 호출) */}
          <boxGeometry />

          <meshStandardMaterial
            // args={[{ color: 'red', wireframe: true }]}
            color="mediumpurple"
            wireframe={false}
          />
        </mesh>
      </group>

      {/* 바닥 플레인: 시각적 기준면. backface culled 방지 위해 회전 */}
      <mesh position-y={-1} rotation-x={-Math.PI * 0.5} scale={10}>
        <planeGeometry />
        <meshStandardMaterial color="greenyellow" />
      </mesh>

      <CustomObject />
    </>
  );
}

✅ Experience 주요 파라미터 정리 (역할/타입)

  • useThree() 반환값
    • camera: THREE.Camera — 현재 활성 카메라(기본: PerspectiveCamera 또는 <Canvas orthographic />면 OrthographicCamera).
    • gl: THREE.WebGLRenderer — .domElement는 <canvas> 노드.
  • useFrame((state, delta))
    • state: { clock, size, pointer, camera, scene, ... } — 렌더 루프 컨텍스트.
    • delta: number — 초 단위 프레임 간격.
  • <orbitControls args={[camera, gl.domElement]} />
    • args[0]: THREE.Camera — 제어할 카메라.
    • args[1]: HTMLCanvasElement — 입력을 받을 DOM 엘리먼트.
  • <directionalLight position intensity />
    • position: [number, number, number] — 광원 위치.
    • intensity: number — 광 강도(기본 1).
  • <mesh rotation-y position-x scale />
    • rotation-y: number(rad) — Y축 회전(대시 케이스로 부분 접근).
    • position-x: number — X축 위치(부분 접근).
    • scale: number | [x,y,z] — 메쉬 스케일. number면 setScalar.

3) CustomObject.jsx (커스텀 BufferGeometry)

import * as THREE from 'three';
import { useRef, useMemo, useEffect } from 'react';

export default function CustomObject() {
  const geometryRef = useRef(); // (type: React.MutableRefObject<THREE.BufferGeometry | undefined>)

  const verticesCount = 10 * 3;
  // 의미: 삼각형 10개 × 버텍스 3 = 총 30 버텍스
  // - 아래 positions 배열 길이는 '버텍스 수 × itemSize(=3)' = 90 float로 생성됩니다.

  // 리렌더링 시 최적화를 위해 useMemo로 관리하는 게 좋다.
  const positions = useMemo(() => {
    const positions = new Float32Array(verticesCount * 3);

    for (let i = 0; i < verticesCount * 3; i++) {
      positions[i] = (Math.random() - 0.5) * 3; // [-1.5, 1.5] 범위 랜덤 위치
    }

    return positions;
  }, []);
  // - 메모이제이션으로 배열 재할당을 피함(불필요한 GPU 업로드 방지)
  // - positions 내용을 "동적"으로 바꿔야 한다면 .needsUpdate=true 또는 setAttribute를 사용

  useEffect(() => {
    geometryRef.current.computeVertexNormals(); // 최초 렌더링 시 동작하지 않으므로 useEffect 사용
    // - 표면 법선(normal)이 없으면 StandardMaterial가 어둡게 보이거나 조명이 비정상.
    // - ref는 초기 마운트 후에만 안전하므로 effect에서 호출.
  }, []);

  return (
    <mesh>
      <bufferGeometry ref={geometryRef}>
        <bufferAttribute
          attach="attributes-position"
          count={verticesCount} // 버텍스 개수(= array.length / itemSize)
          itemSize={3}          // 한 버텍스를 이루는 구성요소 개수(x,y,z)
          array={positions}     // Float32Array(길이: verticesCount * 3)
        />
      </bufferGeometry>
      <meshStandardMaterial
        color="red"
        side={THREE.DoubleSide} // double side 설정
      />
    </mesh>
  );
}

✅ CustomObject 파라미터 정리 (역할/타입)

  • bufferAttribute
    • attach: "attributes-position" — 부모 BufferGeometry의 attributes.position에 바인딩.
      • (attach는 명시적 바인딩용. R3F가 타입 이름으로 자동 추론할 수도 있으나, 명시하면 명확)
    • count: number — 버텍스 개수(= array.length / itemSize).
    • itemSize: number — 한 버텍스를 구성하는 요소 수(좌표 3개 → 3).
    • array: TypedArray — GPU로 업로드될 원시 데이터.
  • computeVertexNormals(): BufferGeometry의 법선 재계산.
    • 동적으로 포지션을 갱신했다면, 다시 호출하여 조명 결과를 갱신해야 함.
    • 포지션을 자주 바꿀 경우 geometry.attributes.position.needsUpdate = true 설정도 필요.

보충 개념

1) Declarative R3F vs Imperative three.js

  • Imperative(명령형): new Mesh() → set → add 순서대로 호출.
  • Declarative(선언형): <mesh><boxGeometry /><meshStandardMaterial/></mesh>처럼 트리 구조를 선언하면,
    R3F가 생성/속성 적용/관계 설정을 자동으로 reconcile합니다.

2) Dash-case property piercing

  • position-x={2}, rotation-y={...} 처럼 중첩 객체의 특정 축/필드만 부분 업데이트.
    내부적으로 object.position.x = 2에 대응.
  • position={[x,y,z]}와 동시에 쓰면 나중에 적용되는 값이 최종적용됩니다.

3) Orthographic vs Perspective

  • 기본은 PerspectiveCamera.
  • <Canvas orthographic /> 시 OrthographicCamera: 거리와 무관하게 같은 크기 →
    zoom=1이면 화면 픽셀 스케일에 종속되어 매우 작게 보일 수 있음 → zoom으로 프레이밍.

4) flat vs Tone Mapping/Color Space

  • flat: NoToneMapping + Linear 출력 → 색 "있는 그대로".
  • PBR로 현실감(빛의 범위/하이라이트)을 원하면 ACES + sRGB 조합 권장.
  • 하나의 씬에서 3D는 PBR, UI는 flat처럼 혼용이 필요하면 후처리 단계에서 제어하는 편이 안전.

5) OrbitControls 등록 방식

  • extend({ OrbitControls }) + <orbitControls args={[camera, gl.domElement]} />
  • 또는 @react-three/drei의 <OrbitControls makeDefault /> (권장: 간단하고 충돌 적음).

6) BufferGeometry 성능 팁

  • 정적 데이터 → useMemo로 배열 재사용.
  • 동적 데이터 → setAttribute + .needsUpdate = true, 변경 시에만 GPU 업로드.
  • 매우 큰 버퍼는 DynamicDrawUsage 설정 고려:
    geometryRef.current.attributes.position.setUsage(THREE.DynamicDrawUsage).

자주 겪는 함정 & 팁

  • <boxGeometry scale={1.5} />: geometry에는 scale 메서드만 있고 필드가 아님 → JSX prop으로는 호출되지 않음.
    → **메쉬의 scale**로 처리하거나, geometry ref로 geometry.scale(x,y,z) 호출.
  • OrbitControls vs 수동 카메라 이동: 둘을 동시에 쓰면 충돌 → 한쪽만 사용.
  • toneMapping/flat 혼용: flat을 켜면 gl.toneMapping/outputColorSpace 설정이 사실상 무력화.
  • 정사영에서 “멀어 보임”: zoom으로 프레이밍하는 게 정상 패턴.
  • 라이트/재질 매칭: MeshStandardMaterial는 조명 필요. MeshBasicMaterial는 조명 무시.

 

'Graphic > R3F' 카테고리의 다른 글

57 R3F Debug  (7) 2025.08.15
56 Drei  (2) 2025.08.15
53, 54 React와 React Three Fiber  (9) 2025.08.11
react-three/drei  (1) 2025.06.09
무료로 R3F 아바타 생성하기  (1) 2025.05.26