Three.jsのMaterial(マテリアル)は、3Dオブジェクトの表面をどう描画するか決める要素です。色だけでなく、光への反応、金属感、粗さ、透明度、テクスチャ、表裏、wireframeなどを制御します。
最初の選択で迷ったら、次を基準にしてください。
- ライト不要の一定色・UI的な表現:
MeshBasicMaterial - 一般的な3D・PBR表現:
MeshStandardMaterial - ガラス・自動車の塗装・布など高度なPBR:
MeshPhysicalMaterial - アニメ調:
MeshToonMaterial - ライトなしで陰影のあるモデルを見せる:
MeshMatcapMaterial - 法線のデバッグ:
MeshNormalMaterial
この記事では、Materialの種類を目的別に比較し、作成、変更、透明度、side、wireframe、needsUpdate、共有、破棄まで解説します。材質を決めたあと、色の指定方法も確かめたい場合はThree.jsでMaterialの色を設定する方法を参照してください。
Three.jsのMaterialとは
Three.jsでは、Geometryが頂点・面などの形状データを持ち、Materialが表面の描画方法を持ちます。両方をMeshに渡してシーンに追加します。
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({
color: 0x38bdf8,
roughness: 0.45,
metalness: 0.1,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
色や粗さを変えても、Geometryの形状は変わりません。ただし、displacementMapのように描画時の頂点位置へ作用する設定は別です。同じGeometryでもMaterialを交換すれば、表面の見え方を変えられます。反対に同じMaterialを複数Meshで共有することもできます。
「THREE.MeshStandardMaterialを使うべきか」と迷ったときの答えは単純です。ライトを置いて現実的な材質を見せたいならMeshStandardMaterialを選びます。ライトなしで一定の色だけ出したいならMeshBasicMaterial、光沢のない面を軽く描きたいならMeshLambertMaterialが候補です。いずれもMeshの第2引数に渡す点は同じで、違いはライトへの反応と調整できるプロパティの数です。
シーン・カメラ・レンダラーを含む最小構成から確認したい場合は、先にThree.js入門チュートリアルを参照してください。
Materialの種類を比較する
Meshに使う主なMaterialを比較すると次のとおりです。
| Material | ライト | 特徴 | 主な用途 |
|---|---|---|---|
| MeshBasicMaterial | 不要・反応しない | 単純で軽量 | 一定色、背景、小物、デバッグ |
| MeshLambertMaterial | 必要 | 光沢を伴わない拡散反射 | 光沢不要の軽量表現 |
| MeshPhongMaterial | 必要 | 光沢のハイライト | 簡易な光沢表現 |
| MeshStandardMaterial | 必要 | PBR、roughness・metalness | 一般的な3Dモデル |
| MeshPhysicalMaterial | 必要 | Standardを拡張したPBR | ガラス、自動車の塗装、布、薄膜 |
| MeshToonMaterial | 必要 | 段階的な陰影 | アニメ・セルシェーディング |
| MeshMatcapMaterial | 不要・反応しない | MatCapテクスチャで陰影 | プレビュー、造形重視の表現 |
| MeshNormalMaterial | 不要 | 法線方向をRGB表示 | Geometryのデバッグ |
| MeshDepthMaterial | 不要 | カメラからの奥行きを表示 | 奥行きを使った効果・デバッグ |
高機能なMaterialほど常に優れているわけではありません。必要な見た目を満たす中で、最も単純なものを選ぶとシェーダー計算と調整項目を抑えられます。
MeshBasicMaterialはライトなしで表示する
MeshBasicMaterialはライトの影響を受けません。シーンにライトがなくても表示され、表面の向きによる明暗もつきません。
const material = new THREE.MeshBasicMaterial({
color: 0x22c55e,
});
ライトの調整なしで一定の色を見せたいオブジェクト、背景的なオブジェクト、デバッグ表示に向きます。ただし「ライトを追加したのに立体に陰影が出ない」ときは、MeshBasicMaterialを使っていないか確認してください。
wireframe表示にも使える
const material = new THREE.MeshBasicMaterial({
color: 0x60a5fa,
wireframe: true,
});
wireframeは三角形の辺を表示する機能です。輪郭線を自由に描く機能ではなく、Geometryの分割数によって線の密度が変わります。
MeshLambertMaterialとMeshPhongMaterial
どちらもライトに反応する従来型のMaterialです。PBRほど現実の材質に沿ったパラメータではありませんが、単純な見た目や軽量化を優先するときに使えます。
MeshLambertMaterialは光沢のない表面向け
const material = new THREE.MeshLambertMaterial({
color: 0xf97316,
});
光沢を伴わない拡散反射を表現します。現在の公式ドキュメントでは、MeshLambertMaterialはフラグメント単位で陰影を計算すると説明されています。古い資料にある「頂点単位(Gouraud)で計算するため粗い」という説明は、現行バージョンには当てはまりません。光沢のハイライトを持たない分、PhongやStandardより計算は軽く、光沢のいらない壁・床・小物に向きます。
MeshPhongMaterialは簡易な光沢向け
const material = new THREE.MeshPhongMaterial({
color: 0x3b82f6,
specular: 0xffffff,
shininess: 80,
});
shininessを上げると光沢のハイライトが小さく鋭くなります。既存プロジェクトでPhong表現を維持する場合には有効ですが、新規の一般的な3Dモデルではroughness・metalnessを使うMeshStandardMaterialから検討すると材質を整理しやすくなります。
MeshStandardMaterialを基本のPBRにする
MeshStandardMaterialは金属度と粗さで材質を調整するPBR(物理ベースレンダリング)のMaterialです。一般的な3Dモデルの第一候補にできます。調整するプロパティは主にcolor、roughness、metalnessの3つで、ライトと環境マップがあれば金属もプラスチックも同じMaterialで表現できます。
const material = new THREE.MeshStandardMaterial({
color: 0x94a3b8,
roughness: 0.35,
metalness: 0.8,
});
roughness = 0: 滑らかで反射が鋭いroughness = 1: 粗く、反射がぼけるmetalness = 0: 木材、プラスチック、石など非金属の基準metalness = 1: 金属の基準
さびの境界などテクスチャで連続値を使うことはありますが、均一材質ではmetalnessを0か1から考えると迷いにくくなります。
環境マップと組み合わせる
PBR Materialはライトだけでなく周囲の環境を反射します。金属を自然に見せるには、処理済みの環境マップをscene.environmentに設定することが重要です。背景画像をscene.backgroundに設定しただけでは、Materialを照らす周囲の環境にはなりません。
MeshPhysicalMaterialは高度な材質に使う
MeshPhysicalMaterialはMeshStandardMaterialを拡張し、より高度なPBR プロパティを追加します。
| プロパティ | 表現例 |
|---|---|
| clearcoat | 自動車の塗装、ニス、濡れた表面 |
| transmission・thickness・ior | ガラスや透過材 |
| sheen | 布、ベルベット |
| iridescence | シャボン玉、油膜、昆虫の翅 |
| anisotropy | ヘアライン加工した金属 |
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
roughness: 0.08,
metalness: 0,
transmission: 1,
thickness: 0.4,
ior: 1.5,
});
MeshPhysicalMaterialは機能を有効にするほどピクセルあたりの負荷が増えます。Standardで足りるオブジェクトまでPhysicalに統一せず、ガラスなど必要なオブジェクトに限定します。良い結果を得るには環境マップも用意します。
MeshToonMaterialでアニメ調にする
MeshToonMaterialはライトに反応し、段階的な陰影を作ります。
const material = new THREE.MeshToonMaterial({
color: 0xfacc15,
});
陰影の段階を変える場合は、横1列のgradientMapを指定します。これは色画像ではなく、陰影の強さを表すデータです。colorSpaceは初期値のTHREE.NoColorSpaceを使い、minFilterとmagFilterにはTHREE.NearestFilterを設定して階調の境界を保ちます。
アニメ表現はMaterialだけで完成するとは限りません。輪郭線、ライト方向、影、モデルの形状、色設計も合わせて調整します。
MeshMatcapMaterialはライトなしで陰影を作る
MeshMatcapMaterialはMatCapテクスチャから色と陰影を取得し、シーンのライトには反応しません。
const matcapTexture = textureLoader.load("/textures/matcap.png");
matcapTexture.colorSpace = THREE.SRGBColorSpace;
const material = new THREE.MeshMatcapMaterial({
matcap: matcapTexture,
});
少ない設定で造形を見せやすいため、モデルのプレビューやデフォルメした表現に向きます。一方、シーン内ライトと整合した現実的な照明を作る用途にはStandard・Physicalなどを使います。
デバッグ・特殊用途のMesh Material
一般的な完成表現とは別に、確認や特殊な描画処理に使うMaterialがあります。
MeshNormalMaterial
視点座標系の法線方向をRGBとして表示します。ライトは不要です。面の向きや法線が壊れていないかを確認できます。
mesh.material = new THREE.MeshNormalMaterial();
MeshDepthMaterial
カメラのnear・farに基づく奥行きを描画します。奥行きを使った効果やデバッグに使います。単純な「カメラから遠いほど任意色に変えるMaterial」ではありません。
ShadowMaterial
影を受け取る部分以外を透明に見せたい床などで使います。影を投げるオブジェクト用ではなく、影を受け取って合成する表面向けです。
const shadowMaterial = new THREE.ShadowMaterial({
color: 0x000000,
opacity: 0.25,
});
シャドウマップの有効化やライト側の設定はThree.jsで影を落とす方法で解説しています。
Line・Points・Spriteには専用Materialを使う
MaterialはMesh用だけではありません。描画対象に合う種類を組み合わせます。
| オブジェクト | Material | 用途 |
|---|---|---|
| Line・LineSegments | LineBasicMaterial | 実線 |
| Line | LineDashedMaterial | 破線 |
| Points | PointsMaterial | パーティクル・点群 |
| Sprite | SpriteMaterial | カメラに正対する2D表示 |
LineDashedMaterialを使うだけでは破線に必要な距離データがありません。Line作成後にcomputeLineDistances()を呼びます。
const line = new THREE.Line(
geometry,
new THREE.LineDashedMaterial({
color: 0xffffff,
dashSize: 0.25,
gapSize: 0.12,
})
);
line.computeLineDistances();
scene.add(line);
WebGLRendererではlinewidthを1より大きくしても、多くの環境で太くなりません。太い線が必要ならthree/addonsのLine2系など、幅を持つ別手法を検討します。
Materialを比較できる最小シーン
MeshStandardMaterialを使う場合はライトが必要です。次は回転するトーラス結び目を表示する最小例です。Three.jsを導入済みで、importを解決できる環境のJavaScriptへ置きます。描画領域を800×600に固定しているため、カメラの縦横比も800 / 600にそろえています。
import * as THREE from "three";
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111827);
const camera = new THREE.PerspectiveCamera(45, 800 / 600, 0.1, 100);
camera.position.set(4, 3, 6);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(800, 600);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.TorusKnotGeometry(1, 0.35, 128, 24);
const material = new THREE.MeshStandardMaterial({
color: 0x38bdf8,
roughness: 0.35,
metalness: 0.15,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.25);
const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
directionalLight.position.set(4, 5, 3);
scene.add(ambientLight, directionalLight);
renderer.setAnimationLoop(() => {
mesh.rotation.y += 0.005;
renderer.render(scene, camera);
});
Materialを比較するときは、Geometry、カメラ、ライト、背景を固定し、Materialだけを交換します。条件を同じにしないと、Material差と照明差を混同します。比較中にカメラを回して裏側や反射を確認したいときはOrbitControlsで視点を操作する方法、roughnessやmetalnessをスライダーで動かしながら見比べたいときはlil-guiとstats.jsの使い方が役立ちます。ライトの種類と物理光量はThree.js ライトの使い方を参照してください。
作成後にMaterialの値を変更する
多くのプロパティはMaterial生成後にも変更できます。
material.color.set(0xf43f5e);
material.roughness = 0.8;
material.metalness = 0;
material.wireframe = true;
material.colorは数値ではなくTHREE.Color インスタンスです。既存インスタンスを保ったまま.set()などで変更します。色変更だけなら通常needsUpdateは不要です。
Materialそのものを交換する
const oldMaterial = mesh.material;
mesh.material = new THREE.MeshNormalMaterial();
oldMaterial.dispose();
元のMaterialをほかのMeshも共有している場合、ここでdisposeしてはいけません。どのオブジェクトが所有しているかを確認してから破棄します。Meshをclone()したときにMaterialが共有されるのか複製されるのかは、Three.jsでオブジェクトを複製する方法で整理しています。
needsUpdateが必要な変更を理解する
Materialの数値を変えるたびにneedsUpdate = trueを設定する必要はありません。color、roughness、metalness、opacityなどuniformとして更新される値は通常は再描画時に反映されます。
一方、シェーダープログラムの構成が変わる変更では再コンパイルが必要です。代表例は次のとおりです。
flatShadingを切り替える- テクスチャがない状態から
mapを追加する mapを使う状態から完全に外す
material.map = colorTexture;
material.needsUpdate = true;
毎フレーム無条件にneedsUpdateを立てると不要なシェーダーのコンパイルを招きます。プロパティの種類に応じて使い分けます。
透明度はopacityだけでは変わらない
通常のアルファブレンドで半透明にするには、transparent: trueとopacityを組み合わせます。
const material = new THREE.MeshStandardMaterial({
color: 0x60a5fa,
transparent: true,
opacity: 0.45,
});
transparentがfalseのままでは、opacityを0.45に変えてもMaterialは不透明として扱われます。半透明オブジェクトは不透明オブジェクトの後に描画され、重なり順の並べ替えが必要になるため、交差する面や複雑なモデルでは表示破綻が起こる場合があります。
切り抜きにはalphaTestを使う
葉や金網のように「半透明」ではなく「表示するか捨てるか」でよい部分はalphaTestを検討します。
const leafMaterial = new THREE.MeshStandardMaterial({
map: leafTexture,
alphaTest: 0.5,
side: THREE.DoubleSide,
});
alphaTestはしきい値未満のフラグメントを描画しないため、半透明の重なり順の問題を避けやすくなります。
alphaHashも選択肢にする
alphaHashはランダムなしきい値でフラグメントを間引き、アルファブレンドの並べ替え問題を避けながら半透明を近似します。粒状のノイズが出るため、TAAなどとの組み合わせや見た目の長所と短所を確認します。
depthWriteを安易に切らない
透明オブジェクトの重なり問題を避けるためdepthWrite = falseを使う例がありますが、本来隠れるはずの面が見える問題を生む可能性があります。alphaTest、オブジェクト分割、描画順、alphaHashなどを含め、シーンに合う方法を比較してください。
side・flatShading・wireframe・vertexColors
共通プロパティは見た目だけでなく描画負荷や不具合にも影響します。
sideは通常FrontSideのままにする
THREE.FrontSide: 表面だけ。初期値THREE.BackSide: 裏面だけTHREE.DoubleSide: 両面
Planeなど両側から見える表面にはDoubleSideが便利ですが、閉じた立体に無条件に使うと不要な描画が増えます。裏面から消える場合は、まずGeometryの法線と面の向きを確認します。
flatShadingで面を強調する
material.flatShading = true;
material.needsUpdate = true;
滑らかな補間をやめてポリゴン面を強調します。切り替え後はシェーダーの再コンパイルが必要です。
vertexColorsは真偽値で指定する
現行Three.jsではvertexColors: trueを使います。古いサンプルにあるTHREE.VertexColorsやTHREE.FaceColorsは使いません。Geometry側にcolor属性が必要です。
const material = new THREE.MeshStandardMaterial({
vertexColors: true,
});
Geometryの構造やBufferAttributeの更新はThree.js Geometryの種類と変更方法で解説しています。
テクスチャとMaterialの関係
多くのMesh Materialはmap、normalMap、roughnessMapなどを持ちます。ただしテクスチャのチャンネルと色空間は用途ごとに異なります。
map・emissiveMap: 色データ。一般的な画像はSRGBColorSpacenormalMap・roughnessMap・metalnessMap: 非色データ。初期値のNoColorSpace- Materialの
colorとmap: 基本的に乗算される
const colorTexture = textureLoader.load("/textures/base-color.jpg");
colorTexture.colorSpace = THREE.SRGBColorSpace;
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
map: colorTexture,
});
画像をそのまま見せたい場合、colorを白にして不要な着色を避けます。TextureLoader、UV、wrap、フィルターはThree.jsでテクスチャを設定する方法で手順を確認できます。
Materialを共有・clone・複数割り当てする
同じMaterialのインスタンスを複数Meshで共有すると、シェーダーや設定を再利用しやすくなります。
const sharedMaterial = new THREE.MeshStandardMaterial({
color: 0x64748b,
});
meshA.material = sharedMaterial;
meshB.material = sharedMaterial;
この状態でsharedMaterial.colorを変えると両方が変わります。片方だけ変更したい場合はcloneします。
meshB.material = sharedMaterial.clone();
meshB.material.color.set(0xef4444);
Geometryのグループへ複数Materialを使う
Meshの第2引数にMaterial配列を渡し、GeometryのグループのmaterialIndexで使い分けられます。
const mesh = new THREE.Mesh(geometry, [materialA, materialB]);
Materialが増えるとグループごとに描画命令が増えます。単純な色違いのためだけに細かく分割する前に、頂点カラー、テクスチャアトラス、インスタンス属性なども検討します。
Materialの描画性能を改善する
Material選択はGPU負荷と描画命令に影響します。
- ライトが不要ならMeshBasicMaterialを使う
- Standardで足りるオブジェクトにPhysicalの高度な機能を有効にしない
- 同一設定のMaterialのインスタンスを共有する
- Material配列・Geometryのグループの増加による描画命令を確認する
- 透明オブジェクトとDoubleSideを必要な範囲に絞る
- 毎フレームのneedsUpdateを避ける
- モバイル実機でGPU時間と見た目を確認する
公式マニュアルでは、一般にBasic、Lambert、Phong、Standard、Physicalの順でシェーダーが高機能になり、計算負荷も増えると説明されています。ただし最終的な負荷はライト、テクスチャ、影、ピクセル数、有効な機能などを含むため、実際のシーンで計測します。
不要なMaterialをdisposeする
Meshをシーンから削除しただけでは、MaterialのGPUリソースは自動解放されません。再利用しないことを確認してdispose()を呼びます。
scene.remove(mesh);
mesh.geometry.dispose();
mesh.material.dispose();
Materialが参照するテクスチャはMaterial.dispose()だけでは破棄されません。テクスチャも不要なら個別にdisposeします。
material.map?.dispose();
material.normalMap?.dispose();
material.dispose();
共有Material・共有テクスチャを早くdisposeすると、まだ使っているMeshの表示に影響します。リソースの管理元と参照関係を決めてから解放してください。
Materialが反映されないときの確認
オブジェクトが真っ黒になる
MeshStandard・Physical・Phong・Lambert・Toonはライトに反応します。ライトをシーンへ追加し、強度、オブジェクトとの位置、Materialのcolor・metalness、周囲の環境を確認します。切り分け時は一度MeshNormalMaterialかMeshBasicMaterialへ交換すると、Geometryとカメラが正常か確認できます。
ライトを追加しても陰影が出ない
MeshBasicMaterialまたはMeshMatcapMaterialはシーンのライトに反応しません。ライト対応Materialに変更します。
opacityを下げても透明にならない
通常の半透明ならtransparent = trueも設定します。切り抜き用途ならalphaTestを比較します。
テクスチャの色が違う
色を表すテクスチャのcolorSpace、Material colorとの乗算、ライト、トーンマッピング、周囲の環境を確認します。法線・roughnessなど非色テクスチャにSRGBColorSpaceを設定しないようにします。
Planeが片側から消える
Materialの初期値はFrontSideです。カメラ位置と面の向きが正しいか確認し、両面が本当に必要な場合だけDoubleSideへ変更します。
wireframeの線が太くならない
wireframeLinewidth、wireframeLinecap、wireframeLinejoinはWebGLRendererでは期待どおり機能せず、公式ドキュメントではSVGRendererのみとされています。太線は専用のline表現で作ります。
プロパティ変更が反映されない
colorやroughnessは通常、次の再描画で反映されます。mapの追加・削除やflatShadingなどシェーダー構成が変わる変更ではneedsUpdateを設定します。変更対象が共有Materialなのかcloneなのかも確認してください。
Material交換後にメモリ使用量が増え続ける
古いMaterial、Geometry、Textureが不要になった時点でdisposeします。アニメーション中に毎フレーム新しいMaterialを生成しないようにします。
Material選択チェックリスト
- ライト不要ならMeshBasicMaterialを選んだ
- 一般的なPBRはMeshStandardMaterialから試した
- Physical固有機能が必要なオブジェクトだけMeshPhysicalMaterialにした
- アニメならToon、ライト不要のプレビューならMatcapを比較した
- Line・Points・Spriteに専用Materialを使った
- Materialとライトの対応を確認した
- opacityとtransparentを用途に合わせて設定した
- alphaTest・alphaHash・並べ替えの長所と短所を確認した
- sideとDoubleSideを必要な範囲に限定した
- vertexColorsは真偽値で指定した
- テクスチャの用途に合うcolorSpaceを設定した
- needsUpdateを毎フレーム無条件に設定していない
- 共有Materialの変更・dispose範囲を確認した
- 不要なMaterial・Texture・Geometryをdisposeした
まとめ
Three.jsのMaterialは、表面の色だけでなく、ライトへの反応、材質、透明度、テクスチャ、表裏、描画負荷を決めます。迷ったら、ライト不要ならMeshBasicMaterial、一般的な3DならMeshStandardMaterial、高度なガラス・clearcoat・布表現が必要な部分だけMeshPhysicalMaterialから始めます。
Materialが暗い・透明にならない・変更が反映されない問題は、ライト対応、transparent、テクスチャの色空間、needsUpdate、共有インスタンスを順に確認すると切り分けやすくなります。完成後は実機で描画性能を測り、不要なリソースをdisposeしてください。Materialを決めたあとは、Meshのposition・rotation・scaleの操作に進むと、シーンを組み立てる流れがつながります。
MeshToonMaterial公式ドキュメントでも、gradientMapは非色データとして扱い、NearestFilterを指定することが示されています。