やさいのつくりかた.

目次/第14章

14VertexColor

模様を貼らずに、頂点で塗る

vertex colors & baked occlusion — 頂点カラーと焼き込み遮蔽

この章の3行まとめ
  • 06章では模様をテクスチャに描きました。でもブロッコリーにはUVが張れません。
  • 枝分かれした構造に、破綻のない展開図は作れないからです。
  • 代わりに頂点1個ずつに色を持たせ、さらに「谷の暗さ」をそこへ焼き込みます。

06章では、模様をテクスチャ(2Dの絵)に描いて立体に貼りました。 第I部で扱った野菜は、それで足りました。どれも1個のかたまりで、 地球儀のように「経度・緯度」を割り当てられたからです。

ブロッコリーではこれができません。枝が分かれて、その先でまた分かれた構造を、 1枚の紙に破れ目なく広げることはできないからです。 そして困ったことに、ブロッコリー.js はテクスチャを1枚も使っていません (ざるの網代だけは別です)。

では色はどこから来るのか。頂点そのものが色を持っています。 3Dモデルは座標・三角形の番号表・法線・UVでできている、と05章で書きました。 そこにもう1本、colors という配列を足すだけです。 三角形の内側は、3つの頂点の色を混ぜて塗られます。

vd.positions = ...;
vd.normals   = ...;
vd.colors    = colors;     // 頂点1個につき RGBA の4つ
vd.indices   = ...;
vd.applyToMesh(mesh, false);

curd.albedoColor = new BABYLON.Color3(1, 1, 1);   // 実色は頂点カラー
ブロッコリー.js。マテリアルの色は白のまま、頂点の色が掛かる

必要な情報はぜんぶ「その粒がどこにあって、どっちを向いているか」から決まります。 だったら、わざわざ2Dの絵を経由するより、頂点に直接書いたほうが正確で速い。 これがブロッコリー.js の判断です。

質感を作っているのは、粒ではなく「谷の暗さ」

ブロッコリーの蕾の粒を2〜4万個ちゃんと並べても、それだけでは 粒があるのに平板に見えます。足りないのは影です。 粒と粒のあいだの谷には、まわりの光がほとんど届きません。この暗さが質感の正体です。

ところが、この谷は1〜2mmしかありません。11章に出てきた SSAO は画面上での処理なので、 この細かさは解像できません。そこでブロッコリー.js は、 頂点を作るときに、その場で暗さを計算して色に焼き込みます。

// 隣の房が「空をどれだけ覆っているか」を立体角で足す
for (const d of list) {
    const dist = /* 粒から隣の房の中心までの距離 */;
    const nd = /* 粒の向きと、その方向の内積。裏側は数えない */;
    if (nd <= 0) continue;
    const rr = d.R / Math.max(dist, d.R);
    // 半径 R の球が距離 dist から張る立体角の、半球に対する比
    occ += 0.5 * (1 - Math.sqrt(1 - rr * rr)) * nd;
}
return clamp(1 - occ * strength, floor, 1);
ブロッコリー.js の sphereAO()。房を球とみなして解析的に解いている

本物の影のように光線を飛ばして調べるのではなく、 隣の房を球だと思って、公式で解いています。だから速い。 数万個の頂点それぞれで、近くの房だけを見て計算します。

実験 — 谷の暗さを消してみる

強さを0にすると、粒はそのままなのに絵が平らになります。これが「谷の暗さ」の効果です。

暗くするのに、黒を掛けてはいけない

遮蔽を色に反映するとき、素直に「色 × 明るさ」とやると、谷が黒く濁ります。 実物の谷は黒ではなく、深い緑です。 そこでブロッコリー.js は掛け算ではなく、谷の色へ寄せる書き方をしています。

// 遮蔽は「黒く掛ける」のではなく谷の色へ寄せる
const dr = mix(DEEP[0], cr, ao);
const dg = mix(DEEP[1], cg, ao);
const db = mix(DEEP[2], cb, ao);
ao が 1 なら元の色、0 なら谷の色。あいだは混ぜる

白いカリフラワーでは、この差がもっとはっきり出ます。 白い野菜は谷に落ちた光も回り込んでくるので、暗くなりません。 ブロッコリーと同じ遮蔽を掛けると、白い塊に墨を流したようになります。 実験の「白い花蕾にする」を入れて、下限が0.16のままだとどうなるか見てください (16章へ続きます)。

つまずきポイント

粒をインスタンスにしてはいけません。 同じ形を何万個も並べるとき、09章で出てきた「インスタンス」を使いたくなります。 でもインスタンスは1個につき1色しか持てません。 この野菜の質感は「同じ粒のなかで、外を向いた面は明るく谷を向いた面は暗い」という 頂点単位の濃淡がほぼ全部なので、それが出せなくなります。 1枚のメッシュに焼き込めば濃淡も出るし、描画も1回で済みます。

もうひとつ。頂点カラーを RGBA の4つ組で入れると、Babylon.js は hasVertexAlpha を立てて透過パスへ回します。 透過パスは深度書き込みが切られるので、粒どうしが互いを正しく隠せなくなります。 不透明なら mesh.hasVertexAlpha = false を明示します。

やってみよう
  1. 遮蔽の強さを0にする。粒の数は変わらないのに平板に見えることを確かめる。
  2. 強さを2.2まで上げる。今度は「煤けた」感じになることを見る。
  3. 「白い花蕾」を入れたまま、下限をいくつまで上げると自然に見えるか探す。
  4. ブロッコリーの実物を上から見て、谷がどれくらい暗いか観察する。