Three.jsのMaterial(マテリアル)は、3D objectの表面をどう描画するか決める要素です。色だけでなく、光への反応、金属感、粗さ、透明度、texture、表裏、wireframeなどを制御します。
最初の選択で迷ったら、次を基準にしてください。
- Light不要の一定色・UI的な表現:
MeshBasicMaterial - 一般的な3D・PBR表現:
MeshStandardMaterial - glass・car paint・clothなど高度なPBR:
MeshPhysicalMaterial - cartoon調:
MeshToonMaterial - Lightなしで陰影付きmodelを見せる:
MeshMatcapMaterial - normalのdebug:
MeshNormalMaterial
この記事では、Materialの種類を目的別に比較し、作成、変更、透明度、side、wireframe、needsUpdate、共有、破棄まで解説します。色の入力形式と色空間はThree.jsでMaterialの色を設定する方法へ分離し、ここでは「どれを選び、どう扱うか」に集中します。
Three.jsのMaterialとは
Three.jsでは、Geometryが頂点・面などの形状データを持ち、Materialが表面の描画方法を持ちます。両方をMeshに渡してsceneに追加します。
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);
MaterialはGeometryそのものを変形しません。同じGeometryでもMaterialを交換すれば、表面の見え方を変えられます。反対に同じMaterialを複数Meshで共有することもできます。
「THREE.MeshStandardMaterialを使うべきか」と迷ったときの答えは単純です。Lightを置いて現実的な材質を見せたいならMeshStandardMaterialを選びます。Lightなしで一定の色だけ出したいならMeshBasicMaterial、光沢のないmatteな面を軽く描きたいならMeshLambertMaterialが候補です。いずれもMeshの第2引数に渡す点は同じで、違いはLightへの反応と調整できるpropertyの数です。
Scene・Camera・Rendererを含む最小構成から確認したい場合は、先にThree.js入門チュートリアルを参照してください。
Materialの種類を比較する
Meshに使う主なMaterialを比較すると次のとおりです。
| Material | Light | 特徴 | 主な用途 |
|---|---|---|---|
| MeshBasicMaterial | 不要・反応しない | 単純で軽量 | 一定色、背景、小物、debug |
| MeshLambertMaterial | 必要 | matteな拡散反射 | 光沢不要の軽量表現 |
| MeshPhongMaterial | 必要 | specular highlight | 簡易な光沢表現 |
| MeshStandardMaterial | 必要 | PBR、roughness・metalness | 一般的な3D model |
| MeshPhysicalMaterial | 必要 | Standardを拡張したPBR | glass、car paint、cloth、薄膜 |
| MeshToonMaterial | 必要 | 段階的な陰影 | cartoon・cell shading |
| MeshMatcapMaterial | 不要・反応しない | MatCap textureで陰影 | preview、造形重視の表現 |
| MeshNormalMaterial | 不要 | normal方向をRGB表示 | Geometryのdebug |
| MeshDepthMaterial | 不要 | cameraからのdepthを表示 | depth effect・debug |
高機能なMaterialほど常に優れているわけではありません。必要な見た目を満たす中で、最も単純なものを選ぶとshader計算と調整項目を抑えられます。
MeshBasicMaterialはLightなしで表示する
MeshBasicMaterialはLightの影響を受けません。sceneにLightがなくても表示され、surfaceの向きによる明暗もつきません。
const material = new THREE.MeshBasicMaterial({
color: 0x22c55e,
});
Lightの調整なしで一定の色を見せたいobject、背景的なobject、debug表示に向きます。ただし「Lightを追加したのに立体に陰影が出ない」ときは、MeshBasicMaterialを使っていないか確認してください。
wireframe表示にも使える
const material = new THREE.MeshBasicMaterial({
color: 0x60a5fa,
wireframe: true,
});
wireframeはtriangle edgeを表示する機能です。輪郭線を自由に描く機能ではなく、Geometryの分割数によって線の密度が変わります。
MeshLambertMaterialとMeshPhongMaterial
どちらもLightに反応する従来型のMaterialです。PBRほど現実の材質に沿ったparameterではありませんが、単純な見た目や軽量化を優先するときに使えます。
MeshLambertMaterialは光沢のない表面向け
const material = new THREE.MeshLambertMaterial({
color: 0xf97316,
});
matteな拡散反射を表現します。現在の公式docsでは、MeshLambertMaterialはfragment単位でshadingを計算すると説明されています。古い資料にある「vertex単位(Gouraud)で計算するため粗い」という説明は、現行versionには当てはまりません。specular highlightを持たない分、PhongやStandardより計算は軽く、光沢のいらない壁・床・小物に向きます。
MeshPhongMaterialは簡易な光沢向け
const material = new THREE.MeshPhongMaterial({
color: 0x3b82f6,
specular: 0xffffff,
shininess: 80,
});
shininessを上げるとspecular highlightが小さく鋭くなります。既存projectでPhong表現を維持する場合には有効ですが、新規の一般的な3D modelではroughness・metalnessを使うMeshStandardMaterialから検討すると材質を整理しやすくなります。
MeshStandardMaterialを基本のPBRにする
MeshStandardMaterialはmetallic-roughness workflowを使うPBR Materialです。一般的な3D modelの第一候補にできます。調整するpropertyは主にcolor、roughness、metalnessの3つで、Lightとenvironment mapがあれば金属もplasticも同じMaterialで表現できます。
const material = new THREE.MeshStandardMaterial({
color: 0x94a3b8,
roughness: 0.35,
metalness: 0.8,
});
roughness = 0: 滑らかでreflectionが鋭いroughness = 1: 粗く、reflectionがぼけるmetalness = 0: wood、plastic、stoneなど非金属の基準metalness = 1: metalの基準
rustの境界などtextureで連続値を使うことはありますが、均一材質ではmetalnessを0か1から考えると迷いにくくなります。
environment mapと組み合わせる
PBR MaterialはLightだけでなく周囲のenvironmentを反射します。金属を自然に見せるには、処理済みのenvironment mapをscene.environmentに設定することが重要です。背景画像をscene.backgroundに設定しただけでは、Materialを照らすenvironmentにはなりません。
MeshPhysicalMaterialは高度な材質に使う
MeshPhysicalMaterialはMeshStandardMaterialを拡張し、より高度なPBR propertyを追加します。
| property | 表現例 |
|---|---|
| clearcoat | car paint、varnish、wet surface |
| transmission・thickness・ior | glassや透過材 |
| sheen | cloth、velvet |
| iridescence | soap bubble、油膜、昆虫の翅 |
| anisotropy | brushed metal |
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
roughness: 0.08,
metalness: 0,
transmission: 1,
thickness: 0.4,
ior: 1.5,
});
MeshPhysicalMaterialはfeatureを有効にするほどpixelあたりのcostが増えます。Standardで足りるobjectまでPhysicalに統一せず、glassなど必要なobjectに限定します。良い結果を得るにはenvironment mapも用意します。
MeshToonMaterialでcartoon調にする
MeshToonMaterialはLightに反応し、段階的な陰影を作ります。
const material = new THREE.MeshToonMaterial({
color: 0xfacc15,
});
独自の段階数や境界を使う場合は、横1列のgradientMapを指定します。gradient textureはcolor textureとして扱い、filter設定によって階調の境界がぼけないようにします。
cartoon表現はMaterialだけで完成するとは限りません。outline、Light方向、shadow、modelの形状、色設計も合わせて調整します。
MeshMatcapMaterialはLightなしで陰影を作る
MeshMatcapMaterialはMatCap textureから色と陰影を取得し、sceneのLightには反応しません。
const matcapTexture = textureLoader.load("/textures/matcap.png");
matcapTexture.colorSpace = THREE.SRGBColorSpace;
const material = new THREE.MeshMatcapMaterial({
matcap: matcapTexture,
});
少ない設定で造形を見せやすいため、model previewやstylized表現に向きます。一方、scene内Lightと整合した現実的な照明を作る用途にはStandard・Physicalなどを使います。
debug・特殊用途のMesh Material
一般的な完成表現とは別に、確認や特殊なpassに使うMaterialがあります。
MeshNormalMaterial
view spaceのnormal方向をRGBとして表示します。Lightは不要です。面の向きやnormalが壊れていないかを確認できます。
mesh.material = new THREE.MeshNormalMaterial();
MeshDepthMaterial
cameraのnear・farに基づくdepthを描画します。depth effectやdebugに使います。単純な「cameraから遠いほど任意色に変えるMaterial」ではありません。
ShadowMaterial
shadowを受け取る部分以外を透明に見せたい床などで使います。影を投げるobject用ではなく、影を受け取って合成するsurface向けです。
const shadowMaterial = new THREE.ShadowMaterial({
color: 0x000000,
opacity: 0.25,
});
shadow mapの有効化やLight側の設定はThree.jsで影を落とす方法で解説しています。
Line・Points・Spriteには専用Materialを使う
MaterialはMesh用だけではありません。render objectに合う種類を組み合わせます。
| object | Material | 用途 |
|---|---|---|
| Line・LineSegments | LineBasicMaterial | 実線 |
| Line | LineDashedMaterial | 破線 |
| Points | PointsMaterial | particle・point cloud |
| Sprite | SpriteMaterial | camera-facing 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より大きくしても、多くのplatformで太くなりません。太い線が必要ならthree/addonsのLine2系など、幅を持つ別手法を検討します。
Materialを比較できる最小scene
MeshStandardMaterialを使う場合はLightが必要です。次は回転するtorus knotを表示する最小例です。
import * as THREE from "three";
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111827);
const camera = new THREE.PerspectiveCamera(45, 1, 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、camera、Light、backgroundを固定し、Materialだけを交換します。条件を同じにしないと、Material差と照明差を混同します。比較中にcameraを回して裏側や反射を確認したいときはOrbitControlsで視点を操作する方法、roughnessやmetalnessをsliderで動かしながら見比べたいときはlil-guiとstats.jsの使い方が役立ちます。Lightの種類と物理光量はThree.js Lightの使い方を参照してください。
作成後にMaterialの値を変更する
多くのpropertyはMaterial生成後にも変更できます。
material.color.set(0xf43f5e);
material.roughness = 0.8;
material.metalness = 0;
material.wireframe = true;
material.colorはnumberではなくTHREE.Color instanceです。既存instanceを保ったまま.set()などで変更します。色変更だけなら通常needsUpdateは不要です。
Materialそのものを交換する
const oldMaterial = mesh.material;
mesh.material = new THREE.MeshNormalMaterial();
oldMaterial.dispose();
元のMaterialをほかのMeshも共有している場合、ここでdisposeしてはいけません。どのobjectが所有しているかを確認してから破棄します。Meshをclone()したときにMaterialが共有されるのか複製されるのかは、Three.jsでオブジェクトを複製する方法で整理しています。
needsUpdateが必要な変更を理解する
Materialの数値を変えるたびにneedsUpdate = trueを設定する必要はありません。color、roughness、metalness、opacityなどuniformとして更新される値は通常そのまま反映されます。
一方、shader programの構成が変わる変更では再compileが必要です。代表例は次のとおりです。
flatShadingを切り替える- textureがない状態から
mapを追加する mapを使う状態から完全に外す
material.map = colorTexture;
material.needsUpdate = true;
毎frame無条件にneedsUpdateを立てると不要なshader compileを招きます。propertyの種類に応じて使い分けます。
透明度はopacityだけでは変わらない
通常のalpha blendingで半透明にするには、transparent: trueとopacityを組み合わせます。
const material = new THREE.MeshStandardMaterial({
color: 0x60a5fa,
transparent: true,
opacity: 0.45,
});
transparentがfalseのままでは、opacityを0.45に変えてもMaterialは不透明として扱われます。transparent objectは不透明objectの後に描画され、重なり順のsortingが必要になるため、交差する面や複雑なmodelでは表示破綻が起こる場合があります。
切り抜きにはalphaTestを使う
葉や金網のように「半透明」ではなく「表示するか捨てるか」でよい部分はalphaTestを検討します。
const leafMaterial = new THREE.MeshStandardMaterial({
map: leafTexture,
alphaTest: 0.5,
side: THREE.DoubleSide,
});
alphaTestはthreshold未満のfragmentを描画しないため、通常の半透明sorting問題を避けやすくなります。
alphaHashも選択肢にする
alphaHashはrandom thresholdでfragmentを間引き、alpha blendingのsorting問題を避けながら半透明を近似します。grainが出るため、TAAなどとの組み合わせや見た目のtrade-offを確認します。
depthWriteを安易に切らない
透明objectの重なり問題を避けるためdepthWrite = falseを使う例がありますが、別のocclusion問題を生む可能性があります。alphaTest、object分割、描画順、alphaHashなどを含め、sceneに合う方法を比較してください。
side・flatShading・wireframe・vertexColors
共通propertyは見た目だけでなく描画costや不具合にも影響します。
sideは通常FrontSideのままにする
THREE.FrontSide: front faceだけ。defaultTHREE.BackSide: back faceだけTHREE.DoubleSide: 両面
Planeなど両側から見えるsurfaceにはDoubleSideが便利ですが、閉じた立体に無条件に使うと不要な描画が増えます。裏面から消える場合は、まずGeometryのnormalと面の向きを確認します。
flatShadingで面を強調する
material.flatShading = true;
material.needsUpdate = true;
smoothな補間をやめてpolygon面を強調します。切り替え後はshader再compileが必要です。
vertexColorsはbooleanで指定する
現行Three.jsではvertexColors: trueを使います。古いsampleにあるTHREE.VertexColorsやTHREE.FaceColorsは使いません。Geometry側にcolor属性が必要です。
const material = new THREE.MeshStandardMaterial({
vertexColors: true,
});
Geometryの構造やBufferAttributeの更新はThree.js Geometryの種類と変更方法で解説しています。
textureとMaterialの関係
多くのMesh Materialはmap、normalMap、roughnessMapなどを持ちます。ただしtextureのchannelとcolor spaceは用途ごとに異なります。
map・emissiveMap: 色データ。一般的な画像はSRGBColorSpacenormalMap・roughnessMap・metalnessMap: 非色データ。defaultの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を白にして不要なtintを避けます。TextureLoader、UV、wrap、filterはThree.jsでtextureを設定する方法へ分離しています。
Materialを共有・clone・複数割り当てする
同じMaterial instanceを複数Meshで共有すると、shaderや設定を再利用しやすくなります。
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 groupへ複数Materialを使う
Meshの第2引数にMaterial配列を渡し、Geometry groupのmaterialIndexで使い分けられます。
const mesh = new THREE.Mesh(geometry, [materialA, materialB]);
Materialが増えるとgroupごとにdraw callが増えます。単純な色違いのためだけに細かく分割する前に、vertex color、texture atlas、instance属性なども検討します。
Materialのperformanceを改善する
Material選択はGPU負荷とdraw callに影響します。
- Lightが不要ならMeshBasicMaterialを使う
- Standardで足りるobjectにPhysicalの高度なfeatureを有効にしない
- 同一設定のMaterial instanceを共有する
- Material配列・Geometry groupの増加によるdraw callを確認する
- 透明objectとDoubleSideを必要な範囲に絞る
- 毎frameのneedsUpdateを避ける
- mobile実機でGPU時間と見た目を確認する
公式manualでは、一般にBasic、Lambert、Phong、Standard、Physicalの順でshaderが高機能になり、計算costも増えると説明されています。ただし最終的な負荷はLight、texture、shadow、pixel数、enabled featureなどを含むため、実際のsceneで計測します。
不要なMaterialをdisposeする
Meshをsceneから削除しただけでは、MaterialのGPU resourceは自動解放されません。再利用しないことを確認してdispose()を呼びます。
scene.remove(mesh);
mesh.geometry.dispose();
mesh.material.dispose();
Materialが参照するtextureはMaterial.dispose()だけでは破棄されません。textureも不要なら個別にdisposeします。
material.map?.dispose();
material.normalMap?.dispose();
material.dispose();
共有Material・共有textureを早くdisposeすると、まだ使っているMeshの表示に影響します。resource ownerと参照関係を決めてから解放してください。
Materialが反映されないときの確認
objectが真っ黒になる
MeshStandard・Physical・Phong・Lambert・ToonはLightに反応します。Lightをsceneへ追加し、intensity、objectとの位置、Materialのcolor・metalness、environmentを確認します。切り分け時は一度MeshNormalMaterialかMeshBasicMaterialへ交換すると、Geometryとcameraが正常か確認できます。
Lightを追加しても陰影が出ない
MeshBasicMaterialまたはMeshMatcapMaterialはsceneのLightに反応しません。Light対応Materialに変更します。
opacityを下げても透明にならない
通常の半透明ならtransparent = trueも設定します。切り抜き用途ならalphaTestを比較します。
textureの色が違う
color textureのcolorSpace、Material colorとの乗算、Light、tone mapping、environmentを確認します。normal・roughnessなど非色textureにSRGBColorSpaceを設定しないようにします。
Planeが片側から消える
MaterialのdefaultはFrontSideです。camera位置と面の向きが正しいか確認し、両面が本当に必要な場合だけDoubleSideへ変更します。
wireframeの線が太くならない
wireframeLinewidth、wireframeLinecap、wireframeLinejoinはWebGLRendererでは期待どおり機能せず、公式docsではSVGRendererのみとされています。太線は専用のline表現で作ります。
property変更が反映されない
colorやroughnessは通常即時反映されます。mapの追加・削除やflatShadingなどshader構成が変わる変更ではneedsUpdateを設定します。変更対象が共有Materialなのかcloneなのかも確認してください。
Material交換後にmemory使用量が増え続ける
古いMaterial、Geometry、Textureが不要になった時点でdisposeします。animation中に毎frame新しいMaterialを生成しないようにします。
Material選択チェックリスト
- Light不要ならMeshBasicMaterialを選んだ
- 一般的なPBRはMeshStandardMaterialから試した
- Physical固有featureが必要なobjectだけMeshPhysicalMaterialにした
- cartoonならToon、Light不要のpreviewならMatcapを比較した
- Line・Points・Spriteに専用Materialを使った
- MaterialとLightの対応を確認した
- opacityとtransparentを用途に合わせて設定した
- alphaTest・alphaHash・sortingのtrade-offを確認した
- sideとDoubleSideを必要な範囲に限定した
- vertexColorsはbooleanで指定した
- textureの用途に合うcolorSpaceを設定した
- needsUpdateを毎frame無条件に設定していない
- 共有Materialの変更・dispose範囲を確認した
- 不要なMaterial・Texture・Geometryをdisposeした
まとめ
Three.jsのMaterialは、表面の色だけでなく、Lightへの反応、材質、透明度、texture、表裏、描画costを決めます。迷ったら、Light不要ならMeshBasicMaterial、一般的な3DならMeshStandardMaterial、高度なglass・clearcoat・cloth表現が必要な部分だけMeshPhysicalMaterialから始めます。
Materialが暗い・透明にならない・変更が反映されない問題は、Light対応、transparent、texture color space、needsUpdate、共有instanceを順に確認すると切り分けやすくなります。完成後は実機でperformanceを測り、不要なresourceをdisposeしてください。Materialを決めたあとは、Meshのposition・rotation・scaleの操作に進むと、sceneを組み立てる流れがつながります。