概要

React Three Fiber を使用して GLTF モデルを表示した際、テクスチャがぼやけて見える、または荒く表示される問題に遭遇しました。本記事では、その原因と解決策を解説します。

症状

  • GLTF モデルのテクスチャがぼやけて表示される
  • 他の 3D ビューアでは正常に表示される同じモデルが、自作のビューアでは荒く見える
  • dpr(デバイスピクセル比)や antialias を設定しても改善しない

原因

Three.js r152 以降、デフォルトの出力カラースペースが変更されました。

GLTF モデルのテクスチャは通常 sRGB 色空間 で保存されています。しかし、Three.js のデフォルト設定では出力が Linear 色空間 になっているため、以下の問題が発生します:

  1. テクスチャの補間処理が Linear 空間で行われる
  2. ミップマップの生成が正しい色空間で行われない
  3. 結果として、テクスチャがぼやけたり、色がおかしくなる

解決策

Canvas の onCreated コールバックで outputColorSpace を設定します。

Before(問題のあるコード)

import { Canvas } from '@react-three/fiber';

<Canvas
  camera={{ position: [5, 5, 5], fov: 50 }}
  dpr={[1, 2]}
  gl={{ antialias: true }}
>
  <Scene />
</Canvas>

After(修正後のコード)

import { Canvas } from '@react-three/fiber';
import * as THREE from 'three';

<Canvas
  camera={{ position: [5, 5, 5], fov: 50 }}
  dpr={Math.min(window.devicePixelRatio, 2)}
  gl={{
    antialias: true,
    preserveDrawingBuffer: true,
    powerPreference: 'high-performance',
    toneMapping: THREE.ACESFilmicToneMapping,
    toneMappingExposure: 1,
  }}
  onCreated={({ gl }) => {
    gl.outputColorSpace = THREE.SRGBColorSpace;
  }}
>
  <Scene />
</Canvas>

各設定の説明

outputColorSpace: THREE.SRGBColorSpace

最も重要な設定 。出力カラースペースを sRGB に設定することで、テクスチャが正しく表示されます。

toneMapping: THREE.ACESFilmicToneMapping

HDR(High Dynamic Range)から SDR(Standard Dynamic Range)への変換方式を指定します。ACES Filmic は映画業界で使われるトーンマッピングで、自然な見た目になります。

toneMappingExposure: 1

トーンマッピングの露出値。1 がデフォルトで、値を上げると明るく、下げると暗くなります。

preserveDrawingBuffer: true

Canvas の内容を保持します。スクリーンショットを撮る場合などに必要です。

powerPreference: 'high-performance'

GPU の電力設定。‘high-performance’ を指定すると、可能な場合は高性能な GPU を使用します。

補足:Raycast を使った法線取得

3D アノテーションのカメラ制御で、アノテーション位置からモデル表面の法線を取得する方法も実装しました。

function findSurfaceNormal(
  position: THREE.Vector3,
  scene: THREE.Scene
): THREE.Vector3 | null {
  const raycaster = new THREE.Raycaster();
  const meshes: THREE.Mesh[] = [];

  scene.traverse((child) => {
    if (child instanceof THREE.Mesh) {
      meshes.push(child);
    }
  });

  // 6方向にレイを飛ばして最も近い面を見つける
  const directions = [
    new THREE.Vector3(0, 0, 1),
    new THREE.Vector3(0, 0, -1),
    new THREE.Vector3(1, 0, 0),
    new THREE.Vector3(-1, 0, 0),
    new THREE.Vector3(0, 1, 0),
    new THREE.Vector3(0, -1, 0),
  ];

  let closestHit = null;

  for (const dir of directions) {
    raycaster.set(position, dir);
    const intersects = raycaster.intersectObjects(meshes, true);

    if (intersects.length > 0 && intersects[0].face) {
      const hit = intersects[0];
      if (!closestHit || hit.distance < closestHit.distance) {
        const normal = hit.face.normal.clone();
        const normalMatrix = new THREE.Matrix3().getNormalMatrix(hit.object.matrixWorld);
        normal.applyMatrix3(normalMatrix).normalize();
        closestHit = { distance: hit.distance, normal };
      }
    }
  }

  // 法線がモデルの外向きになるように調整
  if (closestHit) {
    const modelCenter = getModelCenter(scene);
    const outwardDir = position.clone().sub(modelCenter).normalize();
    if (closestHit.normal.dot(outwardDir) < 0) {
      closestHit.normal.negate();
    }
  }

  return closestHit?.normal || null;
}

まとめ

Three.js + React Three Fiber で GLTF モデルを表示する際は、outputColorSpaceTHREE.SRGBColorSpace に設定することを忘れずに。これだけで、テクスチャの品質が大幅に改善されます。

環境

  • Three.js: r152+
  • React Three Fiber: 8.x
  • @react-three/drei: 9.x
  • Next.js: 16.x

参考リンク