Ambient Occlusion: від SSAO до GTAO
Ambient occlusion апроксимує те, для чого глобальне освітлення зазвичай потребує повної симуляції транспорту світла: м'яке затемнення у кутах, складках і точках контакту, де сусідня геометрія блокує вхідне навколишнє світло. З того часу, як Crysis у 2007 році популяризував screen-space AO, техніка пройшла кілька поколінь — SSAO (Screen-Space Ambient Occlusion, «екранне» навколишнє затінення), HBAO, HBAO+ і сьогоднішній GTAO — кожне з яких скорочувало розрив між дешевим screen-space хаком і фізично коректною відповіддю, яку дав би path tracer.
1. Інтеграл оклюзії
Ambient occlusion у точці поверхні p з нормаллю n визначається як частка півсфери над p, не заблокована сусідньою геометрією, проінтегрована з косинусною вагою (той самий косинусний член, що і в рівнянні рендерингу, але з видимістю V замість повного радіансу):
AO(p) = 1 − (1/π) ∫_Ω V(p, ω) · (n·ω) dω
V(p, ω) = 0, якщо сусідня поверхня блокує напрямок ω в межах радіуса r, інакше 1
AO у діапазоні [0,1]: 1 = повністю відкрито, 0 = повністю заблоковано (глибока щілина)
Повна відповідь через трасування променів пускає сотні променів на піксель і перевіряє перетин зі сценою для кожного — саме це вже обчислює path tracer як побічний ефект глобального освітлення. Кожна з технік реального часу нижче — апроксимація того самого інтеграла у набагато жорсткішому часовому бюджеті — зазвичай менше 1 мс за кадр.
2. SSAO: випадковий семплінг півсфери у просторі глибини
SSAO від Crytek (Mittring, 2007) апроксимує інтеграл, використовуючи лише буфер глибини, вже доступний після проходу геометрії — без додаткового обходу сцени, без BVH. Для кожного пікселя береться жменька точок у ядрі-півсфері навколо точки поверхні, їхня позиція у view space реконструюється і порівнюється з буфером глибини, щоб вгадати, чи вони заблоковані.
// SSAO фрагментний шейдер, спрощено
vec3 pos = reconstructViewPos(gl_FragCoord.xy, depthTex);
vec3 normal = texture(normalTex, uv).xyz;
vec3 tangent = normalize(texture(noiseTex, uv * noiseScale).xyz);
mat3 tbn = buildTBN(normal, tangent);
float occlusion = 0.0;
for (int i = 0; i < kernelSize; i++) {
vec3 samplePos = pos + tbn * kernel[i] * radius;
vec4 offset = projection * vec4(samplePos, 1.0);
offset.xyz /= offset.w;
offset.xyz = offset.xyz * 0.5 + 0.5;
float sceneDepth = linearizeDepth(texture(depthTex, offset.xy).r);
float rangeCheck = smoothstep(0.0, 1.0, radius / abs(pos.z - sceneDepth));
occlusion += (sceneDepth >= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck;
}
float ao = 1.0 - (occlusion / float(kernelSize));
Це дешево, але грубувато: техніка знає лише про глибину, а не про реальну геометрію, тож може пропустити оклюдери позаду видимої поверхні (неповнота screen-space) і потребує 16–64 зразків на піксель, щоб уникнути сильного шуму.
3. Шум, блюр і компроміс з кількістю зразків
З малим ядром вихід SSAO помітно шумний — випадкове поворотне ядро для кожного пікселя (через невелику тайловану текстуру шуму) обмінює структуроване смугування на менш помітний високочастотний шум, який потім прибирається білатеральним блюром (враховуючи глибину/нормаль, щоб AO не "перетікав" через силует-краї).
| Розмір ядра | Сирий шум | Типовий радіус блюру | Вартість |
|---|---|---|---|
| 8–16 зразків | Дуже шумно | 5×5–7×7 | Найдешевше |
| 16–32 зразки | Помірно | 4×4 | Збалансовано (більшість ігор) |
| 64+ зразків | Мало | 3×3 | Дорого, рідко без обробки |
4. HBAO: оклюзія на основі горизонту
Horizon-Based Ambient Occlusion (Bavoil, Sainz, Dimitrov — NVIDIA, 2008) переформульовує задачу геометрично замість довільних семплів у півсфері: для кожного з кількох напрямків навколо пікселя у screen space, маршуємо назовні вздовж буфера глибини, щоб знайти "кут горизонту" — найвищий кут піднесення, під яким сусідня геометрія блокує півсферу.
Для напрямку φ: кут горизонту h(φ) = максимальний кут піднесення будь-якої зразкової точки вздовж цього напрямку
Внесок AO від цього напрямку ∝ sin(h(φ)) − sin(t(φ))
t(φ) = кут дотичної площини у p (враховує нахил поверхні відносно погляду)
Інтегруємо по кількох напрямках φ (зазвичай 4–8) → фінальний AO
Оскільки техніка маршує вздовж реальних зразків буфера глибини, а не перевіряє довільні зміщені точки, HBAO точніше вловлює тонкі оклюдери та контактні тіні, ніж SSAO на основі ядра, за подібну або трохи вищу вартість. HBAO+ (2013) додав блюр, що зберігає деталі, і апроксимацію багатократних відбиттів кольору.
5. GTAO: ambient occlusion на основі точного розв'язку
Ground Truth Ambient Occlusion (Jimenez та ін., Activision, 2016) зберігає структуру пошуку горизонту з HBAO, але замінює її наближений косинусно-зважений інтеграл точним аналітичним розв'язком для інтеграла видимості, обмеженого дугою горизонту — звідси "ground truth": для заданого горизонту це математично коректний внесок AO, а не евристика.
Точний інтеграл по зрізу (Jimenez 2016):
InnerIntegral(h1,h2,n) = ¼·(−cos(2h1−γ) + cosγ + 2h1·sinγ − cos(2h2−γ) + cosγ + 2h2·sinγ)
де γ — кут між спроєктованою нормаллю та площиною зрізу
Результат: візуально майже ідентичний трасованому AO при набагато меншій кількості зразків горизонту, ніж потрібно HBAO для тієї ж якості
GTAO зазвичай використовує лише 2 напрямки зрізу з кількома кроками горизонту у кожному, поєднані з сильним темпоральним накопиченням (повторне використання зразків попередніх кадрів через репроекцію), щоб досягти переконливої якості за частку вартості HBAO+ за кадр — саме ця техніка стоїть за AO за замовчуванням у більшості сучасних ігрових рушіїв (Unreal, багато власних рушіїв).
6. Апроксимація багатократних відбиттів і колір, забарвлений AO
Чистий AO лише затемнює — у нього немає кольору, оскільки він призначений модулювати навколишнє/непряме світло, а не пряме. Реальне непряме світло відбивається від кольорових поверхонь перед тим, як повернутись, тож наївний AO, застосований до кольорових сцен (скажімо, червоної кімнати), виглядає надто темним і сірим у кутах. Multi-bounce GTAO апроксимує це, пропускаючи базовий альбедо через поліноміальну апроксимацію, каліброван проти еталонного path-traced багатократного GI, освітлюючи і забарвлюючи член оклюзії замість того, щоб лишати його плоским сірим множником.
Однократний AO
Простий сірий множник для навколишнього світла. Швидко, але щілини у кольорових сценах виглядають неприродно темними.
Multi-bounce GTAO
Поліноміальна корекція з використанням альбедо поверхні — кути забарвлені у відбитий колір, ближче до еталонного GI.
Ray-traced AO (RTAO)
Справжні апаратні промені проти реальної геометрії сцени, без обмеження screen-space, але потребує RT-заліза.
7. Порівняння та нотатки з реалізації
| Техніка | Рік | Основна ідея | Типова вартість |
|---|---|---|---|
| SSAO | 2007 | Випадкове ядро-півсфера проти буфера глибини | Низька, але шумна при малій кількості зразків |
| HBAO | 2008 | Пошук горизонту вздовж буфера глибини | Помірна, краща геометрична точність |
| HBAO+ | 2013 | HBAO + блюр зі збереженням деталей, апрокс. багатократних відбиттів | Помірна–висока |
| GTAO | 2016 | Точний інтеграл по зрізу горизонту + темпоральне перевикористання | Низька з TAA, висока якість |
Для WebGL2/WebGPU-реалізації практичний пайплайн такий: прохід глибини + нормалей → compute-прохід AO (SSAO-ядро або пошук горизонту GTAO) у половинній чи повній роздільності → білатеральний блюр → множення лише у навколишній/непрямий член освітлення (ніколи у пряме світло чи emissive). Темпоральне перевикористання GTAO потребує векторів руху та буфера репроекції, що додає реальну складність реалізації — розумний середній варіант для браузерної демонстрації — пошук горизонту у стилі HBAO без темпорального накопичення, натомість з просторовим шумозаглушенням.