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

в 14:36, , рубрики: c++, CGI (графика), OpenGL, графика, Программирование, программирование игр, Работа с 3D-графикой, разработка игр

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

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

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

Array Texture

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

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

Преимущества использования этого подхода по сравнению с атласами в том, что каждый слой рассматривается как отдельная текстура с точки зрения 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 / SSBO (используется также для передачи матриц, да и многих других данных, но это как-то в другой раз). Если уж кому не терпится тык_1 и тык_2, можете почитать.

Что касается размеров, то есть 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 из С++. Пока существует хоть один указатель на текстуру, исходная текстура не будет удалена драйвером.

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

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

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 рекомендовала, что б разработчик разделял данные вершин и индексов в разные буферы, но сейчас это не обязательно (долгая история почему необязательно).
Все, что нам нужно, это сохранить индексы перед вершинами и сообщить где вершины начинаются (точнее смещение), для этого есть команда glVertexArrayVertexBuffer

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

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, его основная цель — векторный 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js