Three.js는 3D Javascript 라이브러리이다.
개발자가 web에서 3D 경험을 개발할 수 있도록 해준다.
WebGL로 작동한다.
<canvas>
에 결과물을 그릴 수 있다.점들을 배치 및 어떻게 그려줄지 정해주는 것이 shaders이다.
모든 점들과 픽셀을 컨트롤 해야하기 때문에 WebGL은 힘들다
이를 보완하기 위해서 나온 것이 Three.js이다.
WebGL과 상호작용 가능하다.
shader를 직접 만들 수 있다.
Node.JS
Vite
Three.js
4가지 요소
Scene
objects, models, particles, lights, etc등을 보관하는 container
Objects
objects, models, particles, lights, etc등이 될 수 있다.
Mesh - geometry, material
position
Vector3 메소드
3차원 에서 배치하기 어려울 때
Scale Object
Rotate Object
Rotation
Quaternion
LookAt메소드
Group
requestAnimationFrame
시간을 활용한 Animations
Three.js에서 제공하는 Clock
GSAP
Camera 종류
ArrayCamera
StereoCamera
CubeCamera
OrthographicCamera
PerspectiveCamera
PerspectiveCamera의 특징 및 활용
FOV
Aspect ratio
Near and Far
OrthographicCamera
OrthographicCamera를 canvas aspect ratio 맞추는 법
const aspectRatio = sizes.width / sizes.height
const camera = new THREE.OrthographicCamera(- 1 * aspectRatio, 1 * aspectRatio, 1, - 1, 0.1, 100)
Controls
Custom Controls
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)
renderer.render(scene, camera)
window.requestAnimationFrame(tick)
}
// Cursor
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)
})
tick()
Built in Controls
OrbitControls
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'에서 가져온다.
const controls = new OrbitControls(camera, canvas)
controls.target.y = 2
controls.update()
Damping
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
// ...
const tick = () =>
{
// ...
// Update controls
controls.update()
// ...
}
FullScreen
*
{
margin: 0;
padding: 0;
}
.webgl
{
position: fixed;
top: 0;
left: 0;
outline: none;
}
html,
body
{
overflow: hidden;
}
Resize
pixel ratio
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})
Built-in Geometries
https://threejs.org/docs/#api/en/geometries/BoxGeometry
BufferGeometry
// Create an empty BufferGeometry
const geometry = new THREE.BufferGeometry()
// Create a Float32Array containing the vertices position (3 by 3)
const positionsArray = new Float32Array([
0, 0, 0, // First vertex
0, 1, 0, // Second vertex
1, 0, 0 // Third vertex
])
// Create the attribute and name it 'position'
const positionsAttribute = new THREE.BufferAttribute(positionsArray, 3)
geometry.setAttribute('position', positionsAttribute)
Debut UI 종류
lil-gui
https://lil-gui.georgealways.com/
lil-gui 세팅법
import GUI from 'lil-gui'
/**
* Debug
*/
const gui = new GUI()
기능 조절 종류
tweaks gui에 추가하는 법
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
gui.add(mesh.position, 'y')
gui
.add(mesh.position, 'y')
.min(- 3)
.max(3)
.step(0.01)
.name('elevation')
gui.add(mesh, 'visible')
gui.add(material, 'wireframe')
Color
첫번째 방법
gui
.addColor(material, 'color')
.onChange((value) =>
{
console.log(value.getHexString())
})
두번째 방법
gui
.addColor(debugObject, 'color')
.onChange(() =>
{
material.color.set(debugObject.color)
})
Function / Button
debugObject.spin = () =>
{
gsap.to(mesh.rotation, { duration: 1, y: mesh.rotation.y + Math.PI * 2 })
}
gui.add(debugObject, 'spin')
Geometry 변경
gui
.add(debugObject, 'subdivision')
.min(1)
.max(20)
.step(1)
.onFinishChange(() =>
{
mesh.geometry.dispose()
mesh.geometry = new THREE.BoxGeometry(
1, 1, 1,
debugObject.subdivision, debugObject.subdivision, debugObject.subdivision
)
})
folder
const cubeTweaks = gui.addFolder('Awesome cube')
cubeTweaks
.add(mesh.position, 'y')
// ...
cubeTweaks
.add(mesh, 'visible')
cubeTweaks
.add(material, 'wireframe')
cubeTweaks
.addColor(material, 'color')
// ...
// ...
cubeTweaks
.add(debugObject, 'spin')
// ...
cubeTweaks
.add(debugObject, 'subdivision')
// ...
gui h키로 끄고 키는 코드
window.addEventListener('keydown', (event) =>
{
if(event.key == 'h')
gui.show(gui._hidden)
})
Textures 종류
Color
Alpha
Height
Normal
Ambient Collusion
Metalness
Roughness
PBR
Texture 로드하는 법
Javascript로 Texture 로딩하는 법
const image = new Image()
const texture = new THREE.Texture(image)
image.onload = () => {
texture.needsUpdate = true
}
image.src = '/door/color.jpg'
const material = new THREE.MeshBasicMaterial({ map: texture })
TextureLoader로 Texture 로딩하는 법
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load('/door/color.jpg')
const material = new THREE.MeshBasicMaterial({ map: texture })
TextureLoader의 특징
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load(
'/textures/door/color.jpg',
() =>
{
console.log('loading finished')
},
() =>
{
console.log('loading progressing')
},
() =>
{
console.log('loading error')
}
)
LoadingManager
const loadingManager = new THREE.LoadingManager()
loadingManager.onStart = () =>
{
console.log('loading started')
}
loadingManager.onLoad = () =>
{
console.log('loading finished')
}
loadingManager.onProgress = () =>
{
console.log('loading progressing')
}
loadingManager.onError = () =>
{
console.log('loading error')
}
const textureLoader = new THREE.TextureLoader(loadingManager)
UV unwrapping
Texture Transforming
Reapeat
const textureLoader = new THREE.TextureLoader(loadingManager)
const colorTexture = textureLoader.load('/door/color.jpg')
colorTexture.colorSpace = THREE.SRGBColorSpace
colorTexture.repeat.x = 2
colorTexture.repeat.y = 3
colorTexture.wrapS = THREE.RepeatWrapping
colorTexture.wrapT = THREE.RepeatWrapping
colorTexture.wrapS = THREE.MirroredRepeatWrapping
colorTexture.wrapT = THREE.MirroredRepeatWrapping
Offset
colorTexture.offset.x = 0.5
colorTexture.offset.y = 0.5
Rotation
Filtering and Mipmapping
Minification filter
THREE.NearestFilter
THREE.LinearFilter
THREE.NearestMipmapNearestFilter
THREE.NearestMipmapLinearFilter
THREE.LinearMipmapNearestFilter
THREE.LinearMipmapLinearFilter
colorTexture.minFilter = THREE.NearestFilter
Magnification filter
colorTexture.magFilter = THREE.NearestFilter
mipmap과 minFilter를 같이 사용할 필요는 없다.
아래와 같이 minFilter를 NearestFilter로 사용하면 mipmaps은 꺼도 된다.
colorTexture.generateMipmaps = false
colorTexture.minFilter = THREE.NearestFilter
Texture format과 최적화
Weight
Size
Data
Material Setting법
const textureLoader = new THREE.TextureLoader()
const doorColorTexture = textureLoader.load('./textures/door/color.jpg')
doorColorTexture.colorSpace = THREE.SRGBColorSpace
const material = new THREE.MeshBasicMaterial({map: doorColorTexture})
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5,16,16),
material
)
MeshBasicMaterial
Map
material.map = doorColorTexture
Color
material.color = new THREE.Color('#ff0000')
material.color = new THREE.Color('#f00')
material.color = new THREE.Color('red')
material.color = new THREE.Color('rgb(255, 0, 0)')
material.color = new THREE.Color(0xff0000)
Wireframe
material.wireframe = true
Opacity
material.transparent = true
material.opacity = 0.5
위 예시그림에서 투명도가 없는 이유는 배경이 검정이기 때문이다.
AlphaMap
material.transparent = true
material.alphaMap = doorAlphaTexture
Side
MeshNormal Material
// MeshNormalMaterial
const material = new THREE.MeshNormalMaterial()
material.flatShading = true
MeshMatcap Material
https://github.com/nidorx/matcaps
// MeshMatcapMaterial
const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture
MeshDepth Material
// MeshDepthMaterial
const material = new THREE.MeshDepthMaterial()
MeshLambert Material
// MeshLambertMaterial
const material = new THREE.MeshLambertMaterial()
MeshPhong Material
// MeshPhongMaterial
const material = new THREE.MeshPhongMaterial()
material.shininess = 100
material.specular = new THREE.Color(0x1188ff)
MeshToon Material
// MeshToonMaterial
const material = new THREE.MeshToonMaterial()
gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
gradientTexture.generateMipmaps = false
material.gradientMap = gradientTexture
MeshStandard Material
const material = new THREE.MeshStandardMaterial()
material.metalness = 1
material.roughness = 1
material.map = doorColorTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1
material.displacementMap = doorHeightTexture
material.displacementScale = 0.1
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture
material.normalMap = doorNormalTexture
material.normalScale.set(0.5, 0.5)
material.transparent = true
material.alphaMap = doorAlphaTexture
material.clearcoat = 1
material.clearcoatRoughness = 0
Environment map
const rgbeLoader = new RGBELoader()
rgbeLoader.load('/environmentMaps/0/2k.hdr', (environmentMap) =>
{
environmentMap.mapping = THREE.EquirectangularReflectionMapping
scene.background = environmentMap
scene.environment = environmentMap
})
MeshPhysical Material
// Clearcoat
material.clearcoat = 1
material.clearcoatRoughness = 0
clearcoat
sheen
iridescence
transmission
FontLoader
import {FontLoader} from 'three/examples/jsm/loaders/FontLoader'
const fontLoader = new FontLoader()
fontLoader.load(
'/fonts/helvetiker_regular.typeface.json',
(font) =>
{
const textGeometry = new TextGeometry(
'Hello Three.js',
{
font: font,
size: 0.5,
height: 0.2,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
bevelOffset: 0,
bevelSegments: 5
}
)
const textMaterial = new THREE.MeshBasicMaterial()
textMaterial.wireframe = true
const text = new THREE.Mesh(textGeometry,textMaterial)
scene.add(text)
}
)
Bounding
textGeometry.computeBoundingBox()
Center Text
위에서 boundingBox로 변경한 이유는 Text를 Center하기 위해서이다.
boudningBox의 center를 변경해주어야한다.
buffer geometry의 translate 함수를 활용해서 움직여준다.
가장 쉬운 방법은 .center() 함수를 사용하는 것이다.
textGeometry.translate(
- (textGeometry.boundingBox.max.x - 0.02) * 0.5, // Subtract bevel size
- (textGeometry.boundingBox.max.y - 0.02) * 0.5, // Subtract bevel size
- (textGeometry.boundingBox.max.z - 0.03) * 0.5 // Subtract bevel thickness
)
textGeometry.center()
const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45)
for(let i=0;i<1000;i++){
const donut = new THREE.Mesh(donutGeometry,material)
donut.position.x = (Math.random()-0.5) *10
donut.position.y = (Math.random()-0.5) *10
donut.position.z = (Math.random()-0.5) *10
const scale = Math.random()
donut.scale.set(scale,scale,scale)
donut.rotation.x = Math.random() *Math.PI
donut.rotation.y = Math.random() *Math.PI
scene.add(donut)
}