Віртуальні текстури

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

Коріння віртуальної текстури упирається ще далекий 1998 рік, коли Christopher C. Tanner, Christopher J. Migdal і Michael T. Jones (Silicon Graphics Computer Systems) представили у своїй доповіді, так звані, clipmaps[1]. p align="justify"> Технологія clipmaps знайшла своє застосування на текстуруванні ландшафтів і мала різні обмеження, зокрема, її не можна було використовувати для текстурування довільної геометрії. Пізніше технологія була розвинена Джоном Кармаком (John Carmack) з id Software і була застосована в грі Rage (до цього на своїй зорі, як експеримент використовувалася на ландшафтах QuakeWars) [2]. Так звана мегатекстура (або віртуальна текстура), на відміну від clipmaps, накладалася на довільну геометрію. Далі ми розглянемо, у чому на відміну від звичайних тайловых (тайлінгових) текстур.

2. Відмінність від звичайних тайлових текстур

3. Структура віртуальної текстури

Технологія віртуального текстурування є особливим підходом до менеджменту текстур. Віртуальна текстура складається з кількох mip-рівнів, кожен із яких поділений на сторінки фіксованого розміру. Кількість рівнів визначається розміром віртуальної текстури і може бути визначена за такою формулою:

де n – кількість mip-рівнів, VtSize – розмір віртуальної текстури за однією з осей, PageSize – розмір сторінки за однією з осей. Зазначимо, що технологія, що описується в цій статті, передбачає, що розміривіртуальної текстури по вертикалі та горизонталі рівні та кратні ступеня двійки, як і розміри сторінок. Розмір сторінки вибирається довільно з потреб, так, розмір сторінки в idTech 5 дорівнює 128 x 128 пікселів[2].

Для зручності нумеруватимемо mip-рівні наступним чином: нульовий рівень відповідає розміру текстури 128х128 піксел (відповідає розміру однієї сторінки), перший 256х256, другий 512х512, третій 2048х2048 і так далі. Зазначимо, що це відповідає нумерації прийнятої в OpenGL, т.к. там нульовий mip-рівень відповідає максимальному розміру текстури. Чому нумерація обрана таким чином, стане зрозуміло далі. Нумерація сторінок у mip-рівнях представлена ​​малюнку 1.

сторінки

Рисунок 1 – Нумерація сторінок віртуальної текстури всередині mip-рівнів

Як видно з малюнка 1, очевидно, що найзручніше віртуальну текстуру зберігатиме на жорсткому диску як послідовності сторінок 0,1,2,3,4… тощо. Таким чином, доступ до даних сторінки просто обчислити за такою формулою:

де: PageFileOffset – зміщення щодо заголовка файлу; PageNumber – номер сторінки; PageSize - розмір сторінки; Bpp – кількість байт на піксель.

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

4. Конвеєр

Далі ми розглянемо кожен пункт докладніше.

4.1 Визначення видимих ​​сторінок віртуальної текстури

На даному етапі нам потрібно визначити номери видимих ​​сторінок, ну і відповідно номер mip-рівня, до якого ця сторінка належить.

Я пробував два способи визначення видимих ​​сторінок віртуальної текстури. Перший грунтувався на принципірендеринга в сцени у текстурному просторі [4]. Тобто, ми малюємо сцену в текстуру, використовуючи не світові координати вершин, а текстурні. При цьому розмір однієї сторінки текстурного простору відповідав одному пікселю. Даний метод має купу проблем, тому що при рендерингу в текстурному просторі стандартні способи відсікання вже не працюють, а в шейдер потрібно передавати інформацію про площини піраміди видимості (frustum) та розраховувати clipping distances. Крім того, необхідний ручний backface-culling. Ще одним істотним недоліком даного методу є необхідність визначення, чи не загороджує один об'єкт інший, щоб не допустити рендерингу невидимих ​​в даний момент об'єктів. Ну і останній недолік, який змусив мене вибрати інший метод, це необхідність у так званому консервативному розтеризаторі (conservative rasterizer) [3]. Справа в тому, що якщо видно лише близько 50% відсотків сторінки, то стандартний GPU розтеризатор просто не розтеризуватиме її в один піксель, таким чином, ми втрачаємо цінну інформацію про потрібну сторінку. З огляду на складність реалізації консервативного растеризатора[3] цей метод у разі виявився неефективним.

Другий метод простий. Він полягає в ренедеринг сцени в текстуру (feedback buffer), при цьому в кожному пікселі отриманої текстури кодується номер сторінки віртуальної текстури. Очевидний недолік даного методу – він надмірний, тому що в різних пікселях feedback буфера може бути закодована інформація про однакові сторінки. Використовуючи деяке наближення, практично розмір feedback буфера виявляється достатнім вибрати вдесятеро менше поточного дозволу екрана [5], що у рази знижує навантаження аналіз цього буфера.

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

На вході вершинного шейдера ми оперуємо віртуальними координатами текстури (virtUV). Етап отримання номера сторінки складається з визначення поточного mip-рівня (miplevel) та положення сторінки щодо поточного mip-рівня (pageOffset):

На виході в pageOffset буде номер сторінки по x і y щодо поточного mip-рівня. Далі не складе складності простими арифметичними операціями перетворити pageOffset на pageNumber (рисунок 1). Тут є невелика тонкість: річ у тому, що у шейдері ми можемо закодувати число від 0 до 255 на один канал. При розмірі віртуальної текстури, яка використовується в грі Rage (128к x 128k пікселів), на верхньому mip-рівні pageOffset може перевищити число 255. Вирішити це обмеження можна за допомогою додаткового каналу, в якому кодуються додаткові вісім біт (по чотири на pageOff. .y). Таким чином, максимальна кількість сторінок, які ми можемо закодувати збільшується до 4095 x і стільки ж y. У четвертий канал, що залишився, ми записуємо номер mip-рівня сторінки. Ще одним способом зняти обмеження є використання float текстур із 32-бітовими каналами, але в нашому випадку це накладно, т.к. згодом нам потрібно буде передавати текстуру в оперативну пам'ять для її аналізу.

Тепер, коли ми маємо feedback буфер, ми можемо приступити до його аналізу.

4.2 Аналіз feedback буфера

Підсумовуючи сказане вище, грубий алгоритм аналізу feedback буфера може бути представлений у вигляді наступного псевдокоду:

Вводячи прапорець Needed, ми виключаємо попадання в чергу на завантаження однакових сторінок, а по прапорі Cached ми виключаємо завантаження сторінок, які вже завантажені в текстурнийкеш, у разі ми просто оновлюємо час останнього запиту сторінки. Надалі нам знадобиться визначати, як давно ця сторінка використовувалася, щоб ефективно оновлювати текстурний кеш – ця інформація зберігається у pageCacheInfo.

При аналізі feedback буфера також необхідно сортувати сторінки на завантаження за пріоритетами так, щоб першими в черзі на завантаження були сторінки з більш високим пріоритетом.

4.3 Завантаження віртуальних сторінок

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

4.4 Оновлення текстурного кешу

Тут необхідно визначити, які сторінки в кеші давно не використовувалися, для чого нам знадобиться вищезгадана pageCacheInfo. Далі просто копіюємо сторінку в потрібну позицію текстурного кешу, помічаємо сторінку як Cached і знімаємо прапорець Cached зі сторінки, яка була раніше на цьому місці.

4.6 Рендеринг сцени

Далі ми можемо розрахувати фізичні координати:

Тут CACHE_SIZE дорівнює кількості сторінок у кеші по одній з осей. У разі розмір кешу по обох осях однаковий, тобто. складає 32 сторінки. Загальна кількість сторінок у кеші 32х32 = 1024.

Для наочності міп рівні відображені на малюнку 2:

віртуальної текстури
Малюнок 2