Фізично коректний рендеринг (PBR)
PBR — це стандартна модель шейдингу для сучасної графіки в реальному часі. Замість довільних повзунків «дифузне + дзеркальне + блиск», вона моделює світло так, як це робить фізика — мікрофасетки, відбивання за Френелем та збереження енергії — використовуючи на вході лише металевість і шорсткість.
1. Рівняння рендерингу
Джеймс Каджія (1986) сформулював головне рівняння, яке має задовольняти кожен фізично коректний рендерер:
L_o — вихідна яскравість у напрямку ωo
L_e — випромінювана яскравість
f_r — BRDF: двонапрямлена функція розподілу відбивання
L_i — вхідна яскравість із напрямку ωi
ωi · n̂ = cos θ (косинусний член Ламберта)
Точне розв'язання цього інтеграла потребує трасування шляхів. PBR у реальному часі апроксимує його за допомогою: (1) прямих точкових джерел світла та (2) попередньо обчислених карт оточення для дифузного та дзеркального інтегралів.
2. Теорія мікрофасеток
На мікроскопічному рівні жодна поверхня не є ідеальним дзеркалом. Кожна поверхня — це сукупність крихітних плоских мікрофасеток, кожна з яких — ідеальне дзеркало, але орієнтоване випадково. Розподіл орієнтацій мікрофасеток визначає макроскопічний вигляд.
Шорстка поверхня має багато фасеток, спрямованих у випадкових напрямках → великі, дифузні на вигляд відблиски. Гладка поверхня має фасетки, майже всі вирівняні з нормаллю поверхні → крихітні, різкі дзеркальні «гарячі точки».
Шорсткість α² = roughness², де roughness ∈ [0, 1] — це введене художником значення. Два ефекти зменшують кількість видимих відбивальних фасеток:
- Маскування: Мікрофасетка заблокована сусідніми фасетками з боку камери.
- Затінення: Мікрофасетка лежить у тіні сусідніх фасеток з боку джерела світла.
3. Члени BRDF Кука — Торренса
Мікрофасетковий дзеркальний BRDF (Кука — Торренса) має три члени:
D — функція нормального розподілу (скільки фасеток обернено до h)
G — член геометрії/маскування (самозатінення)
F — член Френеля (відбивання залежно від кута)
D(h, α) = α² / (π · ((n·h)²(α²−1) + 1)²). Найпопулярніший: чудово виглядає, швидко семплюється.
G(n,v,k) = (n·v) / ((n·v)(1−k)+k), k = α²/2 (пряме світло). Застосовується окремо для напрямків зору + світла.
F = F₀ + (1 − F₀)(1 − cosθ)⁵. Просто, точно. F₀ — це відбивання за нормального падіння.
Повний BRDF = дзеркальний Кука — Торренса + дифузний Ламберта,
зважений за членом Френеля: дифузний внесок дорівнює (1 − F) × albedo / π.
Метали поглинають усе заломлене світло → дифузного члена немає.
4. Френель і F₀
F₀ — це відбивання за нормального падіння (кут падіння 0°). Це «базове відбивання» поверхні:
- Неметали (діелектрики): F₀ ≈ 0,02–0,08 (вода: 0,02, скло: 0,04, шкіра: 0,028)
- Поширені метали: F₀ ≈ 0,50–0,95 і є хроматичним (різні R, G, B). Золото: (1.0, 0.71, 0.29). Мідь: (0.95, 0.64, 0.54).
Під ковзними кутами (близько 90°) усе стає сильно відбивальним — ви
можете бачити відображення неба на гладкому озері, навіть попри те, що вода має низький F₀.
Це ефект Френеля, а степеневий закон (1 − cosθ)⁵
— напрочуд гарне наближення для більшості матеріалів.
5. Робочий процес металевість — шорсткість
Художники використовують три текстурні карти. Це «робочий процес металевості», який використовують Three.js, Unreal Engine, Unity, Blender та glTF:
- Альбедо / базовий колір (RGB): Для діелектриків: дифузний колір. Для металів: дзеркальний колір (F₀). Зберігається в sRGB.
- Шорсткість (R, один канал): 0 = дзеркало, 1 = повністю шорстке, дифузоподібне дзеркальне. Зберігається як лінійне.
- Металевість (R, один канал): 0 = діелектрик, 1 = метал. Зазвичай бінарна — частково металевих матеріалів фізично не існує (за винятком плями на хромі).
Додаткові необов'язкові карти: нормалей, амбієнтного затінення, випромінювання, висоти.
6. Освітлення на основі зображень (IBL)
Точкові джерела світла (точкове, напрямлене, прожекторне) покривають лише частку реального освітлення. IBL використовує HDR-карту оточення як нескінченну сферу площинних джерел світла. Інтеграл рівняння рендерингу розбивається:
- Карта дифузної опроміненості: Попередньо згорніть карту оточення з косинусно-зваженим інтегралом по півсфері → розмита кубічна карта. Семплюйте за світовою нормаллю, щоб отримати дифузний внесок.
- Дзеркальна попередньо відфільтрована карта: Попередньо згорніть на кількох рівнях шорсткості (мипмапи). Семплюйте за відбитим вектором зору.
-
BRDF LUT: Попередньо обчислена 2D-таблиця пошуку (NdotV × шорсткість),
що зберігає інтеграл (G × F) по півсфері — наближення
«розщепленої суми» (Epic Games, 2013). Поєднайте під час виконання:
specular = prefiltered.rgb * (F₀ * BRDF.r + BRDF.g)
Усі три текстури обчислюються один раз офлайн. IBL у реальному часі зі статичних оточень за вартістю на кадр по суті «безкоштовне».
7. PBR-шейдер на GLSL
Основні функції BRDF Кука — Торренса в GLSL ES 3.0:
const float PI = 3.14159265359;
// Функція нормального розподілу GGX
float D_GGX(float NdotH, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float d = (NdotH * NdotH) * (a2 - 1.0) + 1.0;
return a2 / (PI * d * d);
}
// Геометрична функція Сміта — Шліка
float G_SchlickGGX(float NdotV, float roughness) {
float r = roughness + 1.0;
float k = (r * r) / 8.0;
return NdotV / (NdotV * (1.0 - k) + k);
}
float G_Smith(float NdotV, float NdotL, float roughness) {
return G_SchlickGGX(NdotV, roughness)
* G_SchlickGGX(NdotL, roughness);
}
// Fresnel-Schlick
vec3 F_Schlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
// Повний BRDF Кука — Торренса (одне напрямлене світло)
vec3 PBR(vec3 N, vec3 V, vec3 L,
vec3 albedo, float roughness, float metalness) {
vec3 H = normalize(V + L);
float NdotV = max(dot(N, V), 0.0001);
float NdotL = max(dot(N, L), 0.0);
float NdotH = max(dot(N, H), 0.0);
float VdotH = max(dot(V, H), 0.0);
// F0: діелектрик = 0.04, метал = albedo
vec3 F0 = mix(vec3(0.04), albedo, metalness);
float D = D_GGX(NdotH, roughness);
float G = G_Smith(NdotV, NdotL, roughness);
vec3 F = F_Schlick(VdotH, F0);
vec3 specular = (D * G * F) / (4.0 * NdotV * NdotL + 0.001);
vec3 kD = (1.0 - F) * (1.0 - metalness); // метали: без дифузного
vec3 diffuse = kD * albedo / PI;
return (diffuse + specular) * NdotL;
}
THREE.ShaderMaterial та
підключити вбудовані GLSL-фрагменти Three через
#include <lights_pars_begin>, щоб автоматично отримати джерела світла сцени.