본문 바로가기
Graphic/ThreeJS

12 텍스트(3D Text)

by curious week 2025. 7. 23.

Three.js 텍스트 포트폴리오 구현 (Ilithya 스타일)

개요

  • 화면 중앙에 3D 텍스트를 배치하고, 주변에 도넛(Torus)을 무작위로 떠다니게 배치
  • 고성능 MeshMatcapMaterial을 사용해 멋진 외관과 성능을 동시에 확보
  • 글꼴은 .typeface.json 형식을 사용하며, FontLoader와 TextGeometry로 구현
  • center()와 boundingBox를 활용해 텍스트 정렬

글꼴 불러오기

  1. helvetiker_regular.typeface.json  .typeface.json 파일 필요
  2. 파일을 /static/fonts/ 같은 정적 폴더에 위치
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';

const fontLoader = new FontLoader();
fontLoader.load('/fonts/helvetiker_regular.typeface.json', (font) => {
  // 이후 로직 작성
});

텍스트 기하 구조 만들기

import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';

const textGeometry = new TextGeometry('Hello Three.js', {
  font,                 // 필수: FontLoader로 로드한 폰트 객체
  size: 0.5,            // 텍스트의 기본 크기 (기본값: 100)
  depth: 0.2,           // 텍스트의 두께 (extrude 깊이)
  curveSegments: 12,    // 글자의 곡선을 얼마나 부드럽게 표현할지 (값이 높을수록 곡선이 정밀해짐)
  bevelEnabled: true,   // 베벨(모서리 라운드 처리) 활성화 여부
  bevelThickness: 0.03, // 베벨의 두께 (앞면에서 얼마나 돌출될지)
  bevelSize: 0.02,      // 외곽선으로부터의 거리 (베벨이 얼마나 퍼질지)
  bevelOffset: 0,       // 베벨 시작 위치 오프셋 (기본값은 0, 음수면 안쪽으로 들어감)
  bevelSegments: 5      // 베벨 부분을 구성하는 세그먼트 수 (값이 클수록 부드러움)
});

텍스트 정렬

  • textGeometry.center()로 간단 정렬 가능
  • 또는 boundingBox 수동 정렬 방식도 가능
textGeometry.center(); // 자동 중앙 정렬

재질 설정 (Matcap)

const matcapTexture = textureLoader.load('/textures/matcaps/1.png');
matcapTexture.colorSpace = THREE.SRGBColorSpace;

const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture });

도넛 100개 배치

const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45);

for (let i = 0; i < 100; i++) {
  const donut = new THREE.Mesh(donutGeometry, material);

  donut.position.x = (Math.random() - 0.5) * 10;
  donut.position.y = (Math.random() - 0.5) * 10;
  donut.position.z = (Math.random() - 0.5) * 10;

  donut.rotation.x = Math.random() * Math.PI;
  donut.rotation.y = Math.random() * Math.PI;

  const scale = Math.random();
  donut.scale.set(scale, scale, scale);

  scene.add(donut);
}

최적화 팁

  • 지오메트리와 재질은 반복문 밖에서 미리 생성하여 공유
  • TextGeometry와 TorusGeometry 모두 같은 MeshMatcapMaterial 사용
const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture });
const text = new THREE.Mesh(textGeometry, material);
const donut = new THREE.Mesh(donutGeometry, material);

참고


import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import GUI from 'lil-gui';
import { FontLoader } from 'three/examples/jsm/Addons.js';
import { TextGeometry } from 'three/examples/jsm/Addons.js';

/**
 * Base
 */
// Debug
const gui = new GUI();

// Canvas
const canvas = document.querySelector('canvas.webgl');

// Scene
const scene = new THREE.Scene();

// Axes Helper
const axesHelper = new THREE.AxesHelper();
scene.add(axesHelper);

/**
 * Textures
 */
const textureLoader = new THREE.TextureLoader();
const matcapTexture = textureLoader.load('/textures/matcaps/2.png');
const matcapTexture2 = textureLoader.load('/textures/matcaps/8.png');
matcapTexture.colorSpace = THREE.SRGBColorSpace;

/**
 * Fonts
 */
const fontLoader = new FontLoader();

fontLoader.load('/fonts/helvetiker_regular.typeface.json', (font) => {
  // console.log(font);
  const textGeometry = new TextGeometry('Hello Three.js', {
    font: font,
    size: 0.5,
    depth: 0.2, // 최신 height에서 depth로 변경됨.
    curveSegments: 5,
    bevelEnabled: true,
    bevelThickness: 0.03,
    bevelSize: 0.02,
    bevelOffset: 0,
    bevelSegments: 4,
  });
  /* 중앙 정렬 방법 1*/
  // textGeometry.computeBoundingBox();
  // 1) console.log(textGeometry.boundingBox);
  // -max 값 * 0.5로 정렬한 뒤 정렬된 max - min으로 세부 조정을 한다.
  // --> -(max - (조정된 max - 조정된 min)) * 0.5
  // textGeometry.translate(
  //   -(textGeometry.boundingBox.max.x - 0.02) * 0.5,
  //   -(textGeometry.boundingBox.max.y - 0.02) * 0.5,
  //   -(textGeometry.boundingBox.max.z - 0.03) * 0.5,
  // );
  // 2) console.log(textGeometry.boundingBox);

  /* 중앙 정렬 방법 2*/
  textGeometry.center();

  const textMaterial = new THREE.MeshMatcapMaterial({ matcap: matcapTexture });
  const text = new THREE.Mesh(textGeometry, textMaterial);
  scene.add(text);

  console.time('donuts');
  // ** loop문 밖에서 Geometry를 선언하면 로딩 시간이 줄어든다.
  const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 20, 45);
  const donutMaterial = new THREE.MeshMatcapMaterial({
    matcap: matcapTexture2,
  });

  for (let i = 0; i < 300; i++) {
    const donut = new THREE.Mesh(donutGeometry, donutMaterial);

    donut.position.x = (Math.random() - 0.5) * 10;
    donut.position.y = (Math.random() - 0.5) * 10;
    donut.position.z = (Math.random() - 0.5) * 10;

    donut.rotation.x = Math.random() * Math.PI;
    donut.rotation.y = Math.random() * Math.PI;

    const scale = Math.random();
    donut.scale.set(scale, scale, scale);

    scene.add(donut);
  }
  console.timeEnd('donuts');
});

/**
 * Sizes
 */
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};

window.addEventListener('resize', () => {
  // Update sizes
  sizes.width = window.innerWidth;
  sizes.height = window.innerHeight;

  // Update camera
  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();

  // Update renderer
  renderer.setSize(sizes.width, sizes.height);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(
  75,
  sizes.width / sizes.height,
  0.1,
  100,
);
camera.position.x = 1;
camera.position.y = 1;
camera.position.z = 2;
scene.add(camera);

// Controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

/**
 * Animate
 */
const clock = new THREE.Clock();

const tick = () => {
  const elapsedTime = clock.getElapsedTime();

  // Update controls
  controls.update();

  // Render
  renderer.render(scene, camera);

  // Call tick again on the next frame
  window.requestAnimationFrame(tick);
};

tick();

'Graphic > ThreeJS' 카테고리의 다른 글

14 조명(Light)  (0) 2025.07.26
13 Go Live  (1) 2025.07.23
11 머티리얼(Material)  (1) 2025.07.23
10 텍스처(Texture)  (3) 2025.07.23
09 Three.js 디버그 UI - lil-gui 사용법  (3) 2025.07.23