たべもののつくりかた.

目次/第15章

15Grains

粒を1400個置いて、谷を暗くする

baked ambient occlusion — 擬似AOの焼き込み

この章の3行まとめ
  • 茶碗一杯のごはんは、約1400粒を1粒ずつ置いて作っています。
  • 説得力を決めるのは粒の形ではなく、粒と粒のあいだの暗がりです。
  • その暗がりは SSAO では出ません。配置するときに計算して焼き込みます。

ごはんを描くとき、いちばん難しいのは1粒の形ではありません。 1粒だけを取り出すと、どのやり方でもそれなりに見えます。 難しいのは集まったときで、粒間の暗がりが無いと、 ごはんは「発泡スチロールの塊」になります。

ふつう、こういう陰りは SSAO(画面空間アンビエントオクルージョン)という 後処理で付けます。ところが、ご飯.js はそれを使いません。理由がコメントに書いてあります。

「SSAO が拾えるのは画面空間で隣接している面の陰りだけで、 ごはんの谷は深さ数ミリ・幅数ミリの密集した溝なので、 半径を合わせると今度は粒の輪郭が全部汚れる」

そこで別の手を使います。粒を配置するときに、 その粒が近傍の最高点からどれだけ沈んでいるかを計算し、 その値をインスタンスの色として書き込む。沈んでいる粒ほど暗い色を持たせるわけです。 描画時のコストはゼロで、しかも粒の形に沿って正確に暗くなります。

// 9. Grains : 配置と「粒間の暗がり」の焼き込み
const hi = neighborMaxHeight(x, z);        // 近傍でいちばん高い粒の頭
const sink = clamp((hi - y) / depth, 0, 1); // どれだけ沈んでいるか
const k = mix(1.0, aoFloor, sink);          // aoFloor = いちばん深い谷の明るさ
colors.push(k, k, k, 1);                    // インスタンスカラーへ
ご飯.js。GUI の「粒間の暗がり」スライダーは aoFloor を動かしている

実験 — 粒間の暗がりを、切る / 入れる

—

谷の明るさを 1.0 にすると暗がりが消えます。粒の形も数も同じなのに、 急にごはんに見えなくなるのが分かります。

近傍の「最高点」を使う理由

沈み具合を測るとき、近傍の平均の高さと比べるとうまくいきません。 ご飯.js のコメントに「最高点と同じ高さになり、暗がりがまったく出ない」という 注意が残っています。平均と比べると、山の粒も谷の粒も同じくらいの差になってしまうからです。 比べる相手はいちばん高い粒でなければいけません。

握ると、沈み込みが浅くなる

お寿司.js は、ご飯.js の米粒・マテリアル・暗がりの焼き込みをそのまま流用しています。 変わるのは盛りの形(茶碗の山 → 俵)だけ……ではありません。 酢飯は握って締めるので、粒の沈み込みが浅くなります。 同じ仕組みのまま、パラメータ1つで「ふんわり」と「締まっている」を作り分けています。 この考え方は17章につながります。

1400 という数

茶碗一杯は実際には数千粒あります。1400 は、見た目が破綻しない下限として選ばれた数です。 表面から見えない粒は置かないので、実際には「表面から2〜3層ぶん」だけ配置しています。 中身は盛りの形(Mound)という1枚の曲面で塞いであり、粒はその上に生えています。

つまずきポイント

粒をランダムに置くだけでは、ごはんになりません。 向きが完全にばらばらだと「もみ殻の山」に見えます。 実物は表面に沿って寝ている粒が多い。盛りの曲面の法線を使って、 粒の向きをある程度そちらへ倒しています。

インスタンスカラーは、ライティングの前に掛かります。 焼き込んだ暗さは「その粒に届く光の量」を表しているので、 アルベドに掛けるのが正しい。あとから画面全体を暗くするのとは意味が違います。

やってみよう
  1. 谷の明るさを 1.0 にして、ごはんが何に見えるか判定する。
  2. 粒の数を 60 まで減らして、どこから「ごはん」でなくなるか探す。
  3. 暗がりの届く深さを 1.5 にして、暗すぎるとどうなるか見る。
  4. ご飯.js の GUI で「粒間の暗がり」を 0 にした画面と見比べる。