Three.js | 기본 구성 요소
1. 기본구성요소
Threejs는 3차원 객체로 구성되는 Scene(장면)이 있고
이 Scene(장면)을 모니터와 같은 출력장치에 출력(렌더링) 할 수 있는 Renderer가 있다
Scene을 렌더링 할때에는 어떤 시점에서 보느냐에 따라 다양한 모습으로 렌더링 될 수 있는데
그 시점을 Camera로 정의한다.
Scene은 Light와 3차원 모델인 Mesh로 구성된다.
Light : Mesh가 화면상에 표시되기 위해서는 적절한 광원이 필요
Mesh : Mesh는 Object3D의 파생 클래스이다.
형상을 정의하는 Geometry와
색상 재질 투명도 등을 정의하는 Material로 정의된다.
2. 코드
우선 contstructor 부터 보자
class App {
constructor() {
const divContainer = document.querySelector("#webgl-container");
this._divContainer = divContainer;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
divContainer.appendChild(renderer.domElement);
this._renderer = renderer;
const scene = new THREE.Scene();
this._scene = scene;
this._setupCamera();
this._setupLight();
this._setupModel();
window.onresize = this.resize.bind(this);
this.resize();
requestAnimationFrame(this.render.bind(this));
}
}
디스플레이 역할을 할 타겟 div를 가져오기 위한 작업이라고 보면된다
코드에서는 id값이 webgl_container인 태그를 가져오고있다
const divContainer = document.querySelector("#webgl-container");
그리고 이 divContainer를 클래스의 필드로 정의해준다
필드로 정의한 이유는 이 divContainer를 this._divContainer로
다른 메서드에서 참조할 수 있도록 하기 위함이다.
this._divContainer = divContainer;
다음은 렌더러를 생성하는 코드이다.
렌더러 객체는 THREE.js 라이브러리의 WebGLRenderer라는 클래스로 생성할 수 있다.
생성을 할때에는 다양한 옵션을 설정할 수 있는데
여기서는 antialias를 true로 설정하고 있다.
antialias를 true로 설정하면 3차원 장면이 렌더링 될때 object 들의 경계선이 계단 현상 없이 부드럽게 표현된다.
const renderer = new THREE.WebGLRenderer({ antialias: true });
픽셀의 비율을 설정하는 코드인데,
이 pixelRatio는 보통 window.devicePixelRatio로 쉽게 얻을 수 있다.
renderer.setPixelRatio(window.devicePixelRatio);
이렇게 생성된 렌더러의 DOM엘리먼트를 id가 webgl-container인 div의 자식으로 추가해준다
참고로 renderer.domElement는 cavas 타입의 dom객체이다
divContainer.appendChild(renderer.domElement);
그리고 이 렌더러가 다른 메소드에서 참조 할 수 있도록 this._renderer로 정의해준다
this._renderer = renderer;
다음은 scene객체이다
scene객체는 scene클래스로 간단히 생성가능하다
const scene = new THREE.Scene();
이후 app클래스의 다른 메소드에서 참조할 수 있게
scene객체를 필드화 시켜준다
this._scene = scene;
다음 카메라, 라이트, 모델을 설정해주는 메소드를 호출한다
메소드는 따로 정의해줘야함
this._setupCamera();
this._setupLight();
this._setupModel();
참고로 코드를 보면
_setupLight
_divContainer
이런식으로 필드명 혹은 메소드 앞에 _(언더바)로 시작하는 경우가 있는데
이 App클래스 내부에서만 쓰이는 Private 필드, Private 메소드라는 의미이다.
자바스크립트에는 클래스를 정의할때 Private 성격을 부여할 수 있는 기능이 없다.
그래서 _로 시작함으로써 개발자들간의 약속을 정한것.
_로 정의했으므로 App클래스 외부에서는 _로 시작하는 메소드나 필드를 호출해서는 안된다
다음은 창크기가 변경될때 발생하는 onresize이벤트를 재지정해줘야한다
onresize에 App클래스에서 정의한 resize()라는 메소드를 지정해주고 있다
resize가 필요한 이유는 렌더러나 카메라는 창 크기가 변경될때마다 그 크기에 맞게 속성값을 재설정해줘야하는데
그래서 이 resize 이벤트가 필요한것이다.
그런데 보면 resize를 메소드를 지정 할때 bind를 사용해서 지정하고 있는데
그 이유는 resize메소드 안에서 this가 가리키는 객체가 event 객체가 아닌
이 app클래스의 객체가 되도록 하기 위함이다.
window.onresize = this.resize.bind(this);
그리고 다음 라인을 보면
resize메소드를 창크기가 변경될때 발생하는 resize이벤트와는 상관없이
생성자에서 무조건적으로 한번 호출해주고 있는데
이렇게 함으로써 초기에 딱 열었을때 렌더러나 카메라의 속성을 창크기에 맞게 설정해주게 된다
this.resize();
이제 생성자의 마지막 코드인데
render메서드는 실제로 3차원 그래픽 장면을 만들어주는 메서드인데
이 메서드를 requestAnimationFrame에 넘겨줌으로써
requestAnimationFrame은 적당한 시점에 또한 최대한 빠르게
넘겨받은 render() 메서드를 호출해주게 된다.
그런데 여기서 render()메서드를 bind를 통해 넘겨주고있는데
그렇게한 이유는 render()메서드의 코드 안에서 쓰이는 this가 App클래스의 객체를 가리키도록 하기 위함이다.
requestAnimationFrame(this.render.bind(this));
다음은 위에서 호출했던 메소드들을 살펴보겠다
먼저 _setupCamera() 메소드이다
_setupCamera() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
camera.position.z = 2;
this._camera = camera;
}
먼저 three.js가 3차원 그래픽을 출력할 영역에 대한 가로와 세로에 대한 크기를 얻는 코드이다
참고로 width와 clientWidth의 차이는
width는 12px 과 같이 문자열을 반환하지만
clientWidth는 12 와 같이 숫자형을 반환한다
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
그 다음 얻어온 크기를 이용해서 카메라 객체를 생성한다
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
camera.position.z = 2;
그리고 이렇게 생성한 카메라 객체를 또다른 메서드에서도 사용할 수 있도록
this._camera 라는 필드 객체로 정의하고 있다
this._camera = camera;
다음은 광원을 생성하는 setupLight()메소드이다
_setupLight() {
const color = 0xffffff;
const intensity = 10;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
this._scene.add(light);
}
광원의 색상과 세기를 이용하여 광원객체를 생성한다
const color = 0xffffff;
const intensity = 10;
const light = new THREE.DirectionalLight(color, intensity);
이후 광원의 위치를 지정해준다
x축 y축 z축
light.position.set(-1, 2, 4);
이후 생성한 광원을 scene객체에 구성요소로 추가해준다
this._scene.add(light);
다음은 _setupMode()이다
_setupModel() {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshPhongMaterial({ color: 0x44a88 });
const cube = new THREE.Mesh(geometry, material);
this._scene.add(cube);
this._cube = cube;
}
이 메서드는 파랑색 정육면체Mesh를 생성하는 코드이다
먼저 정육면체에 대한 형상을 정의하기 위해서 boxGeometry클래스를 이용해서 geometry 객체를 생성한다
가로 세로 깊이에 대한 옵션을 받는다
const geometry = new THREE.BoxGeometry(1, 1, 1);
이후 색상재질을 생성하기 위해 MeshPhongMaterial을 이용하여 material를 생성한다
const material = new THREE.MeshPhongMaterial({ color: 0x44a88 });
이렇게해서 geometry객체와 material객체를 통해 Mesh가 생성된다
const cube = new THREE.Mesh(geometry, material);
이후에 scene객체의 구성요소로 추가해준다
this._scene.add(cube);
또다른 메소드에서 참조되어 사용할 수 있도록 this._cube로 필드를 정의해준다
this._cube = cube;
다음은 resize()메서드이다
resize() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
this._camera.aspect = width / height;
this._camera.updateProjectionMatrix();
this._renderer.setSize(width, height);
}
먼저 this._divContainer의 크기를 얻어온다
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
이후 카메라의 속성값을 설정해준다
this._camera.aspect = width / height;
this._camera.updateProjectionMatrix();
이후 렌더러의 크기를 설정해준다
this._renderer.setSize(width, height);
다음은 render() 메서드이다
render(time) {
this._renderer.render(this._scene, this._camera);
this.update(time);
requestAnimationFrame(this.render.bind(this));
}
렌더메서드는 time이라는 인자를 받는데
이 인자는 렌더링이 처음 시작된 이후에 경과된 시간 값으로
단위가 밀리초 이다.
이 time인자를 통해서 장면의 애니메이션에 이용할 수 있다
이 코드는 렌더러가 scene을 camera의 시점을 이용해서 렌더링하라는 코드이다
this._renderer.render(this._scene, this._camera);
그리고 update메소드를 실행한다
update메소드에 time인자값을 넘겨주고있다.
그러면 이 update 메소드 안에서는 어떤 속성값을 변경하는데
그 속성값을 변경함으로써 애니메이션 효과를 발생시킨다
this.update(time);
그리고 requestAnimationFrame(this.render.bind(this))를 실행한다
이 코드는 앞서 생성자에서 호출했던 코드와 동일하다
즉, 이 코드를 통해서 계속 이 렌더 메서드가 무한으로 반복해서 호출이 되게 된다.
물론 무조건적으로 호출되는것이 아니라, 적당한 시점에 최대한 빠르게 호출을 해준다.
requestAnimationFrame(this.render.bind(this));
다음은 update메소드이다
update(time) {
time *= 0.001; // second unit
this._cube.rotation.x = time;
this._cube.rotation.y = time;
}
update메서드는 time이라는 인자를 받아오는데
이 전달 받은 time을 0.001을 곱해줌으로써
밀리초 단위를 초단위로 바꿔준다.
time *= 0.001;
이후 이 변경된 time 값을 가지고
큐브의 회전값에 지정해준다
this._cube.rotation.x = time;
this._cube.rotation.y = time;
참고로 이 time값은 requestAnimationFrame 함수가 render함수에 전달해주는 값이다.
3. 결과
'웹 개발 > 🧊 ThreeJS' 카테고리의 다른 글
Three.js | Geometry - ConeGeometry (0) | 2024.05.06 |
---|---|
Three.js | Geometry - CircleGeometry (0) | 2024.05.06 |
Three.js | Geometry - BoxGeometry (0) | 2024.05.06 |
Three.js | Geometry & OrbitControls (0) | 2024.05.06 |
Three.js | 개발환경구성 & 실행 (0) | 2024.05.05 |