Qt Quick 3D - 自定义材质示例

演示如何编写着色自定义材质。

此示例展示了如何编写着色 自定义材质。采用了着色材质后,我们不必编写完整的着色器程序。相反,我们编写修改 Qt 标准着色器的函数。这样生成的材质将默认参与光照、阴影映射,并与光探针兼容。我们只需编写希望执行特殊行为的案例的定制逻辑。这是通过在顶点着色器和片元着色器的特定阶段调用我们自己的自定义函数来有效地扩展为PrincipledMaterial 生成着色器代码实现的。

要创建一个着色自定义材质,将 shadingMode 属性设置为 CustomMaterial.Shaded

一个简单的材质

第一个模型使用了一个简单的材质,它不添加任何自定义逻辑。我们就像对任何其他材质一样在模型上设置自定义材质

Model {
    source: "weirdShape.mesh"
    scale: Qt.vector3d(100, 100, 100)
    rotation: Quaternion.fromEulerAngles(-90, 0, 0)
    x: v3d.radius

    materials: [
        CustomMaterial {
            shadingMode: CustomMaterial.Shaded
            fragmentShader: "material_simple.frag"
            property color uDiffuse: "fuchsia"
            property real uSpecular: 1.0
        }
    ]
}

除了设置 shadingModefragmentShader,我们还在材质中添加了两个属性:uDiffuseSpecular。这些将被片元着色器获取

片段着色器的代码很短

void MAIN()
{
    SPECULAR_AMOUNT = uSpecular;
    BASE_COLOR = uDiffuse;
}

所有着色器都必须实现 MAIN 函数。在这个函数中,我们使用在材质中定义的属性来设置 Qt 标准着色器代码将使用的值。请注意,我们不必将这些声明为 uniform:我们只需要确保名称匹配。如果材质没有匹配的属性,我们将得到一个着色器编译错误。

特殊变量 SPECULAR_AMOUNTBASE_COLOR 对应于 specularAmountbaseColorPrincipledMaterial。然后,标准着色器代码将使用这些值来执行光照计算,就像我们使用了 PrincipledMaterial 一样。

自定义处理光线

下一个对象使用了一个更复杂的材质,它实现自定义的照明。材质有不同的一致名称,但除此之外,我们以相同的方式使用它

materials: [
    CustomMaterial {
        shadingMode: CustomMaterial.Shaded
        fragmentShader: "material_customlights.frag"
        property color uDiffuse: "orange"
        property real uShininess: 150
    }
]

片段着色器为所有不同类型的照明实现自定义逻辑

void MAIN()
{
    SPECULAR_AMOUNT = 1.0;
    ROUGHNESS = 0.5;
    BASE_COLOR = uDiffuse;
}

void AMBIENT_LIGHT()
{
    DIFFUSE += uDiffuse.rgb * TOTAL_AMBIENT_COLOR;
}

void DIRECTIONAL_LIGHT()
{
    DIFFUSE += uDiffuse.rgb * LIGHT_COLOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(NORMAL), TO_LIGHT_DIR)));
}

void POINT_LIGHT()
{
    DIFFUSE += uDiffuse.rgb * LIGHT_COLOR * LIGHT_ATTENUATION * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(NORMAL), TO_LIGHT_DIR)));
}

void SPOT_LIGHT()
{
     DIFFUSE += uDiffuse.rgb * LIGHT_COLOR * LIGHT_ATTENUATION * SPOT_FACTOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR)));
}

在这里,我们使用了许多新的特殊关键词来指代各种光照类型的功能。请参阅CustomMaterial文档以了解每个关键词的描述。请注意,每种光照类型都有自己的功能。未实现的功能将使用默认实现,类似于PrincipledMaterial。例如:在这个着色器中,我们没有实现SPECULAR_LIGHT(),因此我们将得到内置的镜面反射。

添加顶点着色器

自定义材质也可以使用顶点着色器来修改模型的几何形状。这里我们指定了片段和顶点着色器,并添加了几个属性,这些属性将被作为统一值拾取

materials: [
    CustomMaterial {
        id: material
        shadingMode: CustomMaterial.Shaded
        vertexShader: "material_distortion.vert"
        fragmentShader: "material_customlights.frag"
        property real uTime: 0.0
        property real uAmplitude: 0.3
        property color uDiffuse: "yellow"
        property real uShininess: 50
        NumberAnimation {
            target: material
            property: "uTime"
            from: 0.0
            to: 31.4
            duration: 10000
            loops: Animation.Infinite
            running: true
        }
    }
]

顶点着色器非常短

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

void MAIN()
{
    VERTEX.y += sin(uTime + VERTEX.x*10.0) * uAmplitude;
}

这通过根据随时间变化的正弦波将每个顶点位移来变形模型。

透明材质

最后,我们添加了一个带有透明材质的球体。出于性能原因,Qt并没有以完全现实的方式实现透明度。相反,Qt将场景中所有非透明对象渲染到一个纹理上,然后透明材质读取这个纹理。这意味着透明材质如果放置在其它物体前面会得到最佳效果。

Model {
    id: screenSphere
    source: "#Sphere"
    scale: Qt.vector3d(0.75, 0.75, 0.75)
    y: 60
    z: 750;
    materials: [
        CustomMaterial {
            shadingMode: CustomMaterial.Shaded
            fragmentShader: "material_transparent.frag"
        }
    ]

对于这个示例,我们实现了一个简单的畸变函数,该函数并没有尝试进行真实的物理折射

void MAIN()
{
    vec2 size = vec2(textureSize(SCREEN_TEXTURE, 0));
    vec2 uv = FRAGCOORD.xy / size;

    vec3 view = normalize(VIEW_VECTOR);
    vec3 projection = view - view * normalize(NORMAL);
    vec3 refraction = projection * projection;
    uv += refraction.xy * 0.5;

    vec3 col = texture(SCREEN_TEXTURE, uv).rgb;
    col = col * 0.8 + vec3(0.2);

    BASE_COLOR = vec4(col, 1.0);
}

SCREEN_TEXTURE指的是显示场景中所有非透明对象的纹理。我们首先计算这个纹理中匹配当前顶点屏幕位置的uv坐标。然后我们给这个位置添加一个偏移量,模拟折射效果,然后再进行纹理查找。

最后,我们混合了20%的白色来得到轻微的云状效果。请注意,输出被赋予BASE_COLOR,因此Qt将在这个基础上添加光照。这就是为什么我们可以在球体表面看到反射。

无阴影材质

还有可能存在使用完整的着色程序(同时使用便利关键词)的自定义材质。自定义着色器示例customshaders演示了另一组自定义材质:无阴影自定义材质。

文件

© 2024 Qt公司。本文件中包含的文档贡献的版权归其各自的所有者所有。本文件提供的文档是根据自由软件基金会发布并由其维护的GNU自由文档许可版本1.3许可的。Qt及其标志是芬兰和/或世界上其它国家的The Qt Company Ltd.的商标。所有其它商标均属于其各自的所有者。