3D 모델 데이터에 대한 Progressive Loading

three.js에서 모델 데이터를 로딩할때, 기본적으로 해당 모델 데이터가 완전히 로딩된 이후에 장면에 추가될 수 있고 그럼으로써 화면에 레더링된다. 이러한 처리로 인해 모델 데이터의 용량이 클 경우 사용자는 해당 모델 데이터가 완전히 로딩되기까지 기다려야 한다. 아래는 이러한 상황을 보여주는 동영상이다. 네트워크 속도를 느리게 설정해두었고 3개의 모델 데이터(각각의 용량은 32M, 50M, 54M 임)를 렌더링하는 경우이다.

위에서 보는 것처럼 각 모델 데이터가 완전히 로딩되어야 화면에서 볼 수 있고, 화면에 렌더링되기까지 시간도 제법 많이 소요되는것을 알 수 있다. 이러한 문제점을 개선하기 위해 점진적 로딩(Progressive Loading) 기법이 사용된다. 모델 데이터에 대해 여러개의 LOD 데이터를 미리 구축해두고 순차적으로 로딩하는 것인데, 이 LOD 데이터에는 지오메트리 뿐만 아니라 GPU에 최적화된 텍스쳐 이미지 데이터로의 처리가 되어 매우 빠르게 렌더링된다. 그 결과는 다음과 같다.

동일한 네트워크 환경에서 같은 품질의 모델 데이터를 로딩하는 상황인데, 앞서 봤던 것보다 훨씬 더 빠르게 모델이 표시되는 것을 알 수 있다.

다행히도 모델 데이터에 대한 프로그래시브 로딩을 위해 처음부터 개발할 필요는 없다. @needle-tools/gltf-progressive 패지키를 사용하면 매우 쉽게 만들 수 있다. 한번 알아보자.

먼저 모델 데이터를 점진적 로딩이 될 수 있게 변환해줘야 한다. 변환 프로그램은 다음처럼 임시로 설치해 이용할 수 있다.

npx @needle-tools/gltf-build-pipeline@latest

변환하고자 하는 모델 데이터가 MODEL1.glb라면 이를 점진적 로딩을 위한 모델 데이터로 생성하여 PROGRESSIVE_MODEL1 폴더에 저장해 주는 명령은 다음과 같다.

npx @needle-tools/gltf-build-pipeline@latest transform ./public/MODEL1.glb ./PROGRESSIVE_MODEL1

해당 폴더에는 다음처럼 여러 개의 glb 파일이 생성된다. 총 5개의 LOD 단계로 생성된 지오메트리와 텍스쳐에 대한 데이터이다.

이제 이렇게 만들어진 모델 데이터를 three.js에서 렌더링해서 시각화하는 코드를 살펴보자. 먼저 다음과 같은 패키지의 설치가 필요하다.

npm i @needle-tools/gltf-progressive

그리고 코드는 다음과 같다.

import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { useNeedleProgressive } from "@needle-tools/gltf-progressive";

const loader = new GLTFLoader();
useNeedleProgressive(loader, renderer); // 플러그인 등록 (1회)

loader.load('./PROGRESSIVE_MODEL1/MODEL1.glb',
  (gltf) => {
    const model = gltf.scene;
    this._scene.add(model);
  }
);

적용이 매우 쉽다. 하지만 안타깝게도 @needle-tools/gltf-progressive는 WebGL 환경만을 지원한다. WebGPU 환경에 대한 지원도 곧 기대해본다.

`three.js 프로젝트 구성` SKILL

이 스킬은 전역 스킬로 사용되어야 하므로 ~/.gemini/antigravity/skills/<skill-folder>/ 에 입력(threejs-project-setup/SKILL.md)되어야 함.

---
name: threejs-project-setup
description: >
  - 사용자가 three.js에 대한 프로젝트를 구성 또는 생성해 달라는 요청이 있을때 사용한다.
  - 프롬프트에는 프로젝트를 구성할 경로나 WebGL 기반인지 WebGPU 기반인지에 대한 내용이 있을 수 있다. 
---

## 실행순서

이 스킬은 다음 단계로 순차적으로 실행한다.

1. 사용자가 three.js에 대한 프로젝트를 구성 또는 생성해 달라고 요청하면 다음 bash 명령을 실행한다.
    - 사용자가 WebGPU 기반을 언급한 경우 : `git clone https://github.com/GISDEVCODE/threejs-webgpu-with-javascript-starter.git {경로}`
    - 사용자가 WebGL 기반을 언급한 경우 : `git clone https://github.com/GISDEVCODE/threejs-with-javascript-starter.git {경로}`
    - 사용자가 WebGL과 WebGPU에 대해 언급하지 않은 경우 WebGPU를 언급한 것으로 간주한다.
    - 프로젝트를 구성할 {경로}를 사용자가 지정하지 않았을 경우 현재 경로(`.`)로 간주한다.
    - 프로젝트를 구성하는 경로에 어떠한 파일이나 폴더가 존재할 경우 이 스킬의 실행을 중지하고 사용자에게 "프로젝트를 구성하는 폴더에는 어떠한 파일이나 폴더도 존재해서는 안됩니다."라는 메세지를 밝은 빨강색으로 프롬프트 대화창에 출력한다.
1. 구성된 프로젝트에 대한 최신 패키지 업데이트를 위해 다음 bash 명령을 실행한다.
    - `npx npm-check-updates -u`
    - 이 bash 명령은 {경로}에서 실행되어야 함
1. 패키지 설치를 위해 다음 bash 명령을 실행한다.
    - `npm i`
    - 이 bash 명령은 {경로}에서 실행되어야 함
1. 개발 서버 실행을 위해 다음 bash 명령을 실행하지 말고 사용자에게 안내한다. `npm run dev`
1. 위의 실행이 모두 완료 되었다면 사용자의 요구가 없다면 구성된 프로젝트를 절대 분석하지말고 어떠한 변경을 시도하지마. 오직 사용자에게 "Happy three.js Coding!"이라면 메세지를 밝은 초록색으로 프롬프트 대화창에서 출력한다.

아래는 이 스킬을 이용한 프로젝트 구성 예시

Model Space → World Space → View Space → Clip Space에서의 행렬은 어디서 구하나?

먼저 Model Space → World Space로 변환하는 것은 변환하고자 하는 Object3D 객체의 matrixWorld이다. World Space → View Space로 변환하는 행렬은 카메라의 matrixWorldInverse이다. 여기서 matrixWorld는 카메라의 이동,회전에 대한 행렬이고 이의 역행렬을 이용해 World Space → View Space로 변환하는 것이다. View Space → Clip Space는 역시 카메라의 projectionMatrix이다.

PlayWright를 이용한 웹앱 테스트

플레이라이트는 웹앱에 대한 테스트 자동화 라이브러리로 JS 코드(또는 TS)로 테스트를 작성할 수 있다. 바이브 코딩 시 기능에 대한 테스트를 AI 통해 하고자할때 매우 유용한데, 웹브라우저의 특정 버튼을 AI가 클릭하고 그 결과가 기대한 것와 같은지에 대한 인터렉티브한 행동 테스트도 가능하다. 우와, 몇달 전까지만 해도 이런건 사람의 영역이라 생각했다. 하나씩 하나씩 인간의 만용을 AI는 박살내고 있다. 겸손 겸손…

플레이라이트의 목적이 특정 웹앱에 대한 테스트에 있다. 즉, 테스트를 위한 독립된 프로젝트를 구성할 수 있다. 명령은 다음과 같다.

npm init playwright@latest

위의 명령이 실행되면 인간은 다음과 같은 내용을 입력해야 한다. (음.. AI를 에이전트라고 부른다면 인간은 어떻게 불러야 구분이 될까? 개발자라고 하기엔 AI 역시 코딩을 작성하므로 개발자가 아닌게 아니니..)

Initializing project in '.'
✔ Do you want to use TypeScript or JavaScript? · TypeScript
✔ Where to put your end-to-end tests? · tests
✔ Add a GitHub Actions workflow? (Y/n) · false
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true

테스트 프로젝트를 위한 파일들이 자동으로 구성되는데, 여기서 테스트를 위한 코드 작성은 tests 폴더(앞서 인간이 입력한 값)에 example.spec.ts 파일이 보이는데, 그냥 이 파일에 테스트 코드를 입력하면 된다. 이미 테스트로 잘 잘동하는 테스트 코드가 있는데, 그건 지우고 다음과 같은 코드를 작성해 테스트해 보았다.

import { test, expect } from '@playwright/test';
import { chromium } from '@playwright/test';

test('login', async () => {
  const browser = await chromium.launch({
    headless: false,
    args: [
      '--enable-unsafe-webgpu',
      '--enable-features=Vulkan'
    ]
  });

  const context = await browser.newContext();
  const page = await context.newPage();

  await page.goto('https://www.hyyyydai-3d.com');

  await page.fill('.user-id input', 'gizmo');  
  await page.fill('.user-pw input', '*********');

  await page.locator('.user-id input').press('Tab');

  await page.click('.btn-login');

  await expect(page.getByText('공지사항')).toBeVisible();
});

위의 테스트는 딱 1개인데, 주어진 url에 들어가서 id와 암호를 자동으로 채우고 로그인 버튼을 클릭해서 ‘공지사항’이라는 텍스트가 보이면 테스트 성공이라는 것이다.

내가 테스트해야할 웹 페이지가 3D 그래픽인지라 WebGPU에 대한 활성화 없이는 테스트에 실패함으로, 위의 코드에는 WebGPU에 대한 활성화 코드가 있고 테스트가 잘되는것을 확인할 수 있었다. 실제 테스트 실행은 다음과 같다.

npx playwright test

테스트를 위한 전용 웹브라우저를 보면서 테스트가 진행되는 것을 살펴보고 싶은 꼼꼼이라면 (대신 사는게 고달프겠지.. 나처럼 ㅜ_ㅜ) 다음과 같은 방식의 테스트 실행도 가능하다.

npx playwright test --ui

이 글은 플레이라이트를 처음 사용하는 인간을 위한 가이드일뿐이다. 보다 상세한 방법은 AI에게 물어보고 가이드 받으라.

Mermaid 문법

```mermaid
graph TD
    A[사각박스] --> B{다이아몬드박스}
    B -->|라벨1| C((원형박스))
    B -->|라벨2| D(라운드박스)
    C --> E([텍스트])
    subgraph 서브그래프
        D --> E
        E --> F[[서브루틴]]
        E --> G[(저장소박스)]
    end
    E --> H([경기장박스])
    E --> I>리본박스]
    E --> J[/기울박스1/]
    E --> K[\기울박스2\]
    J ---|실선| K
    A -.->|점선| F
    G ==>|굵은화살| H
    F ===|굵은실선| H
    K --> L{{육각박스}}
```

```mermaid
classDiagram

class 클래스명 {
    +공용_필드: string
    -비공개_필드
    #보호된_필드
    ~패키지_내부_필드
    +공용_메서드(a: int): void
    -비공개_메서드()
    #보호된_메서드()
    ~패키지_내부_매서드()
}
```

```mermaid
classDiagram
    class 오리 {}
    class 물고기
    class 얼룩말 {
        <<abstract>>
    }
    class 동물 {
        <<interface>>
    }
    class 닭
    class 날개
    
    동물 <-- 오리
    동물 <.. 물고기
    동물 <|-- 얼룩말
    동물 <|.. 닭
    얼룩말 -- 물고기
    오리 *--> 날개
    닭 *-->"2" 날개
    비늘"*"--"1"물고기
```

Mermaid 참조