3D 모델

3D

가장 많이 사용되는 형식은 3DS, FBX, glTF, OBJ 등 이고, 특히 glTF는 Khronos Group에서 정의한 표준으로 효율성이 아주 뛰어난 형식이라고 한다.

https://www.mixamo.com/

위 사이트에서 3D모델과 애니메이션을 다운로드 받을 수 있다.

모델파일과 애니메이션파일을 블렌더라는 프로그램을 통해 하나의 glTF파일로 생성할 수 있다.

나는 제공하는 예시를 가지고 진행

app.js

<Canvas
  shadows
  camera={{
    near: 1,
    far: 100,
    position: [7, 7, 0],
  }}
>
  <ThreeD />
</Canvas>

ThreeD.jsx

function ThreeD() {
  const model = useGLTF("./models/model.glb");
  return (
    <>
      <OrbitControls />

      <Environment preset="city" />

      <primitive scale={5} object={model.scene} />
    </>
  );
}

export default ThreeD;

3D 모델을 useGLTF("./models/model.glb");을 통해서 가져오고

<primitive object={model.scene} />을 통해서 화면에 추가한다.

또한 화면에 추가했으나 광원이 없어 보이지 않기 때문에 Environment을 추가한다.

Environment 의 preset 속성을 이용하면 기본적으로 제공되는 광원을 사용할 수 있다.

이 모델의 중심이 발끝에 맞춰져있다. 따라서 Y축에 대해서 모델의 높이값의 절만만큼 아래로 이동하면 된다.

이를 위해 모델의 높이를 구하려면

useEffect(() => {
  let minY = Infinity,
    maxY = -Infinity;

  model.scene.traverse((item) => {
    if (item.isMesh) {
      const geomBbox = item.geometry.boundingBox;
      if (minY > geomBbox.min.y) minY = geomBbox.min.y;
      if (maxY < geomBbox.max.y) maxY = geomBbox.max.y;
    }
  });

  const h = maxY - minY;
  console.log(h); // 1.8047408568527317
  setHeight(h);
}, [model.scene]);

let minY = Infinity, maxY = -Infinity; - y축에 대한 최소값과 최대값을 얻기위한 변수 정의

model.scene.traverse() - 구성 객체를 하나씩 순회한다.

traverse( callback ) {
	callback( this );
	const children = this.children;
	for ( let i = 0, l = children.length; i < l; i++ ) {
		children[ i ].traverse( callback );
	}
}
model.scene.traverse((item) => {
  if (item.isMesh) {
    const geomBbox = item.geometry.boundingBox;
    if (minY > geomBbox.min.y) minY = geomBbox.min.y;
    if (maxY < geomBbox.max.y) maxY = geomBbox.max.y;
  }
});

여기서 item은 하나의 객체이며, 아이템이 메쉬일 경우(if (item.isMesh)) 최소경계 상자(item.geometry.boundingBox)를 구한다.

경계 상자의 y축에 대한 값을 얻어오는데, 결국 minY, maxY에는 Y축의 최소값과 최대값이 저장된다.

Infinity는 0을 넣은 것과 동일하다.

이후 useState로 height값을 저장하고 position-y={-(height / 2) * 5} 추가. 5를 곱한 이유는 scale이 5이기 때문

애니메이션


애니메이션 정보를 얻기 위해 useAnimation 사용
const animations = useAnimations(model.animations, model.scene)

재생할 수 있는 애니메이션을 UI로 표현하자

const { actionName } = useControls({
  actionName: {
    value: animations.names[1],
    options: animations.names,
  },
});

애니메이션을 재생하려면

useEffect(() => {
  const action = animations.actions[actionName];
  action.play();
}, [actionName]);

하지만 다른 애니메이션을 클릭하면 이전 상태의 애니메이션도 동시에 적용되고 있는 상태이기 때문에, 이전 상태를 지우고 현재상태를 유지해야한다.

action.reset().fadeIn(0.5).play();

return () => {
  action.fadeOut(0.5);
};

reset() - 새롭게 선택된 애니메이션을 첫번째 프레임으로 갖는다.

fadeIn(0.5) - 0.5초에 걸쳐서 서서의 애니메이션이 재생될 수 있도록

return () => {action.fadeOut(0.5)} 이전의 애니메이션을 0.5초에 걸쳐서 서서히 사라지도록한다.

댓글남기기