3차원 그래픽 및 애니메이션을 위한 js library
WebGL 기반
(WebGL : 2d, 3d 그래픽 렌더링을 위한 로우 레벨 js api)
- 모던 웹 브라우저
- No plug-in, Only JS
- Canvas DOM요소
- GPU 지원
- WebGL Wrapper API
공식 사이트
threejs.org
Renderer : 장치에 Scene과 Camera를 렌더링 해 줌
│
└─── Scene : 3차원 모델과 빛 등으로 구성된 장면
│ │
│ └─── Light : 장면을 비추는 조명, 재질에 따라 광원이 필요할 때가 있음.
│ │
│ └─── Mesh (Object3D) : 3차원 모델
│ │
│ └─── Geometry : 3차원 모델의 형상을 정의
│ │
│ └─── Material : 3차원 모델의 색상, 투명도 등, 재질 정의
│
└─── Camera
: 장면을 어떤 관점으로 볼 것인지 정의하는 카메라
Scene 내부에 포함할 수도, 외부에 둘 수도 있다.
네모네모박스
Renderer
생성Scene
생성Camera
생성Light
생성 (Mesh가 시야에 보이도록 비춰줌)Mesh
생성 (3차원 오브젝트, geometry
와 material
로 구성되어 있음.)<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="basic.css">
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js"
}
}
</script>
<script type="module" src="basic.js" defer></script>
<title>Basic</title>
</head>
<body>
<div id="webgl-container"></div>
</body>
</html>
rederer.domElement
는 canvas를 리턴해준다.
(THREE.WebGLRenderer()로 생성한 rederer객체)
canvas를 동적으로 붙이는 방법도 있지만, 유동적인 대응을 위해 미리 html에 요소를 만들어 놓자.
* {
outline: none;
margin: 0;
}
body {
/* 한 화면에 들어올 수 있도록 스크롤 없애기 */
overflow: hidden;
}
#webgl-container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
import * as THREE from "../build/three.module.js"
import { OrbitControls } from "../examples/jsm/controls/OrbitControls.js";
class App {
#domContainer;
#renderer;
#scene;
#camera;
#mesh;
#control;
constructor() {
// 1. DOM 요소 참조
const domContainer = document.querySelector('#webgl-container');
this.#domContainer = domContainer;
// 2. Renderer 설정
const renderer = new THREE.WebGLRenderer({ antialias: true }); // canvas 생성
renderer.setPixelRatio(window.devicePixelRatio); // canvas 화소 설정
domContainer.appendChild(renderer.domElement);
this.#renderer = renderer;
// 3. Scene 설정
const scene = new THREE.Scene();
this.#scene = scene;
this.#setupCamera(); // Camera 설정
this.#setupLight(); // Light: Scene을 비추는 광원
this.#setupModel(); // Mesh: 3차원 모델 설정
this.#setupControls();
// resize 업데이트
window.onresize = this.resize.bind(this);
this.resize(); // 첫 세팅을 위함.
requestAnimationFrame(this.render.bind(this));
}
resize() {
const width = this.#domContainer.clientWidth;
const height = this.#domContainer.clientHeight;
this.#camera.aspect = width / height;
this.#camera.updateProjectionMatrix();
this.#renderer.setSize(width, height);
}
// 4. Camera 생성
#setupCamera() {
const width = this.#domContainer.clientWidth;
const height = this.#domContainer.clientHeight;
const camera = new THREE.PerspectiveCamera(
75,
width / height,
0.1,
100
);
camera.position.set(0, 0, 2);
this.#camera = camera;
}
// 5. Light 생성
#setupLight() {
const color = 0xfffffff;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4); // 광원의 위치를 세팅
this.#scene.add(light); // 광원은 장면의 구성요소이므로 부챠줌.
}
// 6. Mesh 생성
/**
* Mesh는 Geometry와 Material로 구성된다.
*/
#setupModel() {
// Geometry
const geometry = new THREE.BoxGeometry(1, 1, 1);
// Material
const fillMaterial = new THREE.MeshPhongMaterial({
color: 0x44aa88;
// emissiv: 0xffff00, // mesh의 자체 발광
// flatShading: true,
// opacity: 0.1,
// transparent: true
});
// Mesh
const mesh = new THREE.Mesh(geometry, fillMaterial);
const lineMaterial = new THREE.LineBasicMaterial({ color: 'red' });
const line = new THREE.LineSegments(
new THREE.WireframeGeometry(geometry),
lineMaterial
);
// this.#scene에 mesh, line 각각 추가해도 되지만 Three.js에서 Group을 지원해줌.
const group = new THREE.Group();
group.add(mesh);
group.add(line);
this.#scene.add(group);
this.#mesh = group;
}
#setupControls() {
const control = new OrbitControls(this.#camera, this.#domContainer);
this.#control = control;
}
update(time) {
time *= 0.001; // 'ms' -> 'sec'로 변환
// control 업데이트 : 딱히 실행 결과가 달라지는 것은 없지만 해줘야 함.
this.#control.update();
}
// render : 3차원 요소로 시각화 해줌.
render(time) {
this.#renderer.render(this.#scene, this.#camera);
this.update(time);
requestAnimationFrame(this.render.bind(this));
}
}
// 3) onload 이벤트 후에 객체 생성해준다.
window.onload = function () {
new App();
}
Class App {
constructor () {
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
domContainer.appendChild(renderer.domElement);
this.#renderer = renderer;
}
}
antialis
: (optional) 엘리어싱 제거,
Default false
setPixelRatio
: 고해상도의 경우 window.devicePixelRatio
값이 2 이상이 나옴.
렌더러에 값을 세팅해줘야 미려하게 렌더됨.
#setupCamera() {
const width = this.#domContainer.clientWidth;
const height = this.#domContainer.clientHeight;
const camera = new THREE.PerspectiveCamera(
75, // field of view
width / height, // aspect
0.1, // near plane
100 // far plane
);
camera.position.set(0, 0, 2);
this.#camera = camera;
}
PerspectiveCamera(fov, aspect, near, far)
mesh(obejct3d)가 near와 far사이에 있어야 하며, 시야각안에 들어와야 관찰 가능.
#setupLight() {
const color = 0xfffffff;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4); // 광원의 위치를 세팅
this.#scene.add(light); // 광원은 장면의 구성요소이므로 부챠줌.
}
intensity
: (optional) 빛의 강도, numeric value of the light's strength/intensity.
Default 1
DirectionalLight
: 특정 방향의 빛, 그림자 제공
position.set(position.x, position.y, position.z)
: 축약 가능
Mesh = Geometry + Material
#setupModel() {
// geometry
const geometry = new THREE.BoxGeometry(1, 1, 1);
// material
const fillMaterial = new THREE.MeshPhongMaterial({ color: 0x44aa88 });
const lineMaterial = new THREE.LineBasicMaterial({ color: "red" });
// mesh (mesh)
const mesh = new THREE.Mesh(geometry, fillMaterial);
// mesh (line)
const line = new THREE.LineSegments(
new THREE.WireframeGeometry(geometry),
lineMaterial
);
const group = new THREE.Group();
group.add(mesh);
group.add(line);
this.#scene.add(group);
this.#mesh = group;
}
#setupControls() {
const control = new OrbitControls(this.#camera, this.#domContainer);
// (카메라, 마우스 컨트롤 할 오브젝트)
this.#control = control;
}
render(time) {
this.#renderer.render(this.#scene, this.#camera);
// 어떤 장면을 어떤 카메라에 렌더를 할 것인지 인자로 넣음
this.update(time);
requestAnimationFrame(this.render.bind(this));
}