diff options
author | Miikka Heikkinen <miikka.heikkinen@qt.io> | 2016-11-15 09:57:32 +0200 |
---|---|---|
committer | Miikka Heikkinen <miikka.heikkinen@qt.io> | 2016-12-09 14:14:28 +0000 |
commit | 4f2011cef1565bb9c9f2c0e92d63bb67fb3f661d (patch) | |
tree | 41b8220d83ac0fae130e3994403780125435db90 | |
parent | 79e308da8dd303070e989280f940a8bd6a274fcc (diff) |
GLTF exporting materials with custom shaders
Generic materials can now be exported with GLTF exporter
and imported back with GLTF importer.
Change-Id: I05f1f4d4be414d9a17c7ba91ee139e801dbccae8
Reviewed-by: Antti Määttä <antti.maatta@qt.io>
-rw-r--r-- | src/plugins/sceneparsers/gltf/gltfimporter.cpp | 842 | ||||
-rw-r--r-- | src/plugins/sceneparsers/gltf/gltfimporter.h | 18 | ||||
-rw-r--r-- | src/plugins/sceneparsers/gltfexport/gltfexporter.cpp | 1365 | ||||
-rw-r--r-- | src/plugins/sceneparsers/gltfexport/gltfexporter.h | 48 | ||||
-rw-r--r-- | tests/auto/render/gltfplugins/images.qrc | 4 | ||||
-rw-r--r-- | tests/auto/render/gltfplugins/ontopmaterial.frag | 14 | ||||
-rw-r--r-- | tests/auto/render/gltfplugins/ontopmaterial.vert | 20 | ||||
-rw-r--r-- | tests/auto/render/gltfplugins/ontopmaterialES2.frag | 10 | ||||
-rw-r--r-- | tests/auto/render/gltfplugins/ontopmaterialES2.vert | 18 | ||||
-rw-r--r-- | tests/auto/render/gltfplugins/tst_gltfplugins.cpp | 410 |
10 files changed, 1924 insertions, 825 deletions
diff --git a/src/plugins/sceneparsers/gltf/gltfimporter.cpp b/src/plugins/sceneparsers/gltf/gltfimporter.cpp index 62a309a2f..61c940eb1 100644 --- a/src/plugins/sceneparsers/gltf/gltfimporter.cpp +++ b/src/plugins/sceneparsers/gltf/gltfimporter.cpp @@ -40,89 +40,71 @@ #include "gltfimporter.h" -#include <QtCore/QDir> -#include <QtCore/QFileInfo> -#include <QtCore/QJsonArray> -#include <QtCore/QJsonObject> -#include <QtCore/QtMath> - -#include <QtGui/QVector2D> - -#include <Qt3DRender/QCameraLens> -#include <Qt3DRender/QCamera> -#include <Qt3DCore/QEntity> -#include <Qt3DCore/QTransform> - -#include <Qt3DRender/private/qurlhelper_p.h> - -#include <Qt3DRender/QAlphaCoverage> -#include <Qt3DRender/QBlendEquation> -#include <Qt3DRender/QBlendEquationArguments> -#include <Qt3DRender/QColorMask> -#include <Qt3DRender/QCullFace> -#include <Qt3DRender/QNoDepthMask> -#include <Qt3DRender/QDepthTest> -#include <Qt3DRender/QEffect> -#include <Qt3DRender/QFrontFace> -#include <Qt3DRender/QGeometry> -#include <Qt3DRender/QGeometryRenderer> -#include <Qt3DRender/QMaterial> -#include <Qt3DRender/QGraphicsApiFilter> -#include <Qt3DRender/QParameter> -#include <Qt3DRender/QPolygonOffset> -#include <Qt3DRender/QRenderState> -#include <Qt3DRender/QScissorTest> -#include <Qt3DRender/QShaderProgram> -#include <Qt3DRender/QTechnique> -#include <Qt3DRender/QTexture> -#include <Qt3DRender/QDirectionalLight> -#include <Qt3DRender/QSpotLight> -#include <Qt3DRender/QPointLight> - -#include <Qt3DExtras/QPhongMaterial> -#include <Qt3DExtras/QPhongAlphaMaterial> -#include <Qt3DExtras/QDiffuseMapMaterial> -#include <Qt3DExtras/QDiffuseSpecularMapMaterial> -#include <Qt3DExtras/QNormalDiffuseMapMaterial> -#include <Qt3DExtras/QNormalDiffuseMapAlphaMaterial> -#include <Qt3DExtras/QNormalDiffuseSpecularMapMaterial> -#include <Qt3DExtras/QGoochMaterial> -#include <Qt3DExtras/QPerVertexColorMaterial> +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qmath.h> + +#include <QtGui/qvector2d.h> + +#include <Qt3DCore/qentity.h> +#include <Qt3DCore/qtransform.h> + +#include <Qt3DRender/qcameralens.h> +#include <Qt3DRender/qcamera.h> +#include <Qt3DRender/qalphacoverage.h> +#include <Qt3DRender/qalphatest.h> +#include <Qt3DRender/qblendequation.h> +#include <Qt3DRender/qblendequationarguments.h> +#include <Qt3DRender/qclipplane.h> +#include <Qt3DRender/qcolormask.h> +#include <Qt3DRender/qcullface.h> +#include <Qt3DRender/qdithering.h> +#include <Qt3DRender/qmultisampleantialiasing.h> +#include <Qt3DRender/qpointsize.h> +#include <Qt3DRender/qnodepthmask.h> +#include <Qt3DRender/qdepthtest.h> +#include <Qt3DRender/qseamlesscubemap.h> +#include <Qt3DRender/qstencilmask.h> +#include <Qt3DRender/qstenciloperation.h> +#include <Qt3DRender/qstenciloperationarguments.h> +#include <Qt3DRender/qstenciltest.h> +#include <Qt3DRender/qstenciltestarguments.h> +#include <Qt3DRender/qeffect.h> +#include <Qt3DRender/qfrontface.h> +#include <Qt3DRender/qgeometry.h> +#include <Qt3DRender/qgeometryrenderer.h> +#include <Qt3DRender/qmaterial.h> +#include <Qt3DRender/qgraphicsapifilter.h> +#include <Qt3DRender/qparameter.h> +#include <Qt3DRender/qpolygonoffset.h> +#include <Qt3DRender/qrenderstate.h> +#include <Qt3DRender/qscissortest.h> +#include <Qt3DRender/qshaderprogram.h> +#include <Qt3DRender/qtechnique.h> +#include <Qt3DRender/qtexture.h> +#include <Qt3DRender/qdirectionallight.h> +#include <Qt3DRender/qspotlight.h> +#include <Qt3DRender/qpointlight.h> + +#include <Qt3DExtras/qphongmaterial.h> +#include <Qt3DExtras/qphongalphamaterial.h> +#include <Qt3DExtras/qdiffusemapmaterial.h> +#include <Qt3DExtras/qdiffusespecularmapmaterial.h> +#include <Qt3DExtras/qnormaldiffusemapmaterial.h> +#include <Qt3DExtras/qnormaldiffusemapalphamaterial.h> +#include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h> +#include <Qt3DExtras/qgoochmaterial.h> +#include <Qt3DExtras/qpervertexcolormaterial.h> + +#include <private/qurlhelper_p.h> #ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature # define qUtf16PrintableImpl(string) \ static_cast<const wchar_t*>(static_cast<const void*>(string.utf16())) #endif -QT_BEGIN_NAMESPACE - -using namespace Qt3DCore; -using namespace Qt3DExtras; - -namespace { - -inline QVector3D jsonArrToVec3(const QJsonArray &array) -{ - return QVector3D(array[0].toDouble(), array[1].toDouble(), array[2].toDouble()); -} - -inline QColor vec4ToQColor(const QVariant &vec4Var) -{ - const QVector4D v = vec4Var.value<QVector4D>(); - return QColor::fromRgbF(v.x(), v.y(), v.z()); -} - -inline QVariant vec4ToColorVariant(const QVariant &vec4Var) -{ - return QVariant(vec4ToQColor(vec4Var)); -} - -} // namespace - -namespace Qt3DRender { - -Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport") - #define KEY_CAMERA QLatin1String("camera") #define KEY_CAMERAS QLatin1String("cameras") #define KEY_SCENES QLatin1String("scenes") @@ -186,7 +168,6 @@ Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport") #define KEY_DIRECTIONAL_LIGHT QLatin1String("directional") #define KEY_SPOT_LIGHT QLatin1String("spot") #define KEY_AMBIENT_LIGHT QLatin1String("ambient") -#define KEY_TYPE QLatin1String("type") #define KEY_COLOR QLatin1String("color") #define KEY_FALLOFF_ANGLE QLatin1String("falloffAngle") #define KEY_DIRECTION QLatin1String("direction") @@ -201,6 +182,10 @@ Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport") #define KEY_BUFFER_VIEW QLatin1String("bufferView") #define KEY_VERTEX_SHADER QLatin1String("vertexShader") #define KEY_FRAGMENT_SHADER QLatin1String("fragmentShader") +#define KEY_TESS_CTRL_SHADER QLatin1String("tessCtrlShader") +#define KEY_TESS_EVAL_SHADER QLatin1String("tessEvalShader") +#define KEY_GEOMETRY_SHADER QLatin1String("geometryShader") +#define KEY_COMPUTE_SHADER QLatin1String("computeShader") #define KEY_INTERNAL_FORMAT QLatin1String("internalFormat") #define KEY_COMPONENT_TYPE QLatin1String("componentType") #define KEY_ASPECT_RATIO QLatin1String("aspect_ratio") @@ -211,6 +196,57 @@ Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport") #define KEY_BLEND_FUNCTION QLatin1String("blendFuncSeparate") #define KEY_TECHNIQUE_CORE QLatin1String("techniqueCore") #define KEY_TECHNIQUE_GL2 QLatin1String("techniqueGL2") +#define KEY_GABIFILTER QLatin1String("gapifilter") +#define KEY_API QLatin1String("api") +#define KEY_MAJORVERSION QLatin1String("majorVersion") +#define KEY_MINORVERSION QLatin1String("minorVersion") +#define KEY_PROFILE QLatin1String("profile") +#define KEY_VENDOR QLatin1String("vendor") +#define KEY_FILTERKEYS QLatin1String("filterkeys") +#define KEY_RENDERPASSES QLatin1String("renderpasses") +#define KEY_EFFECT QLatin1String("effect") +#define KEY_EFFECTS QLatin1String("effects") + +QT_BEGIN_NAMESPACE + +using namespace Qt3DCore; +using namespace Qt3DExtras; + +namespace { + +inline QVector3D jsonArrToVec3(const QJsonArray &array) +{ + return QVector3D(array[0].toDouble(), array[1].toDouble(), array[2].toDouble()); +} + +inline QColor vec4ToQColor(const QVariant &vec4Var) +{ + const QVector4D v = vec4Var.value<QVector4D>(); + return QColor::fromRgbF(v.x(), v.y(), v.z()); +} + +inline QVariant vec4ToColorVariant(const QVariant &vec4Var) +{ + return QVariant(vec4ToQColor(vec4Var)); +} + +Qt3DRender::QFilterKey *buildFilterKey(const QString &key, const QJsonValue &val) +{ + Qt3DRender::QFilterKey *fk = new Qt3DRender::QFilterKey; + fk->setName(key); + if (val.isString()) + fk->setValue(val.toString()); + else + fk->setValue(val.toInt()); + return fk; +} + +} // namespace + +namespace Qt3DRender { + +Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport") + GLTFImporter::GLTFImporter() : QSceneImporter(), m_parseDone(false) @@ -591,13 +627,13 @@ QString GLTFImporter::standardAttributeNameFromSemantic(const QString &semantic) return QString(); } -QParameter *GLTFImporter::parameterFromTechnique(QTechnique *technique, const QString ¶meterName) +QParameter *GLTFImporter::parameterFromTechnique(QTechnique *technique, + const QString ¶meterName) { - const auto parameters = technique->parameters(); + const QList<QParameter *> parameters = m_techniqueParameters.value(technique); for (QParameter *parameter : parameters) { - if (parameter->name() == parameterName) { + if (parameter->name() == parameterName) return parameter; - } } return nullptr; @@ -615,99 +651,116 @@ Qt3DCore::QEntity* GLTFImporter::defaultScene() QMaterial *GLTFImporter::materialWithCustomShader(const QString &id, const QJsonObject &jsonObj) { - //Default ES2 Technique - QString techniqueName = jsonObj.value(KEY_TECHNIQUE).toString(); - const auto it = qAsConst(m_techniques).find(techniqueName); - if (Q_UNLIKELY(it == m_techniques.cend())) { - qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", - qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); - return NULL; - } - QTechnique *technique = *it; - technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGLES); - technique->graphicsApiFilter()->setMajorVersion(2); - technique->graphicsApiFilter()->setMinorVersion(0); - technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile); - + QString effectName = jsonObj.value(KEY_EFFECT).toString(); + if (effectName.isEmpty()) { + // GLTF custom shader material (with qgltf tool specific customizations) - //Optional Core technique - QTechnique *coreTechnique = nullptr; - QTechnique *gl2Technique = nullptr; - QString coreTechniqueName = jsonObj.value(KEY_TECHNIQUE_CORE).toString(); - if (!coreTechniqueName.isNull()) { - const auto it = qAsConst(m_techniques).find(coreTechniqueName); + // Default ES2 Technique + QString techniqueName = jsonObj.value(KEY_TECHNIQUE).toString(); + const auto it = qAsConst(m_techniques).find(techniqueName); if (Q_UNLIKELY(it == m_techniques.cend())) { qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", - qUtf16PrintableImpl(coreTechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); - } else { - coreTechnique = it.value(); - coreTechnique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL); - coreTechnique->graphicsApiFilter()->setMajorVersion(3); - coreTechnique->graphicsApiFilter()->setMinorVersion(1); - coreTechnique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::CoreProfile); + qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); + return NULL; } - } - //Optional GL2 technique - QString gl2TechniqueName = jsonObj.value(KEY_TECHNIQUE_GL2).toString(); - if (!gl2TechniqueName.isNull()) { - const auto it = qAsConst(m_techniques).find(gl2TechniqueName); - if (Q_UNLIKELY(it == m_techniques.cend())) { - qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", - qUtf16PrintableImpl(gl2TechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); - } else { - gl2Technique = it.value(); - gl2Technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL); - gl2Technique->graphicsApiFilter()->setMajorVersion(2); - gl2Technique->graphicsApiFilter()->setMinorVersion(0); - gl2Technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile); + QTechnique *technique = *it; + technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGLES); + technique->graphicsApiFilter()->setMajorVersion(2); + technique->graphicsApiFilter()->setMinorVersion(0); + technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile); + + //Optional Core technique + QTechnique *coreTechnique = nullptr; + QTechnique *gl2Technique = nullptr; + QString coreTechniqueName = jsonObj.value(KEY_TECHNIQUE_CORE).toString(); + if (!coreTechniqueName.isNull()) { + const auto it = qAsConst(m_techniques).find(coreTechniqueName); + if (Q_UNLIKELY(it == m_techniques.cend())) { + qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", + qUtf16PrintableImpl(coreTechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); + } else { + coreTechnique = it.value(); + coreTechnique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL); + coreTechnique->graphicsApiFilter()->setMajorVersion(3); + coreTechnique->graphicsApiFilter()->setMinorVersion(1); + coreTechnique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::CoreProfile); + } } - } - - - // glTF doesn't deal in effects, but we need a trivial one to wrap - // up our techniques - // However we need to create a unique effect for each material instead - // of caching because QMaterial does not keep up with effects - // its not the parent of. - QEffect* effect = new QEffect; - effect->setObjectName(techniqueName); - effect->addTechnique(technique); - if (coreTechnique != nullptr) - effect->addTechnique(coreTechnique); - if (gl2Technique != nullptr) - effect->addTechnique(gl2Technique); - - QMaterial* mat = new QMaterial; - mat->setEffect(effect); - - renameFromJson(jsonObj, mat); - - const QJsonObject values = jsonObj.value(KEY_VALUES).toObject(); - for (auto it = values.begin(), end = values.end(); it != end; ++it) { - const QString vName = it.key(); - QParameter *param = parameterFromTechnique(technique, vName); - - if (param == nullptr && coreTechnique != nullptr) { - param = parameterFromTechnique(coreTechnique, vName); + //Optional GL2 technique + QString gl2TechniqueName = jsonObj.value(KEY_TECHNIQUE_GL2).toString(); + if (!gl2TechniqueName.isNull()) { + const auto it = qAsConst(m_techniques).find(gl2TechniqueName); + if (Q_UNLIKELY(it == m_techniques.cend())) { + qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", + qUtf16PrintableImpl(gl2TechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); + } else { + gl2Technique = it.value(); + gl2Technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL); + gl2Technique->graphicsApiFilter()->setMajorVersion(2); + gl2Technique->graphicsApiFilter()->setMinorVersion(0); + gl2Technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile); + } } - if (param == nullptr && gl2Technique != nullptr) { - param = parameterFromTechnique(gl2Technique, vName); - } + // glTF doesn't deal in effects, but we need a trivial one to wrap + // up our techniques + // However we need to create a unique effect for each material instead + // of caching because QMaterial does not keep up with effects + // its not the parent of. + QEffect *effect = new QEffect; + effect->setObjectName(techniqueName); + effect->addTechnique(technique); + if (coreTechnique != nullptr) + effect->addTechnique(coreTechnique); + if (gl2Technique != nullptr) + effect->addTechnique(gl2Technique); + + QMaterial *mat = new QMaterial; + mat->setEffect(effect); + + renameFromJson(jsonObj, mat); + + const QJsonObject values = jsonObj.value(KEY_VALUES).toObject(); + for (auto it = values.begin(), end = values.end(); it != end; ++it) { + const QString vName = it.key(); + QParameter *param = parameterFromTechnique(technique, vName); + + if (param == nullptr && coreTechnique != nullptr) + param = parameterFromTechnique(coreTechnique, vName); + + if (param == nullptr && gl2Technique != nullptr) + param = parameterFromTechnique(gl2Technique, vName); + + if (Q_UNLIKELY(!param)) { + qCWarning(GLTFImporterLog, "unknown parameter: %ls in technique %ls processing material %ls", + qUtf16PrintableImpl(vName), qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id)); + continue; + } - if (Q_UNLIKELY(!param)) { - qCWarning(GLTFImporterLog, "unknown parameter: %ls in technique %ls processing material %ls", - qUtf16PrintableImpl(vName), qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id)); - continue; - } + ParameterData paramData = m_parameterDataDict.value(param); + QVariant var = parameterValueFromJSON(paramData.type, it.value()); - ParameterData paramData = m_parameterDataDict.value(param); - QVariant var = parameterValueFromJSON(paramData.type, it.value()); + mat->addParameter(new QParameter(param->name(), var)); + } // of material technique-instance values iteration - mat->addParameter(new QParameter(param->name(), var)); - } // of material technique-instance values iteration + return mat; + } else { + // Qt3D exported QGLTF custom material + QMaterial *mat = new QMaterial; + renameFromJson(jsonObj, mat); + QEffect *effect = m_effects.value(effectName); + if (effect) { + mat->setEffect(effect); + } else { + qCWarning(GLTFImporterLog, "Effect %ls missing for material %ls", + qUtf16PrintableImpl(effectName), qUtf16PrintableImpl(mat->objectName())); + } + const QJsonObject params = jsonObj.value(KEY_PARAMETERS).toObject(); + for (auto it = params.begin(), end = params.end(); it != end; ++it) + mat->addParameter(buildParameter(it.key(), it.value().toObject())); - return mat; + return mat; + } } QMaterial *GLTFImporter::commonMaterial(const QJsonObject &jsonObj) @@ -941,10 +994,6 @@ void GLTFImporter::parse() for (auto it = programs.begin(), end = programs.end(); it != end; ++it) processJSONProgram(it.key(), it.value().toObject()); - const QJsonObject techniques = m_json.object().value(KEY_TECHNIQUES).toObject(); - for (auto it = techniques.begin(), end = techniques.end(); it != end; ++it) - processJSONTechnique(it.key(), it.value().toObject()); - const QJsonObject attrs = m_json.object().value(KEY_ACCESSORS).toObject(); for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) processJSONAccessor(it.key(), it.value().toObject()); @@ -965,6 +1014,18 @@ void GLTFImporter::parse() for (auto it = extensions.begin(), end = extensions.end(); it != end; ++it) processJSONExtensions(it.key(), it.value().toObject()); + const QJsonObject passes = m_json.object().value(KEY_RENDERPASSES).toObject(); + for (auto it = passes.begin(), end = passes.end(); it != end; ++it) + processJSONRenderPass(it.key(), it.value().toObject()); + + const QJsonObject techniques = m_json.object().value(KEY_TECHNIQUES).toObject(); + for (auto it = techniques.begin(), end = techniques.end(); it != end; ++it) + processJSONTechnique(it.key(), it.value().toObject()); + + const QJsonObject effects = m_json.object().value(KEY_EFFECTS).toObject(); + for (auto it = effects.begin(), end = effects.end(); it != end; ++it) + processJSONEffect(it.key(), it.value().toObject()); + m_defaultScene = m_json.object().value(KEY_SCENE).toString(); m_parseDone = true; } @@ -992,6 +1053,9 @@ void GLTFImporter::cleanup() m_shaderPaths.clear(); delete_if_without_parent(m_programs); m_programs.clear(); + for (auto params : m_techniqueParameters.values()) + delete_if_without_parent(params); + m_techniqueParameters.clear(); delete_if_without_parent(m_techniques); m_techniques.clear(); delete_if_without_parent(m_textures); @@ -999,6 +1063,10 @@ void GLTFImporter::cleanup() m_imagePaths.clear(); m_defaultScene.clear(); m_parameterDataDict.clear(); + delete_if_without_parent(m_renderPasses); + m_renderPasses.clear(); + delete_if_without_parent(m_effects); + m_effects.clear(); } void GLTFImporter::processJSONBuffer(const QString &id, const QJsonObject& json) @@ -1068,10 +1136,12 @@ void GLTFImporter::processJSONShader(const QString &id, const QJsonObject &jsonO void GLTFImporter::processJSONProgram(const QString &id, const QJsonObject &jsonObject) { - QString fragName = jsonObject.value(KEY_FRAGMENT_SHADER).toString(), - vertName = jsonObject.value(KEY_VERTEX_SHADER).toString(); - const auto fragIt = qAsConst(m_shaderPaths).find(fragName), - vertIt = qAsConst(m_shaderPaths).find(vertName); + const QString fragName = jsonObject.value(KEY_FRAGMENT_SHADER).toString(); + const QString vertName = jsonObject.value(KEY_VERTEX_SHADER).toString(); + + const auto fragIt = qAsConst(m_shaderPaths).find(fragName); + const auto vertIt = qAsConst(m_shaderPaths).find(vertName); + if (Q_UNLIKELY(fragIt == m_shaderPaths.cend() || vertIt == m_shaderPaths.cend())) { qCWarning(GLTFImporterLog, "program: %ls missing shader: %ls %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(fragName), qUtf16PrintableImpl(vertName)); @@ -1082,6 +1152,33 @@ void GLTFImporter::processJSONProgram(const QString &id, const QJsonObject &json prog->setObjectName(id); prog->setFragmentShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(fragIt.value()))); prog->setVertexShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(vertIt.value()))); + + const QString tessCtrlName = jsonObject.value(KEY_TESS_CTRL_SHADER).toString(); + if (!tessCtrlName.isEmpty()) { + const auto it = qAsConst(m_shaderPaths).find(tessCtrlName); + prog->setTessellationControlShaderCode( + QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); + } + + const QString tessEvalName = jsonObject.value(KEY_TESS_EVAL_SHADER).toString(); + if (!tessEvalName.isEmpty()) { + const auto it = qAsConst(m_shaderPaths).find(tessEvalName); + prog->setTessellationEvaluationShaderCode( + QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); + } + + const QString geomName = jsonObject.value(KEY_GEOMETRY_SHADER).toString(); + if (!geomName.isEmpty()) { + const auto it = qAsConst(m_shaderPaths).find(geomName); + prog->setGeometryShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); + } + + const QString computeName = jsonObject.value(KEY_COMPUTE_SHADER).toString(); + if (!computeName.isEmpty()) { + const auto it = qAsConst(m_shaderPaths).find(computeName); + prog->setComputeShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); + } + m_programs[id] = prog; } @@ -1090,115 +1187,121 @@ void GLTFImporter::processJSONTechnique(const QString &id, const QJsonObject &js QTechnique *t = new QTechnique; t->setObjectName(id); - // Parameters - QHash<QString, QParameter*> paramDict; - const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); - for (auto it = params.begin(), end = params.end(); it != end; ++it) { - const QString pname = it.key(); - const QJsonObject po = it.value().toObject(); - - //QString semantic = po.value(KEY_SEMANTIC).toString(); - QParameter *p = new QParameter(t); - p->setName(pname); - m_parameterDataDict.insert(p, ParameterData(po)); - - //If the parameter has default value, set it - QJsonValue value = po.value(KEY_VALUE); - if (!value.isUndefined()) { - int dataType = po.value(KEY_TYPE).toInt(); - p->setValue(parameterValueFromJSON(dataType, value)); + const QJsonObject gabifilter = jsonObject.value(KEY_GABIFILTER).toObject(); + if (gabifilter.isEmpty()) { + // Regular GLTF technique + + // Parameters + QHash<QString, QParameter *> paramDict; + const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); + for (auto it = params.begin(), end = params.end(); it != end; ++it) { + const QString pname = it.key(); + const QJsonObject po = it.value().toObject(); + QParameter *p = buildParameter(pname, po); + m_parameterDataDict.insert(p, ParameterData(po)); + // We don't want to insert invalid parameters to techniques themselves, but we + // need to maintain link between all parameters and techniques so we can resolve + // parameter type later when creating materials. + if (p->value().isValid()) + t->addParameter(p); + paramDict[pname] = p; } - t->addParameter(p); + // Program + QRenderPass *pass = new QRenderPass; + addProgramToPass(pass, jsonObject.value(KEY_PROGRAM).toString()); - paramDict[pname] = p; - } // of parameters iteration - - // Program - QRenderPass* pass = new QRenderPass; - QString programName = jsonObject.value(KEY_PROGRAM).toString(); - const auto progIt = qAsConst(m_programs).find(programName); - if (Q_UNLIKELY(progIt == m_programs.cend())) { - qCWarning(GLTFImporterLog, "technique %ls: missing program %ls", - qUtf16PrintableImpl(id), qUtf16PrintableImpl(programName)); - } else { - pass->setShaderProgram(progIt.value()); - } - - // Attributes - const QJsonObject attrs = jsonObject.value(KEY_ATTRIBUTES).toObject(); - for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) { - QString pname = it.value().toString(); - QParameter *parameter = paramDict.value(pname, nullptr); - QString attributeName = pname; - if (Q_UNLIKELY(!parameter)) { - qCWarning(GLTFImporterLog, "attribute %ls defined in instanceProgram but not as parameter", - qUtf16PrintableImpl(pname)); - continue; - } - //Check if the parameter has a standard attribute semantic - const auto paramDataIt = m_parameterDataDict.find(parameter); - QString standardAttributeName = standardAttributeNameFromSemantic(paramDataIt->semantic); - if (!standardAttributeName.isNull()) { - attributeName = standardAttributeName; - t->removeParameter(parameter); - m_parameterDataDict.erase(paramDataIt); - delete parameter; - } - - } // of program-instance attributes + // Attributes + const QJsonObject attrs = jsonObject.value(KEY_ATTRIBUTES).toObject(); + for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) { + QString pname = it.value().toString(); + QParameter *parameter = paramDict.value(pname, nullptr); + QString attributeName = pname; + if (Q_UNLIKELY(!parameter)) { + qCWarning(GLTFImporterLog, "attribute %ls defined in instanceProgram but not as parameter", + qUtf16PrintableImpl(pname)); + continue; + } + //Check if the parameter has a standard attribute semantic + const auto paramDataIt = m_parameterDataDict.find(parameter); + QString standardAttributeName = standardAttributeNameFromSemantic(paramDataIt->semantic); + if (!standardAttributeName.isNull()) { + attributeName = standardAttributeName; + t->removeParameter(parameter); + m_parameterDataDict.erase(paramDataIt); + paramDict.remove(pname); + delete parameter; + } - // Uniforms - const QJsonObject uniforms = jsonObject.value(KEY_UNIFORMS).toObject(); - for (auto it = uniforms.begin(), end = uniforms.end(); it != end; ++it) { - const QString pname = it.value().toString(); - QParameter *parameter = paramDict.value(pname, nullptr); - if (Q_UNLIKELY(!parameter)) { - qCWarning(GLTFImporterLog, "uniform %ls defined in instanceProgram but not as parameter", - qUtf16PrintableImpl(pname)); - continue; - } - //Check if the parameter has a standard uniform semantic - const auto paramDataIt = m_parameterDataDict.find(parameter); - if (hasStandardUniformNameFromSemantic(paramDataIt->semantic)) { - t->removeParameter(parameter); - m_parameterDataDict.erase(paramDataIt); - delete parameter; - } - } // of program-instance uniforms + } // of program-instance attributes + // Uniforms + const QJsonObject uniforms = jsonObject.value(KEY_UNIFORMS).toObject(); + for (auto it = uniforms.begin(), end = uniforms.end(); it != end; ++it) { + const QString pname = it.value().toString(); + QParameter *parameter = paramDict.value(pname, nullptr); + if (Q_UNLIKELY(!parameter)) { + qCWarning(GLTFImporterLog, "uniform %ls defined in instanceProgram but not as parameter", + qUtf16PrintableImpl(pname)); + continue; + } + //Check if the parameter has a standard uniform semantic + const auto paramDataIt = m_parameterDataDict.find(parameter); + if (hasStandardUniformNameFromSemantic(paramDataIt->semantic)) { + t->removeParameter(parameter); + m_parameterDataDict.erase(paramDataIt); + paramDict.remove(pname); + delete parameter; + } + } // of program-instance uniforms - // States - QJsonObject states = jsonObject.value(KEY_STATES).toObject(); + m_techniqueParameters.insert(t, paramDict.values()); - //Process states to enable - const QJsonArray enableStatesArray = states.value(KEY_ENABLE).toArray(); - QVector<int> enableStates; - for (const QJsonValue &enableValue : enableStatesArray) - enableStates.append(enableValue.toInt()); + populateRenderStates(pass, jsonObject.value(KEY_STATES).toObject()); - //Process the list of state functions - const QJsonObject functions = states.value(KEY_FUNCTIONS).toObject(); - for (auto it = functions.begin(), end = functions.end(); it != end; ++it) { - int enableStateType = 0; - QRenderState *renderState = buildState(it.key(), it.value(), enableStateType); - if (renderState != nullptr) { - //Remove the need to set a default state values for enableStateType - enableStates.removeOne(enableStateType); - pass->addRenderState(renderState); + t->addRenderPass(pass); + } else { + // Qt3D exported custom technique + + // Graphics API filter + t->graphicsApiFilter()->setApi(QGraphicsApiFilter::Api(gabifilter.value(KEY_API).toInt())); + t->graphicsApiFilter()->setMajorVersion(gabifilter.value(KEY_MAJORVERSION).toInt()); + t->graphicsApiFilter()->setMinorVersion(gabifilter.value(KEY_MINORVERSION).toInt()); + t->graphicsApiFilter()->setProfile(QGraphicsApiFilter::OpenGLProfile( + gabifilter.value(KEY_PROFILE).toInt())); + t->graphicsApiFilter()->setVendor(gabifilter.value(KEY_VENDOR).toString()); + QStringList extensionList; + QJsonArray extArray = gabifilter.value(KEY_EXTENSIONS).toArray(); + for (const QJsonValue &extValue : extArray) + extensionList << extValue.toString(); + t->graphicsApiFilter()->setExtensions(extensionList); + + // Filter keys (we will assume filter keys are always strings or integers) + const QJsonObject fkObj = jsonObject.value(KEY_FILTERKEYS).toObject(); + for (auto it = fkObj.begin(), end = fkObj.end(); it != end; ++it) + t->addFilterKey(buildFilterKey(it.key(), it.value())); + + t->setObjectName(jsonObject.value(KEY_NAME).toString()); + + // Parameters + const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); + for (auto it = params.begin(), end = params.end(); it != end; ++it) + t->addParameter(buildParameter(it.key(), it.value().toObject())); + + // Render passes + const QJsonArray passArray = jsonObject.value(KEY_RENDERPASSES).toArray(); + for (const QJsonValue &passValue : passArray) { + const QString passName = passValue.toString(); + QRenderPass *pass = m_renderPasses.value(passName); + if (pass) { + t->addRenderPass(pass); + } else { + qCWarning(GLTFImporterLog, "Render pass %ls missing for technique %ls", + qUtf16PrintableImpl(passName), qUtf16PrintableImpl(id)); + } } } - //Create render states with default values for any remaining enable states - for (int enableState : qAsConst(enableStates)) { - QRenderState *renderState = buildStateEnable(enableState); - if (renderState != nullptr) - pass->addRenderState(renderState); - } - - - t->addRenderPass(pass); - m_techniques[id] = t; } @@ -1423,6 +1526,49 @@ void GLTFImporter::processJSONExtensions(const QString &id, const QJsonObject &j } +void GLTFImporter::processJSONEffect(const QString &id, const QJsonObject &jsonObject) +{ + QEffect *effect = new QEffect; + renameFromJson(jsonObject, effect); + + const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); + for (auto it = params.begin(), end = params.end(); it != end; ++it) + effect->addParameter(buildParameter(it.key(), it.value().toObject())); + + const QJsonArray techArray = jsonObject.value(KEY_TECHNIQUES).toArray(); + for (const QJsonValue &techValue : techArray) { + const QString techName = techValue.toString(); + QTechnique *tech = m_techniques.value(techName); + if (tech) { + effect->addTechnique(tech); + } else { + qCWarning(GLTFImporterLog, "Technique pass %ls missing for effect %ls", + qUtf16PrintableImpl(techName), qUtf16PrintableImpl(id)); + } + } + + m_effects[id] = effect; +} + +void GLTFImporter::processJSONRenderPass(const QString &id, const QJsonObject &jsonObject) +{ + QRenderPass *pass = new QRenderPass; + const QJsonObject passFkObj = jsonObject.value(KEY_FILTERKEYS).toObject(); + for (auto it = passFkObj.begin(), end = passFkObj.end(); it != end; ++it) + pass->addFilterKey(buildFilterKey(it.key(), it.value())); + + const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); + for (auto it = params.begin(), end = params.end(); it != end; ++it) + pass->addParameter(buildParameter(it.key(), it.value().toObject())); + + populateRenderStates(pass, jsonObject.value(KEY_STATES).toObject()); + addProgramToPass(pass, jsonObject.value(KEY_PROGRAM).toString()); + + renameFromJson(jsonObject, pass); + + m_renderPasses[id] = pass; +} + void GLTFImporter::loadBufferData() { for (auto &bufferData : m_bufferDatas) { @@ -1485,6 +1631,8 @@ QVariant GLTFImporter::parameterValueFromJSON(int type, const QJsonValue &value) return QVariant(static_cast<GLuint>(value.toInt())); case GL_FLOAT: return QVariant(static_cast<GLfloat>(value.toDouble())); + default: + break; } } else if (value.isArray()) { @@ -1650,29 +1798,28 @@ QRenderState *GLTFImporter::buildStateEnable(int state) //By calling buildState with QJsonValue(), a Render State with //default values is created. - if (state == GL_BLEND) { + switch (state) { + case GL_BLEND: //It doesn't make sense to handle this state alone return nullptr; - } - - if (state == GL_CULL_FACE) { + case GL_CULL_FACE: return buildState(QStringLiteral("cullFace"), QJsonValue(), type); - } - - if (state == GL_DEPTH_TEST) { + case GL_DEPTH_TEST: return buildState(QStringLiteral("depthFunc"), QJsonValue(), type); - } - - if (state == GL_POLYGON_OFFSET_FILL) { + case GL_POLYGON_OFFSET_FILL: return buildState(QStringLiteral("polygonOffset"), QJsonValue(), type); - } - - if (state == GL_SAMPLE_ALPHA_TO_COVERAGE) { + case GL_SAMPLE_ALPHA_TO_COVERAGE: return new QAlphaCoverage(); - } - - if (state == GL_SCISSOR_TEST) { + case GL_SCISSOR_TEST: return buildState(QStringLiteral("scissor"), QJsonValue(), type); + case GL_DITHER: // Qt3D Custom + return new QDithering(); + case 0x809D: // GL_MULTISAMPLE - Qt3D Custom + return new QMultiSampleAntiAliasing(); + case 0x884F: // GL_TEXTURE_CUBE_MAP_SEAMLESS - Qt3D Custom + return new QSeamlessCubemap(); + default: + break; } qCWarning(GLTFImporterLog, "unsupported render state: %d", state); @@ -1707,6 +1854,7 @@ QRenderState* GLTFImporter::buildState(const QString& functionName, const QJsonV blendArgs->setSourceAlpha((QBlendEquationArguments::Blending)values.at(1).toInt(GL_ONE)); blendArgs->setDestinationRgb((QBlendEquationArguments::Blending)values.at(2).toInt(GL_ZERO)); blendArgs->setDestinationAlpha((QBlendEquationArguments::Blending)values.at(3).toInt(GL_ZERO)); + blendArgs->setBufferIndex(values.at(4).toInt(-1)); return blendArgs; } @@ -1777,10 +1925,132 @@ QRenderState* GLTFImporter::buildState(const QString& functionName, const QJsonV return scissorTest; } + // Qt3D custom functions + if (functionName == QLatin1String("alphaTest")) { + QAlphaTest *args = new QAlphaTest; + args->setAlphaFunction(QAlphaTest::AlphaFunction(values.at(0).toInt())); + args->setReferenceValue(float(values.at(1).toDouble())); + return args; + } + + if (functionName == QLatin1String("clipPlane")) { + QClipPlane *args = new QClipPlane; + args->setPlaneIndex(values.at(0).toInt()); + args->setNormal(QVector3D(float(values.at(1).toDouble()), + float(values.at(2).toDouble()), + float(values.at(3).toDouble()))); + args->setDistance(float(values.at(4).toDouble())); + return args; + } + + if (functionName == QLatin1String("pointSize")) { + QPointSize *pointSize = new QPointSize; + pointSize->setSizeMode(QPointSize::SizeMode(values.at(0).toInt(QPointSize::Programmable))); + pointSize->setValue(float(values.at(1).toDouble())); + return pointSize; + } + + if (functionName == QLatin1String("stencilMask")) { + QStencilMask *stencilMask = new QStencilMask; + stencilMask->setFrontOutputMask(uint(values.at(0).toInt())); + stencilMask->setBackOutputMask(uint(values.at(1).toInt())); + return stencilMask; + } + + if (functionName == QLatin1String("stencilOperation")) { + QStencilOperation *stencilOperation = new QStencilOperation; + stencilOperation->front()->setStencilTestFailureOperation( + QStencilOperationArguments::Operation(values.at(0).toInt( + QStencilOperationArguments::Keep))); + stencilOperation->front()->setDepthTestFailureOperation( + QStencilOperationArguments::Operation(values.at(1).toInt( + QStencilOperationArguments::Keep))); + stencilOperation->front()->setAllTestsPassOperation( + QStencilOperationArguments::Operation(values.at(2).toInt( + QStencilOperationArguments::Keep))); + stencilOperation->back()->setStencilTestFailureOperation( + QStencilOperationArguments::Operation(values.at(3).toInt( + QStencilOperationArguments::Keep))); + stencilOperation->back()->setDepthTestFailureOperation( + QStencilOperationArguments::Operation(values.at(4).toInt( + QStencilOperationArguments::Keep))); + stencilOperation->back()->setAllTestsPassOperation( + QStencilOperationArguments::Operation(values.at(5).toInt( + QStencilOperationArguments::Keep))); + return stencilOperation; + } + + if (functionName == QLatin1String("stencilTest")) { + QStencilTest *stencilTest = new QStencilTest; + stencilTest->front()->setComparisonMask(uint(values.at(0).toInt())); + stencilTest->front()->setReferenceValue(values.at(1).toInt()); + stencilTest->front()->setStencilFunction( + QStencilTestArguments::StencilFunction(values.at(2).toInt( + QStencilTestArguments::Never))); + stencilTest->back()->setComparisonMask(uint(values.at(3).toInt())); + stencilTest->back()->setReferenceValue(values.at(4).toInt()); + stencilTest->back()->setStencilFunction( + QStencilTestArguments::StencilFunction(values.at(5).toInt( + QStencilTestArguments::Never))); + return stencilTest; + } + qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName)); return nullptr; } +QParameter *GLTFImporter::buildParameter(const QString &key, const QJsonObject ¶mObj) +{ + QParameter *p = new QParameter; + p->setName(key); + QJsonValue value = paramObj.value(KEY_VALUE); + + if (!value.isUndefined()) { + int dataType = paramObj.value(KEY_TYPE).toInt(); + p->setValue(parameterValueFromJSON(dataType, value)); + } + + return p; +} + +void GLTFImporter::populateRenderStates(QRenderPass *pass, const QJsonObject &states) +{ + // Process states to enable + const QJsonArray enableStatesArray = states.value(KEY_ENABLE).toArray(); + QVector<int> enableStates; + for (const QJsonValue &enableValue : enableStatesArray) + enableStates.append(enableValue.toInt()); + + // Process the list of state functions + const QJsonObject functions = states.value(KEY_FUNCTIONS).toObject(); + for (auto it = functions.begin(), end = functions.end(); it != end; ++it) { + int enableStateType = 0; + QRenderState *renderState = buildState(it.key(), it.value(), enableStateType); + if (renderState != nullptr) { + //Remove the need to set a default state values for enableStateType + enableStates.removeOne(enableStateType); + pass->addRenderState(renderState); + } + } + + // Create render states with default values for any remaining enable states + for (int enableState : qAsConst(enableStates)) { + QRenderState *renderState = buildStateEnable(enableState); + if (renderState != nullptr) + pass->addRenderState(renderState); + } +} + +void GLTFImporter::addProgramToPass(QRenderPass *pass, const QString &progName) +{ + const auto progIt = qAsConst(m_programs).find(progName); + if (Q_UNLIKELY(progIt == m_programs.cend())) + qCWarning(GLTFImporterLog, "missing program %ls", qUtf16PrintableImpl(progName)); + else + pass->setShaderProgram(progIt.value()); +} + + } // namespace Qt3DRender QT_END_NAMESPACE diff --git a/src/plugins/sceneparsers/gltf/gltfimporter.h b/src/plugins/sceneparsers/gltf/gltfimporter.h index 5033c4f05..8f6b9b356 100644 --- a/src/plugins/sceneparsers/gltf/gltfimporter.h +++ b/src/plugins/sceneparsers/gltf/gltfimporter.h @@ -52,12 +52,13 @@ // We mean it. // -#include <QtCore/QJsonDocument> -#include <QtCore/QMultiHash> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qhash.h> #include <Qt3DRender/qattribute.h> #include <Qt3DRender/qbuffer.h> -#include <Qt3DRender/private/qsceneimporter_p.h> +#include <private/qsceneimporter_p.h> QT_BEGIN_NAMESPACE @@ -80,6 +81,7 @@ class QTechnique; class QParameter; class QGeometryRenderer; class QAbstractLight; +class QRenderPass; Q_DECLARE_LOGGING_CATEGORY(GLTFImporterLog) @@ -141,7 +143,7 @@ private: static void renameFromJson(const QJsonObject& json, QObject * const object ); static bool hasStandardUniformNameFromSemantic(const QString &semantic); static QString standardAttributeNameFromSemantic(const QString &semantic); - static QParameter *parameterFromTechnique(QTechnique *technique, const QString ¶meterName); + QParameter *parameterFromTechnique(QTechnique *technique, const QString ¶meterName); Qt3DCore::QEntity *defaultScene(); QMaterial *material(const QString &id); @@ -160,6 +162,8 @@ private: void processJSONImage(const QString &id, const QJsonObject &jsonObject); void processJSONTexture(const QString &id, const QJsonObject &jsonObject); void processJSONExtensions(const QString &id, const QJsonObject &jsonObject); + void processJSONEffect(const QString &id, const QJsonObject &jsonObject); + void processJSONRenderPass(const QString &id, const QJsonObject &jsonObject); void loadBufferData(); void unloadBufferData(); @@ -172,6 +176,9 @@ private: static QRenderState *buildStateEnable(int state); static QRenderState *buildState(const QString& functionName, const QJsonValue &value, int &type); + QParameter *buildParameter(const QString &key, const QJsonObject ¶mObj); + void populateRenderStates(QRenderPass *pass, const QJsonObject &states); + void addProgramToPass(QRenderPass *pass, const QString &progName); QMaterial *materialWithCustomShader(const QString &id, const QJsonObject &jsonObj); QMaterial *commonMaterial(const QJsonObject &jsonObj); @@ -200,6 +207,9 @@ private: QHash<QString, QShaderProgram*> m_programs; QHash<QString, QTechnique *> m_techniques; + QHash<QString, QRenderPass *> m_renderPasses; + QHash<QString, QEffect *> m_effects; + QHash<QTechnique *, QList<QParameter *> > m_techniqueParameters; QHash<QParameter*, ParameterData> m_parameterDataDict; QHash<QString, QAbstractTexture*> m_textures; diff --git a/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp b/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp index e9f1a194e..61a448666 100644 --- a/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp +++ b/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp @@ -77,6 +77,26 @@ #include <Qt3DRender/qgeometry.h> #include <Qt3DRender/qgeometryrenderer.h> #include <Qt3DRender/qgeometryfactory.h> +#include <Qt3DRender/qtechnique.h> +#include <Qt3DRender/qalphacoverage.h> +#include <Qt3DRender/qalphatest.h> +#include <Qt3DRender/qclipplane.h> +#include <Qt3DRender/qcolormask.h> +#include <Qt3DRender/qcullface.h> +#include <Qt3DRender/qdepthtest.h> +#include <Qt3DRender/qdithering.h> +#include <Qt3DRender/qfrontface.h> +#include <Qt3DRender/qmultisampleantialiasing.h> +#include <Qt3DRender/qnodepthmask.h> +#include <Qt3DRender/qpointsize.h> +#include <Qt3DRender/qpolygonoffset.h> +#include <Qt3DRender/qscissortest.h> +#include <Qt3DRender/qseamlesscubemap.h> +#include <Qt3DRender/qstencilmask.h> +#include <Qt3DRender/qstenciloperation.h> +#include <Qt3DRender/qstenciloperationarguments.h> +#include <Qt3DRender/qstenciltest.h> +#include <Qt3DRender/qstenciltestarguments.h> #include <Qt3DExtras/qconemesh.h> #include <Qt3DExtras/qcuboidmesh.h> #include <Qt3DExtras/qcylindermesh.h> @@ -95,17 +115,102 @@ #include <private/qurlhelper_p.h> -#define GLT_UNSIGNED_SHORT 0x1403 -#define GLT_UNSIGNED_INT 0x1405 -#define GLT_FLOAT 0x1406 -#define GLT_ARRAY_BUFFER 0x8892 -#define GLT_ELEMENT_ARRAY_BUFFER 0x8893 - #ifndef qUtf16PrintableImpl # define qUtf16PrintableImpl(string) \ static_cast<const wchar_t*>(static_cast<const void*>(string.utf16())) #endif +namespace { + +inline QJsonArray col2jsvec(const QColor &color, bool alpha = false) +{ + QJsonArray arr; + arr << color.redF() << color.greenF() << color.blueF(); + if (alpha) + arr << color.alphaF(); + return arr; +} + +template <typename T> +inline QJsonArray vec2jsvec(const QVector<T> &v) +{ + QJsonArray arr; + for (int i = 0; i < v.count(); ++i) + arr << v.at(i); + return arr; +} + +inline QJsonArray vec2jsvec(const QVector2D &v) +{ + QJsonArray arr; + arr << v.x() << v.y(); + return arr; +} + +inline QJsonArray vec2jsvec(const QVector3D &v) +{ + QJsonArray arr; + arr << v.x() << v.y() << v.z(); + return arr; +} + +inline QJsonArray vec2jsvec(const QVector4D &v) +{ + QJsonArray arr; + arr << v.x() << v.y() << v.z() << v.w(); + return arr; +} + +inline QJsonArray matrix2jsvec(const QMatrix2x2 &matrix) +{ + QJsonArray jm; + const float *mtxp = matrix.constData(); + for (int j = 0; j < 4; ++j) + jm.append(*mtxp++); + return jm; +} + +inline QJsonArray matrix2jsvec(const QMatrix3x3 &matrix) +{ + QJsonArray jm; + const float *mtxp = matrix.constData(); + for (int j = 0; j < 9; ++j) + jm.append(*mtxp++); + return jm; +} + +inline QJsonArray matrix2jsvec(const QMatrix4x4 &matrix) +{ + QJsonArray jm; + const float *mtxp = matrix.constData(); + for (int j = 0; j < 16; ++j) + jm.append(*mtxp++); + return jm; +} + +inline void promoteColorsToRGBA(QJsonObject *obj) +{ + auto it = obj->begin(); + auto itEnd = obj->end(); + while (it != itEnd) { + QJsonArray arr = it.value().toArray(); + if (arr.count() == 3) { + const QString key = it.key(); + if (key == QStringLiteral("ambient") + || key == QStringLiteral("diffuse") + || key == QStringLiteral("specular") + || key == QStringLiteral("warm") + || key == QStringLiteral("cool")) { + arr.append(1); + *it = arr; + } + } + ++it; + } +} + +} // namespace + QT_BEGIN_NAMESPACE using namespace Qt3DCore; @@ -152,31 +257,6 @@ GLTFExporter::~GLTFExporter() { } -// Calculate bounding box -void calcBB(QVector<float> &minVal, QVector<float> &maxVal, const float *data, - int vertexCount, int compCount, int offset, int stride) -{ - minVal.resize(compCount); - maxVal.resize(compCount); - int dataIndex = offset; - const int adjustedStride = stride > 0 ? stride - compCount : 0; - for (int i = 0; i < vertexCount; ++i) { - for (int j = 0; j < compCount; ++j) { - if (i == 0) { - minVal[j] = data[dataIndex]; - maxVal[j] = data[dataIndex]; - } else { - if (data[dataIndex] < minVal[j]) - minVal[j] = data[dataIndex]; - if (data[dataIndex] > maxVal[j]) - maxVal[j] = data[dataIndex]; - } - dataIndex++; - } - dataIndex += adjustedStride; - } -} - // sceneRoot : The root entity that contains the exported scene. If the sceneRoot doesn't have // any exportable components, it is not exported itself. This is because importing a // scene creates an empty top level entity to hold the scene. @@ -197,11 +277,19 @@ bool GLTFExporter::exportScene(QEntity *sceneRoot, const QString &outDir, m_materialMap.clear(); m_cameraMap.clear(); m_lightMap.clear(); + m_transformMap.clear(); m_imageMap.clear(); + m_textureIdMap.clear(); m_meshInfo.clear(); m_materialInfo.clear(); m_cameraInfo.clear(); + m_lightInfo.clear(); m_exportedFiles.clear(); + m_renderPassIdMap.clear(); + m_shaderInfo.clear(); + m_programInfo.clear(); + m_techniqueIdMap.clear(); + m_effectIdMap.clear(); delNode(m_rootNode); @@ -217,6 +305,8 @@ bool GLTFExporter::exportScene(QEntity *sceneRoot, const QString &outDir, m_nodeCount = 0; m_cameraCount = 0; m_lightCount = 0; + m_renderPassCount = 0; + m_effectCount = 0; m_gltfOpts.binaryJson = options.value(QStringLiteral("binaryJson"), QVariant(false)).toBool(); @@ -260,16 +350,13 @@ bool GLTFExporter::exportScene(QEntity *sceneRoot, const QString &outDir, m_exportDir = exportDir.path(); m_exportDir.append(QStringLiteral("/")); - qCDebug(GLTFExporterLog, "Output directory: &ls", qUtf16PrintableImpl(absoluteOutDir)); - qCDebug(GLTFExporterLog, "Export name: &ls", qUtf16PrintableImpl(m_exportName)); - qCDebug(GLTFExporterLog, "Temp export dir: &ls", qUtf16PrintableImpl(m_exportDir)); - qCDebug(GLTFExporterLog, "Final export dir: &ls", qUtf16PrintableImpl(finalExportDir)); + qCDebug(GLTFExporterLog, "Output directory: %ls", qUtf16PrintableImpl(absoluteOutDir)); + qCDebug(GLTFExporterLog, "Export name: %ls", qUtf16PrintableImpl(m_exportName)); + qCDebug(GLTFExporterLog, "Temp export dir: %ls", qUtf16PrintableImpl(m_exportDir)); + qCDebug(GLTFExporterLog, "Final export dir: %ls", qUtf16PrintableImpl(finalExportDir)); parseScene(); - // Copy textures into temporary directory - copyTextures(); - // Export scene to temporary directory if (!saveScene()) { qCWarning(GLTFExporterLog, "Exporting GLTF scene failed"); @@ -327,54 +414,67 @@ void GLTFExporter::copyTextures() { qCDebug(GLTFExporterLog, "Copying textures..."); QHash<QString, QString> copiedMap; - for (auto it = m_materialInfo.constBegin(); it != m_materialInfo.constEnd(); ++it) { - const MaterialInfo &matInfo = it.value(); - for (auto texIt = matInfo.textures.constBegin(); texIt != matInfo.textures.constEnd(); - ++texIt) { - QString texKey = texIt.key(); - QFileInfo fi(texIt.value()); - QString absoluteFilePath; - if (texIt.value().startsWith(QStringLiteral(":"))) - absoluteFilePath = texIt.value(); - else - absoluteFilePath = fi.absoluteFilePath(); - if (copiedMap.contains(absoluteFilePath)) { - // Texture has already been copied - qCDebug(GLTFExporterLog, " Skipped copying duplicate texture: '%ls'", - qUtf16PrintableImpl(absoluteFilePath)); - if (!m_imageMap.contains(texIt.value())) - m_imageMap.insert(texIt.value(), copiedMap.value(absoluteFilePath)); - } else { - QString fileName = fi.fileName(); - QString outFile = m_exportDir; - outFile.append(fileName); - QFileInfo fiTry(outFile); - if (fiTry.exists()) { - static const QString outFileTemplate = QStringLiteral("%2_%3.%4"); - int counter = 0; - QString tryFile = outFile; - QString suffix = fiTry.suffix(); - QString base = fiTry.baseName(); - while (fiTry.exists()) { - fileName = outFileTemplate.arg(base).arg(counter++).arg(suffix); - tryFile = m_exportDir; - tryFile.append(fileName); - fiTry.setFile(tryFile); - } - outFile = tryFile; - } - if (!QFile(absoluteFilePath).copy(outFile)) { - qCWarning(GLTFExporterLog, " Failed to copy texture: '%ls' -> '%ls'", - qUtf16PrintableImpl(absoluteFilePath), qUtf16PrintableImpl(outFile)); - } else { - qCDebug(GLTFExporterLog, " Copied texture: '%ls' -> '%ls'", - qUtf16PrintableImpl(absoluteFilePath), qUtf16PrintableImpl(outFile)); + for (auto texIt = m_textureIdMap.constBegin(); texIt != m_textureIdMap.constEnd(); ++texIt) { + QFileInfo fi(texIt.key()); + QString absoluteFilePath; + if (texIt.key().startsWith(QStringLiteral(":"))) + absoluteFilePath = texIt.key(); + else + absoluteFilePath = fi.absoluteFilePath(); + if (copiedMap.contains(absoluteFilePath)) { + // Texture has already been copied + qCDebug(GLTFExporterLog, " Skipped copying duplicate texture: '%ls'", + qUtf16PrintableImpl(absoluteFilePath)); + if (!m_imageMap.contains(texIt.key())) + m_imageMap.insert(texIt.key(), copiedMap.value(absoluteFilePath)); + } else { + QString fileName = fi.fileName(); + QString outFile = m_exportDir; + outFile.append(fileName); + QFileInfo fiTry(outFile); + if (fiTry.exists()) { + static const QString outFileTemplate = QStringLiteral("%2_%3.%4"); + int counter = 0; + QString tryFile = outFile; + QString suffix = fiTry.suffix(); + QString base = fiTry.baseName(); + while (fiTry.exists()) { + fileName = outFileTemplate.arg(base).arg(counter++).arg(suffix); + tryFile = m_exportDir; + tryFile.append(fileName); + fiTry.setFile(tryFile); } - // Generate actual target file (as current exportDir is temp dir) - copiedMap.insert(absoluteFilePath, fileName); - m_exportedFiles.insert(fileName); - m_imageMap.insert(texIt.value(), fileName); + outFile = tryFile; + } + if (!QFile(absoluteFilePath).copy(outFile)) { + qCWarning(GLTFExporterLog, " Failed to copy texture: '%ls' -> '%ls'", + qUtf16PrintableImpl(absoluteFilePath), qUtf16PrintableImpl(outFile)); + } else { + qCDebug(GLTFExporterLog, " Copied texture: '%ls' -> '%ls'", + qUtf16PrintableImpl(absoluteFilePath), qUtf16PrintableImpl(outFile)); } + // Generate actual target file (as current exportDir is temp dir) + copiedMap.insert(absoluteFilePath, fileName); + m_exportedFiles.insert(fileName); + m_imageMap.insert(texIt.key(), fileName); + } + } +} + +// Creates shaders to the temporary export directory. +void GLTFExporter::createShaders() +{ + qCDebug(GLTFExporterLog, "Creating shaders..."); + for (auto si : m_shaderInfo) { + const QString fileName = m_exportDir + si.uri; + QFile f(fileName); + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + m_exportedFiles.insert(QFileInfo(f.fileName()).fileName()); + f.write(si.code); + f.close(); + } else { + qCWarning(GLTFExporterLog, " Writing shaderfile '%ls' failed!", + qUtf16PrintableImpl(fileName)); } } } @@ -468,38 +568,36 @@ void GLTFExporter::parseMaterials() matInfo.type = MaterialInfo::TypePerVertex; } else { matInfo.type = MaterialInfo::TypeCustom; - // TODO: Implement support for custom materials - qCDebug(GLTFExporterLog, "Exporting custom materials is not supported: %s", - material->metaObject()->className()); - continue; } - if (material->effect()) { - QVector<QParameter *> parameters = material->effect()->parameters(); - for (auto param : parameters) { - if (param->value().type() == QVariant::Color) { - QColor color = param->value().value<QColor>(); - if (param->name() == MATERIAL_AMBIENT_COLOR) - matInfo.colors.insert(QStringLiteral("ambient"), color); - else if (param->name() == MATERIAL_DIFFUSE_COLOR) - matInfo.colors.insert(QStringLiteral("diffuse"), color); - else if (param->name() == MATERIAL_SPECULAR_COLOR) - matInfo.colors.insert(QStringLiteral("specular"), color); - else if (param->name() == MATERIAL_COOL_COLOR) // Custom Qt3D param for gooch - matInfo.colors.insert(QStringLiteral("cool"), color); - else if (param->name() == MATERIAL_WARM_COLOR) // Custom Qt3D param for gooch - matInfo.colors.insert(QStringLiteral("warm"), color); - else - matInfo.colors.insert(param->name(), color); - } else if (param->value().canConvert<QAbstractTexture *>()) { - QAbstractTexture *texture = param->value().value<QAbstractTexture *>(); - for (auto ti : texture->textureImages()) { - QString urlString; - Qt3DRender::QTextureImage *image = - qobject_cast<Qt3DRender::QTextureImage *>(ti); - if (image) - urlString = QUrlHelper::urlToLocalFileOrQrc(image->source()); - + if (matInfo.type == MaterialInfo::TypeCustom) { + if (material->effect()) { + if (!m_effectIdMap.contains(material->effect())) + m_effectIdMap.insert(material->effect(), newEffectName()); + parseTechniques(material); + } + } else { + // Default materials do not have separate effect, all effect parameters are stored as + // material values. + if (material->effect()) { + QVector<QParameter *> parameters = material->effect()->parameters(); + for (auto param : parameters) { + if (param->value().type() == QVariant::Color) { + QColor color = param->value().value<QColor>(); + if (param->name() == MATERIAL_AMBIENT_COLOR) + matInfo.colors.insert(QStringLiteral("ambient"), color); + else if (param->name() == MATERIAL_DIFFUSE_COLOR) + matInfo.colors.insert(QStringLiteral("diffuse"), color); + else if (param->name() == MATERIAL_SPECULAR_COLOR) + matInfo.colors.insert(QStringLiteral("specular"), color); + else if (param->name() == MATERIAL_COOL_COLOR) // Custom Qt3D gooch + matInfo.colors.insert(QStringLiteral("cool"), color); + else if (param->name() == MATERIAL_WARM_COLOR) // Custom Qt3D gooch + matInfo.colors.insert(QStringLiteral("warm"), color); + else + matInfo.colors.insert(param->name(), color); + } else if (param->value().canConvert<QAbstractTexture *>()) { + const QString urlString = textureVariantToUrl(param->value()); if (param->name() == MATERIAL_DIFFUSE_TEXTURE) matInfo.textures.insert(QStringLiteral("diffuse"), urlString); else if (param->name() == MATERIAL_SPECULAR_TEXTURE) @@ -508,21 +606,17 @@ void GLTFExporter::parseMaterials() matInfo.textures.insert(QStringLiteral("normal"), urlString); else matInfo.textures.insert(param->name(), urlString); - } - } else if (param->name() == MATERIAL_SHININESS) { - matInfo.values.insert(QStringLiteral("shininess"), param->value()); - } else if (param->name() == MATERIAL_BETA) { // Custom Qt3D param for gooch - matInfo.values.insert(QStringLiteral("beta"), param->value()); - } else if (param->name() == MATERIAL_ALPHA) { - if (matInfo.type == MaterialInfo::TypeGooch) - matInfo.values.insert(QStringLiteral("alpha"), param->value()); - else - matInfo.values.insert(QStringLiteral("transparency"), param->value()); - } else if (param->name() == MATERIAL_TEXTURE_SCALE) { // Custom Qt3D param - matInfo.values.insert(QStringLiteral("textureScale"), param->value()); - } else { - if (matInfo.type == MaterialInfo::TypeCustom) { - matInfo.values.insert(param->name(), param->value()); + } else if (param->name() == MATERIAL_SHININESS) { + matInfo.values.insert(QStringLiteral("shininess"), param->value()); + } else if (param->name() == MATERIAL_BETA) { // Custom Qt3D param for gooch + matInfo.values.insert(QStringLiteral("beta"), param->value()); + } else if (param->name() == MATERIAL_ALPHA) { + if (matInfo.type == MaterialInfo::TypeGooch) + matInfo.values.insert(QStringLiteral("alpha"), param->value()); + else + matInfo.values.insert(QStringLiteral("transparency"), param->value()); + } else if (param->name() == MATERIAL_TEXTURE_SCALE) { // Custom Qt3D param + matInfo.values.insert(QStringLiteral("textureScale"), param->value()); } else { qCDebug(GLTFExporterLog, "Common material had unknown parameter: '%ls'", @@ -531,6 +625,7 @@ void GLTFExporter::parseMaterials() } } } + if (GLTFExporterLog().isDebugEnabled()) { qCDebug(GLTFExporterLog, " Material #%i", materialCount); qCDebug(GLTFExporterLog, " name: '%ls'", qUtf16PrintableImpl(matInfo.name)); @@ -581,154 +676,87 @@ void GLTFExporter::parseMeshes() } QAttribute *indexAttrib = nullptr; - QAttribute *verticesAttrib = nullptr; - QAttribute *normalsAttrib = nullptr; - QAttribute *texCoordsAttrib = nullptr; - QAttribute *colorAttrib = nullptr; - QAttribute *tangentsAttrib = nullptr; - - const float *vertexPtr = nullptr; - const float *normalsPtr = nullptr; - const float *texCoordsPtr = nullptr; - const float *colorPtr = nullptr; - const float *tangentsPtr = nullptr; const quint16 *indexPtr = nullptr; - for (auto att : meshGeometry->attributes()) { + struct VertexAttrib { + QAttribute *att; + const float *ptr; + QString usage; + uint offset; + uint stride; + int index; + }; + + QVector<VertexAttrib> vAttribs; + vAttribs.reserve(meshGeometry->attributes().size()); + + uint stride(0); + + for (QAttribute *att : meshGeometry->attributes()) { if (att->attributeType() == QAttribute::IndexAttribute) { indexAttrib = att; indexPtr = reinterpret_cast<const quint16 *>(att->buffer()->data().constData()); - } else if (att->name() == VERTICES_ATTRIBUTE_NAME) { - verticesAttrib = att; - vertexPtr = reinterpret_cast<const float *>(att->buffer()->data().constData()); - } else if (att->name() == NORMAL_ATTRIBUTE_NAME) { - normalsAttrib = att; - normalsPtr = reinterpret_cast<const float *>(att->buffer()->data().constData()); - } else if (att->name() == TEXTCOORD_ATTRIBUTE_NAME) { - texCoordsAttrib = att; - texCoordsPtr = reinterpret_cast<const float *>(att->buffer()->data().constData()); - } else if (att->name() == COLOR_ATTRIBUTE_NAME) { - colorAttrib = att; - colorPtr = reinterpret_cast<const float *>(att->buffer()->data().constData()); - } else if (att->name() == TANGENT_ATTRIBUTE_NAME) { - tangentsAttrib = att; - tangentsPtr = reinterpret_cast<const float *>(att->buffer()->data().constData()); + } else { + VertexAttrib vAtt; + vAtt.att = att; + vAtt.ptr = reinterpret_cast<const float *>(att->buffer()->data().constData()); + if (att->name() == VERTICES_ATTRIBUTE_NAME) + vAtt.usage = QStringLiteral("POSITION"); + else if (att->name() == NORMAL_ATTRIBUTE_NAME) + vAtt.usage = QStringLiteral("NORMAL"); + else if (att->name() == TEXTCOORD_ATTRIBUTE_NAME) + vAtt.usage = QStringLiteral("TEXCOORD_0"); + else if (att->name() == COLOR_ATTRIBUTE_NAME) + vAtt.usage = QStringLiteral("COLOR"); + else if (att->name() == TANGENT_ATTRIBUTE_NAME) + vAtt.usage = QStringLiteral("TANGENT"); + else + vAtt.usage = att->name(); + + vAtt.offset = att->byteOffset() / sizeof(float); + vAtt.index = vAtt.offset; + vAtt.stride = att->byteStride() > 0 + ? att->byteStride() / sizeof(float) - att->vertexSize() : 0; + stride += att->vertexSize(); + + vAttribs << vAtt; } } - Q_ASSERT(verticesAttrib); + int attribCount(vAttribs.size()); + if (!attribCount) { + qCWarning(GLTFExporterLog, "Ignoring mesh without any attributes!"); + continue; + } // Default types use single interleaved buffer for all vertex data, but we must regenerate // it as it is not available on the frontend by default. QByteArray defaultVertexArray; QByteArray defaultIndexArray; if (defaultType) { - defaultVertexArray = verticesAttrib->buffer()->dataGenerator().data()->operator()(); + defaultVertexArray = + vAttribs.at(0).att->buffer()->dataGenerator().data()->operator()(); const float *defaultVertexBufferPtr = reinterpret_cast<const float *>(defaultVertexArray.constData()); - vertexPtr = defaultVertexBufferPtr; - if (normalsPtr) - normalsPtr = defaultVertexBufferPtr; - if (texCoordsPtr) - texCoordsPtr = defaultVertexBufferPtr; - if (colorPtr) - colorPtr = defaultVertexBufferPtr; - if (tangentsPtr) - tangentsPtr = defaultVertexBufferPtr; + for (int i = 0; i < attribCount; i++) + vAttribs[i].ptr = defaultVertexBufferPtr; defaultIndexArray = indexAttrib->buffer()->dataGenerator().data()->operator()(); indexPtr = reinterpret_cast<const quint16 *>(defaultIndexArray.constData()); } - const uint vertexOffset = verticesAttrib->byteOffset() / sizeof(float); - uint normalOffset = 0; - uint textCoordOffset = 0; - uint colorOffset = 0; - uint tangentOffset = 0; - - const uint vertexStride = verticesAttrib->byteStride() > 0 - ? verticesAttrib->byteStride() / sizeof(float) - 3 : 0; - uint normalStride = 0; - uint textCoordStride = 0; - uint colorStride = 0; - uint tangentStride = 0; - - uint stride = 3; - - if (normalsAttrib) { - normalOffset = normalsAttrib->byteOffset() / sizeof(float); - normalStride = normalsAttrib->byteStride() / sizeof(float); - if (normalStride > 0) - normalStride -= 3; - stride += 3; - } - if (texCoordsAttrib) { - textCoordOffset = texCoordsAttrib->byteOffset() / sizeof(float); - textCoordStride = texCoordsAttrib->byteStride() / sizeof(float); - if (textCoordStride > 0) - textCoordStride -= 2; - stride += 2; - } - if (colorAttrib) { - colorOffset = colorAttrib->byteOffset() / sizeof(float); - colorStride = colorAttrib->byteStride() / sizeof(float); - if (colorStride > 0) - colorStride -= 4; - stride += 4; - } - if (tangentsAttrib) { - tangentOffset = tangentsAttrib->byteOffset() / sizeof(float); - tangentStride = tangentsAttrib->byteStride() / sizeof(float); - if (tangentStride > 0) - tangentStride -= 4; - stride += 4; - } - QByteArray vertexBuf; - const int vertexCount = verticesAttrib->count(); + const int vertexCount = vAttribs.at(0).att->count(); vertexBuf.resize(stride * vertexCount * sizeof(float)); float *p = reinterpret_cast<float *>(vertexBuf.data()); - uint vertexIndex = vertexOffset; - uint normalIndex = normalOffset; - uint textCoordIndex = textCoordOffset; - uint colorIndex = colorOffset; - uint tangentIndex = tangentOffset; - // Create interleaved buffer - for (int j = 0; j < vertexCount; ++j) { - *p++ = vertexPtr[vertexIndex++]; - *p++ = vertexPtr[vertexIndex++]; - *p++ = vertexPtr[vertexIndex++]; - vertexIndex += vertexStride; - - if (normalsPtr) { - *p++ = normalsPtr[normalIndex++]; - *p++ = normalsPtr[normalIndex++]; - *p++ = normalsPtr[normalIndex++]; - normalIndex += normalStride; - } - - if (texCoordsPtr) { - *p++ = texCoordsPtr[textCoordIndex++]; - *p++ = texCoordsPtr[textCoordIndex++]; - textCoordIndex += textCoordStride; - } - - if (colorPtr) { - *p++ = colorPtr[colorIndex++]; - *p++ = colorPtr[colorIndex++]; - *p++ = colorPtr[colorIndex++]; - *p++ = colorPtr[colorIndex++]; - colorIndex += colorStride; - } - - if (tangentsPtr) { - *p++ = tangentsPtr[tangentIndex++]; - *p++ = tangentsPtr[tangentIndex++]; - *p++ = tangentsPtr[tangentIndex++]; - *p++ = tangentsPtr[tangentIndex++]; - tangentIndex += tangentStride; + for (int i = 0; i < vertexCount; ++i) { + for (int j = 0; j < attribCount; ++j) { + VertexAttrib &vAtt = vAttribs[j]; + for (uint k = 0; k < vAtt.att->vertexSize(); ++k) + *p++ = vAtt.ptr[vAtt.index++]; + vAtt.index += vAtt.stride; } } @@ -736,8 +764,8 @@ void GLTFExporter::parseMeshes() vertexBufView.name = newBufferViewName(); vertexBufView.length = vertexBuf.size(); vertexBufView.offset = m_buffer.size(); - vertexBufView.componentType = GLT_FLOAT; - vertexBufView.target = GLT_ARRAY_BUFFER; + vertexBufView.componentType = GL_FLOAT; + vertexBufView.target = GL_ARRAY_BUFFER; meshInfo.views.append(vertexBufView); QByteArray indexBuf; @@ -770,60 +798,48 @@ void GLTFExporter::parseMeshes() indexBufView.length = indexBuf.size(); indexBufView.offset = vertexBufView.offset + vertexBufView.length; indexBufView.componentType = indexSize == sizeof(quint32) - ? GLT_UNSIGNED_INT : GLT_UNSIGNED_SHORT; - indexBufView.target = GLT_ELEMENT_ARRAY_BUFFER; + ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; + indexBufView.target = GL_ELEMENT_ARRAY_BUFFER; meshInfo.views.append(indexBufView); } MeshInfo::Accessor acc; uint startOffset = 0; - const float *bufPtr = reinterpret_cast<const float *>(vertexBuf.constData()); - acc.name = newAccessorName(); - acc.usage = QStringLiteral("POSITION"); + acc.bufferView = vertexBufView.name; - acc.offset = 0; acc.stride = stride * sizeof(float); acc.count = vertexCount; acc.componentType = vertexBufView.componentType; - acc.type = QStringLiteral("VEC3"); - calcBB(acc.minVal, acc.maxVal, bufPtr, vertexCount, 3, startOffset, stride); - meshInfo.accessors.append(acc); - startOffset += 3; - - if (normalsAttrib) { + for (int i = 0; i < attribCount; ++i) { + const VertexAttrib &vAtt = vAttribs.at(i); acc.name = newAccessorName(); - acc.usage = QStringLiteral("NORMAL"); + acc.usage = vAtt.usage; acc.offset = startOffset * sizeof(float); - calcBB(acc.minVal, acc.maxVal, bufPtr, vertexCount, 3, startOffset, stride); - meshInfo.accessors.append(acc); - startOffset += 3; - } - if (texCoordsAttrib) { - acc.name = newAccessorName(); - acc.usage = QStringLiteral("TEXCOORD_0"); - acc.offset = startOffset * sizeof(float); - acc.type = QStringLiteral("VEC2"); - calcBB(acc.minVal, acc.maxVal, bufPtr, vertexCount, 2, startOffset, stride); - meshInfo.accessors.append(acc); - startOffset += 2; - } - if (colorAttrib) { - acc.name = newAccessorName(); - acc.usage = QStringLiteral("COLOR"); - acc.offset = startOffset * sizeof(float); - acc.type = QStringLiteral("VEC4"); - calcBB(acc.minVal, acc.maxVal, bufPtr, vertexCount, 4, startOffset, stride); - meshInfo.accessors.append(acc); - startOffset += 4; - } - if (tangentsAttrib) { - acc.name = newAccessorName(); - acc.usage = QStringLiteral("TANGENT"); - acc.offset = startOffset * sizeof(float); - acc.type = QStringLiteral("VEC4"); - calcBB(acc.minVal, acc.maxVal, bufPtr, vertexCount, 4, startOffset, stride); + switch (vAtt.att->vertexSize()) { + case 1: + acc.type = QStringLiteral("SCALAR"); + break; + case 2: + acc.type = QStringLiteral("VEC2"); + break; + case 3: + acc.type = QStringLiteral("VEC3"); + break; + case 4: + acc.type = QStringLiteral("VEC4"); + break; + case 9: + acc.type = QStringLiteral("MAT3"); + break; + case 16: + acc.type = QStringLiteral("MAT4"); + break; + default: + qCWarning(GLTFExporterLog, "Invalid vertex size: %d", vAtt.att->vertexSize()); + break; + } meshInfo.accessors.append(acc); - startOffset += 4; + startOffset += vAtt.att->vertexSize(); } // Index @@ -836,7 +852,6 @@ void GLTFExporter::parseMeshes() acc.count = indexCount; acc.componentType = indexBufView.componentType; acc.type = QStringLiteral("SCALAR"); - acc.minVal = acc.maxVal = QVector<float>(); meshInfo.accessors.append(acc); } @@ -903,10 +918,10 @@ void GLTFExporter::parseCameras() if (GLTFExporterLog().isDebugEnabled()) { qCDebug(GLTFExporterLog, " Camera: #%i: (%ls/%ls)", cameraCount++, qUtf16PrintableImpl(c.name), qUtf16PrintableImpl(c.originalName)); - qCDebug(GLTFExporterLog, " Aspect ratio: %i", c.aspectRatio); - qCDebug(GLTFExporterLog, " Fov: %i", c.yfov); - qCDebug(GLTFExporterLog, " Near: %i", c.znear); - qCDebug(GLTFExporterLog, " Far: %i", c.zfar); + qCDebug(GLTFExporterLog, " Aspect ratio: %f", c.aspectRatio); + qCDebug(GLTFExporterLog, " Fov: %f", c.yfov); + qCDebug(GLTFExporterLog, " Near: %f", c.znear); + qCDebug(GLTFExporterLog, " Far: %f", c.zfar); } } } @@ -952,110 +967,104 @@ void GLTFExporter::parseLights() qCDebug(GLTFExporterLog, " Type: %i", lightInfo.type); qCDebug(GLTFExporterLog, " Color: (%i, %i, %i, %i)", lightInfo.color.red(), lightInfo.color.green(), lightInfo.color.blue(), lightInfo.color.alpha()); - qCDebug(GLTFExporterLog, " Intensity: %i", lightInfo.intensity); + qCDebug(GLTFExporterLog, " Intensity: %f", lightInfo.intensity); qCDebug(GLTFExporterLog, " Direction: (%f, %f, %f)", lightInfo.direction.x(), lightInfo.direction.y(), lightInfo.direction.z()); qCDebug(GLTFExporterLog, " Attenuation: (%f, %f, %f)", lightInfo.attenuation.x(), lightInfo.attenuation.y(), lightInfo.attenuation.z()); - qCDebug(GLTFExporterLog, " CutOffAngle: %i", lightInfo.cutOffAngle); + qCDebug(GLTFExporterLog, " CutOffAngle: %f", lightInfo.cutOffAngle); } } } -static inline QJsonArray col2jsvec(const QColor &color, bool alpha = false) +void GLTFExporter::parseTechniques(QMaterial *material) { - QJsonArray arr; - arr << color.redF() << color.greenF() << color.blueF(); - if (alpha) - arr << color.alphaF(); - return arr; -} + int techniqueCount = 0; + qCDebug(GLTFExporterLog, " Parsing material techniques..."); -template <typename T> -static inline QJsonArray vec2jsvec(const QVector<T> &v) -{ - QJsonArray arr; - for (int i = 0; i < v.count(); ++i) - arr << v.at(i); - return arr; -} + for (auto technique : material->effect()->techniques()) { + QString techName; + if (m_techniqueIdMap.contains(technique)) { + techName = m_techniqueIdMap.value(technique); + } else { + techName = newTechniqueName(); + parseRenderPasses(technique); -static inline QJsonArray vec2jsvec(const QVector2D &v) -{ - QJsonArray arr; - arr << v.x() << v.y(); - return arr; -} + } + m_techniqueIdMap.insert(technique, techName); -static inline QJsonArray vec2jsvec(const QVector3D &v) -{ - QJsonArray arr; - arr << v.x() << v.y() << v.z(); - return arr; -} + techniqueCount++; -static inline QJsonArray vec2jsvec(const QVector4D &v) -{ - QJsonArray arr; - arr << v.x() << v.y() << v.z() << v.w(); - return arr; + if (GLTFExporterLog().isDebugEnabled()) { + qCDebug(GLTFExporterLog, " Technique #%i", techniqueCount); + qCDebug(GLTFExporterLog, " name: '%ls'", qUtf16PrintableImpl(techName)); + } + } } -static inline QJsonArray matrix2jsvec(const QMatrix4x4 &matrix) +void GLTFExporter::parseRenderPasses(QTechnique *technique) { - QJsonArray jm; - const float *mtxp = matrix.constData(); - for (int j = 0; j < 16; ++j) - jm.append(*mtxp++); - return jm; -} + int passCount = 0; + qCDebug(GLTFExporterLog, " Parsing render passes for technique..."); -static inline void setVarToJSonObject(QJsonObject &jsObj, const QString &key, const QVariant &var) -{ - switch (var.type()) { - case QVariant::Bool: - jsObj[key] = var.toBool(); - break; - case QMetaType::Float: - jsObj[key] = var.value<float>(); - break; - case QVariant::Vector2D: - jsObj[key] = vec2jsvec(var.value<QVector2D>()); - break; - case QVariant::Vector3D: - jsObj[key] = vec2jsvec(var.value<QVector3D>()); - break; - case QVariant::Vector4D: - jsObj[key] = vec2jsvec(var.value<QVector4D>()); - break; - case QVariant::Matrix4x4: - jsObj[key] = matrix2jsvec(var.value<QMatrix4x4>()); - break; - default: - qCWarning(GLTFExporterLog, "Unknown value type for '%ls'", qUtf16PrintableImpl(key)); - break; + for (auto pass : technique->renderPasses()) { + QString name; + if (m_renderPassIdMap.contains(pass)) { + name = m_renderPassIdMap.value(pass); + } else { + name = newRenderPassName(); + m_renderPassIdMap.insert(pass, name); + if (pass->shaderProgram() && !m_programInfo.contains(pass->shaderProgram())) { + ProgramInfo pi; + pi.name = newProgramName(); + pi.vertexShader = addShaderInfo(QShaderProgram::Vertex, + pass->shaderProgram()->vertexShaderCode()); + pi.tessellationControlShader = + addShaderInfo(QShaderProgram::Fragment, + pass->shaderProgram()->tessellationControlShaderCode()); + pi.tessellationEvaluationShader = + addShaderInfo(QShaderProgram::TessellationControl, + pass->shaderProgram()->tessellationEvaluationShaderCode()); + pi.geometryShader = addShaderInfo(QShaderProgram::TessellationEvaluation, + pass->shaderProgram()->geometryShaderCode()); + pi.fragmentShader = addShaderInfo(QShaderProgram::Geometry, + pass->shaderProgram()->fragmentShaderCode()); + pi.computeShader = addShaderInfo(QShaderProgram::Compute, + pass->shaderProgram()->computeShaderCode()); + m_programInfo.insert(pass->shaderProgram(), pi); + qCDebug(GLTFExporterLog, " program: '%ls'", qUtf16PrintableImpl(pi.name)); + } + } + passCount++; + + if (GLTFExporterLog().isDebugEnabled()) { + qCDebug(GLTFExporterLog, " Render pass #%i", passCount); + qCDebug(GLTFExporterLog, " name: '%ls'", qUtf16PrintableImpl(name)); + } } } -static inline void promoteColorsToRGBA(QJsonObject *obj) +QString GLTFExporter::addShaderInfo(QShaderProgram::ShaderType type, QByteArray code) { - auto it = obj->begin(); - auto itEnd = obj->end(); - while (it != itEnd) { - QJsonArray arr = it.value().toArray(); - if (arr.count() == 3) { - const QString key = it.key(); - if (key == QStringLiteral("ambient") - || key == QStringLiteral("diffuse") - || key == QStringLiteral("specular") - || key == QStringLiteral("warm") - || key == QStringLiteral("cool")) { - arr.append(1); - *it = arr; - } - } - ++it; + if (code.isEmpty()) + return QString(); + + for (auto si : m_shaderInfo) { + if (si.type == QShaderProgram::Vertex && code == si.code) + return si.name; } + + ShaderInfo newInfo; + newInfo.type = type; + newInfo.code = code; + newInfo.name = newShaderName(); + newInfo.uri = newInfo.name + QStringLiteral(".glsl"); + + m_shaderInfo.append(newInfo); + + qCDebug(GLTFExporterLog, " shader: '%ls'", qUtf16PrintableImpl(newInfo.name)); + + return newInfo.name; } bool GLTFExporter::saveScene() @@ -1125,10 +1134,6 @@ bool GLTFExporter::saveScene() accessor["count"] = int(acc.count); accessor["componentType"] = int(acc.componentType); accessor["type"] = acc.type; - if (!acc.minVal.isEmpty() && !acc.maxVal.isEmpty()) { - accessor["min"] = vec2jsvec(acc.minVal); - accessor["max"] = vec2jsvec(acc.maxVal); - } accessors[acc.name] = accessor; } if (accessors.size()) @@ -1200,46 +1205,11 @@ bool GLTFExporter::saveScene() m_obj["scene"] = QStringLiteral("defaultScene"); QJsonObject materials; - QHash<QString, QString> textureNameMap; - exportMaterials(materials, &textureNameMap); + + exportMaterials(materials); if (materials.size()) m_obj["materials"] = materials; - QJsonObject textures; - QHash<QString, QString> imageKeyMap; // uri -> key - for (auto it = textureNameMap.constBegin(); it != textureNameMap.constEnd(); ++it) { - QJsonObject texture; - if (!imageKeyMap.contains(it.key())) - imageKeyMap[it.key()] = newImageName(); - texture["source"] = imageKeyMap[it.key()]; - texture["format"] = 0x1908; // RGBA - texture["internalFormat"] = 0x1908; - texture["sampler"] = QStringLiteral("sampler_mip_rep"); - texture["target"] = 3553; // TEXTURE_2D - texture["type"] = 5121; // UNSIGNED_BYTE - textures[it.value()] = texture; - } - if (textures.size()) { - m_obj["textures"] = textures; - QJsonObject samplers; - QJsonObject sampler; - sampler["magFilter"] = 9729; // LINEAR - sampler["minFilter"] = 9987; // LINEAR_MIPMAP_LINEAR - sampler["wrapS"] = 10497; // REPEAT - sampler["wrapT"] = 10497; - samplers["sampler_mip_rep"] = sampler; - m_obj["samplers"] = samplers; - } - - QJsonObject images; - for (auto it = imageKeyMap.constBegin(); it != imageKeyMap.constEnd(); ++it) { - QJsonObject image; - image["uri"] = m_imageMap.value(it.key()); - images[it.value()] = image; - } - if (images.size()) - m_obj["images"] = images; - // Lights must be declared as extensions to the top-level glTF object QJsonObject lights; for (auto lightInfo : m_lightInfo.values()) { @@ -1283,7 +1253,192 @@ bool GLTFExporter::saveScene() m_obj["extensions"] = extensions; } - // TODO: Save techniques, programs, and shaders for custom materials + // Save effects for custom materials + // Note that we are not saving effects, techniques, render passes, shader programs, or shaders + // strictly according to GLTF format, but rather in our expanded QGLTF custom format, + // since the GLTF format doesn't quite match our needs. + // Having our own format also vastly simplifies export and import of custom materials, + // since we are not trying to push a round peg into a square hole. + // If use cases arise in future where our exported GLTF scenes need to be loaded by third party + // GLTF loaders, we could add an export option to do so, but the exported scene would never + // be quite the same as the original. + QJsonObject effects; + for (auto it = m_effectIdMap.constBegin(); it != m_effectIdMap.constEnd(); ++it) { + QEffect *effect = it.key(); + const QString effectName = it.value(); + QJsonObject effectObj; + QJsonObject paramObj; + + for (QParameter *param : effect->parameters()) + exportParameter(paramObj, param->name(), param->value()); + if (!effect->objectName().isEmpty()) + effectObj["name"] = effect->objectName(); + if (!paramObj.isEmpty()) + effectObj["parameters"] = paramObj; + QJsonArray techs; + for (auto tech : effect->techniques()) + techs << m_techniqueIdMap.value(tech); + effectObj["techniques"] = techs; + effects[effectName] = effectObj; + } + if (effects.size()) + m_obj["effects"] = effects; + + // Save techniques for custom materials. + QJsonObject techniques; + for (auto it = m_techniqueIdMap.constBegin(); it != m_techniqueIdMap.constEnd(); ++it) { + QTechnique *technique = it.key(); + + QJsonObject techObj; + QJsonObject filterKeyObj; + QJsonObject paramObj; + QJsonArray renderPassArr; + + for (QFilterKey *filterKey : technique->filterKeys()) + setVarToJSonObject(filterKeyObj, filterKey->name(), filterKey->value()); + + for (QRenderPass *pass : technique->renderPasses()) + renderPassArr << m_renderPassIdMap.value(pass); + + for (QParameter *param : technique->parameters()) + exportParameter(paramObj, param->name(), param->value()); + + const QGraphicsApiFilter *gFilter = technique->graphicsApiFilter(); + if (gFilter) { + QJsonObject graphicsApiFilterObj; + graphicsApiFilterObj["api"] = gFilter->api(); + graphicsApiFilterObj["profile"] = gFilter->profile(); + graphicsApiFilterObj["minorVersion"] = gFilter->minorVersion(); + graphicsApiFilterObj["majorVersion"] = gFilter->majorVersion(); + if (!gFilter->vendor().isEmpty()) + graphicsApiFilterObj["vendor"] = gFilter->vendor(); + QJsonArray extensions; + for (auto extName : gFilter->extensions()) + extensions << extName; + if (!extensions.isEmpty()) + graphicsApiFilterObj["extensions"] = extensions; + techObj["gapifilter"] = graphicsApiFilterObj; + } + if (!technique->objectName().isEmpty()) + techObj["name"] = technique->objectName(); + if (!filterKeyObj.isEmpty()) + techObj["filterkeys"] = filterKeyObj; + if (!paramObj.isEmpty()) + techObj["parameters"] = paramObj; + if (!renderPassArr.isEmpty()) + techObj["renderpasses"] = renderPassArr; + techniques[it.value()] = techObj; + } + if (techniques.size()) + m_obj["techniques"] = techniques; + + // Save render passes for custom materials. + QJsonObject passes; + for (auto it = m_renderPassIdMap.constBegin(); it != m_renderPassIdMap.constEnd(); ++it) { + const QRenderPass *pass = it.key(); + const QString passId = it.value(); + + QJsonObject passObj; + QJsonObject filterKeyObj; + QJsonObject paramObj; + QJsonObject stateObj; + + for (QFilterKey *filterKey : pass->filterKeys()) + setVarToJSonObject(filterKeyObj, filterKey->name(), filterKey->value()); + for (QParameter *param : pass->parameters()) + exportParameter(paramObj, param->name(), param->value()); + exportRenderStates(stateObj, pass); + + if (!pass->objectName().isEmpty()) + passObj["name"] = pass->objectName(); + if (!filterKeyObj.isEmpty()) + passObj["filterkeys"] = filterKeyObj; + if (!paramObj.isEmpty()) + passObj["parameters"] = paramObj; + if (!stateObj.isEmpty()) + passObj["states"] = stateObj; + passObj["program"] = m_programInfo.value(pass->shaderProgram()).name; + passes[passId] = passObj; + + } + if (passes.size()) + m_obj["renderpasses"] = passes; + + // Save programs for custom materials + QJsonObject programs; + for (auto it = m_programInfo.constBegin(); it != m_programInfo.constEnd(); ++it) { + const QShaderProgram *program = it.key(); + const ProgramInfo pi = it.value(); + + QJsonObject progObj; + if (!program->objectName().isEmpty()) + progObj["name"] = program->objectName(); + progObj["vertexShader"] = pi.vertexShader; + progObj["fragmentShader"] = pi.fragmentShader; + // Qt3D additions + if (!pi.tessellationControlShader.isEmpty()) + progObj["tessCtrlShader"] = pi.tessellationControlShader; + if (!pi.tessellationEvaluationShader.isEmpty()) + progObj["tessEvalShader"] = pi.tessellationEvaluationShader; + if (!pi.geometryShader.isEmpty()) + progObj["geometryShader"] = pi.geometryShader; + if (!pi.computeShader.isEmpty()) + progObj["computeShader"] = pi.computeShader; + programs[pi.name] = progObj; + + } + if (programs.size()) + m_obj["programs"] = programs; + + // Save shaders for custom materials + QJsonObject shaders; + for (auto si : m_shaderInfo) { + QJsonObject shaderObj; + shaderObj["uri"] = si.uri; + shaders[si.name] = shaderObj; + + } + if (shaders.size()) + m_obj["shaders"] = shaders; + + // Copy textures and shaders into temporary directory + copyTextures(); + createShaders(); + + QJsonObject textures; + QHash<QString, QString> imageKeyMap; // uri -> key + for (auto it = m_textureIdMap.constBegin(); it != m_textureIdMap.constEnd(); ++it) { + QJsonObject texture; + if (!imageKeyMap.contains(it.key())) + imageKeyMap[it.key()] = newImageName(); + texture["source"] = imageKeyMap[it.key()]; + texture["format"] = GL_RGBA; + texture["internalFormat"] = GL_RGBA; + texture["sampler"] = QStringLiteral("sampler_mip_rep"); + texture["target"] = GL_TEXTURE_2D; + texture["type"] = GL_UNSIGNED_BYTE; + textures[it.value()] = texture; + } + if (textures.size()) { + m_obj["textures"] = textures; + QJsonObject samplers; + QJsonObject sampler; + sampler["magFilter"] = GL_LINEAR; + sampler["minFilter"] = GL_LINEAR_MIPMAP_LINEAR; + sampler["wrapS"] = GL_REPEAT; + sampler["wrapT"] = GL_REPEAT; + samplers["sampler_mip_rep"] = sampler; + m_obj["samplers"] = samplers; + } + + QJsonObject images; + for (auto it = imageKeyMap.constBegin(); it != imageKeyMap.constEnd(); ++it) { + QJsonObject image; + image["uri"] = m_imageMap.value(it.key()); + images[it.value()] = image; + } + if (images.size()) + m_obj["images"] = images; m_doc.setObject(m_obj); @@ -1383,70 +1538,75 @@ QString GLTFExporter::exportNodes(GLTFExporter::Node *n, QJsonObject &nodes) return n->uniqueName; } -void GLTFExporter::exportMaterials(QJsonObject &materials, QHash<QString, QString> *textureNameMap) +void GLTFExporter::exportMaterials(QJsonObject &materials) { QHash<QString, bool> imageHasAlpha; - QHashIterator<Qt3DRender::QMaterial *, MaterialInfo> matIt(m_materialInfo); + QHashIterator<QMaterial *, MaterialInfo> matIt(m_materialInfo); for (auto matIt = m_materialInfo.constBegin(); matIt != m_materialInfo.constEnd(); ++matIt) { + const QMaterial *material = matIt.key(); const MaterialInfo &matInfo = matIt.value(); - QJsonObject material; - material["name"] = matInfo.originalName; - - bool opaque = true; - QJsonObject vals; - for (auto it = matInfo.textures.constBegin(); it != matInfo.textures.constEnd(); ++it) { - if (!textureNameMap->contains(it.value())) - textureNameMap->insert(it.value(), newTextureName()); - QString key = it.key(); - if (key == QStringLiteral("normal")) // avoid clashing with the vertex normals - key = QStringLiteral("normalmap"); - // Alpha is supported for diffuse textures, but have to check the image data to decide - // if blending is needed - if (key == QStringLiteral("diffuse")) { - QString imgFn = it.value(); - if (imageHasAlpha.contains(imgFn)) { - if (imageHasAlpha[imgFn]) - opaque = false; - } else { - QImage img(imgFn); - if (!img.isNull()) { - if (img.hasAlphaChannel()) { - for (int y = 0; opaque && y < img.height(); ++y) { - for (int x = 0; opaque && x < img.width(); ++x) { - if (qAlpha(img.pixel(x, y)) < 255) - opaque = false; + QJsonObject materialObj; + materialObj["name"] = matInfo.originalName; + + if (matInfo.type == MaterialInfo::TypeCustom) { + QVector<QParameter *> parameters = material->parameters(); + QJsonObject paramObj; + for (auto param : parameters) + exportParameter(paramObj, param->name(), param->value()); + materialObj["effect"] = m_effectIdMap.value(material->effect()); + materialObj["parameters"] = paramObj; + } else { + bool opaque = true; + QJsonObject vals; + for (auto it = matInfo.textures.constBegin(); it != matInfo.textures.constEnd(); ++it) { + QString key = it.key(); + if (key == QStringLiteral("normal")) // avoid clashing with the vertex normals + key = QStringLiteral("normalmap"); + // Alpha is supported for diffuse textures, but have to check the image data to + // decide if blending is needed + if (key == QStringLiteral("diffuse")) { + QString imgFn = it.value(); + if (imageHasAlpha.contains(imgFn)) { + if (imageHasAlpha[imgFn]) + opaque = false; + } else { + QImage img(imgFn); + if (!img.isNull()) { + if (img.hasAlphaChannel()) { + for (int y = 0; opaque && y < img.height(); ++y) { + for (int x = 0; opaque && x < img.width(); ++x) { + if (qAlpha(img.pixel(x, y)) < 255) + opaque = false; + } } } + imageHasAlpha[imgFn] = !opaque; + } else { + qCWarning(GLTFExporterLog, + "Cannot determine presence of alpha for '%ls'", + qUtf16PrintableImpl(imgFn)); } - imageHasAlpha[imgFn] = !opaque; - } else { - qCWarning(GLTFExporterLog, "Cannot determine presence of alpha for '%ls'", - qUtf16PrintableImpl(imgFn)); } } + vals[key] = m_textureIdMap.value(it.value()); + } + for (auto it = matInfo.values.constBegin(); it != matInfo.values.constEnd(); ++it) { + if (vals.contains(it.key())) + continue; + setVarToJSonObject(vals, it.key(), it.value()); + } + for (auto it = matInfo.colors.constBegin(); it != matInfo.colors.constEnd(); ++it) { + if (vals.contains(it.key())) + continue; + // Alpha is supported for the diffuse color. < 1 will enable blending. + const bool alpha = (it.key() == QStringLiteral("diffuse")) + && (matInfo.type != MaterialInfo::TypeCustom); + if (alpha && it.value().alphaF() < 1.0f) + opaque = false; + vals[it.key()] = col2jsvec(it.value(), alpha); } - vals[key] = textureNameMap->value(it.value()); - } - for (auto it = matInfo.values.constBegin(); it != matInfo.values.constEnd(); ++it) { - if (vals.contains(it.key())) - continue; - setVarToJSonObject(vals, it.key(), it.value()); - } - for (auto it = matInfo.colors.constBegin(); it != matInfo.colors.constEnd(); ++it) { - if (vals.contains(it.key())) - continue; - // Alpha is supported for the diffuse color. < 1 will enable blending. - const bool alpha = it.key() == QStringLiteral("diffuse"); - if (alpha && it.value().alphaF() < 1.0f) - opaque = false; - vals[it.key()] = col2jsvec(it.value(), alpha); - } - if (matInfo.type == MaterialInfo::TypeCustom) { - material["values"] = vals; - // TODO: custom materials - } else { // Material is a common material, so export it as such. QJsonObject commonMat; if (matInfo.type == MaterialInfo::TypeGooch) @@ -1473,10 +1633,10 @@ void GLTFExporter::exportMaterials(QJsonObject &materials, QHash<QString, QStrin commonMat["functions"] = functions; QJsonObject extensions; extensions["KHR_materials_common"] = commonMat; - material["extensions"] = extensions; + materialObj["extensions"] = extensions; } - materials[matInfo.name] = material; + materials[matInfo.name] = materialObj; } } @@ -1504,6 +1664,197 @@ void GLTFExporter::clearOldExport(const QString &dir) } } +void GLTFExporter::exportParameter(QJsonObject &jsonObj, const QString &name, + const QVariant &variant) +{ + QLatin1String typeStr("type"); + QLatin1String valueStr("value"); + + QJsonObject paramObj; + + if (variant.canConvert<QAbstractTexture *>()) { + paramObj[typeStr] = GL_SAMPLER_2D; + paramObj[valueStr] = m_textureIdMap.value(textureVariantToUrl(variant)); + } else { + switch (QMetaType::Type(variant.type())) { + case QMetaType::Bool: + paramObj[typeStr] = GL_BOOL; + paramObj[valueStr] = variant.toBool(); + break; + case QMetaType::Int: // fall through + case QMetaType::Long: // fall through + case QMetaType::LongLong: + paramObj[typeStr] = GL_INT; + paramObj[valueStr] = variant.toInt(); + break; + case QMetaType::UInt: // fall through + case QMetaType::ULong: // fall through + case QMetaType::ULongLong: + paramObj[typeStr] = GL_UNSIGNED_INT; + paramObj[valueStr] = variant.toInt(); + break; + case QMetaType::Short: + paramObj[typeStr] = GL_SHORT; + paramObj[valueStr] = variant.toInt(); + break; + case QMetaType::UShort: + paramObj[typeStr] = GL_UNSIGNED_SHORT; + paramObj[valueStr] = variant.toInt(); + break; + case QMetaType::Char: + paramObj[typeStr] = GL_BYTE; + paramObj[valueStr] = variant.toInt(); + break; + case QMetaType::UChar: + paramObj[typeStr] = GL_UNSIGNED_BYTE; + paramObj[valueStr] = variant.toInt(); + break; + case QMetaType::QColor: + paramObj[typeStr] = GL_FLOAT_VEC4; + paramObj[valueStr] = col2jsvec(variant.value<QColor>(), true); + break; + case QMetaType::Float: + paramObj[typeStr] = GL_FLOAT; + paramObj[valueStr] = variant.value<float>(); + break; + case QMetaType::QVector2D: + paramObj[typeStr] = GL_FLOAT_VEC2; + paramObj[valueStr] = vec2jsvec(variant.value<QVector2D>()); + break; + case QMetaType::QVector3D: + paramObj[typeStr] = GL_FLOAT_VEC3; + paramObj[valueStr] = vec2jsvec(variant.value<QVector3D>()); + break; + case QMetaType::QVector4D: + paramObj[typeStr] = GL_FLOAT_VEC4; + paramObj[valueStr] = vec2jsvec(variant.value<QVector4D>()); + break; + case QMetaType::QMatrix4x4: + paramObj[typeStr] = GL_FLOAT_MAT4; + paramObj[valueStr] = matrix2jsvec(variant.value<QMatrix4x4>()); + break; + default: + qCWarning(GLTFExporterLog, "Unknown value type for '%ls'", qUtf16PrintableImpl(name)); + break; + } + } + + jsonObj[name] = paramObj; +} + +void GLTFExporter::exportRenderStates(QJsonObject &jsonObj, const QRenderPass *pass) +{ + QJsonArray enableStates; + QJsonObject funcObj; + for (QRenderState *state : pass->renderStates()) { + QJsonArray arr; + if (qobject_cast<QAlphaCoverage *>(state)) { + enableStates << GL_SAMPLE_ALPHA_TO_COVERAGE; + } else if (qobject_cast<QAlphaTest *>(state)) { + auto s = qobject_cast<QAlphaTest *>(state); + arr << s->alphaFunction(); + arr << s->referenceValue(); + funcObj["alphaTest"] = arr; + } else if (qobject_cast<QBlendEquation *>(state)) { + auto s = qobject_cast<QBlendEquation *>(state); + arr << s->blendFunction(); + funcObj["blendEquationSeparate"] = arr; + } else if (qobject_cast<QBlendEquationArguments *>(state)) { + auto s = qobject_cast<QBlendEquationArguments *>(state); + arr << s->sourceRgb(); + arr << s->sourceAlpha(); + arr << s->destinationRgb(); + arr << s->destinationAlpha(); + arr << s->bufferIndex(); + funcObj["blendFuncSeparate"] = arr; + } else if (qobject_cast<QClipPlane *>(state)) { + auto s = qobject_cast<QClipPlane *>(state); + arr << s->planeIndex(); + arr << s->normal().x(); + arr << s->normal().y(); + arr << s->normal().z(); + arr << s->distance(); + funcObj["clipPlane"] = arr; + } else if (qobject_cast<QColorMask *>(state)) { + auto s = qobject_cast<QColorMask *>(state); + arr << s->isRedMasked(); + arr << s->isGreenMasked(); + arr << s->isBlueMasked(); + arr << s->isAlphaMasked(); + funcObj["colorMask"] = arr; + } else if (qobject_cast<QCullFace *>(state)) { + auto s = qobject_cast<QCullFace *>(state); + arr << s->mode(); + funcObj["cullFace"] = arr; + } else if (qobject_cast<QDepthTest *>(state)) { + auto s = qobject_cast<QDepthTest *>(state); + arr << s->depthFunction(); + funcObj["depthFunc"] = arr; + } else if (qobject_cast<QDithering *>(state)) { + enableStates << GL_DITHER; + } else if (qobject_cast<QFrontFace *>(state)) { + auto s = qobject_cast<QFrontFace *>(state); + arr << s->direction(); + funcObj["frontFace"] = arr; + } else if (qobject_cast<QFrontFace *>(state)) { + auto s = qobject_cast<QFrontFace *>(state); + arr << s->direction(); + funcObj["frontFace"] = arr; + } else if (qobject_cast<QMultiSampleAntiAliasing *>(state)) { + enableStates << 0x809D; // GL_MULTISAMPLE + } else if (qobject_cast<QNoDepthMask *>(state)) { + arr << false; + funcObj["depthMask"] = arr; + } else if (qobject_cast<QPointSize *>(state)) { + auto s = qobject_cast<QPointSize *>(state); + arr << s->sizeMode(); + arr << s->value(); + funcObj["pointSize"] = arr; + } else if (qobject_cast<QPolygonOffset *>(state)) { + auto s = qobject_cast<QPolygonOffset *>(state); + arr << s->scaleFactor(); + arr << s->depthSteps(); + funcObj["polygonOffset"] = arr; + } else if (qobject_cast<QScissorTest *>(state)) { + auto s = qobject_cast<QScissorTest *>(state); + arr << s->left(); + arr << s->bottom(); + arr << s->width(); + arr << s->height(); + funcObj["scissor"] = arr; + } else if (qobject_cast<QSeamlessCubemap *>(state)) { + enableStates << 0x884F; // GL_TEXTURE_CUBE_MAP_SEAMLESS + } else if (qobject_cast<QStencilMask *>(state)) { + auto s = qobject_cast<QStencilMask *>(state); + arr << int(s->frontOutputMask()); + arr << int(s->backOutputMask()); + funcObj["stencilMask"] = arr; + } else if (qobject_cast<QStencilOperation *>(state)) { + auto s = qobject_cast<QStencilOperation *>(state); + arr << s->front()->stencilTestFailureOperation(); + arr << s->front()->depthTestFailureOperation(); + arr << s->front()->allTestsPassOperation(); + arr << s->back()->stencilTestFailureOperation(); + arr << s->back()->depthTestFailureOperation(); + arr << s->back()->allTestsPassOperation(); + funcObj["stencilOperation"] = arr; + } else if (qobject_cast<QStencilTest *>(state)) { + auto s = qobject_cast<QStencilTest *>(state); + arr << int(s->front()->comparisonMask()); + arr << s->front()->referenceValue(); + arr << s->front()->stencilFunction(); + arr << int(s->back()->comparisonMask()); + arr << s->back()->referenceValue(); + arr << s->back()->stencilFunction(); + funcObj["stencilTest"] = arr; + } + } + if (!enableStates.isEmpty()) + jsonObj["enable"] = enableStates; + if (!funcObj.isEmpty()) + jsonObj["functions"] = funcObj; +} + QString GLTFExporter::newBufferViewName() { return QString(QStringLiteral("bufferView_%1")).arg(++m_bufferViewCount); @@ -1564,6 +1915,64 @@ QString GLTFExporter::newLightName() return QString(QStringLiteral("light_%1")).arg(++m_lightCount); } +QString GLTFExporter::newRenderPassName() +{ + return QString(QStringLiteral("renderpass_%1")).arg(++m_renderPassCount); +} + +QString GLTFExporter::newEffectName() +{ + return QString(QStringLiteral("effect_%1")).arg(++m_effectCount); +} + +QString GLTFExporter::textureVariantToUrl(const QVariant &var) +{ + QString urlString; + QAbstractTexture *texture = var.value<QAbstractTexture *>(); + if (texture->textureImages().size()) { + QTextureImage *image = qobject_cast<QTextureImage *>(texture->textureImages().at(0)); + if (image) { + urlString = QUrlHelper::urlToLocalFileOrQrc(image->source()); + if (!m_textureIdMap.contains(urlString)) + m_textureIdMap.insert(urlString, newTextureName()); + } + } + return urlString; +} + +void GLTFExporter::setVarToJSonObject(QJsonObject &jsObj, const QString &key, const QVariant &var) +{ + switch (QMetaType::Type(var.type())) { + case QMetaType::Bool: + jsObj[key] = var.toBool(); + break; + case QMetaType::Float: + jsObj[key] = var.value<float>(); + break; + case QMetaType::QVector2D: + jsObj[key] = vec2jsvec(var.value<QVector2D>()); + break; + case QMetaType::QVector3D: + jsObj[key] = vec2jsvec(var.value<QVector3D>()); + break; + case QMetaType::QVector4D: + jsObj[key] = vec2jsvec(var.value<QVector4D>()); + break; + case QMetaType::QMatrix4x4: + jsObj[key] = matrix2jsvec(var.value<QMatrix4x4>()); + break; + case QMetaType::QString: + jsObj[key] = var.toString(); + break; + case QMetaType::QColor: + jsObj[key] = col2jsvec(var.value<QColor>(), true); + break; + default: + qCWarning(GLTFExporterLog, "Unknown value type for '%ls'", qUtf16PrintableImpl(key)); + break; + } +} + } // namespace Qt3DRender QT_END_NAMESPACE diff --git a/src/plugins/sceneparsers/gltfexport/gltfexporter.h b/src/plugins/sceneparsers/gltfexport/gltfexporter.h index b96788db7..5dd2e337b 100644 --- a/src/plugins/sceneparsers/gltfexport/gltfexporter.h +++ b/src/plugins/sceneparsers/gltfexport/gltfexporter.h @@ -57,6 +57,7 @@ #include <QtGui/qvector3d.h> #include <Qt3DRender/qabstractlight.h> +#include <Qt3DRender/qshaderprogram.h> #include <private/qsceneexporter_p.h> @@ -74,6 +75,9 @@ namespace Qt3DRender { class QCameraLens; class QMaterial; class QGeometryRenderer; +class QTechnique; +class QRenderPass; +class QEffect; Q_DECLARE_LOGGING_CATEGORY(GLTFExporterLog) @@ -115,8 +119,6 @@ private: uint count; uint componentType; QString type; - QVector<float> minVal; - QVector<float> maxVal; }; QVector<Accessor> accessors; QString name; // generated @@ -141,14 +143,32 @@ private: QString name; QString originalName; MaterialType type; + + // These are only used for default materials QHash<QString, QColor> colors; QHash<QString, QString> textures; QHash<QString, QVariant> values; - QHash<QString, QString> techniques; QVector<int> blendArguments; QVector<int> blendEquations; }; + struct ProgramInfo { + QString name; + QString vertexShader; + QString tessellationControlShader; + QString tessellationEvaluationShader; + QString geometryShader; + QString fragmentShader; + QString computeShader; + }; + + struct ShaderInfo { + QString name; + QString uri; + QShaderProgram::ShaderType type; + QByteArray code; + }; + struct CameraInfo { QString name; QString originalName; @@ -183,18 +203,24 @@ private: }; void copyTextures(); + void createShaders(); void parseEntities(const Qt3DCore::QEntity *entity, Node *parentNode); void parseScene(); void parseMaterials(); void parseMeshes(); void parseCameras(); void parseLights(); + void parseTechniques(QMaterial *material); + void parseRenderPasses(QTechnique *technique); + QString addShaderInfo(QShaderProgram::ShaderType type, QByteArray code); bool saveScene(); void delNode(Node *n); QString exportNodes(Node *n, QJsonObject &nodes); - void exportMaterials(QJsonObject &materials, QHash<QString, QString> *textureNameMap); + void exportMaterials(QJsonObject &materials); void clearOldExport(const QString &dir); + void exportParameter(QJsonObject &jsonObj, const QString &name, const QVariant &variant); + void exportRenderStates(QJsonObject &jsonObj, const QRenderPass *pass); QString newBufferViewName(); QString newAccessorName(); @@ -208,6 +234,11 @@ private: QString newNodeName(); QString newCameraName(); QString newLightName(); + QString newRenderPassName(); + QString newEffectName(); + + QString textureVariantToUrl(const QVariant &var); + void setVarToJSonObject(QJsonObject &jsObj, const QString &key, const QVariant &var); int m_bufferViewCount; int m_accessorCount; @@ -221,6 +252,8 @@ private: int m_nodeCount; int m_cameraCount; int m_lightCount; + int m_renderPassCount; + int m_effectCount; Qt3DCore::QEntity *m_sceneRoot; QString m_exportName; @@ -238,11 +271,18 @@ private: QHash<Node *, Qt3DRender::QAbstractLight *> m_lightMap; QHash<Node *, Qt3DCore::QTransform *> m_transformMap; QHash<QString, QString> m_imageMap; // Original texture URL -> generated filename + QHash<QString, QString> m_textureIdMap; + QHash<Qt3DRender::QRenderPass *, QString> m_renderPassIdMap; + QHash<Qt3DRender::QEffect *, QString> m_effectIdMap; + QHash<Qt3DRender::QTechnique *, QString> m_techniqueIdMap; QHash<Qt3DRender::QGeometryRenderer *, MeshInfo> m_meshInfo; QHash<Qt3DRender::QMaterial *, MaterialInfo> m_materialInfo; QHash<Qt3DRender::QCameraLens *, CameraInfo> m_cameraInfo; QHash<Qt3DRender::QAbstractLight *, LightInfo> m_lightInfo; + QHash<Qt3DRender::QShaderProgram *, ProgramInfo> m_programInfo; + QVector<ShaderInfo> m_shaderInfo; + Node *m_rootNode; bool m_rootNodeEmpty; diff --git a/tests/auto/render/gltfplugins/images.qrc b/tests/auto/render/gltfplugins/images.qrc index 3938bbe18..04d711819 100644 --- a/tests/auto/render/gltfplugins/images.qrc +++ b/tests/auto/render/gltfplugins/images.qrc @@ -4,5 +4,9 @@ <file>qtlogo_normal.png</file> <file>qtlogo_specular.png</file> <file>qtlogo_with_alpha.png</file> + <file>ontopmaterial.frag</file> + <file>ontopmaterial.vert</file> + <file>ontopmaterialES2.frag</file> + <file>ontopmaterialES2.vert</file> </qresource> </RCC> diff --git a/tests/auto/render/gltfplugins/ontopmaterial.frag b/tests/auto/render/gltfplugins/ontopmaterial.frag new file mode 100644 index 000000000..2434f5ac8 --- /dev/null +++ b/tests/auto/render/gltfplugins/ontopmaterial.frag @@ -0,0 +1,14 @@ +#version 150 core + +uniform vec4 handleColor; +uniform float customAlpha; + +uniform sampler2D customTexture; + +out vec4 fragColor; + +void main() +{ + vec3 customColor = texture(customTexture, vec2(0.4, 0.4)).rgb; + fragColor = vec4(handleColor.xy, customColor.z, customAlpha); +} diff --git a/tests/auto/render/gltfplugins/ontopmaterial.vert b/tests/auto/render/gltfplugins/ontopmaterial.vert new file mode 100644 index 000000000..7cb0c5ab1 --- /dev/null +++ b/tests/auto/render/gltfplugins/ontopmaterial.vert @@ -0,0 +1,20 @@ +#version 150 core + +in vec3 vertexPosition; +in vec3 vertexOffset; + +uniform mat4 modelViewProjection; +uniform vec3 globalOffset; +uniform int extraYOffset; +uniform bool reverseOffset; + +void main() +{ + vec4 offset = vec4(globalOffset, 0.0) + vec4(0.0f, float(extraYOffset), 0.0f, 0.0f); + if (reverseOffset) + offset *= -1.0f; + gl_Position = modelViewProjection + * (vec4(vertexPosition, 1.0) + offset + vec4(vertexOffset, 0.0)); + // Set the Z value of the vertex so that it'll always get drawn on top of everything else + gl_Position.z = -1.0; +} diff --git a/tests/auto/render/gltfplugins/ontopmaterialES2.frag b/tests/auto/render/gltfplugins/ontopmaterialES2.frag new file mode 100644 index 000000000..a40f7e810 --- /dev/null +++ b/tests/auto/render/gltfplugins/ontopmaterialES2.frag @@ -0,0 +1,10 @@ +uniform lowp vec4 handleColor; +uniform lowp float customAlpha; + +uniform sampler2D customTexture; + +void main() +{ + vec3 customColor = texture(customTexture, vec2(0.4, 0.4)).rgb; + gl_FragColor = vec4(handleColor.xy, customColor.z, customAlpha); +} diff --git a/tests/auto/render/gltfplugins/ontopmaterialES2.vert b/tests/auto/render/gltfplugins/ontopmaterialES2.vert new file mode 100644 index 000000000..41add49a5 --- /dev/null +++ b/tests/auto/render/gltfplugins/ontopmaterialES2.vert @@ -0,0 +1,18 @@ +attribute highp vec3 vertexPosition; +attribute highp vec3 vertexOffset; + +uniform highp mat4 modelViewProjection; +uniform highp vec3 globalOffset; +uniform int extraYOffset; +uniform bool reverseOffset; + +void main() +{ + vec4 offset = vec4(globalOffset, 0.0) + vec4(0.0f, float(extraYOffset), 0.0f, 0.0f); + if (reverseOffset) + offset *= -1.0f; + gl_Position = modelViewProjection + * (vec4(vertexPosition, 1.0) + offset + vec4(vertexOffset, 0.0)); + // Set the Z value of the vertex so that it'll always get drawn on top of everything else + gl_Position.z = -1.0; +} diff --git a/tests/auto/render/gltfplugins/tst_gltfplugins.cpp b/tests/auto/render/gltfplugins/tst_gltfplugins.cpp index 2e0db3d73..1a49771d8 100644 --- a/tests/auto/render/gltfplugins/tst_gltfplugins.cpp +++ b/tests/auto/render/gltfplugins/tst_gltfplugins.cpp @@ -59,6 +59,14 @@ #include <Qt3DRender/qattribute.h> #include <Qt3DRender/qbuffer.h> #include <Qt3DRender/qeffect.h> +#include <Qt3DRender/qshaderprogram.h> +#include <Qt3DRender/qtechnique.h> +#include <Qt3DRender/qparameter.h> +#include <Qt3DRender/qgraphicsapifilter.h> +#include <Qt3DRender/qfilterkey.h> +#include <Qt3DRender/qtexture.h> +#include <Qt3DRender/qcolormask.h> +#include <Qt3DRender/qblendequation.h> #include <Qt3DExtras/qconemesh.h> #include <Qt3DExtras/qcuboidmesh.h> @@ -120,6 +128,15 @@ private: Qt3DRender::QAttribute::AttributeType type, Qt3DRender::QGeometry *geometry); void compareAttributes(Qt3DRender::QAttribute *a1, Qt3DRender::QAttribute *a2); + void compareParameters(const QVector<Qt3DRender::QParameter *> ¶ms1, + const QVector<Qt3DRender::QParameter *> ¶ms2); + void compareRenderPasses(const QVector<Qt3DRender::QRenderPass *> &passes1, + const QVector<Qt3DRender::QRenderPass *> &passes2); + void compareFilterKeys(const QVector<Qt3DRender::QFilterKey *> &keys1, + const QVector<Qt3DRender::QFilterKey *> &keys2); + QUrl getTextureUrl(Qt3DRender::QAbstractTexture *tex); + Qt3DRender::QGeometryRenderer *createCustomCube(); + Qt3DRender::QEffect *createOnTopEffect(); QTemporaryDir *m_exportDir; Qt3DExtras::Qt3DWindow *m_view1; @@ -493,70 +510,23 @@ void tst_gltfPlugins::createTestScene() transform->setTranslation(QVector3D(4.0f, 3.0f, -15.0f)); transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(1.0f, 1.0f, 1.0f, 270.0f)); - Qt3DRender::QGeometryRenderer *boxMesh = new Qt3DRender::QGeometryRenderer(); - Qt3DRender::QGeometry *boxGeometry = new Qt3DRender::QGeometry(boxMesh); - Qt3DRender::QBuffer *boxDataBuffer = - new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, boxGeometry); + Qt3DRender::QGeometryRenderer *boxMesh = createCustomCube(); Qt3DRender::QBuffer *colorDataBuffer = - new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, boxGeometry); - Qt3DRender::QBuffer *indexDataBuffer = - new Qt3DRender::QBuffer(Qt3DRender::QBuffer::IndexBuffer, boxGeometry); - QByteArray vertexBufferData; + new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, boxMesh->geometry()); QByteArray colorBufferData; - QByteArray indexBufferData; - - vertexBufferData.resize(8 * 3 * sizeof(float)); colorBufferData.resize(8 * 4 * sizeof(float)); - indexBufferData.resize(12 * 3 * sizeof(ushort)); - - float dimension = 1.0f; - - float *vPtr = reinterpret_cast<float *>(vertexBufferData.data()); - vPtr[0] = -dimension; vPtr[1] = -dimension; vPtr[2] = -dimension; - vPtr[3] = dimension; vPtr[4] = -dimension; vPtr[5] = -dimension; - vPtr[6] = dimension; vPtr[7] = -dimension; vPtr[8] = dimension; - vPtr[9] = -dimension; vPtr[10] = -dimension; vPtr[11] = dimension; - vPtr[12] = -dimension; vPtr[13] = dimension; vPtr[14] = -dimension; - vPtr[15] = dimension; vPtr[16] = dimension; vPtr[17] = -dimension; - vPtr[18] = dimension; vPtr[19] = dimension; vPtr[20] = dimension; - vPtr[21] = -dimension; vPtr[22] = dimension; vPtr[23] = dimension; float *cPtr = reinterpret_cast<float *>(colorBufferData.data()); for (int i = 0; i < 8; i++) { - cPtr[i * 4] = vPtr[i * 3]; - cPtr[i * 4 + 1] = vPtr[i * 3 + 1]; - cPtr[i * 4 + 2] = vPtr[i * 3 + 2]; + cPtr[i * 4] = float(i) / 8.0f; + cPtr[i * 4 + 1] = float(8 - i) / 8.0f; + cPtr[i * 4 + 2] = float((i + 4) % 8) / 8.0f; cPtr[i * 4 + 3] = 1.0f; } - ushort *iPtr = reinterpret_cast<ushort *>(indexBufferData.data()); - iPtr[0] = 2; iPtr[1] = 0; iPtr[2] = 1; - iPtr[3] = 2; iPtr[4] = 3; iPtr[5] = 0; - iPtr[6] = 1; iPtr[7] = 6; iPtr[8] = 2; - iPtr[9] = 1; iPtr[10] = 5; iPtr[11] = 6; - iPtr[12] = 2; iPtr[13] = 7; iPtr[14] = 3; - iPtr[15] = 2; iPtr[16] = 6; iPtr[17] = 7; - iPtr[18] = 6; iPtr[19] = 5; iPtr[20] = 4; - iPtr[21] = 6; iPtr[22] = 4; iPtr[23] = 7; - iPtr[24] = 7; iPtr[25] = 0; iPtr[26] = 3; - iPtr[27] = 7; iPtr[28] = 4; iPtr[29] = 0; - iPtr[30] = 4; iPtr[31] = 1; iPtr[32] = 0; - iPtr[33] = 4; iPtr[34] = 5; iPtr[35] = 1; - - boxDataBuffer->setData(vertexBufferData); colorDataBuffer->setData(colorBufferData); - indexDataBuffer->setData(indexBufferData); - - addPositionAttributeToGeometry(boxGeometry, boxDataBuffer, 8); - addColorAttributeToGeometry(boxGeometry, colorDataBuffer, 8); - addIndexAttributeToGeometry(boxGeometry, indexDataBuffer, 36); - boxMesh->setInstanceCount(1); - boxMesh->setIndexOffset(0); - boxMesh->setFirstInstance(0); - boxMesh->setVertexCount(36); - boxMesh->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles); - boxMesh->setGeometry(boxGeometry); + addColorAttributeToGeometry(boxMesh->geometry(), colorDataBuffer, 8); createAndAddEntity(QStringLiteral("Custom cube with per-vertex colors"), boxMesh, material, transform); @@ -580,6 +550,52 @@ void tst_gltfPlugins::createTestScene() createAndAddEntity(QStringLiteral("Child with Phong"), mesh, material, transform, parentEntity); } + // Cube with custom material + { + Qt3DRender::QMaterial *material = new Qt3DRender::QMaterial; + material->setEffect(createOnTopEffect()); + material->addParameter(new Qt3DRender::QParameter(QStringLiteral("globalOffset"), + QVector3D(-3.0f, 0.0f, 3.0f))); + material->addParameter(new Qt3DRender::QParameter(QStringLiteral("extraYOffset"), 3)); + material->effect()->addParameter(new Qt3DRender::QParameter(QStringLiteral("handleColor"), + QColor(Qt::magenta))); + material->effect()->addParameter(new Qt3DRender::QParameter(QStringLiteral("reverseOffset"), + QVariant::fromValue(true))); + + Qt3DCore::QTransform *transform = new Qt3DCore::QTransform; + transform->setTranslation(QVector3D(0.0f, 2.0f, -40.0f)); + transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(1.0f, 2.0f, 3.0f, 90.0f)); + Qt3DRender::QGeometryRenderer *boxMesh = createCustomCube(); + Qt3DRender::QBuffer *offsetBuffer = + new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, boxMesh->geometry()); + QByteArray offsetBufferData; + offsetBufferData.resize(8 * 3 * sizeof(float)); + + float *oPtr = reinterpret_cast<float *>(offsetBufferData.data()); + for (int i = 0; i < 8; i++) { + oPtr[i * 3] = float(i) / 4.0f; + oPtr[i * 3 + 1] = float(8 - i) / 4.0f + 2.0f; + oPtr[i * 3 + 2] = float((i + 4) % 8) / 4.0f; + } + + offsetBuffer->setData(offsetBufferData); + + Qt3DRender::QAttribute *customAttribute = new Qt3DRender::QAttribute(); + customAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); + customAttribute->setBuffer(offsetBuffer); + customAttribute->setDataType(Qt3DRender::QAttribute::Float); + customAttribute->setDataSize(3); + customAttribute->setByteOffset(0); + customAttribute->setByteStride(0); + customAttribute->setCount(8); + customAttribute->setName(QStringLiteral("vertexOffset")); + + boxMesh->geometry()->addAttribute(customAttribute); + + createAndAddEntity(QStringLiteral("Custom cube with on-top material"), + boxMesh, material, transform); + } + #ifdef VISUAL_CHECK m_view1->setGeometry(30, 30, 400, 400); m_view1->setRootEntity(m_sceneRoot1); @@ -775,6 +791,47 @@ void tst_gltfPlugins::compareComponents(Qt3DCore::QComponent *c1, Qt3DCore::QCom QVERIFY(qFuzzyCompare(v1.toFloat(), v2.toFloat())); } } + if (QString::fromLatin1(c1->metaObject()->className()) + .endsWith(QStringLiteral("Qt3DRender::QMaterial"))) { + auto m1 = qobject_cast<Qt3DRender::QMaterial *>(c1); + auto m2 = qobject_cast<Qt3DRender::QMaterial *>(c2); + QVERIFY(m1); + QVERIFY(m2); + auto e1 = m1->effect(); + auto e2 = m2->effect(); + QVERIFY(e1); + QVERIFY(e2); + QCOMPARE(e1->objectName(), e2->objectName()); + QCOMPARE(e1->techniques().size(), e2->techniques().size()); + + compareParameters(m1->parameters(), m2->parameters()); + compareParameters(e1->parameters(), e2->parameters()); + + for (auto t1 : e1->techniques()) { + bool techMatch = false; + for (auto t2 : e2->techniques()) { + if (t1->objectName() == t2->objectName()) { + techMatch = true; + compareParameters(t1->parameters(), t2->parameters()); + compareFilterKeys(t1->filterKeys(), t2->filterKeys()); + compareRenderPasses(t1->renderPasses(), t2->renderPasses()); + QCOMPARE(t1->graphicsApiFilter()->api(), + t2->graphicsApiFilter()->api()); + QCOMPARE(t1->graphicsApiFilter()->profile(), + t2->graphicsApiFilter()->profile()); + QCOMPARE(t1->graphicsApiFilter()->minorVersion(), + t2->graphicsApiFilter()->minorVersion()); + QCOMPARE(t1->graphicsApiFilter()->majorVersion(), + t2->graphicsApiFilter()->majorVersion()); + QCOMPARE(t1->graphicsApiFilter()->extensions(), + t2->graphicsApiFilter()->extensions()); + QCOMPARE(t1->graphicsApiFilter()->vendor(), + t2->graphicsApiFilter()->vendor()); + } + } + QVERIFY(techMatch); + } + } } } } @@ -803,6 +860,253 @@ void tst_gltfPlugins::compareAttributes(Qt3DRender::QAttribute *a1, Qt3DRender:: } } +void tst_gltfPlugins::compareParameters(const QVector<Qt3DRender::QParameter *> ¶ms1, + const QVector<Qt3DRender::QParameter *> ¶ms2) +{ + QCOMPARE(params1.size(), params2.size()); + for (auto p1 : params1) { + bool pMatch = false; + for (auto p2 : params2) { + if (p1->name() == p2->name()) { + pMatch = true; + if (p1->value().type() == QVariant::Color) { + // Colors are imported as QVector4Ds + QColor color = p1->value().value<QColor>(); + QVector4D vec = p2->value().value<QVector4D>(); + QCOMPARE(color.redF(), vec.x()); + QCOMPARE(color.greenF(), vec.y()); + QCOMPARE(color.blueF(), vec.z()); + QCOMPARE(color.alphaF(), vec.w()); + } else if (p1->value().canConvert<Qt3DRender::QAbstractTexture *>()) { + QUrl u1 = getTextureUrl(p1->value().value<Qt3DRender::QAbstractTexture *>()); + QUrl u2 = getTextureUrl(p2->value().value<Qt3DRender::QAbstractTexture *>()); + QCOMPARE(u1.fileName(), u2.fileName()); + } else { + QCOMPARE(p1->value(), p2->value()); + } + } + } + QVERIFY(pMatch); + } +} + +void tst_gltfPlugins::compareRenderPasses(const QVector<Qt3DRender::QRenderPass *> &passes1, + const QVector<Qt3DRender::QRenderPass *> &passes2) +{ + QCOMPARE(passes1.size(), passes2.size()); + for (auto pass1 : passes1) { + bool passMatch = false; + for (auto pass2 : passes2) { + if (pass1->objectName() == pass2->objectName()) { + passMatch = true; + compareFilterKeys(pass1->filterKeys(), pass2->filterKeys()); + compareParameters(pass1->parameters(), pass2->parameters()); + + QVector<Qt3DRender::QRenderState *> states1 = pass1->renderStates(); + QVector<Qt3DRender::QRenderState *> states2 = pass2->renderStates(); + QCOMPARE(states1.size(), states2.size()); + for (auto state1 : states1) { + bool stateMatch = false; + for (auto state2 : states2) { + if (state1->metaObject()->className() + == state2->metaObject()->className()) { + stateMatch = true; + } + } + QVERIFY(stateMatch); + } + + QCOMPARE(pass1->shaderProgram()->vertexShaderCode(), + pass2->shaderProgram()->vertexShaderCode()); + QCOMPARE(pass1->shaderProgram()->fragmentShaderCode(), + pass2->shaderProgram()->fragmentShaderCode()); + } + } + QVERIFY(passMatch); + } +} + +void tst_gltfPlugins::compareFilterKeys(const QVector<Qt3DRender::QFilterKey *> &keys1, + const QVector<Qt3DRender::QFilterKey *> &keys2) +{ + QCOMPARE(keys1.size(), keys2.size()); + for (auto k1 : keys1) { + bool kMatch = false; + for (auto k2 : keys2) { + if (k1->name() == k2->name()) { + kMatch = true; + QCOMPARE(k1->value(), k2->value()); + } + } + QVERIFY(kMatch); + } +} + +QUrl tst_gltfPlugins::getTextureUrl(Qt3DRender::QAbstractTexture *tex) +{ + QUrl url; + if (tex->textureImages().size()) { + Qt3DRender::QTextureImage *img = + qobject_cast<Qt3DRender::QTextureImage *>( + tex->textureImages().at(0)); + if (img) + url = img->source(); + } + return url; +} + +Qt3DRender::QGeometryRenderer *tst_gltfPlugins::createCustomCube() +{ + Qt3DRender::QGeometryRenderer *boxMesh = new Qt3DRender::QGeometryRenderer; + Qt3DRender::QGeometry *boxGeometry = new Qt3DRender::QGeometry(boxMesh); + Qt3DRender::QBuffer *boxDataBuffer = + new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, boxGeometry); + Qt3DRender::QBuffer *indexDataBuffer = + new Qt3DRender::QBuffer(Qt3DRender::QBuffer::IndexBuffer, boxGeometry); + QByteArray vertexBufferData; + QByteArray indexBufferData; + + vertexBufferData.resize(8 * 3 * sizeof(float)); + indexBufferData.resize(12 * 3 * sizeof(ushort)); + + float dimension = 1.0f; + + float *vPtr = reinterpret_cast<float *>(vertexBufferData.data()); + vPtr[0] = -dimension; vPtr[1] = -dimension; vPtr[2] = -dimension; + vPtr[3] = dimension; vPtr[4] = -dimension; vPtr[5] = -dimension; + vPtr[6] = dimension; vPtr[7] = -dimension; vPtr[8] = dimension; + vPtr[9] = -dimension; vPtr[10] = -dimension; vPtr[11] = dimension; + vPtr[12] = -dimension; vPtr[13] = dimension; vPtr[14] = -dimension; + vPtr[15] = dimension; vPtr[16] = dimension; vPtr[17] = -dimension; + vPtr[18] = dimension; vPtr[19] = dimension; vPtr[20] = dimension; + vPtr[21] = -dimension; vPtr[22] = dimension; vPtr[23] = dimension; + + ushort *iPtr = reinterpret_cast<ushort *>(indexBufferData.data()); + iPtr[0] = 2; iPtr[1] = 0; iPtr[2] = 1; + iPtr[3] = 2; iPtr[4] = 3; iPtr[5] = 0; + iPtr[6] = 1; iPtr[7] = 6; iPtr[8] = 2; + iPtr[9] = 1; iPtr[10] = 5; iPtr[11] = 6; + iPtr[12] = 2; iPtr[13] = 7; iPtr[14] = 3; + iPtr[15] = 2; iPtr[16] = 6; iPtr[17] = 7; + iPtr[18] = 6; iPtr[19] = 5; iPtr[20] = 4; + iPtr[21] = 6; iPtr[22] = 4; iPtr[23] = 7; + iPtr[24] = 7; iPtr[25] = 0; iPtr[26] = 3; + iPtr[27] = 7; iPtr[28] = 4; iPtr[29] = 0; + iPtr[30] = 4; iPtr[31] = 1; iPtr[32] = 0; + iPtr[33] = 4; iPtr[34] = 5; iPtr[35] = 1; + + boxDataBuffer->setData(vertexBufferData); + indexDataBuffer->setData(indexBufferData); + + addPositionAttributeToGeometry(boxGeometry, boxDataBuffer, 8); + addIndexAttributeToGeometry(boxGeometry, indexDataBuffer, 36); + + boxMesh->setInstanceCount(1); + boxMesh->setIndexOffset(0); + boxMesh->setFirstInstance(0); + boxMesh->setVertexCount(36); + boxMesh->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles); + boxMesh->setGeometry(boxGeometry); + + return boxMesh; +} + +Qt3DRender::QEffect *tst_gltfPlugins::createOnTopEffect() +{ + Qt3DRender::QEffect *effect = new Qt3DRender::QEffect; + + Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique(); + technique->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile); + technique->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL); + technique->graphicsApiFilter()->setMajorVersion(2); + technique->graphicsApiFilter()->setMinorVersion(1); + + Qt3DRender::QTechnique *techniqueCore = new Qt3DRender::QTechnique(); + techniqueCore->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::CoreProfile); + techniqueCore->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL); + techniqueCore->graphicsApiFilter()->setMajorVersion(3); + techniqueCore->graphicsApiFilter()->setMinorVersion(1); + + Qt3DRender::QTechnique *techniqueES2 = new Qt3DRender::QTechnique(); + techniqueES2->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGLES); + techniqueES2->graphicsApiFilter()->setMajorVersion(2); + techniqueES2->graphicsApiFilter()->setMinorVersion(0); + techniqueES2->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile); + + Qt3DRender::QFilterKey *filterkey1 = new Qt3DRender::QFilterKey(effect); + Qt3DRender::QFilterKey *filterkey2 = new Qt3DRender::QFilterKey(); + filterkey1->setName(QStringLiteral("renderingStyle")); + filterkey1->setValue(QStringLiteral("forward")); + filterkey2->setName(QStringLiteral("dummyKey")); + filterkey2->setValue(QStringLiteral("dummyValue")); + + Qt3DRender::QParameter *parameter1 = new Qt3DRender::QParameter(QStringLiteral("handleColor"), + QColor(Qt::yellow)); + Qt3DRender::QParameter *parameter2 = new Qt3DRender::QParameter(QStringLiteral("customAlpha"), + 1.0f); + Qt3DRender::QParameter *parameter3 = new Qt3DRender::QParameter(QStringLiteral("handleColor"), + QColor(Qt::blue)); + Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D; + Qt3DRender::QParameter *parameter4 = + new Qt3DRender::QParameter(QStringLiteral("customTexture"), texture); + Qt3DRender::QTextureImage *ti = new Qt3DRender::QTextureImage(); + parameter4->value().value<Qt3DRender::QAbstractTexture *>()->addTextureImage(ti); + ti->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png"))); + + technique->addFilterKey(filterkey1); + technique->addFilterKey(filterkey2); + techniqueES2->addFilterKey(filterkey1); + techniqueES2->addFilterKey(filterkey2); + techniqueCore->addFilterKey(filterkey1); + + technique->addParameter(parameter1); + technique->addParameter(parameter2); + technique->addParameter(parameter4); + techniqueES2->addParameter(parameter1); + techniqueES2->addParameter(parameter2); + + Qt3DRender::QShaderProgram *shader = new Qt3DRender::QShaderProgram(); + Qt3DRender::QShaderProgram *shaderES2 = new Qt3DRender::QShaderProgram(); + shader->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource( + QUrl(QStringLiteral("qrc:/ontopmaterial.vert")))); + shader->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource( + QUrl(QStringLiteral("qrc:/ontopmaterial.frag")))); + shaderES2->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource( + QUrl(QStringLiteral("qrc:/ontopmaterialES2.vert")))); + shaderES2->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource( + QUrl(QStringLiteral("qrc:/ontopmaterialES2.frag")))); + shader->setObjectName(QStringLiteral("Basic shader")); + shaderES2->setObjectName(QStringLiteral("ES2 shader")); + + Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass(); + Qt3DRender::QRenderPass *renderPassES2 = new Qt3DRender::QRenderPass(); + renderPass->setShaderProgram(shader); + renderPassES2->setShaderProgram(shaderES2); + renderPass->addFilterKey(filterkey2); + renderPass->addParameter(parameter3); + Qt3DRender::QColorMask *cmask = new Qt3DRender::QColorMask; + cmask->setRedMasked(false); + renderPass->addRenderState(cmask); + Qt3DRender::QBlendEquation *be = new Qt3DRender::QBlendEquation; + be->setBlendFunction(Qt3DRender::QBlendEquation::Subtract); + renderPass->addRenderState(be); + technique->addRenderPass(renderPassES2); + techniqueES2->addRenderPass(renderPassES2); + techniqueCore->addRenderPass(renderPass); + technique->setObjectName(QStringLiteral("Basic technique")); + techniqueES2->setObjectName(QStringLiteral("ES2 technique")); + techniqueCore->setObjectName(QStringLiteral("Core technique")); + renderPass->setObjectName(QStringLiteral("Basic pass")); + renderPassES2->setObjectName(QStringLiteral("ES2 pass")); + + effect->addTechnique(technique); + effect->addTechnique(techniqueES2); + effect->addTechnique(techniqueCore); + effect->setObjectName(QStringLiteral("OnTopEffect")); + + return effect; +} + Qt3DCore::QEntity *tst_gltfPlugins::findCameraChild(Qt3DCore::QEntity *entity, Qt3DRender::QCameraLens::ProjectionType type) { |