본문 바로가기
Three.js/Intro

[threejs-journey 1-4] : Object Transform(position, rotation, scale)

by SL123 2025. 6. 1.

- 개요

Three.js에서 Object3D는 모든 3D 객체의 기반이 되는 클래스이며, 위치(position), 회전(rotation), 크기(scale) 세 가지 기본 속성을 가지고 있습니다. 이 글에서는 각각의 속성이 어떤 구조를 가지고 있고, 어떻게 동작하며, 코드로 어떻게 조작할 수 있는지를 간단히 정리합니다.

 

import './style.css'
import * as THREE from 'three'
 

// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

/**
 * Objects
 */
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mesh = new THREE.Mesh(geometry, material)




scene.add(mesh)

// Scale
// mesh.scale.x = 1
// mesh.scale.y = 1
// mesh.scale.z = 1
mesh.scale.set(2, 0.5, 0.5)

 
// rotation
mesh.rotation.reorder('YXZ')
mesh.rotation.x = Math.PI * 0.25
mesh.rotation.y = Math.PI * 0.25
 


// position
// mesh.position.x = 0.7
// mesh.position.y = -0.6
// mesh.position.z = 1
mesh.position.set(0.7, -0.6, 1)

 
// Axes helper(오브젝트)
const axesHelper = new THREE.AxesHelper()
scene.add(axesHelper)


/**
 * Sizes
 */
const sizes = {
    width: 800,
    height: 600
}

/**
 * Camera
 */
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height)
camera.position.z = 3
scene.add(camera)

camera.lookAt(mesh.position);

// mesh와 camera 사이의 거리
// console.log(mesh.position.distanceTo(camera.position));


/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.render(scene, camera)
 

- Vector3

https://threejs.org/docs/#api/en/math/Vector3

 

three.js docs

 

threejs.org

Three.js에서 Object3D는 Scene에 랜더링되기 위한 기본 속성들을 가지고 있는 베이스 클래스입니다. Mesh 역시 Object3D를 상속받기 때문에 동일한 속성들을 가지고 있으며, 기본적으로 월드 좌표계의 원점(0, 0, 0)에 배치되도록 설정되어 있습니다.

 

이러한 기본 속성에서는 position, scale, rotation 이 있으며, 이들은 각각 Vector3(x, y, z) 또는 Euler 객체로 구성됩니다. 중요한 점은, 이 값들은 단순히 숫자 배열이나 행렬이 아니라 new Vector3(x, y, z)로 생성된 인스턴스라는 것입니다. 즉 Object3D 내부에 Vector3가 프로퍼티처럼 들어있는 게 아니라, 해당 속성 값이 각각 Vector3 클래스의 인스턴스라는 뜻입니다.

 

이 점은 저수준 그래픽스를 먼저 공부한 분들에게 다소 헷갈릴 수 있는 부분입니다.(저도 실제로 헷갈렸습니다!) 이후에는 이 Vector3 값을 직접 수정하면서 어떻게 위치, 크기, 회전에 영향을 주는 지 확인해봅시다.

 

- Position

positionVector3 인스턴스를 이용하여 3D 공간에서 Object3D 위치를  정의합니다. 즉, 해당 오브젝트가 어느 위치에 놓여 있는지를 나타내는 간단한 벡터 값입니다.

// mesh.position.x = 0.7
// mesh.position.y = -0.6
// mesh.position.z = 1

mesh.position.set(0.7, -0.6, 1)

Vector3는 개별 축(x, y, z)을 직접 대입하여 수정할 수도 있고, 위처럼 set() 메서드를 통해 한 번에 설정할 수도 있습니다.

퍼포먼스 차이는 사실상 없으므로, 가독성이나 코딩 스타일에 따라 자유롭게 선택해도 됩니다.
개인적으로는 코드의 의도가 명확하게 드러나는 set() 방식을 선호합니다.

 

결과

이전 상태와 비교했을 때, Mesh는 오른쪽(+x), 아래쪽(-y), 앞쪽(+z)으로 이동한 것을 확인할 수 있습니다. 즉, position 값을 변경하면 Object3D의 실체 위치가 변경된다는 것을 알 수 있습니다.

 


- Scale

scale은 Vector3의 인스턴스를 통해 Object3D의 크기를 조절하는 속성입니다. position과 마찬가지로 Vector3(x, y, z) 구조를 사용하지만, 위치를 조절하는 것이 아니라, 각 축 방향의 "배율" 을 조절한다는 점에서 다릅니다.

// Scale
// mesh.scale.x = 1
// mesh.scale.y = 1
// mesh.scale.z = 1

mesh.scale.set(2, 0.5, 0.5)

기본적으로 모든 축은 1로 설정되어 있어 원본 크기로 렌더링됩니다.

위와 같이 설정하면 다음과 같습니다. 

- x 축 : 2배 확대

- y, z 축 : 절반 축소

position과 마찬가지로 set() 메서드를 사용해 한 번에 설정하는 것이 코드 가독성과
명확성 면에서 더 좋다고 판단되어 이 방식을 사용합니다.

 

결과

x축은 커지고 y, z축은 작아진 것을 확인할 수 있습니다.

 

- Rotation

https://threejs.org/docs/index.html#api/en/math/Euler

 

three.js docs

 

threejs.org

rotation은 Vector3가 아닌 Euler(오일러) 값을 사용하여 회전을 표현합니다. Three.js의 Object3D는 기본적으로 XYZ축 회전 순서를 따르는 오일러 회전 방식을 사용합니다.

mesh.rotation.x = Math.PI * 0.25
mesh.rotation.y = Math.PI * 0.25

 

- 오일러 각(Euler Angle)이란?

오일러 각은 축은 기준으로 한 회전 값을 의미합니다.

예를 들어 :

- X축 회전 : 통닭이 돌아가는 모양 (pitch)

- Y축 회전 : 피자가 돌듯이 회전 (yaw)

- Z축 회전 : 비행기 프로펠러처럼 회전 (roll)

 이처럼 각 축마다 의미가 다르고, 회전 순서에 따라 결과가 완전히 달라질 수 있습니다.

 

- 각도 표현

Three.js에서는 각도를 라디안(Radian) 단위로 표현합니다.

Math.PI * 0.25 // => 45도 (180 / 4)

- Math.PI는 180도

- 2 * Math.PI는 360도

따라서 위 코드는 X, Y축 각각을 45도씩 회전한 결과입니다.

해당 결과를 살펴보면 회전이 이상해 보이지 않나요? 이 현상을 바로 짐벌락(Gimbal Lock)입니다!

 

 

- 짐벌락(Gimbal Lock)

두 축을 연속 회전해 보면, 회전이 예상과 다르게 비틀어지는 현상이 발생할 수 있습니다. 이것이 바로 짐벌락(Gimbal Lock) 입니다. 

- 오일러 회전은 순차적인 축 회전

- 특정 회전 조합에서 축이 서로 겹처 자유도(Degree of Freedom)가 사라지는 문제

출저 - Wikipedia Gimbal lock

- 자유도가 사라진다는 게 어떤 뜻일까?

위 그림을 보면 X축을 기준으로 90도 돌리면 Y축과 Z축이 겹쳐서 자유도가 줄어듭니다. 즉, 회전축이 겹치면 의도와 다른 방향으로 회전하거나 회전이 아예 제한되는 문제가 발생합니다.

 

 

- 해결 방법 1 : 회전 순서 변경하기

해결방법은 생각보다 간단합니다. Three.js에서는 .rotation.reorder()를 이용해 회전 순서를 바꿀 수 있습니다.

mesh.rotation.reorder('YXZ');  
mesh.rotation.x = Math.PI * 0.25;
mesh.rotation.y = Math.PI * 0.25;

이렇게 하면 회전이 보다 의도한 방향대로 동작하게 됩니다.

 

결과

FPS는 pitch(상하)가 제한되어 하기 때문에 Y축을 먼저 움직입니다. 실제로 확인해보고 싶다면 FPS캐릭터처럼 Y축(좌우 회전) -> X축(상하 회전)을 적용하거나 그 반대 순서를 적용해 보세요.

 

- 해결 방법 2: 쿼터니언 이용하기

https://threejs.org/docs/#api/en/math/Quaternion

 

three.js docs

 

threejs.org

 

오일러 방식의 회전 한계를 해결하는 또 하나의 방법은 Qaternion(쿼터니언)을 사용하는 것입니다. 쿼터니언은 복소수를 확장한 사원수 기반 회전 표현 방식입니다. 오일러와 달리 축 고정 문제 없이 부드럽고 안정적인 회전이 가능합니다. 특히 3D 게임, 애니메이션, AR/VR 등 회전이 많고 복잡한 시스템에서 필수적으로 사용됩니다.

다만 쿼터니언은 수학적으로 복잡한 개념입니다.
추후 별도의 포스터에서 따로 다뤄보겠습니다.

 

 

결론

이번 포스팅에서는 Three.js에서 3D 객체를 다루는 가장 기본적이지만 핵심적인 세 가지 속성, position, scale, rotation에 대해 알아보았습니다. 3D 그래픽에서 이동, 크기, 회전은 단순한 조작이 아니라, 3D 세계를 구성하는 핵심 기술입니다. 이 세 가지 개념을 정확히 이해하는 순간, 여러분은 3D 공간의 원리를  꿰뚫은 것입니다.

"3D는 결국 이동, 크기, 회전의 응용이다."

 

이해하고 직접 다뤄보는 것이 가장 좋은 학습입니다. 앞으로 더 복잡한 개념도 이 기반 위에서 쉽게 접근하실 수 있을겁니다. 해당 차시 부록에서 다시 뵙겠습니다. 감사합니다.