Camera 조작
3D 공간에서 카메라의 위치(position)와 시점(lookAt) 조절의 의미 : 사용자의 시각적 경험 결정
Leva를 활용한 카메라 조작
Leva 라이브러리의 useControls를 사용한 카메라 조작
npm i leva
useControls를 이용해 카메라의 Z축 위치와 Y축 시점을 조절할 수 있는 슬라이더 생성
import { useControls } from "leva";
import { useThree } from "@react-three/fiber";
const Elements = () => {
const { camera } = useThree();
const dirLight = useRef(null);
useHelper(dirLight, THREE.DirectionalLightHelper, 1, "blue");
useControls({
positionZ: {
value: 3,
min: -10,
max: 10,
step: 0.1,
onChange: (v) => (camera.position.z = v),
},
lookAtY: {
value: 0,
min: -10,
max: 10,
step: 0.1,
onChange: (v) => camera.lookAt(0, v, 0),
},
});
return (
<>
{/* <OrbitControls /> → 주석 처리해야 Leva의 조작이 적용됨 */}
<ambientLight intensity={0.7} />
<directionalLight
ref={dirLight}
castShadow
intensity={2}
target-position={[0, -1, 0]}
shadow-mapSize={[5000, 5000]}
position={[-4, -2.1, 4]}
/>
{cardData.map((props) => (
<CardComponent key={props.imageUrl} {...props} />
))}
</>
);
};
- useControls를 활용, UI 슬라이더로 카메라 이동을 조절
positionZ : 카메라의 Z축 위치를 조정
lookAtY : 카메라가 바라보는 Y축 방향을 조정
* <OrbitControls/>을 주석처리해야 useControls의 값이 반영됨
useFrame을 활용한 실시간 카메라 조작
useFrame
매 프레임마다 특정 동작을 실행
→ 카메라를 지속적으로 특정 지점을 바라보게 만들 수 있음
import { useFrame } from "@react-three/fiber";
const Elements = () => {
const { camera } = useThree();
const dirLight = useRef(null);
useHelper(dirLight, THREE.DirectionalLightHelper, 1, "blue");
useFrame(() => {
console.log("1프레임");
camera.lookAt(0, 0.5, 0);
});
return (
<>
<OrbitControls />
<ambientLight intensity={0.7} />
<directionalLight
ref={dirLight}
castShadow
intensity={2}
target-position={[0, -1, 0]}
shadow-mapSize={[5000, 5000]}
position={[-4, -2.1, 4]}
/>
{cardData.map((props) => (
<CardComponent key={props.imageUrl} {...props} />
))}
</>
);
};
- useFrame은 매 프레임마다 특정 동작을 수행
camera.lookAt(0, 0.5, 0);을 실행, 카메라의 시점을 고정
console.log("1프레임") : 매 프레임마다 콘솔에 출력되는 "1 프레임" 메세지를 확인할 수 있음
import './App.css'
import { useRef } from "react";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, useHelper } from '@react-three/drei';
import { useLoader, useThree, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { useControls } from "leva";
const CardData = [
{
imageUrl: "/card_1.jpg",
position: [-1.1, -1.15, 1],
rotationY: 50,
rotationZ: 30,
},
{
imageUrl: "/card_2.jpg",
position: [-0.7, -1.05, 0.4],
rotationY: 45,
rotationZ: 10,
},
{
imageUrl: "/card_3.jpg",
position: [0, -1.05, -0.4],
rotationY: 45,
rotationZ: 0,
},
{
imageUrl: "/card_4.jpg",
position: [0.7, -1.15, -1],
rotationY: 30,
rotationZ: -10,
},
{
imageUrl: "/card_5.jpg",
position: [1.3, -1.30, -1.6],
rotationY: 25,
rotationZ: -30,
},
]
const CardComponent = ({ position, rotationY, rotationZ, imageUrl }) => {
const { scene } = useThree();
const texture = useLoader(THREE.TextureLoader, imageUrl)
texture.colorSpace = THREE.SRGBColorSpace;
scene.rotation.set(0, 20, 0);
const materials = [
new THREE.MeshStandardMaterial(), // 오른쪽
new THREE.MeshStandardMaterial(), // 왼쪽
new THREE.MeshStandardMaterial(), // 윗면
new THREE.MeshStandardMaterial(), // 바닥면
new THREE.MeshStandardMaterial({ map: texture }), // 앞면 (이미지 적용)
new THREE.MeshStandardMaterial({ map: texture }), // 뒷면 (이미지 적용)
];
return (
<mesh
castShadow
receiveShadow
position={position}
rotation-y={THREE.MathUtils.degToRad(rotationY)}
rotation-z={THREE.MathUtils.degToRad(rotationZ)}
material={materials}
>
<axesHelper />
<boxGeometry args={[1, 1.6, 0.02]} />
</mesh>
)
}
const Elements = () => {
const lightRef = useRef(null);
const { camera } = useThree();
useHelper(lightRef, THREE.DirectionalLightHelper, 1, "blue");
useFrame(() => {
console.log("1프레임");
camera.lookAt(-1.6, 0, 0.3);
});
// useControls({
// positionX: {
// value: 3,
// min: -20,
// max: 10,
// step: 0.1,
// onChange: (v) => (camera.position.x = v),
// },
// positionY: {
// value: 3,
// min: -20,
// max: 10,
// step: 0.1,
// onChange: (v) => (camera.position.y = v),
// },
// positionZ: {
// value: 3,
// min: -20,
// max: 10,
// step: 0.1,
// onChange: (v) => (camera.position.z = v),
// },
// lookAtX: {
// value: 0,
// min: -10,
// max: 10,
// step: 0.1,
// onChange: (v) => camera.lookAt(v, 0, 0), // x축 기준 : (v, 0, 0)
// },
// lookAtY: {
// value: 0,
// min: -10,
// max: 10,
// step: 0.1,
// onChange: (v) => camera.lookAt(0, v, 0), // x축 기준 : (v, 0, 0)
// },
// lookAtZ: {
// value: 0,
// min: -10,
// max: 10,
// step: 0.1,
// onChange: (v) => camera.lookAt(0, 0, v), // x축 기준 : (v, 0, 0)
// },
// // lookAtY 사용 시 OrbitControls 주석 처리
// });
return (
<>
{/* <OrbitControls /> */}
<ambientLight intensity={1} /> // ambientLight : 환경광 (모든 방향에서 균일하게 씌워지는 빛)-모든 오브젝트가 동일한 강도로 조명을 받는 빛
<directionalLight
castShadow ref={lightRef}
intensity={3}
target-positon={[0, -1, 0]}
shadow-mapSize={[5000, 5000]}
position={[-2, 1, 5]} />
{
CardData.map((props) => {
return <CardComponent key={props.imageUrl} {...props} />
})
}
</>
)
}
function App() {
return (
<Canvas shadows camera={{ position: [2, 0, -1.2] }}>
<Elements />
</Canvas>
);
}
export default App;
'React > ReactThreeFiber' 카테고리의 다른 글
Light & Shadow (0) | 2025.03.07 |
---|---|
Obj 3D & Transform (0) | 2025.03.06 |
Material & Texture (1) | 2025.03.05 |
Geometry (0) | 2025.03.04 |
GUI Controller (2) | 2025.03.03 |