效果 QML 类型

创建后处理效果的基组件。更多...

导入声明import QtQuick3D
继承

Object3D

属性

详细描述

Effect 类型允许用户为 QtQuick3D 实现自己的后处理效果。

后处理效果

后处理效果在概念上与 Qt Quick 的 ShaderEffect 项非常相似。当效果存在时,场景首先渲染到一个单独的纹理中。然后根据 View3D 的渲染模式,通过在主渲染目标上绘制一个纹理四边形来应用效果。效果可以提供顶点着色器、片段着色器或两者都提供。效果始终应用于整个场景,每个 View3D

效果与 SceneEnvironment 中的 SceneEnvironment::effects 属性相关联。该属性是一个列表:效果可以链接在一起;它们按照在列表中的顺序应用,使用前一步骤的输出作为下一步的输入,最后一个效果的输出定义了 View3D 的内容。

注意:SceneEnvironmentExtendedSceneEnvironment 提供了一套内置效果,例如景深、辉光/光晕、镜头光晕、色调分级和晕影。请首先考虑这些是否足以满足应用程序的需求,并优先使用内置功能,而不是实现自定义后处理效果。

效果在许多方面类似于 自定义材质。然而,自定义材质与模型关联,并负责给定网格的着色。而效果的正则着色器始终得到一个四边形(例如,两个三角形)作为其输入,片段着色器则采样场景内容。

与自定义材质不同,效果支持多次遍历。对于许多效果来说,这并不必要;当需要应用多个效果时,通常可以通过在SceneEnvironment中串联多个效果来实现相同的结果。这也由自定义效果示例所演示。然而,遍历有请求额外颜色缓冲区(纹理)的可能性,并指定它们输出到哪个额外缓冲区。这允许实现更复杂图像处理技术,因为后续遍历可以使用一个或多个这些附加缓冲区,加上原始场景的内容,作为其输入。如果需要,这些附加缓冲区可以具有更长的生命周期,这意味着它们的内处于帧之间保持不变,允许实现依赖于累积来自多个帧的内容的效应,例如,运动模糊。

与Qt Quick的2DShaderEffect相比,3D后处理效果的优势在于能够处理深度缓冲区数据,以及实现多遍历和中间缓冲区。此外,纹理相关功能得到了扩展:Qt Quick 3D允许更精细地控制过滤模式,并允许效果使用RGB8以外的纹理格式,例如,浮点格式。

注意: 后处理效果在View3DrenderMode设置为OffscreenUnderlayOverlay时可用。对于Inline模式,效果不会被渲染。

注意: 使用后处理效果时,应用程序提供的着色器应期望线性颜色数据,并且没有应用色调映射。当tonemapMode设置为除SceneEnvironment.TonemapModeNone外的值时,主渲染过程(或如果有天空盒,在天空盒渲染期间)执行色调映射将自动禁用,前提是SceneEnvironment中已指定至少一个后处理效果。链中的最后一个效果(更准确地说,链中最后一个效果的最后一个遍历)将自动获得其片段着色器更新,以执行主渲染过程会执行的相同色调映射。

注意: 执行自己的色调映射的效果应该在使用SceneEnvironment且内置色调映射被通过将tonemapMode设置为SceneEnvironment.TonemapModeNone来禁用的情况下使用。

注意: 默认情况下,用作效果输入的纹理是以浮点纹理格式生成的,例如16位浮点RGBA。输出纹理的格式与此相同,因为默认情况下它遵循输入格式。这可以使用Buffer和空名称来覆盖。默认RGBA16F很有用,因为它允许在不将颜色值压回到0-1范围内的情况下处理未经色调映射的线性数据。

将数据暴露给着色器

CustomMaterialShaderEffect一样,Effect对象的动态属性可以使用通常的QML和Qt Quick功能进行更改和动画处理,并且这些值会自动暴露给着色器。以下列表显示了属性的映射方式

  • bool, int, real -> bool, int, float
  • QColor, color -> vec4,并且假设以QML中指定的颜色值的sRGB空间将颜色转换为线性的。内置的Qt颜色,例如"green"也在sRGB颜色空间中,并且为DefaultMaterial和PrincipledMaterial的所有颜色属性执行相同的转换,因此Effect的行为与这些相同。
  • QRectQRectFrect——————————————> vec4
  • QPointQPointFpointQSizeQSizeFsize——————————————> vec2
  • QVector2Dvector2d——————————————> vec3
  • QVector3Dvector3d——————————————> vec3
  • QVector4Dvector4d——————————————> vec4
  • QMatrix4x4matrix4x4——————————————> mat4
  • QQuaternionquaternion——————————————> vec4,标量值是 w
  • TextureInput——————————————> sampler2D 或 samplerCube,根据是否在 TextureCubeMapTexture 中使用 textures 属性。将 enabled 属性设置为 false 会把一个虚拟纹理暴露给着色器,这意味着着色器仍然有效,但是会采样一个具有不透明黑色图像内容的纹理。请注意,着色器的采样属性必须始终参考 TextureInput 对象,而不是直接参考 Texture。在 Texture 属性方面,只有源、平铺和过滤相关的属性才会被隐式地考虑与效果一起,因为其余的(例如,UV变换)需要自定义着色器根据需要进行实现。

注意:当着色器代码中引用的 uniform 没有相应的属性时,会在运行时处理效果时导致着色器编译错误。有一些例外,例如,sampler uniforms,在没有对应的 QML 属性时将绑定一个虚拟纹理,但作为一般规则,所有 uniform 和 sampler 必须在 Effect 对象中声明相应的属性。

开始使用用户自定义效果

自定义后期处理效果至少需要 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);
}

在这里,名为 tex 的纹理(图像 image.png)暴露给着色器。redLevel 的值在着色器中以同名 float uniform 可用。

片段着色器必须包含一个名为 MAIN 的函数。最终片段颜色由 FRAGCOLOR 决定。主输入纹理包含 View3D 场景的内容,可通过一个名为 sampler2D 的名为 INPUT 的采样器访问。四边形的 UV 坐标位于 INPUT_UV 中。这些 UV 值始终适用于采样 INPUT,无论运行时底层图形 API 如何(因此无论图像中 Y 轴的方向如何,因为必要的调整都是通过 Qt Quick 3D 自动应用的)。使用我们的外部图像采样纹理是通过 TEXTURE_UV 完成的。INPUT_UV 不适用于跨平台应用程序,因为 V 需要翻转以解决之前提到的坐标系统差异,使用与基于图像的纹理和用作渲染目标的纹理不同的逻辑。幸运的是,所有这些都由引擎处理,因此着色器无需做更多的逻辑。

一旦 simpleEffect 可用,就可以将其与 View3DSceneEnvironment 的效果列表相关联。

environment: SceneEnvironment {
    effects: [ simpleEffect ]
}

结果看起来可能如下所示,左侧是原始场景,右侧是应用了效果的场景。

注意:着色器中的 shader 属性值是一个 URL,这与 QML 和 Qt Quick 中的自定义相似,引用包含着色器片段的文件,并且工作方式与 ShaderEffectImage.source 非常相似。只有 fileqrc 计划被支持。也可以省略 file 计划,这样可以方便地指定相对路径。此类路径相对于组件(.qml 文件的)位置来解析。

注意:不管 Qt 在运行时使用哪种图形 API,着色器代码始终使用 Vulkan-style GLSL 提供。

注意:效果提供的效果着色器代码不是完整的、独立的 GLSL 着色器。相反,它们提供了一个 MAIN 函数,也可以选择提供一组 VARYING 声明,然后由引擎通过进一步的着色器代码进行修改。

具有顶点着色器的效果

当存在时,顶点着色器必须提供一个名为 MAIN 的函数。在大多数情况下,自定义顶点着色器将不希望提供自己的齐次顶点位置的计算,但可以使用 POSITIONVERTEXMODELVIEWPROJECTION_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 列表现在应指定顶点和片段片段。

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 - 采样 INPUT 的 UV 坐标。
  • TEXTURE_UV - vec2 - 适合采样由图像文件加载内容的纹理的 UV 坐标。
  • INPUT_SIZE - vec2 - INPUT 纹理的大小,以像素为单位。
  • OUTPUT_SIZE - vec2 - 输出缓冲区的大小,以像素为单位。通常与 INPUT_SIZE 相同,除非通道输出到大小增加的额外缓冲区。
  • FRAME - float - 帧计数器,在 View3D 每帧后增加。
  • DEPTH_TEXTURE - sampler2D - 包含场景不透明物体深度缓冲区内容的深度纹理。类似于 CustomMaterial,此关键字在着色器中的出现会自动触发生成深度纹理。

构建多通道效果

多通道效果通常使用多个着色器套件,并使用 outputcommands 属性。通道列表中的每个条目都对应于一个渲染通道,在通道的输出纹理中绘制一个四边形,同时采样效果输入纹理及可选的其他纹理。

多通道 Effect 的典型轮廓可能如下所示

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
}

如果所需值与默认值匹配,则无需注释属性。

在内部,此缓冲区对象的存在和从通道的 output 属性引用它会导致创建一个大小与 View3D 匹配的纹理,因此隐式输入和输出纹理的大小。如果不需要这一点,则可以使用 sizeMultiplier 属性来获得不同大小的中间纹理。这可能导致着色器的 INPUT_SIZEOUTPUT_SIZE 常量具有不同的值。

默认情况下,效果无法保证纹理在帧之间保留其内容。当创建一个新的中间纹理时,它会清零到 vec4(0.0)。之后,相同的纹理可以用于其他目的。因此,效果通道应始终写入整个纹理,而不对其通过通道开始的内容做假设。这里有一个例外:将 bufferFlags 设置为 Buffer_sceneLifetime 的缓冲区对象。这表示纹理永久关联到效果的一个通道,它不会用于其他目的。此类颜色缓冲区的内容在帧之间保留。这通常用于类似运动模糊的效果中:第一次通道将该持久缓冲区作为其输入,除了主输入纹理,输出到另一个中间缓冲区,而第二次通道输出到持久缓冲区。这样,在第一帧中第一次通道取样一个空的(透明的)纹理,而在后续帧中则是取样前帧第二次通道的输出。然后第三个通道可以混合效果的输入和第二次通道的输出。

使用 BufferInput 命令类型将自定义纹理缓冲区暴露给渲染通道。

例如,要访问 someBuffer 在渲染通道着色器中的名称 mySampler,可以将以下内容添加到其命令列表中

BufferInput { buffer: someBuffer; sampler: "mySampler" }

如果未指定 sampler 名称,则默认使用 INPUT

缓冲区可以用来在渲染通道之间共享中间结果。

为了将预加载的纹理暴露给效果,应使用 TextureInput 而不是。这些可以作为效果的属性定义,并由着色器通过其属性名自动访问。

property TextureInput tex: TextureInput {
    texture: Texture { source: "image.png" }
}

在这里 tex 是效果中所有通道的所有着色器中的一个有效样本器。

对于从属性继承的统一值,Effect 中的所有通道在其着色器中读取相同的值。如果需要,可以为给定的通道覆盖统一值。这通过将 SetUniformValue 命令添加到通道命令列表中来实现。

注意:特定的通道 uniform 值设置器的 target 只能引用效果的属性名称。它可以覆盖属性对应的统一值,但不能引入新的统一值。

性能考虑

请注意,使用后处理效果时,资源使用量会增加,并可能降低性能。与 Qt Quick 层和 ShaderEffect 类似,将场景渲染到纹理中,然后使用该纹理纹理四边形并不是一个便宜的操作,尤其是在低端硬件上,硬件的片段处理能力有限。所需的额外图形内存量以及 GPU 负载的增加都取决于 View3D 的大小(在没有窗口系统嵌入式设备上,这通常与屏幕分辨率相当大)。多通道效果以及应用多个效果会进一步增加资源和性能要求。

因此,强烈建议在开发生命周期的早期确保目标设备和图形堆栈能够应对最终产品屏幕分辨率下的 3D 场景设计中的效果。

尽管需要的技术不可避免,但 DEPTH_TEXTURE 意味着需要对纹理内容进行额外的渲染过程,这可能会导致对性能较低的硬件产生影响。因此,仅在必要时在效果着色器的中使用 DEPTH_TEXTURE

着色器中操作的任务复杂性同样很重要。就像 CustomMaterial 一样,子优化的片段着色器很容易导致渲染性能降低。

在涉及大于 1 的值时对 Buffer 中的 sizeMultiplier 是要谨慎的。例如,4 倍的乘数意味着创建并渲染出比 View3D 大四倍的纹理。就像阴影图和超采样或多采样一样,增加的资源性能成本可能会很快抵消有限的 GPU 功率系统中的质量提升。

另请参阅 ShaderPassBufferBufferInputQt Quick 3D - Custom Effect Example

属性文档

passes : list [只读]

包含由该效果实现的渲染 pass 的列表。


© 2024 The Qt Company Ltd. 以下文档贡献的版权属于各自所有者。提供的文档根据 Free Software Foundation 发布的 GNU 自由文档许可证 1.3 版本 的条款进行许可。Qt 及其相关标志在芬兰及世界其他国家和地区是 The Qt Company Ltd 的商标。所有其他商标属于其各自所有者。