目次/第14章
模様を貼らずに、頂点で塗る
vertex colors & baked occlusion — 頂点カラーと焼き込み遮蔽
- 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); // 実色は頂点カラー
必要な情報はぜんぶ「その粒がどこにあって、どっちを向いているか」から決まります。 だったら、わざわざ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);
本物の影のように光線を飛ばして調べるのではなく、 隣の房を球だと思って、公式で解いています。だから速い。 数万個の頂点それぞれで、近くの房だけを見て計算します。
実験 — 谷の暗さを消してみる
強さを0にすると、粒はそのままなのに絵が平らになります。これが「谷の暗さ」の効果です。
暗くするのに、黒を掛けてはいけない
遮蔽を色に反映するとき、素直に「色 × 明るさ」とやると、谷が黒く濁ります。 実物の谷は黒ではなく、深い緑です。 そこでブロッコリー.js は掛け算ではなく、谷の色へ寄せる書き方をしています。
// 遮蔽は「黒く掛ける」のではなく谷の色へ寄せる const dr = mix(DEEP[0], cr, ao); const dg = mix(DEEP[1], cg, ao); const db = mix(DEEP[2], cb, ao);
白いカリフラワーでは、この差がもっとはっきり出ます。 白い野菜は谷に落ちた光も回り込んでくるので、暗くなりません。 ブロッコリーと同じ遮蔽を掛けると、白い塊に墨を流したようになります。 実験の「白い花蕾にする」を入れて、下限が0.16のままだとどうなるか見てください (16章へ続きます)。
粒をインスタンスにしてはいけません。 同じ形を何万個も並べるとき、09章で出てきた「インスタンス」を使いたくなります。 でもインスタンスは1個につき1色しか持てません。 この野菜の質感は「同じ粒のなかで、外を向いた面は明るく谷を向いた面は暗い」という 頂点単位の濃淡がほぼ全部なので、それが出せなくなります。 1枚のメッシュに焼き込めば濃淡も出るし、描画も1回で済みます。
もうひとつ。頂点カラーを RGBA の4つ組で入れると、Babylon.js は
hasVertexAlpha を立てて透過パスへ回します。
透過パスは深度書き込みが切られるので、粒どうしが互いを正しく隠せなくなります。
不透明なら mesh.hasVertexAlpha = false を明示します。
- 遮蔽の強さを0にする。粒の数は変わらないのに平板に見えることを確かめる。
- 強さを2.2まで上げる。今度は「煤けた」感じになることを見る。
- 「白い花蕾」を入れたまま、下限をいくつまで上げると自然に見えるか探す。
- ブロッコリーの実物を上から見て、谷がどれくらい暗いか観察する。