์ด์ ๊ฒ์๊ธ์์ 3D ๊ณต๊ฐ์ธ Scene์ ๋ง๋ค์๋ค.
์ด์ ํด๋น ๊ณต๊ฐ์ ๋ฐ์ค๋ฅผ ์์ฑํด๋ณด์.
์ฐ์ 3D ๊ฐ์ฒด์ ๊ตฌ์ฑ์ ์์๋ณด์.
3D ๊ฐ์ฒด๋ ๋ค์๊ณผ ๊ฐ์ด ์ด๋ฃจ์ด์ ธ์๋ค.
์ดํ, 3D ๊ฐ์ฒด๋ฅผ ๋ชจ๋ธ๋ก ํํํ๊ฒ ๋ค.
๐ Model
โ ๐ฆ Mesh
Mesh๋, ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ์ํด์ ์ฌ๋ฌ ํด๋ฆฌ๊ณค(๋ค๊ฐํ)์ ์งํฉ์ ์ด๋ฃจ๊ณ ์๋ค.
์ ๋ค์ด ๋ฌด์ํ ๋ชจ์ฌ ์ ์ด ๋๊ณ , ๊ทธ ์ ์ด ๋ชจ์ฌ ํด๋ฆฌ๊ณค์ ์ด๋ฃจ๊ณ , ํด๋น ํด๋ฆฌ๊ณค๋ค์ด ๋ชจ์ฌ Mesh๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ด๋ฃฌ๋ค.
โ ๐ Geometry
๊ณต๊ฐ์ ์์ฑ๊ณผ ๊ด๋ จํ ์ํ ๊ฐ๋ค์ด๋ค.
โ ๐ Material
ํด๋น ๋ฌผ์ง์ ์ด๋ฃจ๋ ์ฌ๋ฃ๋ฅผ ๋ํ๋ธ๋ค.
์ด ์ธ ๊ฐ์ง๊ฐ ์์ผ๋ฉด ๋ชจ๋ธ์ ํํํ ์ ์๋ค.
๊ทธ๋ผ ์ด์ ๋ชจ๋ธ์ ๋ง๋ค์ด๋ณด์.
๐ฆ Box
๋ฐ์ค๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ์ํด ๐ BoxLoader.tsx๋ฅผ ์์ฑํด์ฃผ์๋ค.
๋๋ ๋ฐ์ค๋ฅผ ๋ ๊ฐ ๋ง๋ค ๊ฒ์ด๋ค.
๋ฐ์ค๋ฅผ ๊ตฌ๋ถํ๊ณ , ์์น, ์์ ๊ตฌ๋ถํ๊ธฐ ์ํด ์ ๋ฌ์์ name, position, color ๊ฐ์ ์ค ๊ฒ์ด๋ค.
๐ Editor / ๐ BoxLoader.tsx
import { FC } from "react";
interface BoxLoaderProps {
name: string;
position: [number, number, number];
color: string;
}
export const BoxLoader: FC<BoxLoaderProps> = ({ name, position, color }) => {
return (
<>
<mesh name={name} position={position}>
</mesh>
</>
);
};
<mesh/> ํ๊ทธ๋ฅผ ์ด์ฉํ์ฌ mesh๋ฅผ ์์ฑํ๊ณ , ํด๋น mesh์ ์ด๋ฆ๊ณผ ์์น๊ฐ์ ์ค์ ํด์ฃผ์๋ค.
๊ทผ๋ฐ ๋ญ ๋ง๋ค์ง ์์ง ๋ชจ๋ฅธ๋ค.
์ด๊ฒ ๋ฉ์ฌ๋ ๋ฉ์ฌ์ธ๋ฐ, ๋ฐ์ค์ธ์ง ์์ธ์ง
๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
๐ Editor / ๐ BoxLoader.tsx
import { FC } from "react";
interface BoxLoaderProps {
name: string;
position: [number, number, number];
color: string;
}
export const BoxLoader: FC<BoxLoaderProps> = ({ name, position, color }) => {
return (
<>
<mesh name={name} position={position}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial roughness={1} color={color} />
</mesh>
</>
);
};
args๋ ๋ฐ์ค์ ํฌ๊ธฐ๋ฅผ ์๋ฏธํ๋ค.
์ด์ ๐ Editor / ๐ EditorScene.tsx์์ ๐ BoxLoader๋ฅผ ๋ถ๋ฌ์ค๋ฉด Scene์ ๋ฐ์ค๊ฐ ์์ฑ๋๋๋ฐ,
์ ๋ฌ์์ name๊ณผ position, color๊ฐ์ ๋ฃ์ด์ฃผ์.
๐ Editor / ๐ EditorScene.tsx
import { useState } from "react";
import { Grid, OrbitControls } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import styles from "./CSS/EditorScene.module.css";
import { BoxLoader } from "./BoxLoader";
export const EditorScene = () => {
const [gridConfig, setGridConfig] = useState({
cellSize: 0.25,
/** Cell thickness, default: 0.5 */
// cellThickness: "white",
/** Cell color, default: black */
cellColor: "#afafaf",
/** Section size, default: 1 */
sectionSize: 0.5,
/** Section thickness, default: 1 */
// sectionThickness?: number
/** Section color, default: #2080ff */
sectionColor: "#afafaf",
/** Follow camera, default: false */
// followCamera: true,
/** Display the grid infinitely, default: false */
infiniteGrid: true,
/** Fade distance, default: 100 */
fadeDistance: 30,
/** Fade strength, default: 1 */
fadeStrength: 3,
});
return (
<div className={styles.editorFrame}>
<Canvas
camera={{
aspect: 1280 / 720,
fov: 50,
near: 0.1,
far: 1000,
position: [2.5, 1, 3.5],
}}
>
<OrbitControls />
<BoxLoader name={"box1"} position={[-1, 0, 0]} color={"red"} />
<BoxLoader name={"box2"} position={[1, 0, 0]} color={"skyblue"} />
<Grid {...gridConfig} />
</Canvas>
</div>
);
};
์~ ๊ทธ๋ผ ๋ฐ์ค๊ฐ ์ ๋์๋๋ณด์.
...?
๋ถ๋ช color ๊ฐ์ ์คฌ๋๋ฐ ๊ฒ์์์ด๋ค.
์์ผ๊น... ๐ค
๋ฐ๋ก ๊ณต๊ฐ์ ๋น์ด ์๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ผ์ Scene ๊ณต๊ฐ์ ๋น์ ๋ง๋ค์ด์ฃผ์.
๐ก DirectionalLight
์์ ๋ฑ๊ณผ ๊ฐ์ ๋น์ Directional-light๋ผ๊ณ ํ๋ค. ์ง์ ์ผ๋ก ์ญ์ฑ ๋ป์ด๋๊ฐ๋ ๋น์ด๋ค.
๐ Editor / ๐ EditorScene.tsx
import { useState } from "react";
import { Grid, OrbitControls } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import styles from "./CSS/EditorScene.module.css";
import { BoxLoader } from "./BoxLoader";
export const EditorScene = () => {
const [gridConfig, setGridConfig] = useState({
cellSize: 0.25,
/** Cell thickness, default: 0.5 */
// cellThickness: "white",
/** Cell color, default: black */
cellColor: "#afafaf",
/** Section size, default: 1 */
sectionSize: 0.5,
/** Section thickness, default: 1 */
// sectionThickness?: number
/** Section color, default: #2080ff */
sectionColor: "#afafaf",
/** Follow camera, default: false */
// followCamera: true,
/** Display the grid infinitely, default: false */
infiniteGrid: true,
/** Fade distance, default: 100 */
fadeDistance: 30,
/** Fade strength, default: 1 */
fadeStrength: 3,
});
return (
<div className={styles.editorFrame}>
<Canvas
camera={{
aspect: 1280 / 720,
fov: 50,
near: 0.1,
far: 1000,
position: [2.5, 1, 3.5],
}}
>
<directionalLight
position={[2.5, 1, 3.5]}
intensity={1}
color={"white"}
/>
<OrbitControls />
<BoxLoader name={"box1"} position={[-1, 0, 0]} color={"red"} />
<BoxLoader name={"box2"} position={[1, 0, 0]} color={"skyblue"} />
<Grid {...gridConfig} />
</Canvas>
</div>
);
};
ํ๋ก์ ํธ๋ฅผ ์คํํ๋ฉด
(์ฐ๋ถ๋์ด ๋ณด์ด๋ ๊ฑด ๋ด ํ๋ฉด ๋น์จ ํ...)
๋ชจ๋ธ์ด ์ ๋ฌ์๋ก ์ ๋ฌ ๋ฐ์ ๊ฐ์ ๋ฐ๋ผ ์ ๋์ํ๋ค.
ํ๋ก์ ํธ ํด๋ ์ด๋ฆ์ Editor์ด๋ค. ๊ทธ๋ฌ๋ ์ง๊ธ์ ๋ชจ๋ธ์ ๋๊ณ ์๋ฌด ๊ฒ๋ ํธ์งํ ์ ์๋ Viewer ๊ธฐ๋ฅ๋ง์ ์ ๊ณตํ๋ค.
ํด๋น ๋ฌผ์ฒด๋ฅผ ์์ ํ ์ ์๋๋ก ํด๋ฆญ ์, ์ปจํธ๋กค์ด ๋ํ๋๊ฒ ํด๋ณด์.
์ปจํธ๋กค๋ฌ๋ฅผ ํ ๋ฒ์ ๋ชจ์์ฃผ๊ธฐ ์ํด ๐ Contoller.tsx ํ์ผ์ ์์ฑํด์ฃผ์.
โ๏ธ TransformControls
๐ Editor / ๐ Controller.tsx
import { OrbitControls, TransformControls, useTrail } from "@react-three/drei";
import { useThree } from "@react-three/fiber";
import { FC } from "react";
interface ControllerProps {
mode: any;
selectedName: string;
}
export const Controller: FC<ControllerProps> = ({ mode, selectedName }) => {
const scene = useThree((state) => state.scene);
const object = scene.getObjectByName(selectedName);
return (
<>
{selectedName && <TransformControls object={object} mode={mode} />}
<OrbitControls makeDefault enableDamping={false} />
</>
);
};
๊ธฐ์กด OrbitControls๋ฅผ ์ ๊ฑฐํ๊ณ ํ์ฌ ํ์ผ์ ์์ฑํ์๋ค.
OrbitControls์ ๊ฐ์ด ์ถ๊ฐ๋์๋๋ฐ,
makeDefault๋ฅผ ํตํด ํด๋น ์ปจํธ๋กค์ด ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ค๋ฅธ ์ปจํธ๋กค๋ฌ๊ฐ ์ฌ์ฉ๋ ๋ ์ฌ์ฉ๋์ง ์๋๋ก ์ ์ํ ์ ์๋ค.
enableDamping์ ํตํด ์นด๋ฉ๋ผ์ ์ด๋์ ์ข ๋ฃ๊ฐ ์ฒ์ฒํ ๋ฉ์ถ์ง ์๊ณ ๋ฑ๋ฑ ๋๊ธฐ๋๋ก Editor์ ์์ฑ์ ์ง๋๋๋ก ํ์๋ค.
(ํธ์ง๊ธฐ๊ฐ ์ค๋ฌด์คํ๊ฒ ์ด๋ํ๋ค๋ฉด, ํธ์ง์ด ํ๋ค ๊ฒ์ด๋ ๋ฐ๋ก ๋ฉ์ถ๋๋กํ์ฌ ์ฌ์ฉ์๊ฐ ํธ์ง์ ์ฉ์ดํ๋๋ก ํ์๋ค.)
์ธํฐํ์ด์ค๋ก๋ TransformControls์์ ์ค๋ธ์ ํธ์ ์ด๋ค ๊ฒ์ ๋ฐ๊ฟ์ง์ ๋ํ ๊ฐ์ด ๋ค์ด๊ฐ๋๋ฐ
โ ์์น: "translate"
โ ํ์ : "rotate"
โ ๊ท๋ชจ: "scale"
string ํ์ ์ ๊ฐ์ด ๋ค์ด๊ฐ ์ ์๋ค.
object๋ ์ด๋ ํ ์ค๋ธ์ ํธ์ ํด๋น TransformControls๋ฅผ ๋์ธ์ง์ ๋ํ ๋ชจ๋ธ์ด ๋ค์ด๊ฐ๋ค.
selectedName์ ์ด๋ค ๊ฐ์ฒด๋ฅผ TransformControls๋ฅผ ํตํ์ฌ ์์ ํ ์ง์ ๋ํ ์ค๋ธ์ ํธ์ ์ด๋ฆ๊ฐ์ด๋ค..
useThree๋ Canvas ์์ ์ฌ์ฉํ ์ ์๋ Hook์ผ๋ก Canvas์ ๋ด์ฉ์ ๊ฐ์ ธ์ฌ ์ ์๋ค.
useThree((state) => state.scene);
ํด๋น ์ฝ๋๋ฅผ ํตํด ํ์ฌ ์ํ์ Scene์ ๊ฐ์ ธ์ค๊ณ
const object = scene.getObjectByName(<objectName>);
scene์ .getObjectByName(<objectName>) ๋ฉ์๋๋ฅผ ํตํด scene์์ name๊ฐ์ผ๋ก ์ค๋ธ์ ํธ๋ฅผ ๊ฐ์ ธ์จ๋ค.
์ ํ๋ ๊ฐ์ฒด๋ฅผ ๋ถ๊ฐํ ์ ์๋๋ก ์ ํ๋ ๊ฐ์ฒด์ name ๊ฐ์ ๋ด์ State ์ฝ๋๋ฅผ ์ถ๊ฐํ์.
๐ Editor / ๐ EditorScene.tsx
import { useState } from "react";
import { Grid, OrbitControls } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import styles from "./CSS/EditorScene.module.css";
import { BoxLoader } from "./BoxLoader";
import { Controller } from "./Controller";
export const EditorScene = () => {
const [selectedName, setSelectedName] = useState<string | any>(null);
const [mode, setMode] = useState("translate");
const [gridConfig, setGridConfig] = useState({
cellSize: 0.25,
/** Cell thickness, default: 0.5 */
// cellThickness: "white",
/** Cell color, default: black */
cellColor: "#afafaf",
/** Section size, default: 1 */
sectionSize: 0.5,
/** Section thickness, default: 1 */
// sectionThickness?: number
/** Section color, default: #2080ff */
sectionColor: "#afafaf",
/** Follow camera, default: false */
// followCamera: true,
/** Display the grid infinitely, default: false */
infiniteGrid: true,
/** Fade distance, default: 100 */
fadeDistance: 30,
/** Fade strength, default: 1 */
fadeStrength: 3,
});
return (
<div className={styles.editorFrame}>
<Canvas
camera={{
aspect: 1280 / 720,
fov: 50,
near: 0.1,
far: 1000,
position: [2.5, 1, 3.5],
}}
>
<Controller mode={mode} selectedName={selectedName} />
<directionalLight
position={[2.5, 1, 3.5]}
intensity={1}
color={"white"}
/>
<BoxLoader
name={"box1"}
position={[-1, 0, 0]}
color={"red"}
setSelectedName={setSelectedName}
/>
<BoxLoader
name={"box2"}
position={[1, 0, 0]}
color={"skyblue"}
setSelectedName={setSelectedName}
/>
<Grid {...gridConfig} />
</Canvas>
</div>
);
};
๋ณ๊ฒฝ๋ ์ ์ mode๋ผ๋ State์ selectedName์ด๋ผ๋ ์ ํ๋ ๊ฐ์ฒด์ ์ด๋ฆ์ด ์๋ก ์์ฑ๋์๊ณ
์ด๋ฅผ ํ์ํ ์ปดํฌ๋ํธ์ ์ ๋ฌ์๋ก props๋ฅผ ํตํด ์ ๋ฌํ๊ณ ์๋ค.
๊ฐ์ฒด๋ฅผ ํด๋ฆญํ์ ๋, ๊ฐ์ฒด๊ฐ name๊ฐ์ selectedName์ ์ค์ ํ ์ ์๋๋ก ๐ BoxLoader.tsx์ ์ฝ๋๋ฅผ ์์ ํ์.
๐ Editor / ๐ BoxLoader.tsx
import { Dispatch, FC, SetStateAction, useCallback } from "react";
interface BoxLoaderProps {
name: string;
position: [number, number, number];
color: string;
setSelectedName: Dispatch<SetStateAction<string>>;
}
export const BoxLoader: FC<BoxLoaderProps> = ({
name,
position,
color,
setSelectedName,
}) => {
const handleClick = useCallback(() => {
setSelectedName(name);
}, [name, setSelectedName]);
return (
<>
<mesh name={name} position={position} onPointerDown={handleClick}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={color} />
</mesh>
</>
);
};
handleClick() ํจ์๋ฅผ ์์ฑํ์ฌ mesh์ onPointerDown์ ํตํด ํจ์๋ฅผ ์คํ์์ผ ํด๋น ์ค๋ธ์ ํธ์ name์ ์ค์ ๋๊ฒ ํ์๋ค.
์ด์ ์ค๋ธ์ ํธ๋ฅผ ๋๋ฅด๋ฉด,
๋ชจ๋ธ์ translate ๊ฐ์ ํธ์งํ ์ ์๊ฒ ๋์๋ค.
์ด์ ๋ฒํผ์ ๋ง๋ค๊ณ mode๋ฅผ ๋ณ๊ฒฝํ์ฌ ๋ค๋ฅธ ์์ฑ์ ํธ์งํ ์ ์๊ฒ ๋ค์ ๊ฒ์๊ธ์์ ์์ฑํ๊ฒ ๋ค.
'Library > three.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ three.js ] 3D Box ๋ชจ๋ธ์ Transform์ ๋ณ๊ฒฝํด๋ณด์. (feat. fontAwesome) (0) | 2024.05.20 |
---|---|
[ three.js ] 3D ๊ณต๊ฐ์ ์์ฑํ๊ณ ์ปจํธ๋กคํด๋ณด์. (0) | 2024.05.19 |
[ three.js ] three.js์ ๋ํด ์์๋ณด์. (2) | 2024.05.19 |