Лучший подход к построению динамической цепочки фильтров на основе шейдеров OpenGL ES 2.0
Я работаю на iOS 6 (7 тоже, если хотите, и все будет по-другому) и GL ES 2.0. Идея состоит в том, чтобы CAEAGLLayer имел динамическую цепочку фильтров на основе шейдеров, которая обрабатывает его свойство содержимого и отображает конечный результат. Фильтры могут быть добавлены / удалены в любой точке цепочки.
До сих пор я придумал реализацию, но мне интересно, есть ли лучшие способы сделать это. Моя реализация примерно так выглядит:
- Базовый класс фильтра, от которого наследуются конкретные фильтры, создавая шейдерную программу (вершина / фрагментная комбинация) для любого фильтра / образа, который они реализуют.
Подкласс CAEAGLLayer, который реализует цепочку фильтров и к которым добавляются фильтры. Алгоритм высокоуровневой обработки:
// 1 - Assume whenever the layer's content property is changed to an image, a copy of the image gets stored in a sourceImage property. // 2 - Assume changing the content property or adding / removing an image unit triggers this algorithm. // 3 - Assume the whole filter chain basically processes a quad with position and texture coordinates thru a VBO. // 4 - Assume all shader programs (by shader program I mean a vertex and fragment shader pair in a single program) have access to texture unit 0. // 5 - Assume P shader programs. load imageSource into a texture object bound to GL_TEXTURE2D and pointing to to GL_TEXTURE0 attach bound texture object to GL_FRAMEBUFFER GL_COLOR_ATTACHMENT0 (so we are doing render-to-texture, which will be accessible to fragment shaders) for p = program identifier 0 up to P - 2: glUseProgram(p) glDrawArrays() attach GL_RENDERBUFFER to GL_FRAMEBUFFER GL_COLOR_ATTACHMENT0 (GL_RENDERBUFFER in turn has its storage set to the layer itself); p = program identifier P - 1 (last program in the chain) glUseProgram(p) glDrawArrays() present GL_RENDERBUFFER onscreen
Этот подход, кажется, работает до сих пор, но есть несколько вещей, которые меня интересуют:
Лучший способ реализовать добавление / удаление фильтров:
Добавление и удаление программ кажется наиболее логичным подходом прямо сейчас. Однако это означает, что одна программа на плагин и переключение между ними во время рендеринга. Интересно, как бы сравнились эти другие подходы:
- Присоединение / отсоединение шейдерных пар и повторное связывание одной составной программы вместо добавления / удаления программ. Руководство по программированию OpenGL ES 2.0 говорит, что вы не можете этого сделать. Однако, поскольку настольный GL допускает использование нескольких шейдерных объектов в одной программе, мне все равно любопытно, будет ли это лучше, если ES поддержит его.
- Хранение фильтров в текстовом формате (их код внутри функции, отличной от
main
) и вместо этого скомпилируйте их все в монолитную пару шейдеров (с добавлениемmain
конечно) каждый раз, когда фильтр добавляется / удаляется.
Лучший способ реализовать кэширование для каждого фильтра:
Прямо сейчас, добавление / удаление любого количества фильтров в любой точке цепочки требует повторного запуска всех программ для визуализации окончательного изображения. Было бы хорошо, однако, если бы я мог как-то кэшировать выходные данные каждого фильтра. Таким образом, удаление, добавление или обход фильтра требует только запуска фильтров после точки вставки / удаления / обхода в цепочке. Я могу думать о наивном подходе: на каждом проходе программы, привязывать различные объекты текстуры к GL_TEXTURE0
и к GL_COLOR_ATTACHMENT0
кадрового буфера. Таким образом, я могу сохранить вывод каждого фильтра. Однако создание новой текстуры, привязка и изменение вложения кадрового буфера один раз для каждого фильтра кажется неэффективным.
1 ответ
Я не могу сказать много о проблеме кэширования вывода фильтра, но что касается переключения фильтра... Расширение EXT_separate_shader_objects предназначено для решения этой самой проблемы и поддерживается на каждом устройстве, работающем под управлением iOS 5.0 или более поздней версии. Вот краткий обзор:
Существует новый удобный API для компиляции шейдерных программ, который также заботится о том, чтобы сделать их "разделяемыми":
_vertexProgram = glCreateShaderProgramvEXT(GL_VERTEX_SHADER, 1, &source);
Программные конвейерные объекты управляют состоянием вашей программы и позволяют смешивать и сопоставлять уже скомпилированные шейдеры:
GLuint _ppo; glGenProgramPipelinesEXT(1, &_ppo); glBindProgramPipelineEXT(_ppo); glUseProgramStagesEXT(_ppo, GL_VERTEX_SHADER_BIT_EXT, _vertexProgram); glUseProgramStagesEXT(_ppo, GL_FRAGMENT_SHADER_BIT_EXT, _fragmentProgram);
Смешивание и сопоставление шейдеров может затруднить привязку атрибутов, поэтому вы можете указать это в шейдере (также для вариантов):
#extension GL_EXT_separate_shader_objects : enable layout(location = 0) attribute vec4 position; layout(location = 1) attribute vec3 normal;
Униформа устанавливается для шейдерной программы, к которой они относятся:
glProgramUniformMatrix3fvEXT(_vertexProgram, u_normalMatrix, 1, 0, _normalMatrix.m);