Как создать простую оболочку GLSL для использования шейдеров

ОБНОВЛЕНИЕ: Поскольку мне было нужно что-то сразу, я создал простую шейдерную оболочку, которая делает то, что мне нужно. Вы можете найти его здесь: ShaderManager на GitHub. Обратите внимание, что он предназначен для Objective-C/iOS, поэтому может быть не пригоден для всех. Если у вас есть предложения по улучшению дизайна, пожалуйста, дайте мне знать!

Исходная проблема:

Я новичок в использовании шейдеров GLSL. Я достаточно хорошо знаком с языком GLSL и интерфейсом OpenGL, но мне сложно создавать простой API, с помощью которого можно использовать шейдеры.

Интерфейс OpenGL C для взаимодействия с шейдерами кажется громоздким. Кажется, я не могу найти учебники в сети, которые охватывают дизайн API таких вещей.

Мой вопрос таков: У любого есть хороший, простой, дизайн или шаблон API, чтобы обернуть API-интерфейс шейдерной программы OpenGL?

Возьмем следующий простой пример. Скажем, у меня есть один вершинный шейдер, который просто эмулирует фиксированную функциональность и два флеш-шейдера - один для рисования гладких прямоугольников и один для рисования гладких кругов. У меня есть следующие файлы:

Shader.vsh : Simple vertex shader, with the following inputs/outputs:
    -- Uniforms: mat4 Model, mat4 View, mat4 Projection
    -- Attributes: vec4 Vertex, vec2 TexCoord, vec4 Color
    -- Varying: vec4 vColor, vec2 vTexCoord

Square.fsh : Fragment shader for drawing squares based on tex coord / color
Circle.fsh : Fragment shader for drawing circles based on tex coord / color

Базовая привязка

Теперь, каков стандартный способ их использования? Связать ли эти шейдеры с двумя шейдерами OpenGL? То есть:

Shader.vsh + Square.fsh = SquareProgram
Shader.vsh + Circle.fsh = CircleProgram

Или я вместо этого создаю одну большую программу, где шейдеры фрагментов проверяют некоторые условные равномерные переменные и вызывают функцию шейдера для генерации их результата. Например:

Shader.vsh + Square.fsh + Circle.fsh + Main.fsh = ShaderProgram
//Main.fsh here would simply check whether to call out to square or circle

С двумя отдельными программами мне, вероятно, нужно будет позвонить

glUseProgram(CircleProgram); or glUseProgram(SquareProgram);

Перед каждым типом элемента я хочу рисовать. Затем я должен был установить форму (Модель/Вид/Проекция) и атрибуты каждой программы, прежде чем использовать ее. Это кажется таким громоздким.

С помощью единственной опции ShaderProgram мне все равно нужно установить какой-то булевский переключатель (круг или квадрат) в шейдере фрагмента, который будет проверяться перед рисованием каждого пикселя. Это также кажется сложным.

Как замечание, мне удалось связать два фрагментарных шейдера, каждый с функцией main(), в одну шейдерную программу? Как OpenGL узнает, какой из них вызывать?

Установка переменных

Вызовы:

glUniform*
glVertexAttribPointer

Используются для установки униформ и местоположений указателя атрибутов в текущей программе.

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

То есть для каждой фигуры, которую я хочу рисовать, необходимо будет установить атрибуты координаты вершины и текстуры - требуя дескрипторов для этих атрибутов, сгенерированных OpenGL.

Камера должна будет установить свою матрицу проекции как однородную в вершинном шейдере, тогда как классу, управляющему стеке матрицы модели, необходимо будет установить свою собственную униформу в вершинном шейдере.

Изменение шейдеров на части путем рисования сцены означало бы, что всем этим классам потребуется снова установить их униформы и атрибуты.

Как большинство людей проектируют вокруг этого?

Глобальный словарь шейдеров, к которым обращается дескриптор или имя, с геттерами и сеттерами для их параметров?

Конструкция OO с шейдерными объектами, каждая из которых имеет параметры?

Я просмотрел следующие обертки:

Jon Teapot: GLSL Shader Manager - Это оболочка шейдеров в классах С++. Похоже, что это не что иное, как оболочка, которая обеспечивает принципы OO в C API, в результате чего API С++ практически одинаковый.

Я после любого вида дизайна, который упрощает использование программ Shader, и меня не интересует конкретная используемая парадигма (OO, процедурная и т.д.)

Ответ 1

Основные ссылки:

Здесь нет стандартного способа. Существует как минимум 2 общих подхода:

  • Монолитный - один шейдер охватывает многие случаи, используя единые логические переключатели. Эти ветки не ухудшают производительность, потому что результат условия является постоянным для любой группы фрагментов (фактически, для всех фрагментов).

  • Компоновка многопозиционных программ - основной шейдер объявляет набор точек ввода (например, get_diffuse, get_specular и т.д.), которые реализованы в отдельных прикрепленных объектах шейдера. Это подразумевает индивидуальный шейдер для каждого объекта, но помогает любое кэширование.

Установочные переменные: униформа

Я просто опишу подход, который я разработал.

В каждой шейдерной программе есть список одинаковых словарей. Он использовался для заполнения единого списка источников при связывании программ (пере). Когда программа активирована, она проходит через единый список, извлекает значения из своих источников и загружает их в GL. В результате данные напрямую не связаны с программой пользовательского шейдера, и все, что управляет ею, не заботится о том, чтобы программа использовала ее.

Один из этих словарей может быть, например, ядром, содержащим модель, преобразования вида, проекцию камеры и, возможно, что-то еще.

Установка переменных: Атрибуты

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

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

P.S. Вы можете найти реальную реализацию этих идей здесь: http://code.google.com/p/kri/

Ответ 2

Я вижу, что это помечено iOS, поэтому, если вы частично относитесь к Objective-C, я бы неплохо посмотрел на класс оболочки Джеффа ЛаМарче GLProgram, который он описывает здесь и имеет источник здесь. Я использовал его в своих приложениях, чтобы упростить некоторые настройки программы шейдеров и сделать код немного чистым.

Например, вы можете настроить шейдер и его атрибуты и униформы, используя следующий код:

sphereDepthProgram = [[GLProgram alloc] initWithVertexShaderFilename:@"SphereDepth" fragmentShaderFilename:@"SphereDepth"];
[sphereDepthProgram addAttribute:@"position"];
[sphereDepthProgram addAttribute:@"inputImpostorSpaceCoordinate"];
if (![sphereDepthProgram link])
{
    NSLog(@"Depth shader link failed");
    NSString *progLog = [sphereDepthProgram programLog];
    NSLog(@"Program Log: %@", progLog); 
    NSString *fragLog = [sphereDepthProgram fragmentShaderLog];
    NSLog(@"Frag Log: %@", fragLog);
    NSString *vertLog = [sphereDepthProgram vertexShaderLog];
    NSLog(@"Vert Log: %@", vertLog);
    [sphereDepthProgram release];
    sphereDepthProgram = nil;
}

sphereDepthPositionAttribute = [sphereDepthProgram attributeIndex:@"position"];
sphereDepthImpostorSpaceAttribute = [sphereDepthProgram attributeIndex:@"inputImpostorSpaceCoordinate"];
sphereDepthModelViewMatrix = [sphereDepthProgram uniformIndex:@"modelViewProjMatrix"];
sphereDepthRadius = [sphereDepthProgram uniformIndex:@"sphereRadius"];

Когда вам нужно использовать шейдерную программу, вы затем сделаете что-то вроде следующего:

[sphereDepthProgram use];

Это не затрагивает проблемы ветвления и отдельных шейдеров, которые вы поднимаете выше, но реализация Jeff действительно обеспечивает отличную инкапсуляцию некоторого кода установки шейдерного шейдера OpenGL ES.