Динамічна тесселяция ландшафту


У даній статті розглядаються статичні і динамічні методи розрахунку рівня тесселяции, способи стикування патчів з різною деталізацією і алгоритм відсікання невидимих ​​патчів. Перед читанням цієї статті рекомендується ознайомиться зі статтею про апаратну тесселяцию і displacement mapping.


У кожному розділі статті використовуються свої шейдери, для перемикання між прикладами використовуються клавіші F1..F6. для перемикання режимів служать клавіші 1..8 або (і) - перемикають на попередній і наступний режим відповідно. В якості додаткової можливості можна переглядати нормалі і рівень тесселяції, для цього використовуються клавіші C. N. T. M - для виведення текстури кольору, нормалізує обмін речовин, рівня тесселяции і змішаний режим: колір і рівень тесселяції. Клавіша R служить для перезавантаження поточних шейдеров, P перемикає між відображенням сітки і полігонів.
У прикладах, де підтримується зміна деталізації за допомогою таких клавіш:
- і + змінює максимальний рівень тесселяції,
<и> змінює рівень деталізації,
[І] змінює висоту ландшафту.
Для переміщення використовуються клавіші W. S. A. A. для руху по вертикалі - Shift. Space.
У заголовку вікна відображається вибраних частина прикладу, яка відповідає частці статті, кількість кадрів в секунду, кількість виведених і згенерованих вершин.


Тут як і в інших прикладах малюється сітка розміром 128х128 вершин, використовуються трикутні або квадратні патчі (в залежності від прикладу). Квадратні патчі займають менше місця - для одного патча використовується 4 індексу проти 6 для трикутного патча.
Перед отрисовкой сітки встановлюється розмір патча:

За замовчуванням розмір патча дорівнює 3. Якщо не встановити правильний розмір патча, наприклад поставити 3, коли в шейдера визначено як 4, то помилок не виникне, але малюватися буде неправильно.
Для уникнення подібних помилок можна отримувати значення патча з шейдера функцією glGetProgramiv з параметром GL_TESS_CONTROL_OUTPUT_VERTICES. повернеться значення визначене в control шейдера в рядку:

У evaluation шейдера відбувається читання з карти висот і нормалей, позиція зміщується на висоту з карти висот, а нормаль передається в Фрагментний шейдер, де може бути використання для розрахунку освітлення.

У цьому прикладі використовується рівномірна тесселяция квадратних патчів, рівень тесселяції фіксований і дорівнює максимальному значенню, що сильно знижує продуктивність. У следующеіх частинах буде описана оптимізація рендеринга зі збереженням якості.


Що б зберегти якість розбиття поверхні потрібно використовувати змінну деталізацію, але при цьому виникає проблема - сусідні патчі можуть мати різну деталізацію в результаті чого між ними виходять розриви. Тому для правильного стикування патчів з різною деталізацією використовується параметр gl_TessLevelOuter.
У прикладі для кожної вершини в вершинні шейдери задається випадкове значення рівня тесселяции, перемикаючи
режими клавішами 1 і 2 можна ввімкнути або вимкнути правильну стикування. Результат неправильної стикування:

Динамічна тесселяция ландшафту

На скріншоті видно множинні розриви між патчами з різною деталізацією.

В control шейдера встановлюється рівень тесселяції на кордоні патча, для правильного стикування потрібно щоб ці рівні збігалися. Це виходить за рахунок однакового виконання функції розрахунку рівня тесселяции у всіх шейдерах, в прикладах це функція max:

Можна використовувати будь-які функції розрахунку, результати яких для однакових ребер будуть збігатися.


Трикутні патчі.
Для трикутних патчів використовуються тільки три значення з gl_TessLevelOuter.
На нульовий індекс в gl_TessLevelOuter впливають вершини з індексом 1 і 2, на перший - 0 і 2, на другий - 0 і 1.
приклад:

Квадратні патчі.
Для квадратних патчів використовуються всі чотири значення з gl_TessLevelOuter.
На нульовий індекс в gl_TessLevelOuter Вілія вершини з індексом 0 і 3, на перший - 0 і 1, на другий - 1 і 2, на третій - 2 і 3.
приклад:

Значення gl_TessLevelInner можуть бути будь-якими - вони ніяк не впливають на правильну стикування,
але для рівномірної тесселяции бажано брати максимальне або середнє значення рівнів
тесселяции ребер (gl_TessLevelOuter), наприклад:


Варто зазначити, що під час запису нулів у gl_TessLevelInner і gl_TessLevelOuter патч не створюється,
це властивість можна використовувати для відсікання патчів, але так само варто стежити, щоб
відсікання не відбулося випадково, тому в прикладі використовується функція clamp:

unMaxTessLevel - максимальний рівень тесселяції, визначається в додатку.


У цьому прикладі використовується попередньо згенерувала текстура з рівнем деталізації для кожної вершини. Для зберігання рівня деталізації цілком вистачить текстури формату R8 роздільною здатністю 128х128 (один тексель на вершину).

Код фрагментного шейдера gen_normal_and_tesslvl.prg складається з двох функцій:
ReadHeight - зчитує текстуру в матрицю 4х4, для цього використовуються 4 виклику функції textureGatherOffsets.
GenTessLevel - функція генерації рівня деталізації, розраховуються перепади висот між точками в два проходи: по вертикалі і по горизонталі, результат нормалізується і записується в текстуру.

У шейдера тесселяции доданий читання рівня деталізації в вершинні шейдери.

Динамічна тесселяция ландшафту

Як видно на скріншоті там де перепад висот більше рівень тесселяції теж більше (червоний колір - максимальний рівень деталізації), це дозволило зменшити кількість полігонів більш ніж в 2 рази. Дальні полігони мають занадто високий рівень деталізації, щоб виправити цю проблему використовується наступний спосіб.


Найбільша деталізація потрібна поблизу камери, а чим далі від камери, тим менше деталізація, в цьому прикладі використовується лінійне зміна детелізаціі від відстані до камери.
Рівень деталізації для вершини розраховується в вершинні шейдери, для цього служить функція Level:

unDetailLevel - рівень деталізації.
dist - відстань від камери до вершини, розраховується воно так:


Для правильного розрахунку відстані необхідно перемістити вершину на значення визначене в карті висот по нормале до поверхні і спроектувати в простір екрану. Після чого розраховується відстань до вершини. Так як камера завжди знаходиться в центрі координат, то досить дізнатися довжину вектора.
Це все зміни, які були внесені в програму, все досить просто і кількість полігонів зменшилася більш ніж в 10 разів.

Динамічна тесселяция ландшафту


У попередньому прикладі далекі полігони могли мати недостатню деталізацію, особливо це помітно на великих полігонах, техніка зміни рівня деталізації в залежності від розміру полігону на екрані виправляє цю проблему.
У верховий шейдер доданий переклад позиції вершини в простір екрані:


В control шейдера розраховується рівень тесселяції для кожного ребра:

Функція Level повертає рівень деталізації в залежності від розміру ребра:

unDetailLevel - рівень деталізації, який визначається в додатку, як і в попередньому випадку. значення одеського форуму
занадто велике, тому використовується коефіцієнт 0.01.


У цього підходу є кілька недоліків:
1. При русі змінюється деталізація відразу всіх патчів - це викликає миготіння, частково
виправляється використанням високої деталізації і іншого типу розбиття:


2. При витягуванні полігону його ребра можуть сильно відрізнятися в розмірах.
В результаті з'являються області з низькою деталізацією.
Щоб не викликати проблеми зі стикуванням патчів, не можна підвищувати деталізацію цих ребер -
в сусідніх патчах НЕ буде інформації про це. Рішенням даної проблеми може бути об'єднання з попередньої технікою.

Динамічна тесселяция ландшафту


У цьому прикладі доданий відсікання невидимих ​​патчів для двох динамічних способів розрахунку деталізації. Зміни в них будуть однаковими, тому розбиратися буде тільки один приклад. Клавіші 1 і 2 служать для перемикання між прикладами.
У верховий шейдер додана функція перевірки попадання вершини на екран:

size - визначає розмір екрану, це не зовсім константа - може приймати значення від 1.0 і більше.
Чим більше параметр size тим більше полігонів поблизу краю екрану будуть видні, в тому числі ті,
які точно не потрапляють в екран. Значення 1.0 дає краще відсікання і погану точність - по краях
часто відокремлюється зайве. Значення 1.2 дає хорошу точність і мінімальний оверхед. Щоб подивитися як відбувається відсікання патчів можна встановити значення size менше 1, результат для значення 0.7 паказан на скріншоті:

Динамічна тесселяция ландшафту

Результат перевірки і екранні координати вершини передаються в control шейдер:


Здавалося б, знаючи потрапляють всі вершини патча на екран можна сміливо відсікати патч,
але це не так. У багатьох випадках проблем не виникне, але коли всі вершини патча не були
в область екрану, а полігон все ж видно, тоді і знадобляться додаткові перевірки.

Динамічна тесселяция ландшафту

Перевірка буде досить проста - перевірити те патча з екраном в екранних координатах:

Функція Rect розраховує AABB для вершин патча, константа size та ж що і в функції InScreen. Тут використовується алгоритм перевірки перетину прямокутників.

Ну а далі все просто:

Якщо патч не видко, то k приймає значення нуль і при множенні на рівень тесселяції
виходить теж нуль, в результаті патч відсікається тесселятор.

За скриншотам можна порівняти два способи динамічного розрахунку тесселяции, тут використовуються квадратні патчі і відсікання невидимих ​​патчів, тому можна оцінити ставлення деталізації до кількості виведених примітивів:

Динамічна тесселяция ландшафту

Динамічна тесселяция ландшафту


Для різних випадків можуть підходити різні з розглянутих тут технік, але більш ефективним буде суміш з двох або трьох технік. Наприклад, використання техніки по відстані спільно зі збільшенням внутрішнього розбиття патча (gl_TessLevelInner) в залежності від його розміру на екрані.

На закінчення хочу подякувати Кирила Баженова (aka bazhenovc) за допомогу в написанні прикладів і статті.

Схожі статті