Тип QML Effect
Базовый компонент для создания эффекта пост-обработки. Подробнее...
| Заявление об импорте: | import QtQuick3D |
| Наследует: |
Свойства
- passes : список
Подробное описание
Тип Effect позволяет пользователю реализовывать собственные эффекты пост-обработки для QtQuick3D.
Эффекты пост-обработки
Концептуально эффект пост-обработки очень похож на элемент ShaderEffect в Qt Quick. Когда эффект присутствует, сцена сначала отрисовывается в отдельный текстурный ресурс. Затем эффект применяется путём отрисовки текстурированного квада на главном целевом буфере, в зависимости от режима рендера renderMode элемента View3D. Эффект может обеспечивать вершинный шейдер, фрагментный шейдер или оба. Эффекты всегда применяются ко всей сцене, для каждого элемента View3D.
Эффекты ассоциируются с SceneEnvironment в свойстве SceneEnvironment::effects. Это список: эффекты могут быть объединены; они применяются в порядке их расположения в списке, используя выходной результат предыдущего шага как входной для следующего, а выходной результат последнего эффекта определяет содержимое элемента View3D.
Эффекты во многом похожи на настраиваемые материалы. Однако настраиваемый материал ассоциируется с моделью и отвечает за затенение заданной сетки. В то время как вершинный шейдер эффекта всегда получает квад (например, два треугольника) в качестве входных данных, в то время как его фрагментный шейдер считывает значения из текстуры с содержимым сцены.
В отличие от настраиваемых материалов, эффекты поддерживают несколько проходов. Для многих эффектов это не нужно, и когда требуется применить несколько эффектов, идентичные результаты часто можно получить, объединив несколько эффектов в SceneEnvironment. Это показано в примере Custom Effect. Однако проходы имеют возможность запросить дополнительные цветовые буферы (текстуры) и указать, в какой из этих дополнительных буферов они записывают свои результаты. Это позволяет реализовывать более сложные методы обработки изображений, поскольку последующие проходы могут использовать один или несколько из этих дополнительных буферов, а также исходное содержимое сцены, в качестве своих входных данных. При необходимости эти дополнительные буферы могут иметь расширенное время жизни, то есть их содержимое сохраняется между кадрами, что позволяет реализовывать эффекты, которые основаны на накоплении данных из нескольких кадров, например, размытие движения.
По сравнению с 2D-эффектами ShaderEffect в Qt Quick, 3D-эффекты пост-обработки обладают преимуществом работы с данными буфера глубины, а также возможностью реализации нескольких проходов с промежуточными буферами. Кроме того, возможности, связанные с текстурами, расширены: Qt Quick 3D позволяет более тонко управлять режимами фильтрации и позволяет эффектам работать с форматами текстур, отличными от RGBA8, например, с плавающей точкой.
Примечание: Эффекты пост-обработки в настоящее время доступны только в том случае, если у элемента View3D свойство renderMode установлено в Offscreen. Эффекты не будут рендериться с другими значениями renderMode.
Передача данных шейдерам
Как и в случае с CustomMaterial или ShaderEffect, динамические свойства объекта Effect можно изменять и анимировать, используя стандартные средства QML и Qt Quick, и значения автоматически передаются шейдерам. Следующий список показывает, как отображаются свойства:
- bool, int, real -> bool, int, float
-
QColor, color -> vec4, и цвет преобразуется в линейное представление, предполагая sRGB пространство для значения цвета, указанного в QML. Встроенные цвета Qt, такие как
"green"также находятся в цветовом пространстве sRGB, и для всех свойств цвета в DefaultMaterial и PrincipledMaterial выполняется то же преобразование, поэтому это поведение Effect соответствует этим. - QRect, QRectF, rect -> vec4
- QPoint, QPointF, point, QSize, QSizeF, size -> vec2
- QVector2D, vector2d -> vec3
- QVector3D, vector3d -> vec3
- QVector4D, vector4d -> vec4
- QMatrix4x4, matrix4x4 -> mat4
-
QQuaternion, quaternion -> vec4, скалярное значение равно
w - TextureInput -> sampler2D - Поддерживаются текстуры, ссылающиеся на файлы изображений и слои элементов Qt Quick. Установка свойства enabled в значение false приводит к передаче в шейдер текстуры-заполнителя, что означает, что шейдеры по-прежнему функциональны, но будут считывать значения из текстуры с непрозрачным чёрным изображением. Обратите внимание на то, что свойства для сэмплеров всегда должны ссылаться на объект TextureInput, а не напрямую на Texture. Что касается свойств Texture, то учтены только свойства, относящиеся к источнику, выкладке и фильтрации, а остальное (например, преобразования UV) зависит от реализации пользовательских шейдеров.
Примечание: Если униформа, на которую ссылается код шейдера, не имеет соответствующего свойства, при обработке эффекта во время выполнения будет ошибка компиляции шейдера. Есть исключения, например, униформы сэмплеров, которые получают заполненную текстуру, когда соответствующего свойства QML нет, но в общем случае все униформы и сэмплеры должны иметь соответствующее свойство, объявленное в объекте Effect.
Начало работы с эффектами
Сначала проверьте, доступен ли подходящий эффект в модуле QtQuick3D.Effects. Если это так, нет необходимости реализовывать эффект с помощью собственных шейдеров. В противном случае нужен объект Effect и фрагментный шейдер.
В качестве простого примера создадим эффект, который комбинирует содержимое сцены с изображением, а также изменяет значение красного канала динамически:
Effect {
id: simpleEffect
property TextureInput tex: TextureInput {
texture: Texture { source: "image.png" }
}
property real redLevel
NumberAnimation on redLevel { from: 0; to: 1; duration: 5000; loops: -1 }
passes: Pass {
shaders: Shader {
stage: Shader.Fragment
shader: "effect.frag"
}
}
} |
void MAIN()
{
vec4 c = texture(tex, TEXTURE_UV);
c.r *= redLevel;
FRAGCOLOR = c * texture(INPUT, INPUT_UV);
} |
Здесь текстура с изображением image.png передаётся шейдеру под именем tex. Значение redLevel доступно в шейдере в униформе float с тем же именем.
Фрагментный шейдер должен содержать функцию под названием MAIN. Окончательный цвет фрагмента определяется FRAGCOLOR. Основная входная текстура, содержащая содержимое сцены View3D, доступна под именем INPUT в sampler2D. Координаты UV для квада находятся в INPUT_UV. Эти значения UV всегда подходят для считывания данных из INPUT, независимо от используемого графического API во время выполнения (и, следовательно, независимо от направления оси Y на изображениях, так как необходимые корректировки автоматически выполняются Qt Quick 3D). Считывание данных из нашей внешней текстуры выполняется с помощью TEXTURE_UV. INPUT_UV не подходит для кроссплатформенных приложений, так как V нужно перевернуть, чтобы соответствовать различиям в системах координат, упомянутых выше, используя логику, отличную от логики для текстур на основе изображений и текстур, используемых в качестве целевых буферов рендеринга. К счастью, всем этим управляет движок, поэтому шейдеру не нужна дополнительная логика для этого.
После того, как simpleEffect доступен, его можно связать со списком эффектов View3D's SceneEnvironment:
environment: SceneEnvironment {
effects: [ simpleEffect ]
} Результаты будут примерно такими, как на рисунке, слева — исходная сцена, справа — сцена с применённым эффектом:
Примечание: Значение свойства shader в шейдере — это URL, как и принято в QML и Qt Quick, который ссылается на файл с фрагментом шейдера и работает очень похоже на ShaderEffect или Image.source. Поддерживаются только схемы file и qrc. Также можно опустить схему file, что позволит задать относительный путь удобным способом. Такой путь решается относительно расположения компонента (файла .qml).
Примечание: Код шейдера всегда предоставляется в стиле GLSL для Vulkan, независимо от графического API, используемого Qt во время выполнения.
Примечание: Код вершинного и фрагментного шейдеров, предоставленный эффектом, не являются полными, законченными шейдерами GLSL. Вместо этого они предоставляют функцию MAIN, и необязательно набор VARYING объявлений, которые затем дополняются движком.
Эффекты с вершинными шейдерами
В случае использования, шейдер вершины должен содержать функцию, названную MAIN. В подавляющем большинстве случаев пользовательский шейдер вершин не будет выполнять вычисление однородных координат вершин, но это возможно с помощью POSITION, VERTEX, и MODELVIEWPROJECTION_MATRIX. Если POSITION отсутствует в коде пользовательского шейдера, Qt Quick 3D автоматически вставит оператор, эквивалентный POSITION = MODELVIEWPROJECTION_MATRIX * vec4(VERTEX, 1.0);.
Для передачи данных между шейдером вершин и шейдером фрагментов используется ключевое слово VARYING. Внутренне это будет преобразовано в соответствующее объявление вывода вершин или ввода фрагментов. Шейдер фрагментов может использовать то же объявление, что затем позволит считывать интерполированное значение для текущего фрагмента.
Рассмотрим пример, очень похожий на встроенный эффект DistortionSpiral:
VARYING vec2 center_vec;
void MAIN()
{
center_vec = INPUT_UV - vec2(0.5, 0.5);
center_vec.y *= INPUT_SIZE.y / INPUT_SIZE.x;
} |
VARYING vec2 center_vec;
void MAIN()
{
float radius = 0.25;
float dist_to_center = length(center_vec) / radius;
vec2 texcoord = INPUT_UV;
if (dist_to_center <= 1.0) {
float rotation_amount = (1.0 - dist_to_center) * (1.0 - dist_to_center);
float r = radians(360.0) * rotation_amount / 4.0;
mat2 rotation = mat2(cos(r), sin(r), -sin(r), cos(r));
texcoord = vec2(0.5, 0.5) + rotation * (INPUT_UV - vec2(0.5, 0.5));
}
FRAGCOLOR = texture(INPUT, texcoord);
} |
Список passes объекта Effect теперь должен содержать фрагменты как для вершин, так и для фрагментов:
passes: Pass {
shaders: [
Shader {
stage: Shader.Vertex
shader: "effect.vert"
},
Shader {
stage: Shader.Fragment
shader: "effect.frag"
}
]
} Конечный результат выглядит следующим образом:
Специальные ключевые слова в шейдерах эффектов
-
VARYING— Объявляет вывод вершины или вход фрагмента в зависимости от типа текущего шейдера. -
MAIN— Эта функция должна всегда присутствовать в шейдере эффекта. -
FRAGCOLOR—vec4— Конечный цвет фрагмента; результат работы шейдера фрагментов. (только для шейдера фрагментов) -
POSITION—vec4— Однородные координаты, вычисленные в шейдере вершин. (только для шейдера вершин) -
MODELVIEWPROJECTION_MATRIX—mat4— Матрица преобразования для экрана. -
VERTEX—vec3— Вершины квадрика; вход для шейдера вершин. (только для шейдера вершин) -
INPUT—sampler2D— Сэмплер для входной текстуры с рендерингом сцены, если пасс не перенаправляет вход через объект BufferInput, в этом случаеINPUTссылается на дополнительную текстуру буфера цвета, указанную в BufferInput. -
INPUT_UV—vec2— Координаты UV для выборкиINPUT. -
TEXTURE_UV—vec2— Координаты UV, подходящие для выборки текстуры с содержимым, загруженным из файла изображения. -
INPUT_SIZE—vec2— Размер текстурыINPUTв пикселях. -
OUTPUT_SIZE—vec2— Размер выходного буфера в пикселях. Часто совпадает сINPUT_SIZE, если пасс не выводит в дополнительный буфер с множителем размера. -
FRAME—float— Счётчик кадров, увеличивается после каждого кадра в View3D. -
DEPTH_TEXTURE—sampler2D— Текстура глубины с содержимым буфера глубины с непрозрачными объектами в сцене. Как и с CustomMaterial, присутствие этого ключевого слова в шейдере запускает автоматическое создание текстуры глубины.
Создание эффектов с несколькими проходами
Эффект с несколькими проходами часто использует более одного набора шейдеров и использует свойства output и commands. Каждая запись в списке passes соответствует проходу рендеринга, рисующему квад в выходной текстуре прохода, при этом сэмплируя входную текстуру эффекта и, возможно, другие текстуры.
Типичная структура эффекта с несколькими проходами может быть следующей:
passes: [
Pass {
shaders: [
Shader {
stage: Shader.Vertex
shader: "pass1.vert"
},
Shader {
stage: Shader.Fragment
shader: "pass1.frag"
}
// This pass outputs to the intermediate texture described
// by the Buffer object.
output: intermediateColorBuffer
],
},
Pass {
shaders: [
Shader {
stage: Shader.Vertex
shader: "pass2.vert"
},
Shader {
stage: Shader.Fragment
shader: "pass2.frag"
}
// The output of the last pass needs no redirection, it is
// the final result of the effect.
],
commands: [
// This pass reads from the intermediate texture, meaning
// INPUT in the shader will refer to the texture associated
// with the Buffer.
BufferInput {
buffer: intermediateColorBuffer
}
]
}
] Что такое intermediateColorBuffer?
Buffer {
id: intermediateColorBuffer
name: "tempBuffer"
// format: Buffer.RGBA8
// textureFilterOperation: Buffer.Linear
// textureCoordOperation: Buffer.ClampToEdge
} Закомментированные свойства не нужны, если желаемые значения соответствуют значениям по умолчанию.
Внутренне наличие этого объекта Buffer и его ссылка из свойства output прохода приводят к созданию текстуры с размером, соответствующим View3D, а значит и размеру неявных входных и выходных текстур. Если это не нужно, можно использовать свойство sizeMultiplier для получения промежуточной текстуры с другим размером. Это может привести к тому, что униформы INPUT_SIZE и OUTPUT_SIZE в шейдере будут иметь разные значения.
По умолчанию эффект не может рассчитывать на то, что текстуры сохранят своё содержимое между кадрами. Когда создаётся новая промежуточная текстура, она очищается до vec4(0.0). После этого та же текстура может быть повторно использована для других целей. Поэтому проходы эффекта должны всегда записывать в всю текстуру без предположений о её содержании в начале прохода. Исключение составляют объекты Buffer с установленным свойством bufferFlags в Buffer.SceneLifetime. Это означает, что текстура постоянно связана с проходом эффекта и не будет повторно использована для других целей. Содержимое таких буферов цвета сохраняется между кадрами. Это обычно используется для эффектов типа размытия движения: первый проход принимает постоянный буфер в качестве входных данных, помимо основной входной текстуры эффекта, выводит в другой промежуточный буфер, в то время как второй проход выводит в постоянный буфер. Таким образом, в первом кадре первый проход сэмплирует пустую (прозрачную) текстуру, а в последующих кадрах — вывод второго прохода из предыдущего кадра. Третий проход затем может смешать входной эффект и вывод второго прохода вместе.
Тип команды BufferInput используется для экспорта пользовательских буферов текстур в проходы рендеринга.
Например, чтобы получить доступ к someBuffer в шейдерах прохода рендеринга под именем mySampler, можно добавить следующее в его список команд:
BufferInput { buffer: someBuffer; sampler: "mySampler" } Если имя sampler не указано, по умолчанию будет использовано INPUT.
Буферы могут быть полезны для совместного использования промежуточных результатов между проходами рендеринга.
Для экспорта предварительно загруженных текстур в эффект следует использовать TextureInput. Эти текстуры могут быть определены как свойства самого эффекта и будут автоматически доступны в шейдерах по их именам свойств.
property TextureInput tex: TextureInput {
texture: Texture { source: "image.png" }
} Здесь tex является допустимым сэмплером во всех шейдерах всех проходов эффекта.
Что касается униформ из свойств, все проходы в эффекте считывают одни и те же значения в своих шейдерах. При необходимости можно переопределить значение униформы только для данного прохода. Это достигается добавлением команды SetUniformValue в список команд прохода.
Примечание: Цель прохода установки значения униформы может ссылаться только на имя, являющееся именем свойства эффекта. Она может переопределить значение униформы, соответствующей свойству, но не может ввести новые униформы.
Учёт производительности
Будьте внимательны к увеличению использования ресурсов и потенциальному снижению производительности при использовании эффектов после обработки. Как и в случае слоёв Qt Quick и ShaderEffect, рендеринг сцены в текстуру и затем использование этой текстуры для текстурирования квадрика — не дешёвая операция, особенно на устройствах с низкопроизводительной графикой. Необходимый объём дополнительной графической памяти, а также увеличение нагрузки на графический процессор зависят от размера View3D (который на встраиваемых устройствах без системного окна часто может быть таким же большим, как разрешение экрана). Эффекты с несколькими проходами, а также применение нескольких эффектов, ещё больше увеличивают требования к ресурсам и производительности.
Поэтому крайне желательно на ранних этапах разработки убедиться, что целевое устройство и графический стек способны справиться с эффектами, включёнными в дизайн 3D-сцены, при конечном разрешении экрана.
Хотя это неизбежно для тех техник, которым это нужно, DEPTH_TEXTURE подразумевает дополнительный проход рендеринга для генерации содержимого этой текстуры, что также может повлиять на менее мощное оборудование. Поэтому используйте DEPTH_TEXTURE в шейдерах эффекта только в случае крайней необходимости.
Важно учитывать и сложность операций в шейдерах. Как и с CustomMaterial, неэффективный шейдер фрагментов может легко привести к снижению производительности рендеринга.
Будьте осторожны с sizeMultiplier в Buffer, когда значения больше 1. Например, множитель 4 означает создание и рендеринг текстуры, которая в 4 раза больше размера View3D. Как и в случае с картами теней и многократным или сверхсэмплированием, увеличенные затраты ресурсов и производительности быстро могут превысить преимущества от улучшения качества на системах с ограниченной мощностью графического процессора.
См. также Shader, Pass, Buffer, BufferInput, Qt Quick 3D - Пример пользовательского эффекта и Qt Quick 3D - Пример эффектов.
Документация по свойствам
passes : list
Содержит список проходов рендеринга, реализованных эффектом.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.2/qml-qtquick3d-effect.html