Steps3d - tutorials - рендеринг в верховий буфер в opengl

Як відомо, передача даних від CPU до GPU є досить дорогою операцією і тому бажано кількість подібних передач мінімізувати.

Однак у багатьох випадках виникає необхідність в постійній передачі великих верхових масивів GPU. Подібна ситуація виникає у випадках, коли має місце занадто складний закон перетворення даних в вершинах (який або взагалі не можна реалізувати через вершинний шейдер або це дуже дорого з точки зору швидкодії) або ж коли відбувається зміна топології об'єкта (змінюється кількість вершин, організація їх в примітиви і т.п.).

У таких випадках було б вкрай зручно мати можливість побудови таких вершинних масивів безпосередньо на GPU. Тоді відпала б необхідність у дорогій пересилання даних від CPU до GPU і всі операції з вершинами велися б цілком на GPU і в межах GPU.

На жаль GPU (не будемо розглядати SM4) можуть будувати самі тільки текстури - для цього досить використовувати розширення EXT_framebuffer_object. що дозволяє здійснювати рендеринг прямо в текстуру. В результаті цього ми можемо побудувати прямо на GPU текстуру будь-якого розміру і формату (підтримуваного даними GPU, природно).

Однак якщо уважно подивитися, то стає видно, що текстура відрізняється від вершинного масиву виключно способом використання - і текстура і верховий масив являє собою масив векторів (1-4-мірних) заданого типу (в тому числі і float).

Тому якщо додати можливість просто копіювати текстуру (або її частина) в верховий масив (а це буде операція копіювання всередині пам'яті GPU, що відбувається з дуже великою швидкістю), то ми як раз і отримуємо те, що нам треба.

Саме таку можливість і надають розширення EXT_pixel_buffer_object і ARB_pixel_buffer_object.

В результаті можна за допомогою спеціального фрагментного шейдера здійснити побудова текстури (через рендеринг в текстуру, бажано при цьому використовувати текстури з floating-point -компонента), а потім просто скопіювати отримані дані прямо в верховий масив.

Подібний прийом і називається рендерингом в верховий масив (render-to-vertex-buffer).

Побудова ландшафту по карті висот

Як перший приклад розглянемо перетворення текстури, що містить карту висот, в масив вершин, де кожному тексель текстури буде відповідати вершина, z -коордіната якої буде пропорційна яскравості даного пікселя.

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

В результаті цього прямо на GPU на кожному кадрі будується масив з 256 2 вершин і не залишаючи меж GPU даний масив вершин відразу ж виводиться.

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

Для зручності роботи крім класів FrameBuffer і GlslProgram тут використовується також клас VertexBuffer. опис та реалізація якого наводяться нижче.

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

Steps3d - tutorials - рендеринг в верховий буфер в opengl

Рис 1. Ландшафт, побудований по карті висот.

Нижче наводяться фрагментного шейдери для рендеринга в текстуру і рендеринга отриманого вершинного масиву.

Візуалізація анимированной поверхні води

В якості наступного прикладу на використання рендеринга в текстуру розглянемо рендеринг анимированной води. Замість традиційного використання bump mapping 'а ми будемо на ходу будувати анімовані масиви вершин і нормалей, використовуючи в якості анімуйте функції турбулентність (turbulence).

При цьому дуже зручно скористатися можливістю рендеринга відразу в дві текстури (MRT, Multiple Render Targets) - в одну текстуру будуть виводиться координати вершин, а в іншу - вектора нормалі в цих вершинах.

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

На наступному лістингу наводиться Фрагментний шейдер, який використовується для побудови вершин і нормалей в них.

Для рендеринга поверхні води використовується наступний Фрагментний шейдер.

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

На наступному малюнку наведено виходить зображення води.

Steps3d - tutorials - рендеринг в верховий буфер в opengl

Рис 2. Поверхня води.

скелетна анімація

В якості наступного прикладу розглянемо реалізацію скелетної анімації моделей з гри DooM III за допомогою рендеринга в верховий буфер.

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

Це пов'язано з тим, що для кожної кістки необхідно передати досить велика кількість даних. Поки всі ці дані містяться в стандартні вершинні атрибути OpenGL все добре.

Однак при кількості кісток на вершину 5 і більше (а в моделях з DooM III використовується до 10 кісток на вершину) передаються для кожної вершини дані вже не поміщаються в стандартні атрибути OpenGL і доводиться використовувати вершинні атрибути загального вигляду (glVertexAttrib *).

Їх кількість так само обмежена і, що більш важливо, звичайно OpenGL працює зі стандартними вершинними атрибутами більш ефективно (дивлячись статтю з сайту developer.nvidia.com про pseudoinstancing в OpenGL).

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

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

Спочатку розглянемо які саме текстури нам потрібні і які дані будуть в них розміщуватися.

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

Легко видно, що, що при використанні RGBA-текстур з floating-point компонентами нам досить двох текселей на кістку.

Оскільки кісток в скелеті зазвичай буває небагато, то найпростіше використовувати текстуру типу GL_TEXTURE_RECTANGLE_ARB (як і для всіх інших текстур, які використовуються для розрахунку скелетної анімації) розміром 2 * numJoints на 1 текселей.

При цьому вміст цієї текстури змінюватиметься для кожного кадру анімації.

Також нам знадобляться дві текстури для роботи з вершинами. Перша з них (weightsMap) буде для кожного ваги зберігати зміщення (pos), внесок даного ваги (weight) і індекс відповідної кістки (jointIndex).

Тут нам також досить двох текселов (в форматі RGBA) на один вага:

Як формат даної текстури також зручно використовувати GL_TEXTURE_RECTANGLE_ARB, але оскільки кількість ваг може бути досить великим, то краще заздалегідь поставити досить велике значення ширини текстури (наприклад, 512), а висоту текстури обчислювати на ходу, виходячи із загального числа ваг в моделі.

Нарешті нам потрібна текстура (vertexWeightsMap), яка для кожної вершини буде зберігати два значення - номер першого ваги (weightIndex) і кількість поспіль ваг (weightCount).

Тут нам досить одного тексель на вершину.

Зверніть увагу, що більшість моделей з DooM III насправді складаються з декількох подмоделей (зазвичай це пов'язано з необхідністю використання різних шейдеров для різних частин моделі).

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

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

Весь вихідний код можна завантажити за цим посиланням. Також можна завантажити відкомпілювалися версії для M $ Windows. Linux і Mac OS X.