PBR: BRDF Кука-Торренса пояснено
Майже кожен фізично коректний рендерер — від Unreal Engine до
трирядкового MeshStandardMaterial у Three.js —
обчислює свій спекулярний блиск за тією самою формулою 1982 року.
Ось що саме означають D, F і G, чому їх обрали саме такими, і як
написати їх на GLSL (мова шейдерів OpenGL — код, що виконується на
відеокарті для обчислення кольору кожного пікселя).
1. Навіщо мікрофасеточна модель?
Жодна реальна поверхня не є ідеально гладкою на мікроскопічному рівні. Під мікроскопом навіть відполірований метал — це ландшафт з крихітних граней, кожна з яких діє як ідеальне дзеркало, спрямоване трохи по-своєму. Теорія мікрофасетів розглядає точку поверхні як статистичний розподіл цих граней, а не моделює кожну окремо — саме це робить її придатною для рендерингу в реальному часі у фрагментному шейдері.
Лише грані, чия нормаль h (напіввектор між напрямком до камери v та напрямком до світла l) збігається з реальною геометричною нормаллю, можуть відбити світло прямо в камеру. Шорстка поверхня має нормалі граней, розсіяні широко → широкий, тьмяний відблиск. Гладка поверхня має їх щільно згрупованими → маленький, яскравий відблиск. Ця одна ідея пояснює, чому шорсткість керує одночасно і розміром, і яскравістю спекулярного відблиску.
Роберт Кук і Кеннет Торренс опублікували «A Reflectance Model for Computer Graphics» у 1982 році, адаптувавши раніше роботу Торренса і Сперроу (1967) з оптичної інженерії. Метод залишався нішевою технікою офлайн-рендерингу 25 років, доки «принципова BRDF» Disney 2012 року та розподіл GGX не зробили його стандартним вибором для рушіїв реального часу (Unreal 4, Unity HDRP, Filament, Three.js).
2. Формула Кука-Торренса
Спекулярний член BRDF Кука-Торренса — це дріб із трьома множниками у чисельнику та нормалізуючим членом у знаменнику:
- D — функція розподілу нормалей: яка частка мікрофасетів орієнтована саме вздовж напіввектора h?
- G — геометричний член: яка частка цих граней не затінена сусідніми гранями на шляху до світла і не замаскована на шляху до камери?
- F — член Фреснеля: яка частка світла відбивається (а не заломлюється/поглинається) під цим кутом огляду?
- 4·(n·l)·(n·v) — нормалізуючий множник, що коригує зміну міри між простором нормалей мікрофасетів і простором вихідних напрямків.
Кінцевий колір також потребує дифузного (ламбертівського) члена для світла, що заломлюється в поверхню, розсіюється і виходить назад — див. розділ 7.
3. D — функція розподілу нормалей (GGX)
Кук і Торренс спочатку використовували розподіл Бекмана; сучасні рушії практично завжди використовують GGX (також відомий як Трoubridge-Reitz), тому що він має довші «хвости» — повільніше згасає під кутами ковзання, значно краще відповідаючи виміряним реальним матеріалам і створюючи м'які, світлі краї відблиску, характерні для фізично коректного рендерингу.
де α = шорсткість² (перцептивне маппування — піднесення шорсткості до квадрата дає інтуїтивніший контроль повзунка для художників, популяризоване принциповою BRDF Disney)
float distributionGGX(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float denom = (NdotH * NdotH * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return a2 / max(denom, 0.0000001);
}
4. G — геометрія (затінення-маскування Сміта)
Навіть грань, зорієнтована для відбиття світла у камеру, може бути заблокована: затінення зупиняє світло від джерела, а маскування зупиняє відбите світло на шляху до камери. Модель Сміта розкладає це на два незалежні члени, що перемножуються, кожен з яких використовує той самий будівельний блок «Schlick-GGX»:
G1(n, x, k) = (n·x) / ((n·x) · (1 − k) + k)
де k = (шорсткість + 1)² / 8 (пряме освітлення, за Karis/Epic 2013)
float geometrySchlickGGX(float NdotX, float k) {
return NdotX / (NdotX * (1.0 - k) + k);
}
float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;
float ggx1 = geometrySchlickGGX(max(dot(N, V), 0.0), k);
float ggx2 = geometrySchlickGGX(max(dot(N, L), 0.0), k);
return ggx1 * ggx2;
}
Під кутами ковзання обидва скалярні добутки прямують до нуля, тому G коректно затемнює відблиск — саме це не дає шорстким сферам виглядати як світні диски на своєму силуеті.
5. F — Фреснель (наближення Шліка)
Ефект Фреснеля стверджує, що будь-який матеріал
стає більш відбивним під кутами ковзання — саме тому нерухоме
озеро виглядає як дзеркало біля горизонту, але прозоре, якщо
дивитися прямо вниз. Точні рівняння Фреснеля залежать від
показника заломлення матеріалу і дорогі; наближення Крістофа
Шліка 1994 року точне до частки відсотка і коштує один
pow():
F₀ — це відбивна здатність при нормальному падінні (погляд прямо на поверхню). Саме тут підключається робочий процес metalness: діелектрики (пластик, дерево, шкіра) мають низьке, ахроматичне F₀ близько 0.04, тоді як метали мають високе, забарвлене F₀, що дорівнює кольору їхнього альбедо (золото ≈ (1.0, 0.71, 0.29)).
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (vec3(1.0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}
6. Складаємо разом у GLSL
Мінімальний фрагментний шейдер Кука-Торренса з одним джерелом світла, що поєднує усі три члени плюс ламбертівську дифузну складову:
vec3 cookTorrance(vec3 N, vec3 V, vec3 L,
vec3 albedo, float roughness, float metalness,
vec3 radiance) {
vec3 H = normalize(V + L);
vec3 F0 = mix(vec3(0.04), albedo, metalness);
float D = distributionGGX(N, H, roughness);
float G = geometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 kS = F; // частка спекулярного внеску
vec3 kD = (vec3(1.0) - kS) * (1.0 - metalness); // у металів немає дифузії
float NdotL = max(dot(N, L), 0.0);
float NdotV = max(dot(N, V), 0.0);
vec3 numerator = D * G * F;
float denominator = 4.0 * NdotV * NdotL + 0.0001;
vec3 specular = numerator / denominator;
return (kD * albedo / PI + specular) * radiance * NdotL;
}
Для повної сцени ви накопичуєте результат цієї функції для кожного прямого джерела світла, а потім додаєте член image-based lighting (IBL) з попередньо відфільтрованої кубічної карти оточення для фонового внеску — карта дифузного опромінення, попередньо відфільтрований ланцюг mip-рівнів для спекулярності та LUT-таблиця інтеграції BRDF (наближення split-sum, Karis 2013).
7. Збереження енергії та дифузний член
Фізично правдоподібний матеріал не повинен відбивати більше
світла, ніж отримав. Наведений вище код забезпечує це через
kD = (1 - kS) * (1 - metalness): та частка світла,
що не відбилася спекулярно (kS = F), доступна для дифузного
розсіювання, а метали отримують нульову дифузію, бо все світло,
що потрапляє у провідник, майже миттєво поглинається вільними
електронами — у металів немає підповерхневого розсіювання.
Ламбертівський дифузний член albedo / π сам по собі
є наближенням — реальна дифузна відбивна здатність не є ідеально
рівномірною в усіх напрямках (Oren-Nayar — альтернатива для
шорсткіших поверхонь) — але для більшості випадків використання
PBR ця похибка непомітна на тлі внеску спекулярного члена.
🔺 Побачити рендеринг поверхонь наживо
Симуляція Ray Marching дозволяє налаштовувати освітлення на SDF-поверхнях у реальному часі — гарний майданчик для експериментів із шорсткістю та Фресnelем на око.
Читати статтю про Ray Marching →