| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of Qt Quick 3D. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QTest> |
| #include <QSignalSpy> |
| |
| #include <QtQuick3D/private/qquick3dscenemanager_p.h> |
| #include <QtQuick3D/private/qquick3ddefaultmaterial_p.h> |
| #include <QtQuick3D/private/qquick3dcustommaterial_p.h> |
| #include <QtQuick3D/private/qquick3dprincipledmaterial_p.h> |
| #include <QtQuick3D/private/qquick3dviewport_p.h> |
| |
| #include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterial_p.h> |
| #include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h> |
| #include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h> |
| |
| class tst_QQuick3DMaterials : public QObject |
| { |
| Q_OBJECT |
| |
| // Work-around to get access to updateSpatialNode |
| class Material : public QQuick3DDefaultMaterial |
| { |
| public: |
| using QQuick3DMaterial::updateSpatialNode; |
| }; |
| |
| class CustomMaterial : public QQuick3DCustomMaterial |
| { |
| public: |
| using QQuick3DCustomMaterial::updateSpatialNode; |
| }; |
| |
| class PrincipledMaterial : public QQuick3DPrincipledMaterial |
| { |
| public: |
| using QQuick3DPrincipledMaterial::updateSpatialNode; |
| }; |
| |
| class Texture : public QQuick3DTexture |
| { |
| public: |
| using QQuick3DTexture::updateSpatialNode; |
| }; |
| |
| private slots: |
| void testDefaultProperties(); |
| void testDefaultTextures(); |
| void testDefaultEnums(); |
| void testPrincipledProperties(); |
| void testPrincipledTextures(); |
| void testPrincipledEnums(); |
| void testCustomMaterials(); |
| }; |
| |
| void tst_QQuick3DMaterials::testDefaultProperties() |
| { |
| Material material; |
| auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| const auto originalNode = node; // for comparisons later... |
| QVERIFY(node); |
| |
| const float opacity = 0.4f; |
| material.setOpacity(opacity); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QCOMPARE(material.opacity(), node->opacity); |
| |
| const float displacementAmount = 0.5f; |
| material.setDisplacementAmount(displacementAmount); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QCOMPARE(material.displacementAmount(), node->displaceAmount); |
| |
| const float specularAmount = 10.0f; |
| material.setSpecularAmount(specularAmount); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QCOMPARE(material.specularAmount(), node->specularAmount); |
| |
| const float emissiveFactor = 0.5f; |
| material.setEmissiveColor(Qt::white); |
| material.setEmissiveFactor(emissiveFactor); |
| material.setLighting(QQuick3DDefaultMaterial::FragmentLighting); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| // If != NoLighting the emissive color on the node should be emissiveColor * emissiveFactor |
| static const auto toVec3 = [](const QColor &c) { return QVector3D{float(c.redF()), float(c.greenF()), float(c.blueF())}; }; |
| auto expectedEmissiceColor = toVec3(material.emissiveColor()) * material.emissiveFactor(); |
| QCOMPARE(expectedEmissiceColor, node->emissiveColor); |
| material.setLighting(QQuick3DDefaultMaterial::NoLighting); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| expectedEmissiceColor = toVec3(Qt::white); |
| QCOMPARE(expectedEmissiceColor, node->emissiveColor); |
| |
| const float bumpAmount = 0.3f; |
| material.setBumpAmount(bumpAmount); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QCOMPARE(material.bumpAmount(), node->bumpAmount); |
| |
| material.setVertexColorsEnabled(true); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(material.vertexColorsEnabled(), node->vertexColorsEnabled); |
| material.setVertexColorsEnabled(false); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(material.vertexColorsEnabled(), node->vertexColorsEnabled); |
| |
| QColor color1("#12345678"); |
| QVector4D color1Vec4(float(color1.redF()), float(color1.greenF()), |
| float(color1.blueF()), float(color1.alphaF())); |
| material.setDiffuseColor(color1); |
| material.setEmissiveColor(color1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QCOMPARE(color1Vec4, node->color); |
| // Note: emissiveColor doesn't contain alpha |
| QCOMPARE(QVector3D(color1Vec4), node->emissiveColor); |
| |
| } |
| |
| void tst_QQuick3DMaterials::testDefaultTextures() |
| { |
| Material material; |
| auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| const auto originalNode = node; // for comparisons later... |
| QVERIFY(node); |
| |
| Texture texture1; |
| texture1.setSource(QUrl(":/Built_with_Qt.png")); |
| Texture texture2; |
| texture2.setSource(QUrl(":/Built_with_Qt_2.png")); |
| |
| QQuick3DSceneManager sceneManager; |
| sceneManager.updateDirtyNode(&texture1); |
| sceneManager.updateDirtyNode(&texture2); |
| |
| // Diffusemaps |
| material.setDiffuseMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QVERIFY(texture1.getRenderImage()); |
| QCOMPARE(texture1.getRenderImage(), node->colorMap); |
| |
| // Specularmaps |
| QVERIFY(!node->specularMap); |
| QVERIFY(!node->specularReflection); |
| QVERIFY(!material.specularMap()); |
| QVERIFY(!material.specularReflectionMap()); |
| material.setSpecularMap(&texture2); |
| material.setSpecularReflectionMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.specularMap()); |
| QCOMPARE(material.specularMap()->getRenderImage(), node->specularMap); |
| QVERIFY(material.specularReflectionMap()); |
| QCOMPARE(material.specularReflectionMap()->getRenderImage(), node->specularReflection); |
| |
| // Bumpmap |
| QVERIFY(!node->bumpMap); |
| QVERIFY(!material.bumpMap()); |
| material.setBumpMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.bumpMap()); |
| QCOMPARE(material.bumpMap()->getRenderImage(), node->bumpMap); |
| |
| // Opacitymap |
| QVERIFY(!node->opacityMap); |
| QVERIFY(!material.opacityMap()); |
| material.setOpacityMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.opacityMap()); |
| QCOMPARE(material.opacityMap()->getRenderImage(), node->opacityMap); |
| QCOMPARE(material.opacityMap()->getRenderImage(), material.bumpMap()->getRenderImage()); |
| } |
| |
| void tst_QQuick3DMaterials::testDefaultEnums() |
| { |
| Material material; |
| auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| QVERIFY(node); |
| |
| // These test rely on the different enums having the same values |
| auto lightModes = { QQuick3DDefaultMaterial::Lighting::NoLighting, |
| QQuick3DDefaultMaterial::Lighting::FragmentLighting }; |
| for (const auto lightMode : lightModes) |
| { |
| material.setLighting(lightMode); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| QCOMPARE(int(material.lighting()), int(node->lighting)); |
| } |
| |
| auto blendModes = { QQuick3DDefaultMaterial::BlendMode::SourceOver, |
| QQuick3DDefaultMaterial::BlendMode::Screen, |
| QQuick3DDefaultMaterial::BlendMode::Overlay, |
| QQuick3DDefaultMaterial::BlendMode::Multiply, |
| QQuick3DDefaultMaterial::BlendMode::ColorBurn, |
| QQuick3DDefaultMaterial::BlendMode::ColorDodge }; |
| for (const auto blendMode : blendModes) |
| { |
| material.setBlendMode(blendMode); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| QCOMPARE(int(material.blendMode()), int(node->blendMode)); |
| } |
| |
| auto specularModes = { QQuick3DDefaultMaterial::SpecularModel::Default, |
| QQuick3DDefaultMaterial::SpecularModel::KGGX, |
| QQuick3DDefaultMaterial::SpecularModel::KWard }; |
| for (const auto specularMode : specularModes) |
| { |
| material.setSpecularModel(specularMode); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| QCOMPARE(int(material.specularModel()), int(node->specularModel)); |
| } |
| } |
| |
| void tst_QQuick3DMaterials::testPrincipledProperties() |
| { |
| PrincipledMaterial material; |
| auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| const auto originalNode = node; // for comparisons later... |
| QVERIFY(node); |
| |
| const float metalness = 0.1f; |
| material.setMetalness(metalness); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QCOMPARE(metalness, material.metalness()); |
| QCOMPARE(metalness, node->metalnessAmount); |
| |
| // Note: metalness needs to be disabled for specularAmount and specularTint |
| // to affect backend. |
| material.setMetalness(0.0f); |
| |
| const float specularAmount = 0.2f; |
| material.setSpecularAmount(specularAmount); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(specularAmount, material.specularAmount()); |
| QCOMPARE(specularAmount, node->specularAmount); |
| |
| const float specularTint = 0.3f; |
| material.setSpecularTint(specularTint); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(specularTint, material.specularTint()); |
| // Note: Backend uses vector3D for specularTint |
| QCOMPARE(specularTint, node->specularTint.y()); |
| |
| const float roughness = 0.4f; |
| material.setRoughness(roughness); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(roughness, material.roughness()); |
| QCOMPARE(roughness, node->specularRoughness); |
| |
| const float indexOfRefraction = 0.5f; |
| material.setIndexOfRefraction(indexOfRefraction); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(indexOfRefraction, material.indexOfRefraction()); |
| QCOMPARE(indexOfRefraction, node->ior); |
| |
| const float opacity = 0.7f; |
| material.setOpacity(opacity); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(opacity, material.opacity()); |
| QCOMPARE(opacity, node->opacity); |
| |
| const float normalStrength = 0.8f; |
| material.setNormalStrength(normalStrength); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(normalStrength, material.normalStrength()); |
| QCOMPARE(normalStrength, node->bumpAmount); |
| |
| const float occlusionAmount = 0.9f; |
| material.setOcclusionAmount(occlusionAmount); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(occlusionAmount, material.occlusionAmount()); |
| QCOMPARE(occlusionAmount, node->occlusionAmount); |
| |
| const float alphaCutoff = 0.1f; |
| material.setAlphaCutoff(alphaCutoff); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(alphaCutoff, material.alphaCutoff()); |
| QCOMPARE(alphaCutoff, node->alphaCutoff); |
| |
| QColor color1("#12345678"); |
| QVector4D color1Vec4(float(color1.redF()), float(color1.greenF()), |
| float(color1.blueF()), float(color1.alphaF())); |
| QColor color2("#cccccccc"); |
| QVector3D color2Vec3(float(color2.redF()), float(color2.greenF()), |
| float(color2.blueF())); |
| material.setBaseColor(color1); |
| material.setEmissiveColor(color2); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QCOMPARE(color1Vec4, node->color); |
| // Note: emissiveColor doesn't contain alpha |
| QCOMPARE(color2Vec3, node->emissiveColor); |
| } |
| |
| void tst_QQuick3DMaterials::testPrincipledTextures() |
| { |
| PrincipledMaterial material; |
| auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| const auto originalNode = node; // for comparisons later... |
| QVERIFY(node); |
| |
| Texture texture1; |
| texture1.setSource(QUrl(":/Built_with_Qt.png")); |
| Texture texture2; |
| texture2.setSource(QUrl(":/Built_with_Qt_2.png")); |
| |
| QQuick3DSceneManager sceneManager; |
| sceneManager.updateDirtyNode(&texture1); |
| sceneManager.updateDirtyNode(&texture2); |
| |
| // BasecolorMap |
| QVERIFY(!material.baseColorMap()); |
| material.setBaseColorMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QVERIFY(material.baseColorMap()); |
| QVERIFY(texture1.getRenderImage()); |
| QCOMPARE(texture1.getRenderImage(), node->colorMap); |
| |
| // MetalnessMap |
| material.setMetalnessMap(&texture2); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(originalNode, node); |
| QVERIFY(texture2.getRenderImage()); |
| QCOMPARE(texture2.getRenderImage(), node->metalnessMap); |
| |
| // SpecularMap |
| // Note: metalness needs to be disabled for setSpecularMap to affect backend. |
| material.setMetalness(0.0f); |
| QVERIFY(!material.specularMap()); |
| material.setSpecularMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.specularMap()); |
| QCOMPARE(texture1.getRenderImage(), node->specularMap); |
| |
| // RoughnessMap |
| QVERIFY(!material.roughnessMap()); |
| material.setRoughnessMap(&texture2); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.roughnessMap()); |
| QCOMPARE(texture2.getRenderImage(), node->roughnessMap); |
| |
| // EmissiveMap |
| QVERIFY(!material.emissiveMap()); |
| material.setEmissiveMap(&texture2); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.emissiveMap()); |
| QCOMPARE(texture2.getRenderImage(), node->emissiveMap); |
| |
| // OpacityMap |
| QVERIFY(!material.opacityMap()); |
| material.setOpacityMap(&texture2); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.opacityMap()); |
| QCOMPARE(texture2.getRenderImage(), node->opacityMap); |
| |
| // NormalMap |
| QVERIFY(!material.normalMap()); |
| material.setNormalMap(&texture2); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.normalMap()); |
| QCOMPARE(texture2.getRenderImage(), node->normalMap); |
| |
| // SpecularReflectionMap |
| QVERIFY(!material.specularReflectionMap()); |
| material.setSpecularReflectionMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.specularReflectionMap()); |
| QCOMPARE(texture1.getRenderImage(), node->specularReflection); |
| |
| // OcclusionMap |
| QVERIFY(!material.occlusionMap()); |
| material.setOcclusionMap(&texture1); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QVERIFY(material.occlusionMap()); |
| QCOMPARE(texture1.getRenderImage(), node->occlusionMap); |
| } |
| |
| void tst_QQuick3DMaterials::testPrincipledEnums() |
| { |
| PrincipledMaterial material; |
| auto node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(nullptr)); |
| QVERIFY(node); |
| |
| // These test rely on the different enums having the same values |
| auto lightModes = { QQuick3DPrincipledMaterial::Lighting::NoLighting, |
| QQuick3DPrincipledMaterial::Lighting::FragmentLighting }; |
| for (const auto lightMode : lightModes) { |
| material.setLighting(lightMode); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(int(material.lighting()), int(node->lighting)); |
| } |
| |
| auto blendModes = { QQuick3DPrincipledMaterial::BlendMode::SourceOver, |
| QQuick3DPrincipledMaterial::BlendMode::Screen, |
| QQuick3DPrincipledMaterial::BlendMode::Multiply, |
| QQuick3DPrincipledMaterial::BlendMode::Overlay, |
| QQuick3DPrincipledMaterial::BlendMode::ColorBurn, |
| QQuick3DPrincipledMaterial::BlendMode::ColorDodge }; |
| for (const auto blendMode : blendModes) { |
| material.setBlendMode(blendMode); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(int(material.blendMode()), int(node->blendMode)); |
| } |
| |
| auto alphaModes = { QQuick3DPrincipledMaterial::AlphaMode::Opaque, |
| QQuick3DPrincipledMaterial::AlphaMode::Mask, |
| QQuick3DPrincipledMaterial::AlphaMode::Blend }; |
| for (const auto alphaMode : alphaModes) { |
| material.setAlphaMode(alphaMode); |
| node = static_cast<QSSGRenderDefaultMaterial *>(material.updateSpatialNode(node)); |
| QCOMPARE(int(material.alphaMode()), int(node->alphaMode)); |
| } |
| } |
| |
| void tst_QQuick3DMaterials::testCustomMaterials() |
| { |
| CustomMaterial material; |
| QQuick3DCustomMaterialShaderInfo shaderInfo; |
| shaderInfo.version = "1.2.3"; |
| QQuick3DViewport *view3D = new QQuick3DViewport; |
| material.setParent(view3D); |
| material.setShaderInfo(&shaderInfo); |
| auto node = static_cast<QSSGRenderCustomMaterial *>(material.updateSpatialNode(nullptr)); |
| QVERIFY(node); |
| QVERIFY(material.shaderInfo()->version == node->shaderInfo.version); |
| |
| QQuick3DCustomMaterialTextureInput mTexture; |
| QQuick3DTexture qTexture; |
| QSignalSpy spy(&mTexture, SIGNAL(textureDirty(QQuick3DCustomMaterialTextureInput *))); |
| mTexture.setTexture(&qTexture); |
| QCOMPARE(spy.count(), 1); |
| QVERIFY(&qTexture == mTexture.texture()); |
| |
| QQuick3DCustomMaterialBuffer mBuffer; |
| auto format = QQuick3DCustomMaterialBuffer::TextureFormat::RGBA8; |
| mBuffer.setFormat(format); |
| auto filterOp = QQuick3DCustomMaterialBuffer::TextureFilterOperation::Nearest; |
| mBuffer.setTextureFilterOperation(filterOp); |
| QCOMPARE(format, mBuffer.format()); |
| QCOMPARE(filterOp, mBuffer.textureFilterOperation()); |
| |
| // TODO: Extend custom material testing with proper shaders |
| |
| view3D->deleteLater(); |
| } |
| |
| QTEST_APPLESS_MAIN(tst_QQuick3DMaterials) |
| #include "tst_qquick3dmaterials.moc" |