Object3 D의 부모 자식관계.
Three.js에서는 Object3D에 자식요소로 다른 Object3D를 추가할 수 있습니다. 그리고 위치, 배율, 회전과 같은 부모객체에 대해 변경사항이 일어날 시 자식 객체에도 영향을 줍니다. 이러한 방식은 웹브라우저 페이지 스타일링시 부모요소의 position을 기준으로 따라가는 자식요소의 상대위치 개념과 유사합니다. 아래와 같은 오브젝트 3D 계층 구조를 만들고 위치, 스케일, 회전 등으로 객체를 조작해 어떻게 동작하는 지 알아봅시다.
scene.add(cube) //cube은 scene의 자식이 된다.
//오브젝트 3D 계층 구조
//빨간공은 초록공 자식이 있고, 초록공은 파랑공 자식이 있는 구조입니다.
//가시축 AxesHelper은 각 scene과 object의 자식요소로 한 개씩 포함되어있으나 가독성을 위해 오브젝트 3D 계층 구조에서 생략했습니다.
scene
|--object1 (Red Ball)
|--object2 (Green Ball)
|--object3 (Blue Ball)

const object1 = new THREE.Mesh( //빨간공
new THREE.SphereGeometry(),
new THREE.MeshPhongMaterial({ color: 0xff0000 })
)
object1.position.set(4, 0, 0)
scene.add(object1)
object1.add(new THREE.AxesHelper(5)) //나중에 빨간공을 회전시 x,y,z축 방향이 잘 보이도록 빨간공 자식요소로 AxesHelper가시축도 추가했습니다.
const object2 = new THREE.Mesh( //초록공
new THREE.SphereGeometry(),
new THREE.MeshPhongMaterial({ color: 0x00ff00 })
)
object2.position.set(4, 0, 0)
object1.add(object2) //초록공은 빨간공의 자식으로 추가된다.
object2.add(new THREE.AxesHelper(5))
const object3 = new THREE.Mesh( //파랑공
new THREE.SphereGeometry(),
new THREE.MeshPhongMaterial({ color: 0x0000ff })
)
object3.position.set(4, 0, 0)
object2.add(object3)
object3.add(new THREE.AxesHelper(5))
위에 코드는 빨간공의 자식, 초록공. 초록공의 자식, 파랑공으로 구성된 오브젝트 3D 계층 구조를 만든 코드 일부분입니다.
1. object1이라는 빨간 공을 만들었습니다.
2. 빨간 공의 위치값을 (4,0,0)으로 지정하고 scene에 추가했습니다. (이후 회전과 같은 객체들의 변경사항을 더 잘 확인하기 위해 x, y, z 축을 가시화한 AxesHelper를 추가했다.)
3. object2라는 초록공을 만듭니다.
4. 초록공의 위치값을 (4,0,0)으로 지정하고 object1(빨간 공)의 자식요소로 추가했습니다.
여기서 중요한 것은 빨간 공, 초록공 위치값 모두 (4,0,0)으로 지정했지만 화면에 보이는 공들의 위치는 다르다는 것입니다. object를 변형하는 postion(위치변경), rotation(각도변경), scale(크기변경) 등은 기본적으로 부모객체에 따라 영향을 받는 지역참조의 값으로 반환됩니다. 그렇기 때문에 빨간 공의 부모객체는 scene이고 초록공의 부모객체는 빨간공 0 bject1이므로 부모객체의 위치를 기준으로 자식객체의 위치값이 결정되는 것입니다. 이러한 object의 부모와 자식의 관계는 추후 캐릭터의 팔과 다리와 같은 애니메이션 효과를 줄 때 자연스럽게 그 자식 요소인 손가락과 종아리 또한 움직이는 데 유용할 것입니다.
| 빨간공 | 초록공 | 파랑공 | |||
| local position x축 | world position x축 | local position x축 | world position x축 | local position x축 | world position x축 |
| 4 | 4 | 4 | 8 | 4 | 12 |


*AxesHelper
AxesHelper는 x, y, z 축을 시각화해서 보여주는 속성입니다. 인자로 축의 길이를 넘겨줄 수 있습니다.
const axesHelper = new THREE.AxesHelper(5) //길이가 5인 가시축
scene.add(axesHelper) //axesHelper는 scene의 자식이 되어 자동으로 0,0,0에 위치하게 된다.

World Transform
정리하자면 position, rotation/quaternion, scale, 과 같은 object3 D 변형을 일으키면 지역 참조의 값으로 반환된다 로 말할 수 있습니다. 그렇다면 부모기준으로 측정되는 로컬참조의 위치값이 아닌 전역으로 object3D의 위치를 파악하려면 어떻게 해야 할까요? object의 World Transform을 알아봅시다.
// values are local transform
obj.position
obj.rotation
obj.quaternion
obj.scale
//world Transform
const objectsWorldPosition = new THREE.Vector3() //Vector3 생성
object.getWorldPosition(objectsWorldPosition) //object 의 world postion을 추출해 objectsWorldPosition에 담습니다.
console.log(object1WorldPosition.x.toFixed(2)); //Object의 worldpostion의 x 값을 알 수 있다.
const objectsWorldDirection = new THREE.Vector3()
object.getWorldDirection(objectsWorldDirection) //객체의 양의 z 축 방향을 나타내는 벡터를 반환
const objectsWorldQuaternion = new THREE.Quaternion()
object.getWorldQuaternion(objectsWorldQuaternion)
const objectsWorldScale = new THREE.Vector3()
object.getWorldScale(objectsWorldScale)
1. vector3을 생성합니다.
2. object의 메서드 getWorldPosition의 인자로 vector3 변수(여기서는 objectWorldPosition)를 넣어 실행합니다.
3. 해당 변수 objectWorldPosition의 x , y, z 값을 꺼냅니다.
이렇게 만들어진 objectWorldPosition 변수를 통해 World Transform 값을 알 수 있습니다.
*vector3 란 3D 공간에서의 위치, 이동 벡터 등을 표현하기 위해 사용되는 클래스입니다.
object를 변형하는 4 가지 속성
object3 D를 상속받은 모든 class(perspectivecamera, mesh 등)는 위와 같은 속성 4가지(position, scale, rotation, quaternion)를 갖습니다. object를 변형하는 4가지 속성에 대해 자세히 알아봅시다. (해당 속성들은 matrices로 컴파일된다네여.)
- position:위치 변경
- scale : 크기 변경
- rotation: 회전
- quaternion: 회전과 관련된 속성
1. position :위치 변경
주의할 점으로 render 메서드 호출전에 이동시켜야 합니다. position의 속성은 Vector3의 인스턴스입니다. x, y, z 속성으로 object의 위치값을 변경해줄 수 있습니다. 그 외에도 다양한 속성들이 존재합니다.
cube.position.x = 3;
cube.position.y = 4;
cube.position.z = -2;
cube.position.set(1,2,3)// x,y,z 축 한 번에 위치 설정
console.log(mesh.position.length()) // 0,0,0 부터 mesh 까지의 거리
console.log(mesh.position.distanceTo(camera.position)) //object(카메라)와 mesh의 거리
console.log(mesh.position.normalize()) //방향을 유지하면서 length를 1로 설정
Q.mesh.position.normalize()?
2. scale : 크기 변경
기본값은 1:1:1
mesh.scale.x = 2 //두배
mesh.scale.y = 0.5 // 절반
mesh.scale.z = 3
3. rotation : 회전
회전과 관련된 속성은 rotation과 quaternion 두가지가 있습니다. 두 속성중 하나를 업데이트하면 나머지 하나도 자동 변경이 된다네요. rotation 은 Euler라고 합니다. 단위는 *라디안radian 을 사용하며 Math.PI *2 = 360 도 를 의미합니다. 주의할 점은 rotation 회전시 같이 다른 축 방향도 동시에 변한다는 점(좀더 자세히설명)입니다. 이러한 문제로 Quaternion라는 메서드를 사용한다고 합니다.
mesh.rotation.x = Math.PI * 2 // 360도
mesh.rotation.y = Math.PI * 0.5 //90도
mesh.rotation.z = Math.PI *1 //180도
Q.Euler란?
*라디안 : 각을 나타내는 또 다른 단위로, 원의 반지름에 대한 호의 길이의 비로 정의합니다.
전체 코드
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Stats from 'three/examples/jsm/libs/stats.module'
import { GUI } from 'dat.gui'
const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
camera.position.x = 4
camera.position.y = 4
camera.position.z = 4
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.target.set(8, 0, 0)
const light1 = new THREE.PointLight(0xffffff, 400)
light1.position.set(10, 10, 10)
scene.add(light1)
const light2 = new THREE.PointLight(0xffffff, 400)
light2.position.set(-10, 10, 10)
scene.add(light2)
const object1 = new THREE.Mesh(
new THREE.SphereGeometry(),
new THREE.MeshPhongMaterial({ color: 0xff0000 })
)
object1.position.set(4, 0, 0)
scene.add(object1)
object1.add(new THREE.AxesHelper(5))
const object2 = new THREE.Mesh(
new THREE.SphereGeometry(),
new THREE.MeshPhongMaterial({ color: 0x00ff00 })
)
object2.position.set(4, 0, 0)
object1.add(object2)
object2.add(new THREE.AxesHelper(5))
const object3 = new THREE.Mesh(
new THREE.SphereGeometry(),
new THREE.MeshPhongMaterial({ color: 0x0000ff })
)
object3.position.set(4, 0, 0)
object2.add(object3)
object3.add(new THREE.AxesHelper(5))
window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
}
const gui = new GUI()
const object1Folder = gui.addFolder('Object1')
object1Folder.add(object1.position, 'x', 0, 10, 0.01).name('X Position')
object1Folder
.add(object1.rotation, 'x', 0, Math.PI * 2, 0.01)
.name('X Rotation')
object1Folder.add(object1.scale, 'x', 0, 2, 0.01).name('X Scale')
object1Folder.open()
const object2Folder = gui.addFolder('Object2')
object2Folder.add(object2.position, 'x', 0, 10, 0.01).name('X Position')
object2Folder
.add(object2.rotation, 'x', 0, Math.PI * 2, 0.01)
.name('X Rotation')
object2Folder.add(object2.scale, 'x', 0, 2, 0.01).name('X Scale')
object2Folder.open()
const object3Folder = gui.addFolder('Object3')
object3Folder.add(object3.position, 'x', 0, 10, 0.01).name('X Position')
object3Folder
.add(object3.rotation, 'x', 0, Math.PI * 2, 0.01)
.name('X Rotation')
object3Folder.add(object3.scale, 'x', 0, 2, 0.01).name('X Scale')
object3Folder.open()
const stats = new Stats()
document.body.appendChild(stats.dom)
const debug = document.getElementById('debug1') as HTMLDivElement
function animate() {
requestAnimationFrame(animate)
controls.update()
render()
const object1WorldPosition = new THREE.Vector3()
object1.getWorldPosition(object1WorldPosition)
const object2WorldPosition = new THREE.Vector3()
object2.getWorldPosition(object2WorldPosition)
const object3WorldPosition = new THREE.Vector3()
object3.getWorldPosition(object3WorldPosition)
debug.innerText =
'Red\n' +
'Local Pos X : ' +
object1.position.x.toFixed(2) +
'\n' +
'World Pos X : ' +
object1WorldPosition.x.toFixed(2) +
'\n' +
'\nGreen\n' +
'Local Pos X : ' +
object2.position.x.toFixed(2) +
'\n' +
'World Pos X : ' +
object2WorldPosition.x.toFixed(2) +
'\n' +
'\nBlue\n' +
'Local Pos X : ' +
object3.position.x.toFixed(2) +
'\n' +
'World Pos X : ' +
object3WorldPosition.x.toFixed(2) +
'\n'
stats.update()
}
function render() {
renderer.render(scene, camera)
}
animate()
출처
'three.js' 카테고리의 다른 글
| [React] Three.js 적용하기 (1) | 2023.10.12 |
|---|---|
| Geometries (1) | 2023.09.27 |
| 유용한 Stats.js과 dat.gui 적용하기 (0) | 2023.09.23 |
| Three.js 예제 코드로 기본 구조 파악하기 (1) | 2023.09.21 |
| Three.js 프로젝트 세팅하기 with TS (0) | 2023.09.20 |