summaryrefslogtreecommitdiffstats
path: root/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@qt.io>2016-11-15 09:57:32 +0200
committerMiikka Heikkinen <miikka.heikkinen@qt.io>2016-12-09 14:14:28 +0000
commit4f2011cef1565bb9c9f2c0e92d63bb67fb3f661d (patch)
tree41b8220d83ac0fae130e3994403780125435db90 /src/plugins/sceneparsers/gltfexport/gltfexporter.cpp
parent79e308da8dd303070e989280f940a8bd6a274fcc (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>
Diffstat (limited to 'src/plugins/sceneparsers/gltfexport/gltfexporter.cpp')
-rw-r--r--src/plugins/sceneparsers/gltfexport/gltfexporter.cpp1365
1 files changed, 887 insertions, 478 deletions
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