画像ベースライティング(IBL)
屋外や室内で物体を「その場に置いたように」馴染ませられます。環境マップから拡散と鏡面の照明を事前計算し、実行時は数枚のテクスチャ参照だけでPBRを回す仕組みを原理から掴めます。
- 1.IBLは全方向の入射光を環境マップ(多くはHDRキューブマップ)として記録し、これを面光源とみなしてレンダリング方程式の半球積分を照明に使う手法。
- 2.実行時に半球を積分する余裕はないため、拡散はirradiance mapへ、鏡面はroughnessごとのprefiltered環境マップへ事前畳み込みし、フレネル依存はBRDF LUTに分離する(split-sum近似)。
- 3.最終的な鏡面は prefiltered(R, roughness) と BRDF_LUT(N·V, roughness) の積で復元でき、実行時コストはミップ選択付きの2〜3回のテクスチャ参照に収まる。
IBLは「環境そのものを光源にする」照明
点光源や平行光源は、光が来る方向を1本のベクトルに単純化したモデルです。しかし現実の物体は、空・地面・周囲の壁など全方向から届く光に照らされています。この全方向の入射光をあらかじめ画像として記録し、それを巨大な面光源として扱うのが 画像ベースライティング(IBL, Image-Based Lighting) です。
土台は /graphics/path-tracing-global-illumination/ で扱うレンダリング方程式です。ある表面点から視線方向 V へ出る光は、半球上のすべての入射方向 L について BRDF × 入射光(L) × (N·L) を積分した値でした。IBL では「入射光(L)」を環境マップの該当方向の画素値で与え、この半球積分を照明として解きます。BRDF 自体は /graphics/shading-models-phong-pbr/ の Cook-Torrance をそのまま使うため、IBL は PBR の光源側を差し替えた拡張だと捉えると全体像が掴めます。
IBL が解きたい積分(点 p, 視線 V):
Lo(p, V) = ∫_Ω f(L, V) × Lenv(L) × (N·L) dω(L)
Ω : 表面法線まわりの半球
f(L, V) : BRDF(拡散 + 鏡面)
Lenv(L) : 方向 L から届く環境光(環境マップの画素)
N·L : ランバートの余弦項
環境マップの表現 ── 全方向をどう1枚に畳むか
全方向(立体角 4π)の光を格納するには、球面を平面画像へ写す投影が要ります。実務で主流は次の2つです。
| 表現 | 格納形式 | 特徴 | 主な用途 |
|---|---|---|---|
| 正距円筒図法(equirectangular) | 経度×緯度の2:1画像(緯度経度マップ) | 1枚で扱え編集・配布が容易。極付近が引き伸ばされサンプリング密度が不均一 | HDRIの配布・オーサリング入力 |
| キューブマップ(cubemap) | 立方体の6面テクスチャ | ハードウェアが方向ベクトルから面とUVを直接引ける。歪みが少なくGPU効率が高い | 実行時のサンプリング・事前畳み込みの中間表現 |
キューブマップは方向ベクトルを正規化し、絶対値が最大の成分で面(+X〜-Z の6面)を選び、残り2成分を割ってその面の UV を得ます。GPU にはこの機能が組み込まれており、方向ベクトルさえ渡せば1回のサンプルで対応する画素が返ります。オーサリングは equirectangular で行い、初期化時にキューブマップへ変換して以降の処理はキューブマップで進めるのが定石です。
空の太陽や窓など、環境光は輝度が 1.0 をはるかに超える箇所を含みます。8ビット sRGB で格納すると、この高輝度がクリップされ鏡面ハイライトの強さと色が失われます。IBL の環境マップは半精度以上の浮動小数点(fp16)でリニアに保持するのが前提です。ガンマ/リニアの取り違えは陰影を根こそぎ壊すため、境界での符号化規律は /graphics/color-spaces-gamma/ を必ず参照してください。
なぜ事前計算が要るのか ── 実行時に半球は積分できない
上の積分は、1画素ごとに半球上の何百〜何千方向をサンプルしないと解けません。これをリアルタイムに毎フレーム行うのは非現実的です。鍵は、積分の一部が視点・法線に依存せず事前に計算できる点にあります。
BRDF を拡散と鏡面に分け、それぞれで「環境マップと BRDF の畳み込み」を先に済ませておきます。畳み込みの結果は方向(と粗さ)だけの関数になるので、テクスチャに焼き込めます。実行時はそのテクスチャを引くだけで積分の答えが得られる、という発想です。
拡散IBL ── irradiance map
拡散反射(ランバート)の BRDF は方向によらず一定 albedo/π です。定数は積分の外へ出せるため、残るのは「その法線 N の半球にわたって環境光を余弦で重み付け積分した量」だけになります。これを irradiance(放射照度) と呼びます。
拡散IBLの irradiance(法線 N のみに依存):
E(N) = ∫_Ω Lenv(L) × (N·L) dω(L)
拡散の出射:
Lo_diffuse = (albedo / π) × E(N)
E(N) は視点 V にも粗さにも依存せず、法線方向だけの関数です。したがって全方向 N についてこの積分を事前計算し、低解像度のキューブマップ(irradiance map)に格納できます。余弦重みの半球積分は非常になめらか(低周波)な結果になるため、解像度は 16×16 や 32×32 程度の各面で十分で、実行時は法線でこのマップを1回引くだけです。
鏡面IBLの難所とsplit-sum近似
鏡面はやっかいです。GGX の鏡面 BRDF は 視線 V・法線 N・粗さ roughness の3つに依存し、フレネルで反射率も角度で変わるため、拡散のように定数を括り出せません。全パラメータの組み合わせを事前計算すると次元が高すぎてテーブルに載りません。
そこで Epic Games(Unreal Engine 4)が示した split-sum 近似 が標準になりました。鏡面積分を、独立に事前計算できる2つの積へ分解します。
鏡面積分 ≈ (prefiltered 環境項) × (BRDF スケール・バイアス項)
Lo_specular ≈ Lprefiltered(R, roughness)
× ( F0 × scale + bias )
R : 反射ベクトル(≒ 主反射方向)
Lprefiltered : 環境マップを roughness で事前畳み込みした値
scale, bias : (N·V, roughness) から引く BRDF 積分の係数
F0 : 素材の基準反射率(フレネルの基点)
第1項が prefiltered 環境マップ、第2項が BRDF LUT です。両者とも実行時入力に依存しない形で事前計算でき、実行時は掛け合わせるだけになります。
本来の積分は「環境光の重み付き平均」と「BRDF の積分」が絡み合っており、厳密には分離できません。split-sum は両者の積の平均を、それぞれの平均の積で近似します。この近似は roughness が中庸のとき最も正確で、視線と反射方向が大きくずれる斜め視の異方的なハイライトは表現しきれません(反射方向 R を主方向として1つに固定するため)。実写に迫る品質と、リアルタイムに載る計算量とを釣り合わせた実務上の妥協点です。
prefiltered環境マップ ── roughnessをミップに焼く
prefiltered 環境マップは、環境マップを 粗さごとに GGX 分布で畳み込んだ キューブマップ群です。粗い面ほど広い立体角の光を平均してぼやけたハイライトになるので、畳み込みのカーネルも広くなります。この「粗さ=ぼけ幅」を、キューブマップのミップレベルに対応づけます。
- ミップ 0(最大解像度・フル解像)= roughness 0(鏡のような鋭い反射)
- ミップが上がる(低解像度)ほど roughness 大(広く淡い反射)
ミップ構造とトライリニア補間の考え方は /graphics/texture-mapping-filtering/ と同じで、そこに「ミップ=粗さ」という意味づけを与えたものです。実行時は反射ベクトル R で引き、roughness からミップレベルを選ぶだけで、その粗さに応じた事前畳み込み済みの環境色が返ります。
各ミップの生成(GGX 重要度サンプリング):
for each mip level m:
roughness = m / (mipCount - 1)
for each texel dir R in this mip:
sum = 0 ; weight = 0
for each sample i in 1..N: // 例 N = 1024
H = importanceSampleGGX(i, roughness, R) // 分布に沿って H を生成
L = reflect(-R, H) // R を主方向と仮定
w = max(N·L, 0) // N=V=R と近似
sum += Lenv(L) × w
weight += w
prefiltered[m][dir] = sum / weight
GGX 重要度サンプリングを使うのが要点です。粗さに応じた分布の形どおりに H(ハーフベクトル)を生成し、寄与の大きい方向を優先して標本を集めるため、一様サンプリングより少ない標本数でノイズの少ない結果が得られます。この考え方は /graphics/path-tracing-global-illumination/ の重要度サンプリングと同一原理です。
BRDF LUT ── フレネルと幾何遮蔽を2Dテーブルに
split-sum の第2項は、環境光を含まない BRDF そのものの積分 です。Schlick フレネルを F = F0 + (1 − F0)(1 − cosθ)^5 と展開すると、F0 を外に括り出せて、積分は scale(F0 に掛かる係数)と bias(加算項)の2つのスカラーに落ちます。そしてこの2値は N·V と roughness の2変数だけの関数になります。
BRDF LUT(素材に依存しない普遍テーブル):
入力: x軸 = N·V(0〜1), y軸 = roughness(0〜1)
出力: R成分 = scale, G成分 = bias
実行時の鏡面反射率:
specularColor = F0 × scale + bias
N·V と roughness だけの関数なので、素材にも環境にも依存しない普遍的なテーブルです。一度 256×256 程度の2Dテクスチャに焼けば、あらゆるシーン・あらゆる素材で使い回せます(アプリ同梱の固定リソースにできる)。中身は GGX の幾何遮蔽項とフレネルの角度依存を積分した係数で、モンテカルロで一度だけ生成します。
実行時の統合 ── PBRシェーダへの組み込み
事前計算(irradiance map・prefiltered 環境マップ・BRDF LUT)が揃えば、実行時のフラグメントシェーダは軽量です。拡散と鏡面をそれぞれ事前計算テクスチャから引き、フレネルで取り合わせて足すだけです。
IBL 統合(フラグメントシェーダ, 概略):
N = 法線 ; V = 視線 ; R = reflect(-V, N)
NdotV = max(dot(N, V), 0)
// 鏡面フレネル(粗さを考慮した ambient 版)
F = fresnelSchlickRoughness(NdotV, F0, roughness)
kd = (1 - F) × (1 - metallic) // 拡散に回せる割合
// 拡散 IBL:irradiance map を法線で参照
irradiance = sampleCube(irradianceMap, N)
diffuse = irradiance × albedo
// 鏡面 IBL:split-sum
mip = roughness × (maxMip)
prefiltered = sampleCubeLod(prefilteredMap, R, mip)
brdf = sample2D(brdfLUT, vec2(NdotV, roughness)) // .r=scale .g=bias
specular = prefiltered × (F0 × brdf.r + brdf.g)
ambient = kd × diffuse + specular // ← IBL による環境光
color = ambient + directLighting // 点光源等はここに加算
(1 − F) で拡散へ回す割合を絞り、金属では拡散をゼロにするのは /graphics/shading-models-phong-pbr/ の直接光と同じエネルギー保存の作法です。IBL の出力は「環境からの ambient 項」であり、点光源・平行光源の寄与とは加算で合成します。これで屋外・室内どちらの環境マップでも、素材の metallic/roughness を変えずに一貫した見えが得られます。
「なぜ irradiance map は小さくてよいのに prefiltered 環境マップはミップが要るのか」を説明できるかが分かれ目です。拡散は余弦重みで全半球をならすため結果が超低周波(ゆるやか)になり低解像度で足りる一方、鏡面は roughness が小さいほど高周波(鋭い)成分を保つ必要があり、粗さの各段階をミップに分けて持つ必要があります。もう一つの定番は「BRDF LUT が素材非依存な理由」で、Schlick 展開で F0 を括り出した結果、残る積分が N·V と roughness だけの関数になるから、と答えられること。
まとめ
- IBL は全方向の入射光を HDR 環境マップ(オーサリングは equirectangular、実行時はキューブマップ)として記録し、これを面光源にレンダリング方程式の半球積分を照明として解く。
- 実行時に半球は積分できないため、拡散は irradiance map、鏡面は roughness ごとの prefiltered 環境マップへ事前畳み込みし、フレネル依存は BRDF LUT に分離する(split-sum 近似)。
- prefiltered 環境マップは ミップレベルに粗さを対応づけ、GGX 重要度サンプリングで生成する。BRDF LUT は
N·Vとroughnessだけの素材非依存の普遍テーブル。 - 実行時は数回のテクスチャ参照で
diffuse × albedoとprefiltered ×(F0×scale + bias)を求め、(1 − F)で取り合わせて足し、直接光と加算する。PBR の光源側を差し替えた拡張として一貫する。
グラフィックス Article
画像ベースライティング(IBL)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
IBL
比較で見る軸
難易度: advanced / カテゴリ: グラフィックス / タグ数: 6
導入後に効く点
実行時に半球を積分する余裕はないため、拡散はirradiance mapへ、鏡面はroughnessごとのprefiltered環境マップへ事前畳み込みし、フレネル依存はBRDF LUTに分離する(split-sum近似)。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- グラフィックス
- タグ数
- 6
判断チェックリスト
- 自社の用途が「IBL / PBR」に近いか確認する。
- 強みである「IBLは全方向の入射光を環境マップ(多くはHDRキューブマップ)として記録し、これを面光源とみなしてレンダリング方程式の半球積分を照明に使う手法。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。