Использование двух шейдеров вместо одного с операторами IF

9

Я работал над переносом относительно большого исходного кода ES 1.1 opengl на ES 2.0.

В OpenGL ES 2.0 (что означает, что все используют шейдеры), я хочу нарисовать чайник три раза.

  1. Первый, с однородным цветом (аля старый glColor4f).

  2. Второй, с цветом для каждой вершины (чайник также имеет свой массив цветов вершин)

  3. Третий, с текстурой на вершину

  4. И, возможно, четвертый, с текстурой для каждой вершины и цветом. А потом, может быть, 5-й, с нормальными также ..

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

Второй вариант - создать разные шейдеры для каждой ситуации. С некоторой пользовательской предварительной обработкой шейдеров это не так сложно сделать, но проблема заключается в затратах на производительность при переключении шейдеров между чертежными объектами. Я читал, что это не тривиально мало.

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

kamziro
источник

Ответы:

10

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

В худшем случае ветвление будет выглядеть примерно так:

  • Обе стороны ветви оцениваются.
  • компилятор шейдера сгенерирует команду «mix» или «step» и вставит ее в ваш код, чтобы решить, какую сторону использовать.

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

В « Руководстве по программированию OpenGL ES для iOS » от Apple (которое можно считать типичным для целевого оборудования) можно сказать о ветвлении:

Избегайте ветвления

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

  • Наилучшая производительность: ветвление на константе, известной при компиляции шейдера.
  • Приемлемо: ветвь на единой переменной.
  • Потенциально медленный: ветвление на значении, вычисленном внутри шейдера.

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

Даже если вы уверены, что находитесь в слоте «Приемлемо», вам все равно нужно учитывать, что при выборе 4 или 5 вариантов вы будете увеличивать количество команд в своих шейдерах. Вам следует знать об ограничениях на количество команд на целевом оборудовании и следить за тем, чтобы вы не превышали их, цитируя снова ссылку Apple выше:

Реализации OpenGL ES не обязаны реализовывать программный резерв при превышении этих пределов; вместо этого шейдер просто не может скомпилировать или связать.

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

Максимус Минимус
источник
3

Стоимость связывания шейдеров может быть не тривиальной, но она не станет вашим узким местом, если вы не рендерите тысячи элементов без пакетирования всех объектов, которые используют одни и те же шейдеры.

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

Кроме того, есть несколько других способов сделать это: «Uber-шейдеры» и небольшая хитрость с тем, как связаны шейдерные программы OpenGL.

«Uber-шейдеры» по сути являются первым выбором, за исключением ветвления, но у вас будет несколько шейдеров. Вместо того чтобы использовать ifзаявление, можно использовать препроцессор - #define, #ifdef, #else, #endif, и обобщать различные версии, в том числе собственно #defineс для того, что вам нужно.

vec4 color;
#ifdef PER_VERTEX_COLOR
color = in_color;
#else
color = obj_color;
#endif

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

//ins, outs, uniforms

float getShadowCoefficient();

void main()
{
    //shading stuff goes here

    gl_FragColor = color * getShadowCoefficient();
}

Тогда я мог бы иметь несколько других файлов шейдеров, которые определяют getShadowCoefficient(), необходимую униформу и ничего больше. Например, shadow_none.glslсодержит:

float getShadowCoefficient()
{
    return 1;
}

И shadow_simple.glslсодержит (упрощенно от моего шейдера, который реализует CSM):

in vec4 eye_position;

uniform sampler2DShadow shad_tex;
uniform mat4 shad_mat;

float getShadowCoefficient()
{
    vec4 shad_coord = shad_mat * eye_position;
    return texture(shad_tex, shad_coord).x;
}

И вы можете просто выбрать, хотите ли вы использовать затенение или нет, связав другой shadow_*шейдер. Это решение вполне может иметь больше накладных расходов, но я хотел бы думать, что компилятор GLSL достаточно хорош, чтобы оптимизировать любые дополнительные издержки по сравнению с другими способами сделать это. Я не проводил никаких тестов по этому вопросу, но мне нравится это делать.

Роберт Роухани
источник