核心目標
GGX 分布是 PBR 中模擬粗糙表面高光反射的主流模型,其核心是通過統計分布描述微表面的朝向概率。本階段的目標是:
- 基于第一階段生成的環境圖集,預計算 6 個級別的 GGX 過濾結果(對應不同粗糙度);
- 使用蒙特卡洛采樣(Monte Carlo Sampling)加速 GGX 卷積計算;
- 將預過濾結果存儲在環境圖集的指定區域,與 Stage 1 的基礎數據形成完整的環境數據集合。
這些預計算數據將使實時渲染時無需動態計算復雜的 GGX 卷積,只需直接采樣即可獲得符合物理規律的高光反射顏色。
準備工作
- 前置條件:已完成 Stage 1,獲得包含基礎環境數據的
envAtlas
; - 輸入資源:
sourceCube
(原始立方體貼圖)、envAtlas
(待寫入預過濾結果的圖集); - 工具依賴:Three.js 環境(
renderer
、scene
、camera
)、樣本生成工具(SampleGenerator
); - 理論基礎:了解 GGX 分布模型、蒙特卡洛采樣、PBR 高光反射原理。
GGX 分布與預過濾原理
在 PBR 中,表面的高光反射效果由微表面分布函數(NDF) 決定,GGX 是其中應用最廣泛的模型,其分布函數為:
對于環境光反射,需要對整個環境貼圖按 GGX 分布進行卷積(加權平均),得到該粗糙度下的 “模糊” 環境貼圖。由于實時計算這一卷積成本極高,我們通過預計算(離線完成)并存儲結果,實時渲染時直接采樣,大幅提升性能。
實現步驟詳解
步驟 1:明確圖集存儲區域
預過濾結果將存儲在envAtlas
的另一部分區域,與上階段的基礎數據分區存放。以 512x512 圖集為例,布局如下:
級別(i) | 對應粗糙度 | 圖集內位置(x,y) | 分辨率(寬 x 高) | specularPower(高光強度) |
---|---|---|---|---|
1 | 低 | (0, 256*s) | 256x128 | 512 |
2 | 中低 | (0, 256s + 128s) | 128x64 | 128 |
3 | 中 | (0, 256s + 128s + 64*s) | 64x32 | 32 |
4 | 中高 | ... | 32x16 | 8 |
5 | 高 | ... | 16x8 | 2 |
6 | 極高 | ... | 8x4 | 1 |
- 級別遞增對應粗糙度遞增(
specularPower
遞減); - 分辨率隨級別遞增減半(與 Stage 1 邏輯一致,粗糙度越高,所需細節越少)。
步驟 2:頂點著色器(UV 坐標處理)
與 Stage 1 類似,頂點著色器負責傳遞調整后的 UV 坐標,支持接縫處理:
uniform vec4 uvMod; // UV調整參數:(scaleU, scaleV, offsetU, offsetV)
varying vec2 vUv; // 傳遞給片段著色器的UVvoid main() {// 標準頂點變換:投影到裁剪空間gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);// 計算UV:擴展范圍以包含接縫像素vUv = (position.xy * 0.5 + 0.5) * uvMod.xy + uvMod.zw;
}
- 復用 Stage 1 的 UV 擴展邏輯,通過
uvMod
確保渲染內容包含接縫像素,避免邊緣采樣誤差。
步驟 3:片段著色器(GGX 預過濾核心邏輯)
片段著色器是本階段的核心,實現基于蒙特卡洛采樣的 GGX 卷積計算,包含樣本解碼、方向轉換、立方體貼圖采樣等關鍵步驟。
3.1 基礎變量與常量
precision highp float;
varying vec2 vUv; // 頂點傳遞的UV
uniform samplerCube sourceCube; // 原始立方體貼圖
uniform sampler2D samplesTex; // 預生成的GGX樣本紋理
uniform vec2 samplesTexInverseSize;// 樣本紋理尺寸的倒數(用于計算UV)
uniform vec4 params; // [_, specularPower, 接縫縮放系數, _]
const float PI = 3.141592653589793;
const int NUM_SAMPLES = 1024; // 每個像素的采樣數量(平衡質量與性能)
3.2 RGBP 編碼(復用 Stage 1 邏輯)
預過濾結果仍采用 RGBP 編碼存儲,確保 HDR 數據高效壓縮:
vec4 encodeRGBP(vec3 source) {vec3 gamma = pow(source, vec3(0.5)); // gamma校正(平方根)float maxVal = min(8.0, max(1.0, max(gamma.x, max(gamma.y, gamma.z)))); // 限制最大范圍float v = 1.0 - ((maxVal - 1.0) / 7.0); // 編碼縮放因子v = ceil(v * 255.0) / 255.0; // 確保8位精度存儲return vec4(gamma / (-v * 7.0 + 8.0), v); // 縮放顏色并返回
}
3.3 方向計算與接縫處理(復用與擴展)
與 Stage 1 相同,需將等矩形 UV 轉換為三維方向向量,并處理立方體貼圖接縫:
// 球坐標轉三維方向向量
vec3 fromSpherical(vec2 uv) {return vec3(cos(uv.y) * sin(uv.x), // x分量sin(uv.y), // y分量cos(uv.y) * cos(uv.x) // z分量);
}// 從等矩形UV計算方向向量(指向環境中的采樣點)
vec3 getDirectionEquirect() {// 轉換UV范圍至球坐標:U→[-π,π],V→[π/2,-π/2](翻轉V軸)vec2 spherical = (vec2(vUv.x, 1.0 - vUv.y) * 2.0 - 1.0) * vec2(PI, PI * 0.5);return fromSpherical(spherical);
}// 調整方向向量以減輕立方體貼圖接縫
vec3 modifySeams(vec3 dir, float scale