【ゲーム制作】PBRのラフネスと反射率とは?|Cook-Torranceを実装して球で比較

PBRのラフネスと反射率のサムネイル ゲーム制作

今回は、現代の3Dゲームの標準になっている「PBR(物理ベースレンダリング)」の中でも、特に重要なラフネスと反射率について解説していきます。

「PBRって聞くけど、結局何が『物理ベース』なの?」
「ラフネス、メタリックってパラメータをいじっても違いがピンとこない」
「Cook-Torranceの数式が難しすぎて挫折した…」

こんな悩みはありませんか?

PBRの核心はとてもシンプルで、「表面のザラザラ具合(ラフネス)」と「どれだけ光を跳ね返すか(反射率)」の2つで質感が決まるという考え方です。数式は複雑ですが、パラメータの意味さえ掴めば使えるようになります。

言葉で説明するより見た方が早いので、自作のDirectXプロジェクトでCook-Torranceを実装し、パラメータだけを変えた球を並べて描画してみました。

ラフネスと反射率を変えて並べたPBRの球のグリッド
横方向がラフネス(左=つるつる、右=ざらざら)、縦方向が反射率F0(上=よく反射する、下=あまり反射しない)です。同じ色・同じライトなのに、2つの数値だけで金属っぽさから陶器っぽさまで変わります。

この記事では、PBRの考え方・ラフネスと反射率の意味・Cook-Torranceの3つの項・HLSL実装・ハマりどころを解説します。読み終えると、パラメータをどういじれば狙った質感になるかが分かるようになりますので、ぜひ最後まで読んでください。

PBRの考え方|「それっぽく」から「物理的に正しく」へ

昔のライティング(フォン反射など)は、見た目がそれっぽくなるように数式を作ったものでした。そのため、ライトを変えると質感まで変わってしまい、シーンごとに調整が必要でした。

PBRは実際の光の振る舞いを近似するアプローチです。最大の利点は、一度設定した材質が、どんな光の下でも破綻しないことです。屋外でも室内でも、鉄は鉄らしく、プラスチックはプラスチックらしく見えます。

ラフネス|表面のザラザラ具合

物の表面を拡大すると、細かい凸凹があります。この凸凹の激しさを表すのがラフネス(粗さ)です。

  • ラフネスが低い(つるつる)…凸凹が揃っているので、光が同じ方向へ跳ね返る → ハイライトが小さく鋭い
  • ラフネスが高い(ざらざら)…凸凹がバラバラなので、光が四方に散る → ハイライトが大きくぼやける

上の画像の横方向がまさにこれです。左端は点のようなハイライト、右へ行くほど広がってぼんやりしていきます。鏡とすりガラスの違いと考えると分かりやすいです。

反射率(F0)|どれだけ跳ね返すか

F0は「正面から見たときにどれくらい光を反射するか」を表す値です。金属は高く、非金属は低いという性質があります。

  • 金属…F0が高い(0.5〜1.0程度)。反射が強く、金属光沢が出る
  • 非金属(プラスチック・木・肌など)…F0が低い(0.04前後)。反射は控えめ

画像の縦方向がこれで、上の行ほど反射が強く金属的、下の行ほどマットな質感になっています。よく聞くメタリックというパラメータは、この「金属か非金属か」を0か1で切り替え、F0を自動的に決めてくれる仕組みです。

Cook-Torranceの中身|D・F・Gの3つ

PBRの鏡面反射でよく使われるのがCook-Torranceの式です。難しく見えますが、3つの部品の掛け算と考えると整理できます。

  • D(分布項)…凸凹のうち、光を目に向けて跳ね返す向きのものがどれだけあるか。ラフネスが効くのはここ
  • F(フレネル項)…浅い角度から見るほど反射が強くなる現象。F0が効くのはここ
  • G(幾何減衰項)…凸凹同士が影を作って光を遮る分の補正
// 各種ベクトル
float3 N = normalize(In.Normal.xyz);                        // 法線
float3 V = normalize(CameraPosition.xyz - In.WorldPosition.xyz); // 視線
float3 L = -normalize(Light.Direction.xyz);                 // 光へ向かう向き
float3 H = normalize(V + L);                                // ハーフベクトル

float NdotL = saturate(dot(N, L));
float NdotV = saturate(dot(N, V));

// Cook-Torrance スペキュラ
//   spec = (D × F × G) / (4 × NdotL × NdotV)
float D = Beckmann(N, H, roughness);   // 分布項(ラフネス)
float F = Schlick(L, H, F0);           // フレネル項(反射率)
float G = Geometric(N, H, V, L);       // 幾何減衰項

float denominator = max(4.0f * NdotL * NdotV, 0.0001f);
float specular = max((D * F * G) / denominator, 0.0f);

分母のmax(..., 0.0001f)が地味に重要です。NdotLNdotVが0に近いとゼロ除算で発散するため、下限を設けて防いでいます。

【重要】私がPBRでハマった体験談

自主制作でCook-Torranceを実装したとき、球の輪郭にだけ極端に明るい白い線が出るという現象に悩まされました。真ん中はきれいなのに、縁だけがギラッと光ります。

原因は分母のゼロ除算対策を入れていなかったことでした。球の輪郭では視線と法線がほぼ直角になりNdotVが限りなく0に近づきます。そこで分母が極小になり、スペキュラの値が異常に大きくなっていたんです。max()で下限を入れたら、あっさり消えました。

「輪郭だけおかしい=ゼロ除算を疑う」と覚えた出来事でした。

PBR実装のよくある失敗例と対処法

①ラフネスを0にして真っ白に飛ぶ

ラフネス0は「完全な鏡」を意味し、ハイライトが1点に集中して明るさが発散します。実用上は0.05程度を下限にしておくのが安全です。

②アルベドテクスチャに影を描き込む

PBRでは影や陰影はライティングが計算するものです。テクスチャに影を描き込むと、光を動かしたときに矛盾します。アルベドには素材そのものの色だけを入れましょう。

③トーンマッピングを入れていない

PBRの計算結果は1.0を超えることが普通にあります。そのまま出すと白飛びするので、最後にトーンマッピングで表示範囲へ圧縮する工程が必要です。詳しくはPNGとHDRの記事もあわせてどうぞ。

注意点

  • ラフネスは0にしない(下限を設ける)
  • 分母のゼロ除算対策(max())を必ず入れる
  • アルベドに影を焼き込まない

まとめ

  • PBRはラフネスと反射率の2つで質感が決まる
  • ラフネス=ハイライトの広がり(つるつる↔ざらざら)
  • F0=反射の強さ。金属は高く、非金属は低い
  • Cook-TorranceはD・F・Gの掛け算と捉えると理解しやすい

数式を完全に理解していなくても、パラメータの意味さえ分かれば狙った質感は作れます。まずは今回のように値を変えた球を並べて自分の目で確かめるところから始めるのがおすすめです。

関連記事