[Three.js journey 강의노트] 07

9rganizedChaos·2022년 9월 23일
1
post-thumbnail

🙌🏻 해당 글은 Three.js Journey의 강의 노트입니다.

07 Cameras

우리는 이미 이전 레슨에서 PerspectiveCamera에 대해서 다룬 바 있다. ThreeJS 공식문서를 살피면, 더 다양한 종류의 카메라가 있다는 걸 알 수 있다. ThreeJS에서 Camera는 기본적으로 추상클래스이다. 때문에 Camera를 바로 사용하는 일은 없고, Camera를 상속받은 클래스들을 통해 카메라의 속성과 메서드를 이용하게 된다.

카메라의 종류에는 다음과 같은 것들이 있다.

  • ArrayCamera: 여러 대의 카메라를 활용해 scene을 여러 차례 렌더해야 할 때 사용하는 카메라
  • StereoCamera: 스테레오 카메라는 사람의 시점을 흉내내는 카메라이다. 카메라 두 대를 통해 사람의 눈을 모사해, 깊이감을 조성하고 이를 통해, parallaxa 효과 등을 만들 수 있다. 물론 이 결과를 제대로 보기 위해서는 VR headset과 같은 장치들이 필요하다.
  • CubeCamera: 큐브카메라는 상하좌우전후를 각각 렌더할 때 사용된다. 대표적으로 environment map이나 shadow map을 위해 활용되는데 이는 추후에 다루도록 할 것이다.
  • OrthographicCamera: OrthographicCamera는 시점이나 공간감, 원근감을 가능한 배제한 형태를 보여주는 카메라이다. 오브젝트와 카메라 사이의 거리가 얼마나되는지와 무관하게 모든 요소들이 같은 사이즈로 렌더된다.
  • PerspectiveCamera: Orthographic 카메라와 반대로 시점이 반영되어 공간감과 원근감을 느낄 수 있는 모습을 렌더한다.

PerspectiveCamera

Perspective Camera를 인스턴스화하기 위해서는 몇 가지 파라미터들을 넘겨주어야 한다.

const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 100)

각각의 파라미터들이 어떤 역할을 하는지 알아보자.

첫 번째 인자: Field of view

첫 번째 인자는 Field of view인데, 이는 카메라 뷰의 수직 각도 너비에 해당한다. 앵글이 작아지면 망원렌즈의 카메라처럼 보이게 되고, 앵글이 넓어지면, 카메라에 담기는 것이 많기 때문에 어안효과를 얻을 수 있게 된다다. 보통 45에서 75정도의 각도를 사용하면 적절한 결과를 얻을 수 있다.

const camera = new THREE.PerspectiveCamera(25, sizes.width / sizes.height, 1, 100)
const camera = new THREE.PerspectiveCamera(45, sizes.width / sizes.height, 1, 100)
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 100)

두 번째 인자: Aspect ratio

width를 height로 나눈 값, Aspect ratio, 종횡비. 보통은 ThreeJS를 렌더하는 캔버스의 종횡비를 쓰지만, 늘 그러한 것은 아니다. Aspect Ratio는 여러 곳에서 활용하게 되므로, 가급적 sizes라는 객체를 만들어두고 활용하는 것을 추천한다.

const sizes = {
    width: 800,
    height: 600
}

세 번째 인자: Near and far

Near and far는 카메라가 얼마나 멀리까지 볼 수 있는지, 그리고 얼마나 가까이에 있는 물체까지 볼 수 있는지를 결정해주는 인자라고 이해하면 된다. 범위 밖에 존재하는 오브젝트들은 아예 렌더되지 않는다. 애초에 카메라가 얼마나 가까이에 있는 물체까지만 인지할 것인지, 어느 정도 거리까지의 물체들까지만 렌더할지를 설정해줄 수 있는 것이다. 0.1에서 100사이의 합리적인 숫자 내에서 해당 값들을 설정해볼 수 있다. 이 인자는 z-fighting 버그를 방지하기 위해 유용한 인자이다.

현재 코드에서 다음과 같이 콘솔을 찍어서 오브젝트와 카메라 사이의 거리를 측정해보자!

console.log(mesh.position.distanceTo(camera.position))
// 3.4641016151377544

3.4641016151377544 라는 값이 출력된다.

그리고 near가 3이면 아래와 같은 결과를 낳게 된다.

OrthographicCamera

남은 코스를 진행하는 동안 Orthographic Camera에 대해서 다루지는 않을 것이지만, 이 카메라의 경우 특정 프로젝트들에 아주 유용하게 활용될 수 있다. 위에서 언급한 바와 같이 Orthographic Camera는 시점이 배제된 카메라라고 이해하면 된다. 원근감이 배제되어 있는 것이다.

const camera = new THREE.OrthographicCamera(- 1, 1, 1, - 1, 0.1, 100)

Orthographic Camera에 넘겨주는 매개변수는 Perspective Camera와는 아주 다른데, 우선 field of view 대신 카메라각 각 방향으로 얼마나 멀리까지 볼 수 있는지를 알려주어야 한다. 그리고 나서 near과 far 변수를 전달한다.

원근감이 없어진 것은 확인하였으나, 큐브가 위아래로 조금 납작해져있는 점을 발견할 수 있다.
우리가 OrthographicCamera에 넘겨준 첫 네 인자들은 결국, 정방형 공간을 렌더하되 정방형 공간이 우리의 캔버스에 맞게 늘려지도록 하는 것을 의미한다. 때문에 다음과 같이 코드를 약간 수정해주자!

const aspectRatio = sizes.width / sizes.height
const camera = new THREE.OrthographicCamera(- 1 * aspectRatio, 1 * aspectRatio, 1, - 1, 0.1, 100)

Custom controls

이제부터는 마우스를 통해서 카메라를 조종해볼 것이다. 먼저 JS의 mousemove 이벤트를 통해서 마우스의 좌표를 알아낼 것이다.

window.addEventListener('mousemove', (event) =>
{
    console.log(event.clientX, event.clientY)
})

JS에서 제공해주는 값 그대로 사용해도 되지만, 해당 레슨에서는 값을 1이라는 크기의 표준값을 설정하고 값을 변환해서 사용하는 것을 추천한다.

예를 들어, 마우스 커서가 중앙에 있을 때는 0, 오른쪽으로 이동했을 때는 0.5, 왼쪽으로 이동했을 떄는 -0.5와 같은 값이 출력되도록 변환하는 것이다.

const cursor = {
    x: 0,
    y: 0
}

window.addEventListener('mousemove', (event) => {
    cursor.x = event.clientX / sizes.width - 0.5
    cursor.y = - (event.clientY / sizes.height - 0.5)
  // ThreeJS에서와 브라우저에서 y축을 음양의 방향이 서로 다르므로 -1을 곱해준다.

    console.log(cursor.x, cursor.y)
})

카메라의 위치 변환

위와 같이 마우스 커서의 위치값을 알아내고 나면, 이 값들을 tick함수에 적용해주도록 한다.

const tick = () => {
    // ...
    camera.position.x = cursor.x
    camera.position.y = cursor.y

    // ...
} 

위와 같이 코드를 수정해주면 아래와 같은 결과를 얻을 수 있게 된다!

카메라의 각도 변환 (오브젝트를 바라보도록!)

여기에 lookAt(...) 메서드를 추가해주면 카메라의 각도도 조절해줄 수 있다!

const tick = () =>{
    // ...
    camera.position.x = cursor.x * 5
    camera.position.y = cursor.y * 5
    camera.lookAt(mesh.position)
    // ...
}

카메라의 full rotation

Math.sin(...)Math.cos(...)를 이용하면, 오브젝트를 중심으로 카메라를 회전시킬 수도 있다. full rotation을 위한 "tau"라는 값이 존재하지만, 이는 JS에서 지원하지않으므로 우리는 sin과 cos를 조합해서 카메라의 Full Rotation을 시도할 것이다.

const tick = () => {
    // ...
    camera.position.x = Math.sin(cursor.x * Math.PI * 2) * 2
    camera.position.z = Math.cos(cursor.x * Math.PI * 2) * 2
    camera.position.y = cursor.y * 3
    camera.lookAt(mesh.position)
    // ...
}

tick()

지금까지 어떻게 카메라를 컨트롤할 수 있는지 알아보았다. 하지만 사실 ThreeJS는 기본적으로 카메라를 컨트롤하기 위한 몇 가지 클래스들을 내장하고 있다. 빌트인 컨트롤에 대해서 마저 알아보자!

Built-in controls

공식문서를 살피면 ThreeJS는 이미 아주 많은 Pre-made 컨트롤들을 제공하고 있음을 알 수 있다. 우리는 Orbit Control에 대해서만 알아볼 것이지만 다양한 컨트롤들을 간략하게나마 알아보고 넘어가자.

  • DeviceOrientationControls: VR experience를 조성하기 위해 활용가능한 컨트롤. 디바이스를 자동으로 검색하고 OS나 브라우저가 허용한다면 그에 따라 카메라를 움직여준다. (현재는 공식문서에서 검색되지 않는걸로 보아 지원하지 않는 것으로 보인다.)
  • FlyControls: 우주선에 탄듯한 카메라의 무빙을 가능하게 하는 컨트롤. 앞뒤로 이동 가능하며, xyz 세 축 모두로 회전이 가능하다.
  • FirstPersonControls: FlyControl과 비슷하다, 그러나 축이 고정되어있다. barrel roll이 불가능한 새의 시점이라고 생각하면 된다.
  • PointerLockControls: JS의 pointer lock API를 활용한 컨트롤이다. 커서를 숨긴 상태로 가운데에 고정시킨 다음, mousemove 이벤트의 콜백을 통해 그 움직임을 계속해서 감지한다. FPS게임을 만들기에 적합하다.
  • OrbitControls: 이전에 직접 만든 컨트롤과 가장 비슷한 형태의 컨트롤. 마우스 좌측 클릭으로 카메라 회전이 가능하며, 우클릭으로 수직 수평이동이 가능하고, 휠을 통해 줌이 가능하다.
  • TrackballControls: 수직 앵글에 제한이 없는 OrbitControls. scene의 위아래가 바뀌어도 여전히 회전할 수 있다.
  • TransformControls: 특별히 카메라를 컨트롤 할 수는 없고, 물체를 움직이기 위한 gizmo를 추가하기 위해 사용한다. (what's gizmo...?)
  • DragControls: 카메라를 마주본 평면에서 물체를 움직이기 위해 사용.

OrbitControls

Instantiating

먼저 OrbitControls 클래스를 활용하여 인스턴스화를 해준다. 이 때 주의할 점은 OrbitControls의 일부는 THREE 변수에서 기본으로 제공하지 않고 있다는 점이다. (라이브러리의 무게를 줄이기 위한 결정.) OrbitControls를 임포트하기 위해서는 /node_modules/안에 있는 모듈을 직접 꺼내와야 한다.

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

const controls = new OrbitControls(camera, canvas)

꺼내온 후에 OrbitControls에 카메라와 캔버스를 인자로 넘겨주어 OrbitControls를 인스턴스화해준다.

위 gif는 차례로, 마우스 좌측 클릭을 통해 카메라를 회전하는 모습, 마우스 우측 클릭을 통해 카메라를 수평/수직 이동 시키는 모습, 마우스 휠을 통해 줌인/줌아웃하는 모습이다.

Target

기본적으로 카메라는 scene의 중앙을 바라본다. 우리는 target 프로퍼티를 이용해 이를 변경해줄 수 있다. 해당 프로퍼티 역시 Vector3를 값으로 받으므로, x, y, z 각각을 조정해줄 수 있다.

controls.target.y = 2

Damping

Damping은 가속과 마찰의 공식을 활용해 애니메이션을 스무스하게 만들어주는 역할을 하는 속성이다.
Controls의 enableDamping 속성 값을 true로 만들어주면, 해당 효과를 활성화할 수 있다. 이 속성을 올바르게 활용하기 위해서는 컨트롤이 controls.update()를 통해 매 프레임마다 업데이트되어야 한다. (tick 함수를 활용하면 된다.)

const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

const tick = () => {
    controls.update()
}

확연히 부드러워진 움직임을 확인할 수 있다. 사실 이 외에도 컨트롤을 커스텀하기 위한 다양한 속성들이 존재한다. 회전 속도, 줌 속도, 줌 리미트, 각도 리미트, 댐핑의 세기, 그리고 키 바인딩 등 모두 커스텀할 수 있다!

결론!

무작정 내장 컨트롤을 활용하다보면, 클래스가 의도치 않은 방식으로 움직이는 부작용이 있을 수 있다. 항상 작업을 할 때 내가 어떤 컨트롤을 필요로 하는지 리스트업을 하고, 해당 클래스가 그 조건들을 만족하는지 확인을 해야 한다. 그렇지 않다면, 직접 커스텀 컨트롤을 만들어주는 편이 좋다!

profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

0개의 댓글