概要
React Three Fiber を使用して GLTF モデルを表示した際、テクスチャがぼやけて見える、または荒く表示される問題に遭遇しました。本記事では、その原因と解決策を解説します。
症状
- GLTF モデルのテクスチャがぼやけて表示される
- 他の 3D ビューアでは正常に表示される同じモデルが、自作のビューアでは荒く見える
dpr(デバイスピクセル比)やantialiasを設定しても改善しない
原因
Three.js r152 以降、デフォルトの出力カラースペースが変更されました。
GLTF モデルのテクスチャは通常 sRGB 色空間 で保存されています。しかし、Three.js のデフォルト設定では出力が Linear 色空間 になっているため、以下の問題が発生します:
- テクスチャの補間処理が Linear 空間で行われる
- ミップマップの生成が正しい色空間で行われない
- 結果として、テクスチャがぼやけたり、色がおかしくなる
解決策
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 モデルを表示する際は、outputColorSpace を THREE.SRGBColorSpace に設定することを忘れずに。これだけで、テクスチャの品質が大幅に改善されます。
環境
- Three.js: r152+
- React Three Fiber: 8.x
- @react-three/drei: 9.x
- Next.js: 16.x