[Three.js] Geometry

이규성·2025년 6월 14일
post-thumbnail

Primitives


Three.js에서는 자주 사용되는 3D 도형을 쉽게 만들 수 있도록 다양한 기본 Geometry 클래스(Primitives)를 제공합니다.

아래는 대표적인 Primitives의 목록입니다:

기본 도형

  • BoxGeometry: 폭 × 높이 × 깊이 형태의 직육면체
  • SphereGeometry: 반지름, 경도/위도 분할 설정 가능한 구체
  • CylinderGeometry: 위/아래 반지름 설정 가능한 원기둥
  • PlaneGeometry: 2D 면체, 바닥이나 벽처럼 사용
  • TorusGeometry: 중심이 뚫린 3D 링 구조

고급 도형

  • ExtrudeGeometry: 2D 도형을 3D로 돌출
  • TextGeometry: 텍스트를 3D 메쉬로 표현
  • TubeGeometry: 경로를 따라 튜브(관) 형태 생성

그 외 도형 살펴보기

Geometry


Three.js에서 Geometry는 점(vertex)과 면(face)으로 구성된 3D 도형의 기본 구조입니다.
예전 버전에서는 THREE.Geometry 클래스를 사용해 정점과 면 정보를 직접 배열에 넣는 방식으로 모델을 구성했습니다.

const geometry = new THREE.Geometry();
geometry.vertices.push(
  new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(1, 0, 0),
  new THREE.Vector3(0, 1, 0)
);
geometry.faces.push(new THREE.Face3(0, 1, 2));

이 방식은 직관적인 대신 성능이 떨어지고 메모리 낭비가 많아, 최신 버전에서는 더 이상 사용되지 않습니다.

Three.js r125 버전 이후로 Geometry는 완전히 제거되었습니다. 현재는 BufferGeometry만 사용됩니다.

BufferGeometry


BufferGeometry는 현재 Three.js에서 기본이자 표준으로 사용되는 Geometry 클래스입니다.
정점 데이터를 TypedArray 형태로 저장하여 GPU가 직접 읽을 수 있게 함으로써, 훨씬 더 빠르고 효율적인 렌더링이 가능합니다.

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
  -1, -1, 0,
   1, -1, 0,
   1,  1, 0
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

예제코드


import * as THREE from "https://unpkg.com/three@0.150.0/build/three.module.js";
import {OrbitControls} from "https://unpkg.com/three@0.150.0/examples/jsm/controls/OrbitControls.js";
import {FontLoader} from "https://unpkg.com/three@0.150.0/examples/jsm/loaders/FontLoader.js";
import {TextGeometry} from "https://unpkg.com/three@0.150.0/examples/jsm/geometries/TextGeometry.js";

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();
    this._setupControls();

    window.onresize = this.resize.bind(this);
    this.resize();

    requestAnimationFrame(this.render.bind(this));
  }

  _setupControls() {
    new OrbitControls(this._camera, this._divContainer);
  }

  _setupCamera() {
    const width = this._divContainer.clientWidth;
    const height = this._divContainer.clientHeight;
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
    camera.position.x = -15;
    camera.position.z = 15;
    this._camera = camera;
  }

  _setupLight() {
    const color = 0xffffff;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    this._scene.add(light);
  }

  _setupModel() {
    const fontLoader = new FontLoader();
    async function loadFont(that) {
      const url = "https://unpkg.com/three@0.150.0/examples/fonts/helvetiker_regular.typeface.json";
      const font = await new Promise((resolve, reject) => {
        fontLoader.load(url, resolve, undefined, reject);
      });

      const geometry = new TextGeometry("Three", {
        font: font,
        size: 5,
        height: 1.5,
        curveSegments: 4,
        bevelEnabled: true,
        bevelThickness: 0.7,
        bevelSize: 0.7,
        bevelSegments: 3,
      });

      const fillMaterial = new THREE.MeshPhongMaterial({color: 0x515151});
      const cube = new THREE.Mesh(geometry, fillMaterial);

      const lineMaterial = new THREE.LineBasicMaterial({color: 0xffff00});
      const line = new THREE.LineSegments(new THREE.WireframeGeometry(geometry), lineMaterial);

      const group = new THREE.Group();
      group.add(cube);
      group.add(line);

      that._scene.add(group);
      that._cube = group;
    }
    loadFont(this);
  }

  resize() {
    const width = this._divContainer.clientWidth;
    const height = this._divContainer.clientHeight;

    this._camera.aspect = width / height;
    this._camera.updateProjectionMatrix();

    this._renderer.setSize(width, height);
  }

  render(time) {
    this._renderer.render(this._scene, this._camera);
    this.update(time);
    requestAnimationFrame(this.render.bind(this));
  }

  update(time) {
    time *= 0.001;
    //this._cube.rotation.x = time;
    //this._cube.rotation.y = time;
  }
}

window.onload = () => {
  new App();
};

결과

0개의 댓글