- PVSM.RU - https://www.pvsm.ru -

Суперсовременный OpenGL. Часть 2

Суперсовременный OpenGL. Часть 2 - 1

Всем хорошего настроения и температуры за окном пониже. Как и обещал, публикую продолжение статьи по супер-пупер современному OpenGL. Кто не читал первую часть — Суперсовременный OpenGL. Часть 1 [1].

Возможно повезет и я смогу весь оставшийся материал впихнуть в эту статью, это не точно…

Array Texture

Текстурные массивы были добавлены еще в OpenGL 3.0, но почему-то мало кто пишет о них (информация надёжно прячется масонами). Все вы знакомы с программированием и знаете что такое масcив [2], хотя лучше я «подойду» с другой стороны.

Для уменьшения количества переключений между текстурами, а как следствие и снижению операций переключения состояний, люди используют текстурные атласы [3](текстура которая хранит в себе данные для несколько объектов). Но умные ребята из Khronos разработали нам альтернативу — Array texture. Теперь мы можем хранить текстуры как слои в этом массиве, то есть это альтернатива атласам. На OpenGL Wiki немного другое описание, про mipmaps и т.д., но мне оно кажется слишком сложным (ссылка [4]).

Преимущества использования этого подхода по сравнению с атласами в том, что каждый слой рассматривается как отдельная текстура с точки зрения wrapping и mipmapping.

Но вернемся к нашим баранам… Текстурный массив имеет три вида таргета:

  • GL_TEXTURE_1D_ARRAY
  • GL_TEXTURE_2D_ARRAY
  • GL_TEXTURE_CUBE_MAP_ARRAY

Код создания текстурного массива:

GLsizei width = 512;
GLsizei height = 512;
GLsizei layers = 3;
glCreateTextures(GL_TEXTURE_2D_ARRAY, 1, &texture_array);
glTextureStorage3D(texture_array, 0, GL_RGBA8, width, height, layers);

Самые внимательные заметили, что мы создаем хранилище для 2D текстур, но почему-то используем 3D массив, тут нет ошибки или опечатки. Мы храним 2D текстуры, но так как они расположены «слоями» получаем 3D массив (на самом деле, хранятся пиксельные данные, а не текстуры. 3D массив имеет 2D слои с данными пикселей).

Тут легко понять на примере 1D текстуры. Каждая строчка в 2D массиве пикселей представляет собой отдельный 1D слой. Также автоматически могут создаваться mipmap текстур.

На этом все сложности заканчиваются и добавление изображения на определенный слой довольно простое:

glTextureSubImage3D(texarray, mipmap_level, offset.x, offset.y, layer, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

При использование массивов нам надо немного поменять шейдер

#version 450 core

layout (location = 0) out vec4 color;
layout (location = 0) in vec2 texture_0;

uniform sampler2DArray texture_array;
uniform uint diffuse_layer;

float getCoord(uint capacity, uint layer)
{
	return max(0, min(float(capacity - 1), floor(float(layer) + 0.5)));
}

void main()
{
	color = texture(texture_array, vec3(texture_0, getCoord(3, diffuse_layer)));
}

Самым лучшим вариантом будет расчитывать нужный слой за пределами шейдера, для этого мы можем использовать UBO [5] / SSBO [6] (используется также для передачи матриц, да и многих других данных, но это как-то в другой раз). Если уж кому не терпится тык_1 [7] и тык_2 [8], можете почитать.

Что касается размеров, то есть GL_MAX_ARRAY_TEXTURE_LAYERS который равен 256 в OpenGL 3.3 и 2048 в OpenGL 4.5.

Cтоит рассказать про Sampler Object (не относиться к Array texture, но полезная вещь) — это объект который используется для настройки состояний текстурного юнита, независимо от того, какой объект сейчас привзяан к юниту. Он помогает отделать состояния сэмплера от конкретного текстурного объекта, что улучшает абстракцию.

GLuint sampler_state = 0;
glGenSamplers(1, &sampler_state);
glSamplerParameteri(sampler_state, GL_TEXTURE_WRAP_S, GL_REPEAT);
glSamplerParameteri(sampler_state, GL_TEXTURE_WRAP_T, GL_REPEAT);
glSamplerParameteri(sampler_state, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(sampler_state, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glSamplerParameterf(sampler_state, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);

Я только что создал объект сэмплера, включил линейную фильтрацию и 16-кратную анизотропную фильтрацию для любого текстурного юнита.

GLuint texture_unit = 0;
glBindSampler(texture_unit, sampler_state);

Тут мы просто биндим сэмплер к нужному текстурному юниту, а когда он нам перестает быть нужным биндим 0 к данному юниту.

glBindSampler(texture_unit, 0);

Когда мы привязали сэмплер его настройки имеют приоритет над настройками текстурного юнита. Результат: нет необходимости изменять существующую кодовую базу для добавления объектов сэмплера. Вы можете оставить создание текстур как есть (со своими собственными состояниями сэмплера) и просто добавить код для управления и использования объектов сэмплера.

Когда настало время удалить объект, просто вызываем эту функцию:

glDeleteSamplers(1, &sampler_state);

Texture View

Я переведу это как «текстурный указатель(может правильнее ссылки, я хз)», так как не знаю лучшего перевода.

Что же такое указатели в перспективе OpenGL?

Все очень просто, это указатель на данные immutable(именно изменяемой) текстуры, как видим на картинке нижу.

Суперсовременный OpenGL. Часть 2 - 2

По факту это объект, который расшаривает данные текселей определенного текстурного объекта, для аналогии можно привести std::shared_ptr из С++ [9]. Пока существует хоть один указатель на текстуру, исходная текстура не будет удалена драйвером.

В wiki [10] более детально описано, а так же стоит почитать о типах текстуры и таргета (они не обязательно должны совпадать)

Для создания указателя нам надо получить дескриптор текстуры вызвав glGenTexture [11](никаких инициализаций не нужно) и потом glTextureView [12].

glGenTextures(1, &texture_view);
glTextureView(texture_view, GL_TEXTURE_2D, source_name, internal_format, min_level, level_count, 5, 1);

Текстурные указатели могут указывать на N-й уровень mipmap'a, довольно полезно и удобно. Указатели могут быть как текстурными массивами, частями массивов, определенным слоем в этом массиве, а может быть срезом 3D текстуры как 2D текстура.

Single buffer for index and vertex

Ну, тут все будет быстро и просто. Раньше спецификация OpenGL по Vertex Buffer Object [13] рекомендовала, что б разработчик разделял данные вершин и индексов в разные буферы, но сейчас это не обязательно (долгая история почему необязательно).
Все, что нам нужно, это сохранить индексы перед вершинами и сообщить где вершины начинаются (точнее смещение), для этого есть команда glVertexArrayVertexBuffer [14]

Вот как бы мы это сделали:

GLint alignment = GL_NONE;
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment);

const GLsizei ind_len = GLsizei(ind_buffer.size() * sizeof(element_t));
const GLsizei  vrt_len = GLsizei(vrt_buffer.size() * sizeof(vertex_t));

const GLuint  ind_len_aligned = align(ind_len, alignment);
const GLuint  vrt_len_aligned = align(vrt_len, alignment);

GLuint buffer 	= GL_NONE;
glCreateBuffers(1, &buffer);
glNamedBufferStorage(buffer, ind_len_aligned + vrt_len_aligned, nullptr, GL_DYNAMIC_STORAGE_BIT);

glNamedBufferSubData(buffer, 0, ind_len, ind_buffer.data());
glNamedBufferSubData(buffer, ind_len_aligned, vrt_len, vrt_buffer.data());

GLuint vao 	= GL_NONE;
glCreateVertexArrays(1, &vao);
glVertexArrayVertexBuffer(vao, 0, buffer, ind_len_aligned, sizeof(vertex_t));
glVertexArrayElementBuffer(vao, buffer);

Tessellation and compute shading

Я не буду вам рассказывать про шейдер тесселяции, так как материала в гугле по этому поводу очень много(на русском), сразу приступим к рассказу про шейдер для расчетов (блииин, тоже много материала, расскажу вкратце).

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

Картинка, я не знаю как ее назвать (типа потоки группируются).

Суперсовременный OpenGL. Часть 2 - 3

Для чего можем использовать?

  • Обработка изображения
    1. Блур
    2. Алгоритмы на основе плиток (отложенное затенение)
  • Симуляции
    1. Частицы
    2. Вода

Дальше не вижу смысла писать, тоже есть много инфы в гугле, вот простой пример использования:

//биндим пйплайн с расчетным шейдером
glUseProgramStages( pipeline, GL_COMPUTE_SHADER_BIT, cs);
//биндим текстуру, как изображение для чтения/записи
glBindImageTexture( 0, tex, 0, GL_FALSE, 0, GL_WRITE_ONLY,
GL_RGBA8);
//запускаем 80x45 потоковых групп (достаточно для 1280х720)
glDispatchCompute( 80, 45, 1);

Path rendering

Это новое(уже не новое) расширение от NVidia [15], его основная цель — векторный 2D рендеринг. Мы его можем использовать для текстов или UI, а поскольку графика векторная, она не зависит от разрешение, что несомненно большой плюс и наш UI'чик будет прекрасно смотреться.

Основной концепцией является — трафарет, затем покрытие(cover в оригинале). Устанавливаем трафарет пути, затем визуалезируем пиксели.

Для менеджмента используются стандартный GLuint, а так же функции создания и удаления имеют стандартное именование.

glGenPathsNV // генерация
glDeletePathsNV // удаление

Вот немного о том, как мы можем получить путь:

  • SVG или PostScript в string'e
    glPathStringNV
  • массив команд с соответствующими координатами
    glPathCommandsNV

    и для обновления данных

    glPathSubCommands, glPathCoords, glPathSubCoords
  • шрифты
    glPathGlyphsNV, glPathGlyphRangeNV
  • линейные комбинации существующих путей (интерполирование одного, двух и более путей)
    glCopyPathNV, glInterpolatePathsNV, glCombinePathsNV
  • линейное преобразование существующего пути
    glTransformPathNV

Список стандартных команд:

  • move-to (x, y)
  • close-path
  • line-to (x, y)
  • quadratic-curve (x1, y1, x2, y2)
  • cubic-curve (x1, y1, x2, y2, x3, y3)
  • smooth-quadratic-curve (x, y)
  • smooth-cubic-curve (x1, y1, x2, y2)
  • elliptical-arc (rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y)

Вот как выглядит строка пути в PostScript:

"100 180 moveto 40 10 lineto 190 120 lineto 10 120 lineto 160 10 lineto closepath” //звезда
"300 300 moveto 100 400 100 200 300 100 curveto 500 200 500 400 300 300 curveto
closepath” //сердце

А вот в SVG:

"M100,180 L40,10 L190,120 L10,120 L160,10 z” //звезда
"M300 300 C 100 400,100 200,300 100,500 200,500 400,300 300Z” //сердце

Еще есть много всяких плюшек с видами заполнений, краев, изгибов:

Суперсовременный OpenGL. Часть 2 - 4

Я не буду тут все описывать, так как материала очень много и это займет целую статью (если будет интересно, то как-нибудь напишу).

Вот список примитивов для отрисовки

  • Cubic curves
  • Quadratic curves
  • Lines
  • Font glyphs
  • Arcs
  • Dash & Endcap Style

Вот немного кода, а то уж очень много текста:

//Компилирование SVG пути
glPathStringNV( pathObj, GL_PATH_FORMAT_SVG_NV,
 strlen(svgPathString), svgPathString);
//заполняем трафарета
glStencilFillPathNV( pathObj, GL_COUNT_UP_NV, 0x1F);
//конфигурация
//покрываем трафарет (визуализируем пикселями)
glCoverFillPathNV( pathObj, GL_BOUNDING_BOX_NV);

Вот и все.

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

Хотелось бы попросить написать в комментариях, о чем бы вы хотели почитать и что вам интересно (Cocos2dx, OpenGL, C++, что-то еще).

Всем спасибо за внимание.

Автор: kiwhy

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/opengl/321946

Ссылки в тексте:

[1] Суперсовременный OpenGL. Часть 1: https://habr.com/ru/post/456932/

[2] масcив: https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%81%D1%81%D0%B8%D0%B2_(%D1%82%D0%B8%D0%BF_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85)

[3] текстурные атласы: https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BA%D1%81%D1%82%D1%83%D1%80%D0%BD%D1%8B%D0%B9_%D0%B0%D1%82%D0%BB%D0%B0%D1%81

[4] ссылка: https://www.khronos.org/opengl/wiki/Array_Texture

[5] UBO: https://www.khronos.org/opengl/wiki/Uniform_Buffer_Object

[6] SSBO: https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object

[7] тык_1: http://on-demand.gputechconf.com/gtc/2014/video/S4379-opengl-44-scene-rendering-techniques.mp4

[8] тык_2: https://pdfs.semanticscholar.org/dced/183da56e381975911176033dd2eac0f5638c.pdf

[9] std::shared_ptr из С++: https://ru.cppreference.com/w/cpp/memory/shared_ptr

[10] wiki: https://www.khronos.org/opengl/wiki/Texture_Storage#View_texture_aliases

[11] glGenTexture: http://docs.gl/gl4/glGenTextures

[12] glTextureView: http://docs.gl/gl4/glTextureView

[13] Vertex Buffer Object: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_vertex_buffer_object.txt

[14] glVertexArrayVertexBuffer: http://docs.gl/gl4/glBindVertexBuffer

[15] NVidia: http://developer.download.nvidia.com/assets/gamedev/files/Getting_Started_with_NV_path_rendering.pdf

[16] Источник: https://habr.com/ru/post/457380/?utm_campaign=457380&utm_source=habrahabr&utm_medium=rss