Three.jsのマテリアル・テクスチャ・ライティング入門|「見た目」の作り方

約15分
Three.jsのマテリアル・テクスチャ・ライティング入門|「見た目」の作り方

みなさんこんにちは。フロントエンドエンジニアのしゅん(@shun_webdesign)です。

前回のThree.jsの基本3要素|Scene・Camera・Rendererを図で理解するで、3Dが画面に映るまでの流れは掴めたと思います。ただ、あの箱ってずっと虹色のままでしたよね。

今回はそこから一歩進んで、「見た目」を自分で決める話をします。キーワードはマテリアル・ライト・テクスチャの3つ。この3つを押さえると、箱がいきなり「作品の部品」っぽい顔になります。

同じ形の箱でも、マテリアル・テクスチャ・ライティングによって見た目が変わる

Meshは「形」と「見た目」の合体でできている

まず前提の整理から。前回のコードで箱を作った部分を思い出してください。

const cube = new THREE.Mesh(
  new THREE.BoxGeometry(),        // 形
  new THREE.MeshNormalMaterial()  // 見た目
);

Meshの引数は2つで、Geometry(形)Material(見た目) です。

  • Geometry … 立方体・球・平面といった「かたち」の情報
  • Material … 色・質感・光の反射といった「表面」の情報

つまり3Dの見た目づくりは、ほぼMaterialをどう選んで、どう設定するかの話になります。形はあとから差し替えても、考え方は変わりません。

① マテリアル:まずはこの3つだけ覚える

Three.jsのマテリアルは十数種類ありますが、入門で使うのは実質3つです。

マテリアル光の影響用途
MeshBasicMaterial受けない単色でベタッと塗りたいとき。UIっぽい表現・確認用
MeshStandardMaterial受ける基本これ。金属・プラスチックなど現実的な質感
MeshNormalMaterial受けない面の向きを色で見るデバッグ用。あの虹色の正体

いちばん使うのはMeshStandardMaterialです。まずは色を指定してみます。

const material = new THREE.MeshStandardMaterial({ color: 0x4a90d9 });
const cube = new THREE.Mesh(new THREE.BoxGeometry(), material);
scene.add(cube);

これで保存すると……箱が真っ黒になります。壊れたわけではありません。

「真っ黒になる」は光が無いだけ

ここがThree.js最初の関門です。MeshStandardMaterial光を反射して見えるマテリアルなので、シーンに光がないと反射するものが無く、真っ黒になります。

現実でも、真っ暗な部屋に青い箱を置いたら黒く見えますよね。それと同じことが起きているだけです。

逆にMeshBasicMaterialは光を一切計算せず、指定した色をそのまま塗ります。だから光がなくても見える。「見えない=光を置き忘れている」 と覚えておくと、この先ハマる時間がかなり減ります。

② ライティング:2灯セットが基本形

では光を置きます。入門段階では、次の2灯セットを丸暗記でOKです。

// 全体をふんわり底上げする光
const ambient = new THREE.AmbientLight(0xffffff, 1.5);
scene.add(ambient);

// 方向を持った、太陽のような光
const directional = new THREE.DirectionalLight(0xffffff, 2);
directional.position.set(2, 3, 4);
scene.add(directional);

役割はこう分かれています。

  • AmbientLight(環境光) … シーン全体を均等に照らす。影ができない代わりに、暗すぎる部分を持ち上げる
  • DirectionalLight(平行光源) … 特定の方向から差す光。陰影がついて立体的に見えるのはこれのおかげ

DirectionalLightはpositionで「どこから照らすか」を決めます。真正面から当てるとのっぺりするので、斜め上から当てると一気にそれっぽくなります。上のコードで(2, 3, 4)にしているのはそのためです。

ライトもscene.add()しないと効かないのは、箱と同じルールです。

他のライトは「必要になってから」でいい

Three.jsには他にもライトがあります。最初から全部覚える必要はなくて、「こういう画が欲しい」と思ったときに引っ張り出せば十分です。

ライトイメージよく使う場面
AmbientLight曇り空の全体光暗い部分の底上げ。ほぼ常用
DirectionalLight太陽主光源。影を落とせる
PointLight裸電球電球やランプなど、点から広がる光
SpotLightスポットライト一部だけ照らす演出。影を落とせる
HemisphereLight空と地面の2色光屋外の自然な色味

ライトは数を増やすほど描画が重くなります。まずは2灯で作って、足りないと感じたときだけ足すのがおすすめです。

影を落とすと、一気に「3Dっぽく」なる

明るい面と暗い面(陰影)はライトを置いた時点でつきますが、地面に落ちる影は別物で、明示的にオンにしないと出ません。しかも設定箇所が3つに分かれているのが厄介なところです。

// ① レンダラーで影を有効化する
renderer.shadowMap.enabled = true;

// ② 影を「落とす側」をオンにする
directional.castShadow = true;
cube.castShadow = true;

// ③ 影を「受ける側」の床を用意する
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(10, 10),
  new THREE.MeshStandardMaterial({ color: 0xdddddd })
);
floor.rotation.x = -Math.PI / 2; // 板を寝かせて床にする
floor.position.y = -1;
floor.receiveShadow = true;
scene.add(floor);

ポイントは、「レンダラー・落とす側・受ける側」の3点セットだということ。どれか1つでも欠けると影は出ません。影が出ないときはこの3つを順に確認すれば、だいたい解決します。

ちなみにAmbientLightは方向を持たないので影を作れません。影を落とせるのは、DirectionalLightやSpotLightのような方向のあるライトだけです。

質感は roughness と metalness で決まる

MeshStandardMaterialには、質感を左右する重要なプロパティが2つあります。

const material = new THREE.MeshStandardMaterial({
  color: 0x4a90d9,
  roughness: 0.3,  // 0=ツルツル / 1=ザラザラ
  metalness: 0.8,  // 0=非金属 / 1=金属
});
  • roughness(粗さ) … 下げるほど鏡のように光を反射する。上げるとマットな粘土っぽさ
  • metalness(金属度) … 上げるほど金属っぽくなる

コツとしては、プラスチックなら metalness: 0、金属なら metalness: 1 に振り切って、あとは roughness で光沢を調整するとイメージ通りになりやすいです。中途半端な0.5同士にすると、だいたい「なんか違う」見た目になります。

色と質感以外の、覚えておくと便利なプロパティ

マテリアルには、種類を問わず使える共通プロパティもあります。よく使うのはこのあたりです。

const material = new THREE.MeshStandardMaterial({
  color: 0x4a90d9,
  transparent: true,       // 透明表現を有効にする
  opacity: 0.6,            // 0=完全に透明 / 1=不透明
  side: THREE.DoubleSide,  // 裏面も描画する
  wireframe: false,        // trueにすると線だけの表示になる
});

とくに引っかかりやすいのが2つあります。

  • opacityを下げただけでは透けないtransparent: trueとセットで指定する必要があります
  • 平面を裏から見ると消える … Three.jsは初期状態で表面しか描かないためです。板状のものを扱うときはside: THREE.DoubleSideを思い出してください

③ テクスチャ:画像を表面に貼る

最後に、色ではなく画像を表面に貼る方法です。public/フォルダに画像を1枚置いた前提で書きます。

const loader = new THREE.TextureLoader();
const texture = loader.load("/brick.jpg");
texture.colorSpace = THREE.SRGBColorSpace; // 色味を正しく出すおまじない

const material = new THREE.MeshStandardMaterial({ map: texture });

ポイントは2つ。

  1. mapに入れるcolorではなくmapプロパティに渡すと、表面に貼られます
  2. colorSpaceを指定する … これを忘れると色が白っぽく浅くなります。見た目に直結するので、色として使う画像には必ず付けてください

テクスチャが伸びる・繰り返したいとき

床や壁のような広い面に画像を1枚だけ貼ると、引き伸ばされて間延びします。そういうときは画像をタイル状に繰り返します

texture.wrapS = THREE.RepeatWrapping; // 横方向に繰り返す
texture.wrapT = THREE.RepeatWrapping; // 縦方向に繰り返す
texture.repeat.set(4, 4);             // 縦横4回ずつ敷き詰める

repeatの数字を上げるほど模様が細かくなります。レンガ・木目・タイルのような継ぎ目のない画像(シームレステクスチャ)と組み合わせるのが定番です。

画像は「色」以外の情報も運べる

ここがテクスチャの面白いところで、map(色)以外にもいろんなスロットに画像を差し込めます。

プロパティ渡す画像効果
map色そのもの表面の見た目
normalMap面の向きの情報形を変えずに凸凹があるように見せる
roughnessMap粗さの分布場所ごとにツヤ/マットを変える
aoMap陰の溜まり方隙間や溝を暗くして立体感を出す
alphaMap透明度の分布白い部分だけ残して切り抜く

とくにnormalMapは費用対効果が大きくて、ポリゴン数を1ミリも増やさずに表面のディテールだけが増えます。1つ試すならこれです。

「質感の情報を、画像で細かく指定していく」──これが3Dの見た目づくりの基本的なやり方だと理解しておくと、この先の学習がぐっとスムーズになります。

近道:環境マップを入れると、いきなりリッチになる

ここまでライトを手で置いてきましたが、実はもっと手軽に見た目を良くする方法があります。それが環境マップです。

現実の金属がキレイに見えるのは、周りの景色が映り込んでいるからですよね。同じことをThree.jsでもやります。

import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";

const pmrem = new THREE.PMREMGenerator(renderer);
scene.environment = pmrem.fromScene(new RoomEnvironment()).texture;

RoomEnvironmentはThree.jsに同梱されている「仮想的な部屋」で、それを映り込みの元として使う指定です。scene.environmentに入れておくと、シーン内のStandardMaterialすべてに自動で効きます

これを足すだけで、のっぺりしていた金属がちゃんと金属に見えます。とくにmetalnessが高いマテリアルほど劇的に変わるので、ぜひ一度入れてみてください。慣れてきたら、HDRI画像(.hdr)をRGBELoaderで読み込んで、実写ベースの映り込みに差し替えられます。

パラメータは「触って覚える」のがいちばん速い

roughnessやmetalnessは、数値の説明を読んでも正直ピンときません。僕もそうでした。なのでその場でスライダーをいじれるようにするのがおすすめです。

npm install lil-gui
import GUI from "lil-gui";

const gui = new GUI();
gui.addColor(material, "color");
gui.add(material, "roughness", 0, 1, 0.01);
gui.add(material, "metalness", 0, 1, 0.01);

これだけで画面の右上にパネルが出て、リアルタイムに質感が変わります。「metalnessを上げると何が起きるのか」を体で覚えられるので、記事を10本読むより早いです。制作中は常に置いておくくらいでちょうどいいと思っています。

よくあるつまずき5つ

この分野でハマりやすいポイントをまとめておきます。うまくいかないときはここに戻ってきてください。

症状原因対処
真っ黒で何も見えないライトがない2灯を追加。切り分けに一度MeshBasicMaterialへ変える
色が白っぽく浅い色空間の指定漏れtexture.colorSpace = THREE.SRGBColorSpace
影が出ない設定漏れレンダラー/落とす側/受ける側の3点を確認
透明にならない指定漏れopacitytransparent: trueとセット
板が裏から見えない片面描画side: THREE.DoubleSide

まとめ

  • 見た目は Geometry(形)+ Material(表面) で決まる
  • 迷ったら MeshStandardMaterial。ただし光がないと真っ黒になる
  • ライトは AmbientLight + DirectionalLight の2灯、斜め上から当てるのが基本
  • 影は レンダラー・落とす側・受ける側の3点セットでようやく出る
  • 質感は roughness / metalness、画像を貼るなら map + colorSpace
  • 手っ取り早くリッチにしたいなら 環境マップ、感覚を掴むなら lil-gui

ここまでで「形を置く・光を当てる・質感を決める」が揃いました。実はこれ、専用ソフトで3Dモデルを作るときとまったく同じ考え方です。だから次のステップに自然につながります。

  • 用意されたマテリアルでは出せない表現をしたくなったら → GLSL入門でシェーダーを自分で書く世界へ
  • 全体のロードマップは → Three.js入門ガイド

📕 もっと手を動かして体系的に学びたい人へ

この記事のような「仕組みを理解しながら小さく動かす」を積み重ねて、作品づくりまで導くKindle本を書きました。
『Three.jsでつくる、小さなWebGL表現 ── はじめての”作品づくり”ガイド』
マテリアルやライティングの調整から、自分の作品としてまとめ上げるところまで順を追って解説しています。

👉 Amazonで見る »


僕もXでThree.jsの作品や知見を発信しているので、よかったら@shun_webdesignを覗いてもらえると嬉しいです。それでは、よいThree.jsライフを🌊

しゅん

しゅん

フロントエンドエンジニア / Webデザイナー。 アメリカ カリフォルニア州生まれ。 音楽、映画、芸術を中心としたサブカルが大好き。 お仕事のご相談は下記リンクのポートフォリオの連絡先からお願いします。

← 記事一覧に戻る