three.js 를 사용할 때 기본적으로 고려해야할 사항이 3가지 있다. 바로 Scene, Camere, Renderer 이다.
우리는 이 웹 페이지가 어떤 것을 그려주는 지 알아야할 필요가 있다.
바로 Scene 이다.
이 Scene 은 Object3D 를 instance 를 implement 하여 작성한 객체이다.
이 Scene 객체는 3차원 평면에 어떤 작업들을 설정(set up) 할 수 있도록 해준다.
또한 Scene 은 Scene Graph 라고도 불리기도 한다.
import * as THREE from 'three'
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x123456)
scene.background = new THREE.TextureLoader().load('https://sbcode.net/img/grid.png')
scene.background = new THREE.CubeTextureLoader()
.setPath('https://sbcode.net/img/')
.load(['px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png'])
scene.backgroundBlurriness = 0.5
.background
는 배경을 나타내는 속성이다. 여기에는 Color, 혹은 그냥 이미지 처럼 Texture, 혹은 3D로 욺직일 수 있는 CubeTexture 가 존재한다. 그리고 .load() 메서드의 6개의 속성값은 순서대로 오른쪽, 왼쪽, 위, 아래, 가까운, 먼 배경 순이다.
.backgroundBlurriness
속성값은 blur 를 처리하는 부분이다. default 는 0 이고 0~1 의 값을 float 타입으로 부여하면 된다.
const cube = new THREE.Mesh(geometry, material)
sceneA.add(cube)
sceneC.add(cube)
만약 다음과 같이 Scene 객체를 여러개 만들고, cude 변수를 만들었다고 가정할 때, cude 변수를 각기 다른 Scene에 두번 add() 를 하면 결과는 sceneA 에서 cube가 빠지고 sceneC로 옮겨 간다.
즉, Scene 의 객체를 공유할 순 없기 때문에 따로 또 만들어서 넣어줘야한다.
three.js 에서 카메라는 크게 2개로 나눌 수 있다. Perspective Camera 와 Orthographic Camera
Perspective Camera : 시각적인 카메라로 사람의 눈에 의하여 가까이 있는 물체는 가깝게 멀리 있는 물체는 멀게 보여준다.
Orthographic Camera : 원근법을 무시하여 직각(직교) 투영으로 가깝든 멀든 동일하여 투영하는 방법이다.
import './style.css'
import * as THREE from 'three'
import Stats from 'three/addons/libs/stats.module.js'
import { GUI } from 'dat.gui'
const scene = new THREE.Scene()
scene.add(new THREE.GridHelper())
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 2, 3)
camera.lookAt(0, 0.5, 0)
const renderer = new THREE.WebGLRenderer()
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)
})
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshNormalMaterial({ wireframe: true })
const cube = new THREE.Mesh(geometry, material)
cube.position.y = 0.5
scene.add(cube)
const stats = new Stats()
document.body.appendChild(stats.dom)
const gui = new GUI()
const cameraFolder = gui.addFolder('Camera')
cameraFolder.add(camera.position, 'x', -10, 10)
cameraFolder.add(camera.position, 'y', -10, 10)
cameraFolder.add(camera.position, 'z', -10, 10)
cameraFolder.add(camera, 'fov', 0, 180, 0.01).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'aspect', 0.00001, 10).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'near', 0.01, 10).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'far', 0.01, 10).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.open()
function animate() {
requestAnimationFrame(animate)
//camera.lookAt(0, 0.5, 0)
renderer.render(scene, camera)
stats.update()
}
animate()
생정자를 보면 fov 와 aspect, 그리고 near, far plane 을 설정할 수 있다.
near plane 은 내가 볼 수 있는 최소 거리, far plane 은 내가 볼 수 있는 최대 거리를 뜻한다.
만약 비율이 window.innerWidth / window.innerHeight
< 설정 비율
=> 짜부된 거 처럼 보임
window.innerWidth / window.innerHeight
> 설정 비율
=> 가로로 늘려놓은 거 처럼 보임
그리고 이 비율은 위 코드에서 'resize' 이벤트를 등록하여 다시 창의 가로, 세로 비율을 계산하여 이쁘게 적용하도록 하였다.
그리고 창의 크기을 바꾸어 aspect 가 변경되었으면 해당 ProjectionMatrix 도 함께 맞춰 변경해줘야한다.
만약 이 코드가 없다면 다시 짜부된 혹은 가로로 늘려놓은 것 처럼 보일 것이다.
그리고 set 함수로 조금 더 높고 멀리 설정하고 lookAt 함수로 조금 아래를 보도록 설정하였다.
또한 animate 함수 내부의 lookAt 을 해놓면 카메라를 어디로 옮기든 해당 좌표로 고정된다.
통상 OrbitControl 을 많이 사용하지만 우리는 여기서 Camera를 직접 제어하는 방법을 배웠다.
직교 카메라는 원근 카메라에 비해 훨씬 다루기 어렵다. 창의 크기가 바뀌면 그것에 맞춰 apect 가 바뀌도록 설정이 불가능하기 때문이다.
import './style.css'
import * as THREE from 'three'
import Stats from 'three/addons/libs/stats.module.js'
import { GUI } from 'dat.gui'
const scene = new THREE.Scene()
scene.add(new THREE.GridHelper())
const camera = new THREE.OrthographicCamera(-4, 4, 4, -4, -5, 10)
camera.position.set(1, 1, 1)
camera.lookAt(0, 0.5, 0)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
window.addEventListener('resize', () => {
// 직교 카메라는 고정이기 때문에 aspect 이 없음
//camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshNormalMaterial({ wireframe: true })
const cube = new THREE.Mesh(geometry, material)
cube.position.y = 0.5
scene.add(cube)
const stats = new Stats()
document.body.appendChild(stats.dom)
const gui = new GUI()
const cameraFolder = gui.addFolder('Camera')
cameraFolder.add(camera.position, 'x', -10, 10)
cameraFolder.add(camera.position, 'y', -10, 10)
cameraFolder.add(camera.position, 'z', -10, 10)
cameraFolder.add(camera, 'left', -10, 0).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'right', 0, 10).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'top', 0, 10).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'bottom', -10, 0).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'near', -5, 5).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.add(camera, 'far', 0, 10).onChange(() => {
camera.updateProjectionMatrix()
})
cameraFolder.open()
function animate() {
requestAnimationFrame(animate)
// camera.lookAt(0, 0.5, 0)
renderer.render(scene, camera)
stats.update()
}
animate()
따라서 창의 크기를 조절할 수 있도록 한다면 Orthographic Camera 보만 Perspective Camera 가 훨씬 좋은 선택지가 될 듯 한다.
Three.js 에서 가장 보통적인 Renderer 는 바로 WebGLRenderer
이다. WebGLRenderer 는 scene 과 camera 에 대한 정보를 HTLM Canvas 에 그린다.
WebGL은 Renderer 가 캔버스에 대한 2D 이미지를 생성할 때 GPU 가속 이미지 처리 및 효과를 허용한다. 그래서 3D 같은 그래픽 처리에도 막힘없이 빠르게 처리할 수 있는 것이다.
import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 1.5
// 바로 여기 3줄의 코드가 전체화면의 three.js canvas 를 만든다.
const renderer = new THREE.WebGLRenderer({antialias: true})
// 여기 size 부분은 통상 Camera 의 사이즈와 동일하다.
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)
function animate() {
requestAnimationFrame(animate)
// 어떤 것을 렌더링 할지 설정.
renderer.render(scene, camera)
}
animate()
Three.js 렌더러를 생성할 때 브라우저 창 공간을 100% 사용해야 하는지 아니면 크기를 명시적으로 설정할지 고려하는 것이 좋다. 그리고 위 코드에서는 브라우저 공간은 100% 사용하는 것으로 설정하였다. 또한 통상 Camera 의 사이즈와 동일하게 설정한다.
또한 HTML 에 Canvas 태그를 생성하려면 renderer.domElement
객체를 넣어주면 된다.
그리고 animate 에서 rendering 하는 함수도 역시 빼먹으면 안 된다.
그리고 혹시 수동으로 Canvas 태그를 생성하고 ts 로 추가하고 싶은 사람을 위해 코드는 아래와 같이 사용하면 된다.
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const renderer = new THREE.WebGLRenderer({ canvas: canvas })
renderer.setSize(200, 200)
그리고 앞서 언급했든 renderer 의 사이즈를 위 코드와 같이 명시적으로 설정했다면 window 의 resize 이벤트도 필요없고 PerspectiveCamera 의 aspect 도 동일하게 200/200 인 1로 설정해주면 된다.
게임에서도 많이 나오는 설정으로 안티얼라이싱을 활성화하면 선이 훨씬 더 매끄럽게 보인다.