Object 3D 는 가장 기본적인 class 이다. 그리고 많은 객체들이 이 Object 3D 를 상속받아서 만들어진다.
이 3D 객체는 3차원 공간에서 객체를 조작하기위한 기본적인 메서드들과 속성값들을 제공한다.
가장 대표적인 예로 Meshes
, Lights
, Cameras
, Object3D 그룹
이 있다.
우선 Object 3D 의 가장 대표적인 몇가지 속성에 대해 알아보자.
그러기 위해 기본적인 코드를 작성하자.
import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/addons/libs/stats.module.js'
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.set(1, 2, 3)
const renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
new OrbitControls(camera, renderer.domElement)
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshNormalMaterial({ wireframe: true })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)
const stats = new Stats()
document.body.appendChild(stats.dom)
const gui = new GUI()
const cubeFolder = gui.addFolder('Cube')
cubeFolder.add(cube, 'visible')
cubeFolder.open()
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
stats.update()
}
animate()
우선 아래 속성들을 알아보기 전에 AxesHelper 를 보자.
위 사진과 같이 x,y,z 축에 선을 그어 객체가 정위치에 위치할 수 있도록 도와주는 선이다.
const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))
const rotationFolder = cubeFolder.addFolder('Rotation')
rotationFolder.add(cube.rotation, 'x', 0, Math.PI * 2)
rotationFolder.add(cube.rotation, 'y', 0, Math.PI * 2)
rotationFolder.add(cube.rotation, 'z', 0, Math.PI * 2)
rotationFolder.open()
const positionFolder = cubeFolder.addFolder('Position')
positionFolder.add(cube.position, 'x', -5, 5)
positionFolder.add(cube.position, 'y', -5, 5)
positionFolder.add(cube.position, 'z', -5, 5)
positionFolder.open()
const scaleFolder = cubeFolder.addFolder('Scale')
scaleFolder.add(cube.scale, 'x', -5, 5)
scaleFolder.add(cube.scale, 'y', -5, 5)
scaleFolder.add(cube.scale, 'z', -5, 5)
scaleFolder.open()
cube.visible = false
// truthy, falsy
cubeFolder.add(cube, 'visible')
Object3D 는 모든 것의 부모 class 라고 하였다. 그리고 Scene 역시 Object3D의 자식이다. 그리고 이 scene 에 .add()
를 통하여 어떤 객체를 추가하면 이는 Scene 의 자식이 되며 'resize' 이벤트등을 통하여 Scene 의 크기나 위치가 변경되면 Scene 의 모든 자식들에게 영향을 미친다.
즉, 이 말은 Object3D를 다른 Object3D를 상속받은 객체의 자식으로 만들 수도 있다는 뜻이다.
이는 기존 오브젝트에 새 오브젝트를 계속 추가하여 Object3D 계층 구조를 만들 수 있다는 것이다.
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))
위 코드와 사진을 보면 Scene <- 빨간공 <- 초록공 <- 파란공
이렇게 붙어있는 것을 볼 수 있다.
그리고 코드의 position 을 보면 모두 4,0,0 을 주었고 사진의 왼쪽 빨간색 박스를 보면 local position 은 4,0,0 이지만 world position 은 각각의 Object3D 의 부모의 영향을 받아 4씩 더해지고 있다.
그리고 Object3D 의 world 속성에 관하여 데이터를 얻고 싶다면 .getWorldPosition()
, .getWorldDirection()
, .getWorldQuaternion()
, .getWorldScale()
를 사용하면 된다.
const objectsWorldPosition = new THREE.Vector3()
object.getWorldPosition(objectsWorldPosition)
console.log(objectsWorldPosition)
const objectsWorldDirection = new THREE.Vector3()
object.getWorldDirection(objectsWorldDirection)
console.log(objectsWorldDirection)
const objectsWorldQuaternion = new THREE.Quaternion()
object.getWorldQuaternion(objectsWorldQuaternion)
console.log(objectsWorldQuaternion)
const objectsWorldScale = new THREE.Vector3()
object.getWorldScale(objectsWorldScale)
console.log(objectsWorldScale)
참고로 Quaternion
은 rotation 보다 더 세부적인 값을 설정할 수 있다. 이는 추후 다루도록 하겠다.
import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/addons/libs/stats.module.js'
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.set(4, 4, 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)
controls.update()
const light = new THREE.PointLight(0xffffff, 400)
light.position.set(10, 10, 10)
scene.add(light)
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', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
const gui = new GUI()
const object1Folder = gui.addFolder('Object1 (Red Ball)')
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 (Green Ball)')
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 (Blue Ball)')
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('debug') as HTMLDivElement
function animate() {
requestAnimationFrame(animate)
renderer.render(scene, camera)
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()
}
animate()