×
Menu

NiStencilProperty - description of mechanics

 
Стенсил буфер (Stencil Buffer — буфер шаблона или буфер трафарета) — это дополнительный буфер, соответствующий размеру выводимого кадра, то есть каждому пикселю изображения на экране соответствует свое значение в стенсил буфере. Каждый раз когда точка рисуется на экран, то кроме тестов, вроде сравнения с глубиной в Z-буфере, она проходит еще и стенсил тест. То есть, например, можно сказать — точка рисуется, только если в стенсиле значение больше единицы. С другой стороны, можно сказать, как изменить значение стенсила после того как пиксель в этом месте отрисуется.
Стенсил буфер используется при создании таких спецэффектов, как тени, отражения, плавные переходы из одной картинки в другую, создания конструктивной стереометрии (CSG) и др. (С)
https://gamedev.ru/code/terms/CSG
https://gamedev.ru/code/terms/ZBuffer
Буфер трафарета используется для применения маски к пикселям в изображении для создания специальных эффектов. Маска определяет рисуется пиксель или нет. Специальные эффекты включают компоновку, перевод изображений, растворение, затемнение и вытеснение, контуры и силуэты, а также двусторонний трафарет. (С)
 
Это выдержки из статей по OpernGL с описаниями настроек свойствам трафарета для некоторых случаев.
Тексты относящееся к коду OpGl - удалены, т.к. для ниф файлов без надобности.
https://habr.com/ru/post/344238/ (С)
В свою очередь это перевод:
https://learnopengl.com/#!Advanced-OpenGL/Stencil-testing

Тест трафарета
 
Трафаретный буфер, обычно, содержит 8 бит на каждое трафаретное значение, что в сумме дает 256 различных значений трафарета на фрагмент/пиксель. Мы можем установить эти значения на наш вкус, а затем отбрасывать или сохранять фрагменты, всякий раз, когда определенный фрагмент имеет определенное трафаретное значение.
 
Простой пример трафаретного буфера приведен ниже:
Сначала трафаретный буфер заполняется нулями, и затем область буфера, выглядящая как прямоугольная рамка, заполняется единицами. Отображаются только те фрагменты сцены, трафаретное значение которых равно единице, остальные — отбрасываются.
 
Т.е. Stencil Ref = 1. - прим. ред.
 
Операции трафаретного буфера позволяют нам установить разное значение для трафаретного буфера там, где мы отображаем фрагменты. Изменяя значения трафаретного буфера во время рендеринга, мы осуществляем операцию записи. В той же (или следующей) итерации рендеринга мы можем выполнить чтение значений из буфера, чтобы на основе прочитанных значений, отбросить или принять определенные фрагменты. Используя трафаретный буфер, вы можете дурачиться как угодно, но общая схема такова:
 
    Включить запись в трафаретный буфер.
    Отрисовать объекты, обновить содержимое трафаретного буфера.
    Отключить запись в трафаретный буфер.
    Отрисовать (другие) объекты, на этот раз отбрасывая определенные объекты, основываясь на содержимом трафаретного буфера.
 
В итоге, можно сказать, что, используя трафаретный буфер, мы можем отбросить определенные фрагменты, основываясь на фрагментах других объектов сцены.
 
Вы можете включить трафаретное тестирование, включив GL_STENCIL_TEST. С этого момента все вызовы рендеринга будут, так или иначе, влиять на трафаретный буфер.
(Т.е. Stencil Enabled = 1 в ниф файле, в "нашем" случае - прим. ред.)
 
Для трафаретного буфера есть аналог функции glDepthMask, используемой для параметризации теста глубины.
Функция glStencilMask позволяет нам установить битовую маску, которая будет участвовать в операции побитового И со значениями трафарета, записываемыми в буфер. По умолчанию, битовая маска равна единице, что не влияет на выходные данные, но если бы мы установили маску в 0x00, то все трафаретные значения в конце концов были бы записаны как нули.
 
В большинстве случаев просто записывайте 0x00 или 0xFF в трафаретную маску, но неплохо было бы знать, что существует возможность устанавливать пользовательские битовые маски.
(Stencil Ref и Stencil Mask в ниф файлах - прим ред.)
 
Трафаретные функции
 
Также как и в тесте глубины у нас есть определенная возможность контролировать, когда трафаретный тест будет пройден, а когда — нет, и как это должно повлиять на трафаретный буфер. У нас есть всего две функции, которые мы можем использовать для настройки трафаретного тестирования: glStencilFunc и glStencilOp.
 
Функция glStencilFunc(GLenum func, GLint ref, GLuint mask) имеет три параметра:
 
    func: устанавливает функцию трафаретного тестирования. Эта функция применяется к хранимому значению трафарета и значению параметра ref. Возможные варианты: GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL и GL_ALWAYS. Смысловое значение этих функций схоже с функциями теста глубины.
 
    ref: определяет эталонное значение для трафаретного теста. Содержимое трафаретного буфера сравнивается с этим значением.
    mask: устанавливает маску, использующуюся в операции побитового И с хранимым и эталонным значением перед их непосредственным сравнением. По умолчанию устанавливается в 1.
 
Итак, в случае нашего простого примера трафарета, который мы показали в начале, функция будет такой:
 
glStencilFunc(GL_EQUAL, 1, 0xFF)
 
Это говорит OpenGL, что, всякий раз, когда трафаретное значение фрагмента равно ссылочному значению 1, фрагмент проходит тест и рисуется, в противном случае — отбрасывается.
 
Но функция glStencilFunc описывает только то, что OpenGL следует делать с содержимым трафаретного буфера, а не то, как мы можем обновить буфер. Здесь нам на помощь приходит функция glStencilOp.
 
Функция glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) содержит три параметра, с помощью которых мы можем определить действия для каждого варианта:
 
    sfail: действие, выполняемое в случае провала трафаретного теста.
    dpfail: действие, выполняемое в случае, если трафаретный тест пройден, а тест глубины — нет.
    dppass: действие, выполняемое в случае, если оба теста пройдены
Fail Action Z Fail Action Pass Action - речь об этих параметрах в ниф файле. Прим. ред.
 
Затем, для каждого случая можно выполнить одно из следующих действий:
Действие      Описание
GL_KEEP      Хранящееся в данный момент значение трафарета сохраняется.
GL_ZERO      Трафаретное значение обнуляется.
GL_REPLACE      Трафаретное значение заменяется эталонным значением, установленным функцией glStencilFunc.
GL_INCR      Трафаретное значение увеличивается на единицу, если оно меньше максимального значения.
GL_DECR      Трафаретное значение уменьшается на единицу, если оно превышает минимальное значение.
GL_INVERT      Побитово инвертирует текущее значение буфера трафарета.
 
По умолчанию аргументы функции glStencilOp устанавливаются в (GL_KEEP, GL_KEEP, GL_KEEP), так что, независимо от результатов тестов, значения в буфере трафарета сохраняются. Стандартное поведение не обновляет трафаретный буфер, поэтому, если мы хотим писать в трафаретный буфер, то нам нужно установить по крайней мере одно действие отличное от стандартного для любого варианта.
 
Итак, используя glStencilFunc и glStencilOp мы можем точно установить когда и как мы хотим обновлять трафаретный буфер, и мы также определяем, когда трафаретный тест будет пройден, а когда — нет, то есть когда фрагменты должны быть отброшены.
 
Обводка объектов
 
Маловероятно, что вы полностью поняли, как работает трафаретное тестирование на основе предыдущих разделов, поэтому мы продемонстрируем полезный прием, который может быть реализован с помощью трафаретного тестирования. Это обводка объекта.
Нет нужды объяснять, что подразумевается под обводкой объекта. Для каждого объекта (или только для одного) мы создаем маленькую цветную рамку. Этот эффект особенно полезен, когда нам, к примеру, нужно выделить юнитов в стратегической игре, а затем показать пользователю, какие были выбраны. Алгоритм формирования обводки для объекта такой:
 
    Установить трафаретную функцию в GL_ALWAYS, перед рисованием объектов (которые будут обведены), обновить трафаретный буфер единицами там, где фрагменты объектов будут нарисованы.
    Нарисовать объекты.
    Отключить тестирование глубины и запись в трафаретный буфер.
    Немного увеличить нужный объект.
    Использовать другой фрагментый шейдер, который выводить только один цвет (цвет обводки).
    Снова нарисовать объект, но только если трафаретное значение их фрагментов не равно единице.
    Снова включить запись в трафаретный буфер и тестирование глубины.
 
Этот процесс устанавливает содержимое буфера для каждого фрагмента объекта равным единице, и когда мы хотим нарисовать границы, мы, по сути, рисуем масштабируемые версии объектов, и там, где позволяет тест, масштабируемая версия рисуется (вокруг границ объекта). С помощью теста трафарета мы отбрасываем те фрагменты отмасштабированных объектов, которые наложены на фрагменты исходных объектов.
 
Мы планируем включить обводку только для двух контейнеров. Поэтому сперва нужно два контейнера (с записью в буфер трафарета), а затем — увеличенные версии контейнеров (с отбраковкой фрагментов, наложенных на уже отрисованные фрагменты исходных контейнеров).
 
Порядок размещения объектов в ниф файле критически важен!
Основной объект должен иметь меньший номер, чем "вспомогательный".
Здесь это: куб который следует обвести должен быть выше, чем его "обводка".
Если что-то не отображается, так ка надо, меняйте порядок объектов! - прим. ред.
 
Сначала нам нужно включить трафаретное тестирование и установить действия, выполняемые при успешном или неудачном выполнении любого из тестов:
 
glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 
 
Если какой-нибудь из тестов провалится, то мы ничего не делаем, а просто оставляем текущее значение в трафаретном буфере. Если же и трафаретный тест и тест глубины пройдены успешно, то мы заменяем текущее значение трафарета на эталонное значение, установленное через glStencilFunc, которое мы позже установим в 1.
 
Буфер очищается заполнением нулями, а для контейнеров мы обновляем трафаретный буфер до 1 для каждого нарисованного фрагмента:
 
glStencilFunc(GL_ALWAYS, 1, 0xFF); // каждый фрагмент обновит трафаретный буфер
glStencilMask(0xFF); // включить запись в трафаретный буфер
 
Используя GL_ALWAYS в функции glStencilFunc мы гарантируем, что каждый фрагмент контейнеров обновит трафаретный буфер с трафаретным значением 1. Так как фрагменты всегда проходят трафаретный тест, то трафаретный буфер обновляется ссылочным значением, везде, где мы их нарисовали.
 
Т.е. для базового куба, значения в ниф файле будут:
Stencil Enabled 1
Stencil Function ALWAYS
Stencil Ref 1
Stencil Mask 0
Fail Action KEEP
Z Fail Action KEEP
Pass Action REPLACE
Draw Mode DRAW_CCW_OR_BOTH или DRAW_BOTH - прим. ред.
 
Теперь, когда трафаретный буфер обновлен до единицы, там, где мы нарисовали контейнеры, нам нужно нарисовать увеличенные версии контейнеров, но, теперь отключив запись в трафаретный буфер:
 
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00); // отключить запись в трафаретный буфер
DrawTwoScaledUpContainers();
 
Мы используем аргумент GL_NOTEQUAL в glStencilFunc, который гарантирует, что мы рисуем только те части объектов, которые не равны единице, таким образом, мы рисуем только те части объектов, которые находятся за пределами ранее нарисованных объектов. Обратите внимание, что мы также отключили тест глубины, чтобы элементы увеличенных контейнеров, например, их границы, не были перезаписаны полом.
 
Общий шаблон обводки объекта для нашей сцены выглядит как-то так:
glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 
glStencilMask(0x00); // убедитесь, что мы не обновляем трафаретный буфер во время рисования пола
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
DrawTwoContainers();
 
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
DrawTwoScaledUpContainers();
glStencilMask(0xFF);
glEnable(GL_DEPTH_TEST);
 
Т.е. для куба обводки, значения в ниф файле будут:
Stencil Enabled 1
Stencil Function NOTEQUAL
Stencil Ref 1
Stencil Mask 0
Fail Action KEEP
Z Fail Action KEEP
Pass Action REPLACE
Draw Mode DRAW_BOTH - прим. ред.
 
Если вы понимаете общую идею, лежащую в основе трафаретного тестирования, этот фрагмент кода не должен быть слишком сложен для понимания. В противном случае попробуйте внимательнее прочитать предыдущие секции и полностью понять, что делает каждая функция, теперь, когда вы видели пример ее использования.
 
Результат применения алгоритма обводки к сцене из урока по тесту глубины выглядит так:
 
Можно заметить, что границы между двумя контейнерами перекрываются. Обычно, это именно то, что нужно (вспомните стратегические игры, когда мы выделяем несколько юнитов). Если вам нужна полная граница вокруг каждого объекта, нужно очищать трафаретный буфер для каждого объекта и немного пошаманить с настройкой теста глубины.
 
Алгоритм обводки объектов, который вы видели, довольно часто используется в некоторых играх для визуализации выбранных объектов (вспомните стратегические игры), и такой алгоритм может быть легко реализован в классе модели. Затем можно просто установить логический флаг в классе модели для рисования с границами или без них.
 
С помощью трафаретного тестирования можно делать больше вещей, чем просто обводить объекты, например рисовать текстуры внутри зеркала заднего вида так, что они вписываются в рамку зеркала. Или рендерить тени в реальном времени с помощью техники shadow volumes. Трафаретный буфер обеспечивает нас еще одним прекрасным инструментом в нашем и без того обширном инструментарии OpenGL.
Для МВ, увы, потребует шаманства с кодом. Т.е. непосредственно так, как бы этого хотелось, только через редактирование ниф файлов, многое работать не может. - прим. ред.
Но кое-что, все же, можно получить и прямо через Ниф файлы!

Не кубы конечно, но в качестве теста сойдет!
Прозрачность в обводке - наличие альфа свойств.
Эффект ореола сделанный таким методом может взаимодействовать с ореолами других моделей!