κΉν μ½λ : https://github.com/soheee-bae/React-Three-Fiber/tree/main/basic
OrbitControls (κΆ€λ 컨λλ‘€)
OrbitControls
λ₯Ό μΆκ°νκ² λλ©΄ μΉ΄λ©λΌλ₯Ό νμ μμΌ μ¬λ¬ μκ°μΌλ‘ μ₯λ©΄μ λ³Ό μ μμ΅λλ€. OrbitControls
λ Three.js ν΄λμ€μ μΌλΆκ° μλκΈ°μ μ§μ jsx νμΌμμ μ¬μ©ν μ μλλ‘ React Three Fiberμμ μ 곡νλ extend
λ₯Ό μ¬μ©ν΄μ λ³νμμΌμ€μΌ ν©λλ€.
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { extend, useFrame } from '@react-three/fiber'
extend({ OrbitControls })
OrbitControlsλ₯Ό μ¬μ©ν λ νμμΈ λ κ°μ λ§€κ°λ³μκ° μλλ°μ. μ΄λ μμ κΈμμ λ΄€μλ useFrame
μ state
κ³Ό κ°μ κ°μ μ£Όλ useThree
μμ κ°μ Έμ μ§μ ν΄ μ€ μ μμ΅λλ€.
useThree()μ κ°
import { useThree, extend, useFrame } from '@react-three/fiber'
export default function Experience(){
const { camera, gl } = useThree()
return
<>
<orbitControls args={ [ camera, gl.domElement ] } />
{/* ... */}
</>
}
μμ μ½λλ₯Ό μ€ννλ©΄ λ°κ³Ό κ°μ κΈ°λ₯μ΄ μ μ©λ©λλ€.
Light (λΉ)
3Dκ°λ°μμ νμ€κ°μ μ£ΌκΈ° μν΄μ λΉμ μ₯λ©΄μμ κΌ νμν μμ μ€μ νλμ
λλ€. μ¬κΈ°μ μ€μν 건 κ°μ²΄κ° λΉμ λ°μμ νλ €λ©΄ <meshBasicMaterial>
μ¬μ§μ΄ μλ <meshStandardMaterial>
μ¬μ§μ μ¨μΌ νλ€λ μ μ
λλ€.
λΉμλ μ¬λ¬ μ’
λ₯κ° μλλ°μ. λνμ μΌλ‘ <directionalLight>
κ³Ό <ambientLight>
κ° μμΌλ©° λΉμ μμΉλ κ°λ λ±μ μ‘°μ ν μ μμ΅λλ€.
export default function Experience(){
// ...
return
<>
<directionalLight position={ [ 1, 2, 3 ] } intensity={ 1.5 } />
<ambientLight intensity={ 0.5 } />
{/* ... */}
</>
}
λ΄ λ§λλ‘ νμ λ§λ€κΈ°
μ§κΈκΉμ§ Three.jsμ React Three Fiberμ΄ μ 곡νλ geometry(νμ)
μ μ¬μ©νμλλ°μ. λ¬Όλ‘ μ 곡λλ νμλ€μ μ¬μ©ν μλ μμ§λ§ μ§μ μνλ νμμ λ§λ€μ΄μ μ¬μ©ν μ λ μμ΅λλ€.
μ€λΉλ¨κ³
λͺ¨λ νμμ 3κ°μ κΌμ§μ μΌλ‘ λ§λ€μ΄μ§ μΌκ°νλ€μ΄ λͺ¨μ¬ μ΄λ£¨μ΄μ Έ μλλ°μ. μ°μ μνλ νμμ λ§λ€κΈ° μν΄μλ κΌμ§μ μ μλ₯Ό κ³μ°ν΄ μ£Όκ³ Float32Array
λ₯Ό μ΄μ©ν΄μ κ° κΌμ§μ λ€μ μμΉλ₯Ό array
ννλ‘ μ μ₯ν΄μ£Όμ΄μΌ ν©λλ€.
export default function CustomObject()
{
const verticesCount = 10 * 3 // 10κ°μ μΌκ°νκ³Ό κ°κ° 3κ°μ κΌμ§μ
const positions = useMemo(() => {
const positions = new Float32Array(verticesCount * 3)
// verticesCountμ 3μ κ³±νλ μ΄μ λ νλμ μ λΉ 3κ°μ κ°μ΄ νμνκΈ° λλ¬Έμ
λλ€ (x,y,z)
//for loopμ μ΄μ©ν΄ λλ€ κ°μ arrayμ λ£μ΄μ€λλ€.
for(let i = 0; i < verticesCount * 3; i++)
positions[i] = (Math.random() - 0.5) * 3
return positions
})
// ...
}
μμΉ μμ±μ μ΄μ©ν΄μ νμ λ§λ€κΈ° (+BufferGeometryκ³Ό BufferAttribute)
μμμ λ§λ€μλ κΌμ§μ λ€μ μμΉλ€μ κ°μ§κ³ μλ arrayλ₯Ό BufferAttribute
λ₯Ό ν΅ν΄ BufferGeometry
μ μμ±μ ν¬ν¨μν€λ©΄ κ·Έ μμ±μ κ°μ§ νλμ νμμ λ§λ€ μ μμ΅λλ€.
μ¬κΈ°μ μ μΌ μ€μν 건 BufferAttribute
μ μ΄λ€ μμ±μ ν¬ν¨μν€κ³ μΆμμ§λ₯Ό μλ €μ€μΌ νλλ°μ. μ΄λ attach
λ₯Ό μ¬μ©ν΄μ μλ €μ€ μ μμ΅λλ€.
π‘ μμΉ μμ±μ λ§μ μμ± μ€ νλμ λλ€. μ΄ μΈμλ color, normal, uv, uv2 λ±κ³Ό κ°μ μμ±λ€μ΄ μμ΅λλ€.
<bufferGeometry>
<bufferAttribute
attach="attributes-position" // geometry.attribute.positionκ³Ό κ°μ΅λλ€
count={ verticesCount } //κΌμ§μ κ°―μ
itemSize={ 3 } // arrayμμ νλμ κΌμ§μ μ ꡬμ±νλ νλͺ©μ μ
array={ positions } // κΌμ§μ λ€μ μμΉλ€μ κ°μ§κ³ μλ array
/>
</bufferGeometry>
μμ μ½λλ₯Ό μ€ννλ©΄ λ°κ³Ό κ°μ νμμ΄ λ§λ€μ΄μ§λλ€.
Double Side μΈ‘λ©΄ λ λλ§ νκΈ°
κΈ°λ³Έκ°μΌλ‘ μμ νμμ λ§λ€μμ λ μλ©΄λ§ λ λλ§μ΄ λ©λλ€. λμ€μ orbitControl
λ₯Ό μ¬μ©ν΄ νμμ λͺ¨λ λ©΄μ λ€ λ³Ό λλ₯Ό λλΉν΄μ Double side
(μλ€ λ λ€) μΈ‘λ©΄μ λ λλ§ νλ κ²μ μΆμ²ν©λλ€.
import * as THREE from 'three' λλ import { DoubleSide } from 'three'
<meshBasicMaterial color="red" side={ THREE.DoubleSide } />
computeVertexNormalsμ μ΄μ©ν΄μ normal μμ± ν¬ν¨μν€κΈ°
λ§λ νμμ΄ λΉμ λ°μνκ² νκΈ° μν΄μλ <meshBasicMaterial>
μ¬μ§μ΄ μλ <meshStandardMaterial>
μ¬μ§μ μ¬μ©ν΄μΌ ν©λλ€.
<mesh>
<meshStandardMaterial color="red" side={ THREE.DoubleSide } />
</mesh>
μμ μ½λλ₯Ό μ€ννλ©΄ λ°κ³Ό κ°μ νλ©΄μ΄ λμ€λλ°μ. λ§λ νμμ μμ μ λλ‘ μ
νκΈ° μν΄μ normal
μμ±μ ν¬ν¨μμΌμ€μΌ ν©λλ€. μ¬μ΄ λ°©λ²μΌλ‘ computeVertexNormals
λ₯Ό μ¬μ©ν΄ ν¬ν¨μν€λ λ°©λ²μ΄ μμ΅λλ€.
import { useRef, useMemo } from 'react'
export default function CustomObject(){
const geometryRef = useRef()
const positions = useMemo(() => {
// ...
})
// positionsκ° λ λλ§ λ λλ§λ€ λΆλ¬μ΅λλ€.
useEffect(() => {
geometryRef.current.computeVertexNormals()
}, [ positions ])
// ...
<bufferGeometry ref={ geometryRef }>
{/* ... */}
</bufferGeometry>
}
μμ μ½λλ₯Ό μ€ννλ©΄ λ°κ³Ό κ°μ νλ©΄μ²λΌ λ§λ νμμ λΉμ λ°μνλ μ¬μ§μ΄ μ μ©λ©λλ€.
Camera μ€μ νκΈ°
κΈ°λ³Έ Camera
μΊλ²μ€μ κΈ°λ³Έκ°μΌλ‘ PerspectiveCameraλ₯Ό μ€μ ν΄ μ€ μ μμ΅λλ€. PerspectiveCamera
λ μ¬λμ λμΌλ‘ 보λ λ°©μμ λͺ¨λ°©νμ¬ μ€κ³λμμΌλ©° 3D μ₯λ©΄μ λ λλ§ νλλ° κ°μ₯ λ§μ΄ μ¬μ©λ©λλ€. fov, near, far, position λ± λ μμΈν κ°μ μ§μ ν΄ μ£Όλ©° μΉ΄λ©λΌμ μμΉλ₯Ό λ³κ²½ν μ μμ΅λλ€.
<Canvas camera={ { fov: 45, near: 0.1, far: 200 } }>
<Experience />
</Canvas>
OrthographicCamera μ¬μ©νκΈ°
OrthographicCameraλ λ λλ§ λ μ΄λ―Έμ§μμ κ°μ²΄μ ν¬κΈ°λ μΉ΄λ©λΌμμ 거리μ κ΄κ³μμ΄ μΌμ νκ² μ μ§λ©λλ€.
<Canvas camera={ { fov: 45, near: 0.1, far: 200 } }>
<Experience />
</Canvas>
Cameraμ μ λλ©μ΄μ κΈ°λ₯ μΆκ° νκΈ°
OrbitControlsκ³Ό λΉμ·ν ν¨κ³Όλ₯Ό λ΄κ³ μΆμ§λ§ μ¬μ©μκ° νλ©΄μ μ‘°μ νμ§ λͺ»νκ² νκ³ μΆμ λ μ΄ λ°©λ²μ΄ λ§μ΄ μ¬μ©λλλ°μ. μ΄μ κΈμμ meshμ μ λλ©μ΄μ μ μ£ΌκΈ° μν΄ μΌλ λ°©λ²κ³Ό λΉμ·νμ§λ§ μ΄λ²μ meshκ° μλ μΉ΄λ©λΌμ μ λλ©μ΄μ κΈ°λ₯μ μΆκ°ν΄ μ€ κ²μ λλ€.
κ°λ¨ν μΉ΄λ©λΌμ νμ μ λλ©μ΄μ
μ μ£ΌκΈ° μν΄μλ λ¨Όμ κ°λλ₯Ό μμλ΄μΌ νκ³ κ°λμ sin()
λ° cos()
λ₯Ό μ΄μ©ν΄μ x
λ° z
μ’νλ₯Ό μ»μ΄ μΉ΄λ©λΌμ μμΉλ₯Ό μ§μ ν΄ μ€μΌ ν©λλ€. μ¬κΈ°μ κ°λλ useFrame
μ΄ μ 곡νλ state
μ clock.elapsedTime
μ΄ μ¬μ©λ©λλ€.
π‘ stateμλ μΉ΄λ©λΌ, λ λλ¬, μ₯λ©΄ λ±κ³Ό κ°μ three.jsνκ²½μ λν μ λ³΄κ° ν¬ν¨λμ΄ μμ΅λλ€.
useFrame((state, 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)
// ...
})
μμ μ½λλ₯Ό μ€ννλ©΄ λ°κ³Ό κ°μ μ λλ©μ΄μ μ΄ μ μ©λ©λλ€.