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

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

本示例演示了如何编写着色 custom materials 。有了着色材质,我们就不必编写完整的着色器程序。相反,我们可以编写修改 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 标准着色器代码将使用的值。请注意,我们不必将这些属性声明为制服:我们需要做的只是确保名称相匹配。如果材质没有匹配的属性,我们就会出现着色器编译错误。

特殊变量SPECULAR_AMOUNTBASE_COLORPrincipledMaterialspecularAmountbaseColor 相对应。标准着色器代码会使用这些变量来执行光照计算,就像我们使用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 XML 会在此基础上添加照明。这就是为什么我们可以在球体表面看到反射的原因。

无阴影材质

自定义材质也可以使用完整的着色器程序(同时仍使用方便的关键字)。自定义着色器示例演示了另一类自定义材质:无着色的自定义材质。

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.