2026.08.08 · 3분 읽기

가장 시작점이 되는 녀석이다. WebGPU를 이용해서 모니터에 3차원 그래픽을 렌더링해준다.
출력하기 위해서는 Scene과 Camera가 필요하다.
장면은 3차원 모델과 빛 등으로 구성되어 있다.
카메라는 장면을 어떤 시점, 관점으로 볼 것인지 정의한다.
빛은 장면을 비추는 조명이다.
Mesh는 3차원 모델이다. 2가지 구성 요소로 이루어진다.
Geometry는 3차원 모델의 형상을 정의하며,
Material은 모델의 색상이나 투명도 등을 정의한다.
해당 기본 요소들을 코드로 작성하는 연습을 해보면 좋을 것이다.
Three.js 는 객체지향프로그래밍을 한다.
실제로 깃허브 코드를 들어가보면 class 를 기능 단위로 정의한 후 사용하는 것을 볼 수 있다.
npm create vite@latest .vite 를 통해 typescript 프로젝트를 하나 만들어준다.
npm i threethree.js 를 설치해준다.
// App.ts
export class App {
private constructor() {
console.log("Hello, Three.js");
}
static create(): App {
const app = new App();
return app;
}
}private으로 constructor를 생성하고, 메서드를 정의해 기본 구조를 만든다. 이는 main.ts 에서 사용한다.
import * as THREE from "three/webgpu";
export class App {
private renderer!: THREE.WebGPURenderer;
private constructor() {
console.log("Hello, Three.js");
}
static create(): App {
const app = new App();
return app;
}
}renderer!뒤의 느낌표는 확정 할당 단언 연산자라고 한다.
쉽게 말하면 타입 선언만 해놓고, constructor 내부에서 사용하지 않으면 나는 에러를 방지해주는 장치이다.
import * as THREE from "three/webgpu"; 해당 임포트 부분에서 린트 에러가 뜰텐데, 이는 Three.js 라이브러리에 대한 타입 정보가 없기 때문이다.
터미널을 열고
npm i --save-dev @types/three해당 명령어를 입력하면 타입정보가 등록된다.
// index.html
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>index.html 파일 내부의 id가 app인 div를 렌더링한 그래픽을 배치하기 위한 부모 컨테이너로 사용할 것이다.
그러므로 App.ts에서 해당 DOM 요소를 불러온다.
import * as THREE from "three/webgpu";
export class App {
private renderer!: THREE.WebGPURenderer;
private domApp!: HTMLElement;
private constructor() {
console.log("Hello, Three.js");
}
static create(): App {
const app = new App();
return app;
}
private setupThreeJs() {
this.domApp = document.querySelector("#app") as HTMLElement;
if (!this.domApp) {
throw new Error("Could not find element with id '#app'");
}
}
}
setupThreeJS 메서드를 선언해 내부에서 DOM을 불러온다.
private async setupThreeJs() {
this.domApp = document.querySelector("#app") as HTMLElement;
if (!this.domApp) {
throw new Error("Could not find element with id '#app'");
}
const renderer = new THREE.WebGPURenderer({ antialias: true });
this.domApp.appendChild(renderer.domElement);
await renderer.init();
this.renderer = renderer;
}하나 씩 살펴보면,
const renderer = new THREE.WebGPURenderer({antialias: true }); : 렌더러 객체를 새로 생성한다.
this.domApp.appendChild(renderer.domElement); : 부모 컨테이너 내부에 캔버스(render.domElement)를 자식으로 직접 넣어준다.
await renderer.init(); : GPU랑 연결하는 작업을 기다린다.
this.renderer = renderer; : 해당 renderer를 위에서 선언한 renderer과 연결시켜준다.
static async create(): Promise<App> {
const app = new App();
await app.setupThreeJs();
return app;
}그리고 create 메서드 내부에서 해당 setupThreeJs를 실행시켜준다.
// main.ts
import {App} from './App';
await App.create();main.ts 에서는 await을 붙여 실행시켜주면 된다.
하나의 흐름으로 정리하면
App.create() 호출
→ new App() (빈 껍데기)
→ setupThreeJs() 실행
→ #app 박스 찾기
→ 캔버스 화면에 붙이기
→ GPU 연결 기다리기 (await)
→ this.renderer에 저장
→ 완성된 App 반환이렇게 구성된다.
해당 코드는 본격적으로 뭔가를 그리기 전에, 캔버스와 GPU 연결을 확실히 해두기 위한 단계라고 생각하면 된다.
앞으로 Scene, Camera 등이 추가될텐데, 해당 App 클래스에 다 담아서 관리를 한다.
React, Next.js, React Native로 만들고 기록합니다.
giscus로 동작하며 GitHub Discussions에 저장됩니다.