実践
前編 three.jsの物理ベースレンダリング

2016年2月のthree.js のアップデートに含まれていた「物理ベースレンダリング」について、これまでのマテリアルとの違いや、表示結果、利用方法などを紹介します。

2016年02月25日発行

目次

three.js r74のアップデートが2016年2月にありました。このアップデートには「物理ベースレンダリング」が含まれています。今回は、three.jsにおける物理ベースレンダリングについて、これまでのマテリアルとの違いや、表示結果、利用方法などを紹介します。

このシリーズで紹介するサンプルは次のリポジトリにまとめてあります。併せて、参照してください。

three.js使いこなし

なお、本記事はthree.jsに少し触れたことがある人に向けた内容です。three.jsを含むWebGLの基本については、次のシリーズで解説していますので、併せて参照してください。

物理ベースレンダリングとは

three.jsのマテリアル(表面材質)は、これまで長くに渡りMeshPhongMaterial(フォン・シェーディング)とMeshLambertMaterial(ランバート・シェーディング)が中心となって活躍してきました。この2つのマテリアルは光の当たり方や影を表現できるものの、あくまでも「それっぽく見せる」ことができる技術であり、見栄えの向上のためには経験による数値の微調整や、作り込まれたディフューズテクスチャー(色テクスチャー)に頼るなどの必要がありました。

影などが書き込まれたディフューズテクスチャー
(ソースコード:/grapple-girl.html

このデモの色として適用したディフューズテクスチャーは次の図のようになります。シェーダーは影をもちろん表現できますが、それでは表現しきれない部分に、あらかじめテクスチャー側で色を塗っておくわけです。

このデモの色として適用したディフューズテクスチャー

たとえば、服のしわや角、物体同士が近い場所などは影が付きやすい性質があります。これをオクルージョンといいます。そこにあらかじめ色を塗っておいたり、光が当たっていそうな部分は明るくしておいたりして、レンダリングしたときに雰囲気が出るようにしています。本来、影を付けるためのシェーダーの仕事ですが、人間が頑張って色を塗っておいているのです。

一方で、リアルタイムレンダリングの手法のひとつとして、物理ベースレンダリング(Physically Based Shading)が注目を集めています。2010年頃からディズニー映画(リンク先のPDFは7MB近くあるのでご注意ください)などで利用され、最近では3Dゲーム開発でも採用されるようにもなっています。

物理ベースレンダリングとは、光の反射や散乱など現実の物理現象を再現するレンダリング手法です。three.jsに物理ベースレンダリングが登場したことにより、つるつるであっても、ざらざらであっても、入力値を変更するだけでその違いを再現することができ、かつ、フォンシェーディングやランバートシェーディングよりも現実味のある表現が可能になりました。物理ベースレンダリングが適用されると、同一の指定であってもライティング環境が異なるシーンでは、指定に応じた適切なレンダリング結果を得ることができます。

three.jsの物理ベースレンダリングでのラフネス(粗さ)の値による変化
上が金属、下が非金属。変化を見るために、11個の玉をループで自動で作る際に少しずつラフネスの値を上げている。一番左側の玉が0で、そのとなりは0.1、0.2……と0.1ずつ上げており、一番右側の玉は1に設定した。数値を変えただけで、さまざまな材質の表現が可能になることがわかる。(ソースコード:demo1.html

「物理」と付くため難しく聞こえるかもしれませんが、用意された機能を利用するだけなら、とても簡単です!

一方で、物理ベースレンダリングでは、従来のレンダリング方式のように、影などが描き込まれたディフューズテクスチャーは正確な光の計算の邪魔になってしまいます。そのため、物理ベースレンダリングで色を表現するためには純粋に「色のみ」のディフューズテクスチャーを用意し、アルベド(Albedo、反射する色)の値として利用することになります。

three.jsを含むいくつかの物理ベースレンダリングではディフューズ(カラー)入力がアルベド(反射する色)として利用されます。これにより結果的にディフューズの色が物体の色のベースとなります。加えて、どれくらい鋭く(鈍く)光を反射するのかの値、金属であるのか否かなどによって物体の表示結果が決まります。

アルベド
白い光のもとで赤く見える物体の例。アルベドは入射光に対して反射する光の割合であり、他のレンダリング手法におけるディフューズ(カラー)とは少し異なる。

three.jsの物理ベースレンダリング

物理ベースレンダリングは、UnityUnreal Engineでもすでに利用できましたが、three.jsでもr74からMeshStandardMaterialという名前で、ついに物理ベースのリアルタイムレンダリングが利用できるようになりました(r74で初めて追加されたばかりの機能であるため、今後のthree.jsのアップデートでコンストラクター名や利用できるプロパティ名が変更される可能性があります)。

three.jsの物理ベースレンダリングはInital PR for MeshStandardMaterial #7381というプルリクエストや#5847などのissueで論議を重ね、できるだけ多くのthree.jsユーザーが扱いやすく感じる名前としてMeshPhysicalMaterialではなく、MeshStandardMaterialが採用されました。また難しくなり過ぎないように、必要最低限の物理ベースレンダリングに関するプロパティを追加して実装されました。

MeshStandardMaterialで利用する主なプロパティは、次の通りです。

プロパティ名 効果
color既存アルベドとして利用される色
map既存アルベドとして利用されるディフューズテクスチャー
roughness新設表面の粗さ。ざらざらしていれば1に近づく
metalness新設金属(伝導体)/非金属(非伝導体)(多くの場合は0または1のどちらか)
envMap既存物体への映り込みに使われるキューブテクスチャー

MeshStandardMaterialを利用するための基本的なコードの書き方を見てみましょう。図のような映り込みがあるような金属を例とします。

MeshStandardMaterialを利用した例
(ソースコード:demo2.html
// いつものようにシーン、カメラなどを用意していきます。
var width  = window.innerWidth,
    height = window.innerHeight;
 
var scene = new THREE.Scene();
 
var camera = new THREE.PerspectiveCamera( 40, width / height, 0.1, 100 );
camera.position.set( 0, 0, 3 );
 
// 通常、私達が使う画面では、色がsRGBに補正されています。
// 物理ベースレンダリングでは正確な色の計算を行うために
// リニア色空間での処理する必要があります。
// そのため、テクスチャーなど外からはいってくるRGBに対して
// 色の調整が必要になります。
// rendererの`gammaInput`、`gammaOutput`を
// `true`にすることでthree.jsが自動で色空間の調整を行ってくれます。
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width, height );
renderer.setClearColor( 0xcccccc );
renderer.gammaInput = true;
renderer.gammaOutput = true;
document.body.appendChild( renderer.domElement );
 
scene.add( new THREE.HemisphereLight( 0x443333, 0x222233, 4 ) );
 
// 物理ベースレンダリングでは
// すべての物体は「光を反射」をします。
// 反射の材料となるキューブマップを用意しておきます。
var cubeTextureLoader = new THREE.CubeTextureLoader()
var textureCube = cubeTextureLoader.load( [
  './LancellottiChapel/posx.jpg',
  './LancellottiChapel/negx.jpg',
  './LancellottiChapel/posy.jpg',
  './LancellottiChapel/negy.jpg',
  './LancellottiChapel/posz.jpg',
  './LancellottiChapel/negz.jpg'
] );
 
// - アルベド(`color`)
// - ラフネス
// - メタリック
// - 環境マップ
// をオプションとして、マテリアルのインスタンスを用意します。
var material = new THREE.MeshStandardMaterial( {
 
    color: 0xFFC355,
    roughness: 0,
    metalness: 1,
    envMap: textureCube
 
} );
 
// これまでのthree.jsと同じように
// meshにmaterialを適用し、
// シーンに追加します
var sphere = new THREE.Mesh(
  new THREE.SphereGeometry( 0.4, 16, 16 ),
  material
);
scene.add( sphere );
 
// レンダリングします
( function renderLoop () {
 
  requestAnimationFrame( renderLoop );
  renderer.render( scene, camera );
 
} )();

どうでしょうか? three.jsをこれまで使ったことがあるのであれば、難しくはないでしょう。

この他にもMeshStandardMaterialには多数のプロパティがサポートされており、nomalMap(法線マップ)やaoMap(アンビエントオクルージョン)などを合わせて利用することによりさらに質感が向上します。

物理ベースレンダリングを利用する際には、現実世界での計測値が公開されているので参考になります。なお、roughnessはツールによって値の名称がsmoothnessやglossinessになることもあります。その場合、値の扱いも異なることがあるので、計測値の資料を参考にする際には適宜当てはめてみてください。

各マテリアルの計測値サンプル一例

Materialアルベド
new THREE.Color( 0.560, 0.570, 0.580 )
new THREE.Color( 0.972, 0.960, 0.915 )
アルミnew THREE.Color( 0.913, 0.921, 0.925 )
new THREE.Color( 1.000, 0.766, 0.336 )
new THREE.Color( 0.955, 0.637, 0.538 )
クロミウムnew THREE.Color( 0.550, 0.556, 0.554 )
ニッケルnew THREE.Color( 0.660, 0.609, 0.526 )
チタンnew THREE.Color( 0.542, 0.497, 0.449 )
コバルトnew THREE.Color( 0.662, 0.655, 0.634 )
プラチナnew THREE.Color( 0.672, 0.637, 0.585 )

※測定値サンプルはPhysically Based Materials より

上記サンプル一例の表示結果
左から、鉄、銀、アルミ、金、銅、クロミウム、ニッケル、チタン、コバルト、プラチナ(ソースコード:demo3.html

入力にテクスチャーを使う

MeshStandardMaterialroughnessmetalnessは、メッシュ全体に一様に適用される数値の代わりにグレースケールのテクスチャー画像による入力も受け付けています。その場合、roughnessMapmetalnessMapのプロパティを利用します。

テクスチャー画像による入力
(ソースコード:sword.html
モデルで利用されているテクスチャー各種
左上:アルベド、右上:ラフネス、右下:メタルネス、左下:ノーマル

画像を一度ロードしてテクスチャーとしてキャッシュさせれば、「テクスチャーの色」に応じて、複雑なメッシュでも、部分ごとにさまざまな質感を適用することができます。これにより、「ざらざら」や「つるつる」が混ざり合った質感のオブジェクトであっても、1回のドローコールで表示することができます。

3Dモデリング時にテクスチャーを作り込む必要がありますが、パフォーマンス向上が必要な際には、ぜひ検討したいポイントとなるでしょう。

MeshStandardMaterialでサポートされているプロパティ

現時点でのMeshStandardMaterialでサポートされているプロパティをまとめると以下の通りです。

プロパティ名値の型
color<hex>
roughness<float>
metalness<float>
opacity<float>
mapnew THREE.Texture( <Image> )
roughnessMapnew THREE.Texture( <Image> )
metalnessMapnew THREE.Texture( <Image> )
alphaMapnew THREE.Texture( <Image> )
lightMapnew THREE.Texture( <Image> )
lightMapIntensity<float>
aoMapnew THREE.Texture( <Image> )
aoMapIntensity<float>
emissive<hex>
emissiveIntensity<float>
emissiveMapnew THREE.Texture( <Image> )
bumpMapnew THREE.Texture( <Image> )
bumpScale<float>
normalMapnew THREE.Texture( <Image> )
normalScale<Vector2>
displacementMapnew THREE.Texture( <Image> )
displacementScale<float>
displacementBias<float>
envMapnew THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] )
envMapIntensity<float>
refractionRatio<float>
shadingTHREE.SmoothShading
blendingTHREE.NormalBlending
depthTest<boolean>
depthWrite<boolean>
wireframe<boolean>
wireframeLinewidth<float>
vertexColorsTHREE.NoColors / THREE.VertexColors / THREE.FaceColors
skinning<boolean>
morphTargets<boolean>
morphNormals<boolean>
fog<boolean>
sideTHREE.FrontSide / THREE.BackSide / THREE.DoubleSide
transparent<boolean>
alphaTest<float>
visible<boolean>

まとめ

three.js r74から登場したMeshStandardMaterialによりマテリアルの選択肢が増え、よりリアルな表現を手軽に利用することができるようになりました。

とはいえ、MeshStandardMaterialにはフレネルやクリアコートなど、本格的な物理ベースレンダリングには少し足りない機能があります。

現実世界におけるフレネルの例
床に注目すると、視点が垂直に近いほど反射されているのがわかる。
現実世界のクリアコートの例
車の表面はコーティングの層と塗装の層の性質の異なるレイヤーがあるため、2つの反射が組み合わさっている。

しかし、今後、より本格的な物理ベースレンダリングが利用できるMeshPhysicalMaterialが登場する可能性もあります。そのときに備える意味でも、MeshStandardMaterialに触れ、物理ベースレンダリングの基本を体感してみてはいかがでしょうか。

小山田 晃浩
小山田 晃浩
フロントエンド・エンジニア

2006年よりWeb制作会社にてUI実装や運用業務を経験した後、2010年よりフロントエンド・エンジニアとして株式会社ピクセルグリッドに入社。これまでの経験の大半は大規模Webサイトの壊れにくいHTML/CSS設計、及び実装。また、SVG, Canvas, WebGLの扱いも得意としている。 外部に向けたアウトプットも積極的に行っており、カンファレンスでの講演などを多数こなしている。Tokyo WebGL Meetupの主催者。2011年から2015年まで5連続でMicrosoft MVP for IEを受賞。 著書に『Webデザイナー/コーダーのための HTML5コーディング入門』(共著:エクスナレッジ、2011年3月12日)や『CSS3デザイン プロフェッショナルガイド』(共著:毎日コミュニケーションズ、2011年5月28日)』などがある。