Added ability to read FBX textures embedded in FBX files.

This commit is contained in:
Andrzej Kapolka 2014-04-17 15:42:23 -07:00
parent 6251b6b819
commit 197127fbde
6 changed files with 112 additions and 47 deletions

View file

@ -306,14 +306,14 @@ bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
foreach (FBXMesh mesh, geometry.meshes) { foreach (FBXMesh mesh, geometry.meshes) {
foreach (FBXMeshPart part, mesh.parts) { foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseFilename.isEmpty()) { if (!part.diffuseTexture.filename.isEmpty()) {
if (!addPart(texdir + "/" + part.diffuseFilename, if (!addPart(texdir + "/" + part.diffuseTexture.filename,
QString("texture%1").arg(++_texturesCount))) { QString("texture%1").arg(++_texturesCount))) {
return false; return false;
} }
} }
if (!part.normalFilename.isEmpty()) { if (!part.normalTexture.filename.isEmpty()) {
if (!addPart(texdir + "/" + part.normalFilename, if (!addPart(texdir + "/" + part.normalTexture.filename,
QString("texture%1").arg(++_texturesCount))) { QString("texture%1").arg(++_texturesCount))) {
return false; return false;
} }

View file

@ -543,14 +543,14 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
int totalIndices = 0; int totalIndices = 0;
foreach (const FBXMeshPart& part, mesh.parts) { foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart networkPart; NetworkMeshPart networkPart;
if (!part.diffuseFilename.isEmpty()) { if (!part.diffuseTexture.filename.isEmpty()) {
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture( networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye); _textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content);
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
} }
if (!part.normalFilename.isEmpty()) { if (!part.normalTexture.filename.isEmpty()) {
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture( networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.normalFilename)), true); _textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content);
networkPart.normalTexture->setLoadPriorities(_loadPriorities); networkPart.normalTexture->setLoadPriorities(_loadPriorities);
} }
networkMesh.parts.append(networkPart); networkMesh.parts.append(networkPart);

View file

@ -105,13 +105,22 @@ GLuint TextureCache::getBlueTextureID() {
return _blueTextureID; return _blueTextureID;
} }
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) { /// Extra data for creating textures.
class TextureExtra {
public:
bool normalMap;
const QByteArray& content;
};
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap,
bool dilatable, const QByteArray& content) {
if (!dilatable) { if (!dilatable) {
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>(); TextureExtra extra = { normalMap, content };
return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast<NetworkTexture>();
} }
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url); QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) { if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url), &Resource::allReferencesCleared); texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared);
texture->setSelf(texture); texture->setSelf(texture);
texture->setCache(this); texture->setCache(this);
_dilatableNetworkTextures.insert(url, texture); _dilatableNetworkTextures.insert(url, texture);
@ -215,7 +224,9 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) {
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) { const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new NetworkTexture(url, *(const bool*)extra), &Resource::allReferencesCleared); const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
return QSharedPointer<Resource>(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content),
&Resource::allReferencesCleared);
} }
QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
@ -238,8 +249,8 @@ Texture::~Texture() {
glDeleteTextures(1, &_id); glDeleteTextures(1, &_id);
} }
NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) :
Resource(url), Resource(url, !content.isEmpty()),
_translucent(false) { _translucent(false) {
if (!url.isValid()) { if (!url.isValid()) {
@ -250,12 +261,19 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
glBindTexture(GL_TEXTURE_2D, getID()); glBindTexture(GL_TEXTURE_2D, getID());
loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE); loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
// if we have content, load it after we have our self pointer
if (!content.isEmpty()) {
_startedLoading = true;
QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
}
} }
class ImageReader : public QRunnable { class ImageReader : public QRunnable {
public: public:
ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply); ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply, const QUrl& url = QUrl(),
const QByteArray& content = QByteArray());
virtual void run(); virtual void run();
@ -263,27 +281,37 @@ private:
QWeakPointer<Resource> _texture; QWeakPointer<Resource> _texture;
QNetworkReply* _reply; QNetworkReply* _reply;
QUrl _url;
QByteArray _content;
}; };
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply) : ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply,
const QUrl& url, const QByteArray& content) :
_texture(texture), _texture(texture),
_reply(reply) { _reply(reply),
_url(url),
_content(content) {
} }
void ImageReader::run() { void ImageReader::run() {
QSharedPointer<Resource> texture = _texture.toStrongRef(); QSharedPointer<Resource> texture = _texture.toStrongRef();
if (texture.isNull()) { if (texture.isNull()) {
_reply->deleteLater(); if (_reply) {
_reply->deleteLater();
}
return; return;
} }
QUrl url = _reply->url(); if (_reply) {
QImage image = QImage::fromData(_reply->readAll()); _url = _reply->url();
_reply->deleteLater(); _content = _reply->readAll();
_reply->deleteLater();
}
QImage image = QImage::fromData(_content);
// enforce a fixed maximum // enforce a fixed maximum
const int MAXIMUM_SIZE = 1024; const int MAXIMUM_SIZE = 1024;
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) { if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
qDebug() << "Image greater than maximum size:" << url << image.width() << image.height(); qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height();
image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio); image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio);
} }
@ -315,7 +343,7 @@ void ImageReader::run() {
} }
int imageArea = image.width() * image.height(); int imageArea = image.width() * image.height();
if (opaquePixels == imageArea) { if (opaquePixels == imageArea) {
qDebug() << "Image with alpha channel is completely opaque:" << url; qDebug() << "Image with alpha channel is completely opaque:" << _url;
image = image.convertToFormat(QImage::Format_RGB888); image = image.convertToFormat(QImage::Format_RGB888);
} }
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
@ -327,6 +355,10 @@ void NetworkTexture::downloadFinished(QNetworkReply* reply) {
QThreadPool::globalInstance()->start(new ImageReader(_self, reply)); QThreadPool::globalInstance()->start(new ImageReader(_self, reply));
} }
void NetworkTexture::loadContent(const QByteArray& content) {
QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content));
}
void NetworkTexture::setImage(const QImage& image, bool translucent) { void NetworkTexture::setImage(const QImage& image, bool translucent) {
_translucent = translucent; _translucent = translucent;
@ -348,8 +380,8 @@ void NetworkTexture::imageLoaded(const QImage& image) {
// nothing by default // nothing by default
} }
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) :
NetworkTexture(url, false), NetworkTexture(url, false, content),
_innerRadius(0), _innerRadius(0),
_outerRadius(0) _outerRadius(0)
{ {

View file

@ -44,7 +44,8 @@ public:
GLuint getBlueTextureID(); GLuint getBlueTextureID();
/// Loads a texture from the specified URL. /// Loads a texture from the specified URL.
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false); QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false,
const QByteArray& content = QByteArray());
/// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is
/// used for scene rendering. /// used for scene rendering.
@ -115,7 +116,7 @@ class NetworkTexture : public Resource, public Texture {
public: public:
NetworkTexture(const QUrl& url, bool normalMap); NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content);
/// Checks whether it "looks like" this texture is translucent /// Checks whether it "looks like" this texture is translucent
/// (majority of pixels neither fully opaque or fully transparent). /// (majority of pixels neither fully opaque or fully transparent).
@ -124,10 +125,12 @@ public:
protected: protected:
virtual void downloadFinished(QNetworkReply* reply); virtual void downloadFinished(QNetworkReply* reply);
virtual void imageLoaded(const QImage& image);
Q_INVOKABLE void loadContent(const QByteArray& content);
Q_INVOKABLE void setImage(const QImage& image, bool translucent); Q_INVOKABLE void setImage(const QImage& image, bool translucent);
virtual void imageLoaded(const QImage& image);
private: private:
bool _translucent; bool _translucent;
@ -139,7 +142,7 @@ class DilatableNetworkTexture : public NetworkTexture {
public: public:
DilatableNetworkTexture(const QUrl& url); DilatableNetworkTexture(const QUrl& url, const QByteArray& content);
/// Returns a pointer to a texture with the requested amount of dilation. /// Returns a pointer to a texture with the requested amount of dilation.
QSharedPointer<Texture> getDilatedTexture(float dilation); QSharedPointer<Texture> getDilatedTexture(float dilation);

View file

@ -935,6 +935,14 @@ public:
QVector<float> values; QVector<float> values;
}; };
FBXTexture getTexture(const QString& textureID, const QHash<QString, QByteArray>& textureFilenames,
const QHash<QByteArray, QByteArray>& textureContent) {
FBXTexture texture;
texture.filename = textureFilenames.value(textureID);
texture.content = textureContent.value(texture.filename);
return texture;
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash<QString, ExtractedMesh> meshes; QHash<QString, ExtractedMesh> meshes;
QVector<ExtractedBlendshape> blendshapes; QVector<ExtractedBlendshape> blendshapes;
@ -944,6 +952,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QHash<QString, Cluster> clusters; QHash<QString, Cluster> clusters;
QHash<QString, AnimationCurve> animationCurves; QHash<QString, AnimationCurve> animationCurves;
QHash<QString, QByteArray> textureFilenames; QHash<QString, QByteArray> textureFilenames;
QHash<QByteArray, QByteArray> textureContent;
QHash<QString, Material> materials; QHash<QString, Material> materials;
QHash<QString, QString> diffuseTextures; QHash<QString, QString> diffuseTextures;
QHash<QString, QString> bumpTextures; QHash<QString, QString> bumpTextures;
@ -952,8 +961,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QHash<QString, QString> yComponents; QHash<QString, QString> yComponents;
QHash<QString, QString> zComponents; QHash<QString, QString> zComponents;
printNode(node, 0);
QVariantHash joints = mapping.value("joint").toHash(); QVariantHash joints = mapping.value("joint").toHash();
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight"))); QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight")));
@ -1182,6 +1189,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
textureFilenames.insert(getID(object.properties), filename); textureFilenames.insert(getID(object.properties), filename);
} }
} }
} else if (object.name == "Video") {
QByteArray filename;
QByteArray content;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") {
filename = subobject.properties.at(0).toByteArray();
filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1);
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {
content = subobject.properties.at(0).toByteArray();
}
}
if (!content.isEmpty()) {
textureContent.insert(filename, content);
}
} else if (object.name == "Material") { } else if (object.name == "Material") {
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f }; Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f };
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
@ -1263,7 +1285,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
if (type.contains("diffuse")) { if (type.contains("diffuse")) {
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("bump")) { } else if (type.contains("bump") || type.contains("normal")) {
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "lcl rotation") { } else if (type == "lcl rotation") {
@ -1463,23 +1485,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
if (materials.contains(childID)) { if (materials.contains(childID)) {
Material material = materials.value(childID); Material material = materials.value(childID);
QByteArray diffuseFilename; FBXTexture diffuseTexture;
QString diffuseTextureID = diffuseTextures.value(childID); QString diffuseTextureID = diffuseTextures.value(childID);
if (!diffuseTextureID.isNull()) { if (!diffuseTextureID.isNull()) {
diffuseFilename = textureFilenames.value(diffuseTextureID); diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently // FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) { foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) {
if (textureFilenames.contains(childTextureID)) { if (textureFilenames.contains(childTextureID)) {
diffuseFilename = textureFilenames.value(childTextureID); diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
} }
} }
} }
QByteArray normalFilename; FBXTexture normalTexture;
QString bumpTextureID = bumpTextures.value(childID); QString bumpTextureID = bumpTextures.value(childID);
if (!bumpTextureID.isNull()) { if (!bumpTextureID.isNull()) {
normalFilename = textureFilenames.value(bumpTextureID); normalTexture = getTexture(bumpTextureID, textureFilenames, textureContent);
generateTangents = true; generateTangents = true;
} }
@ -1489,21 +1511,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
part.diffuseColor = material.diffuse; part.diffuseColor = material.diffuse;
part.specularColor = material.specular; part.specularColor = material.specular;
part.shininess = material.shininess; part.shininess = material.shininess;
if (!diffuseFilename.isNull()) { if (!diffuseTexture.filename.isNull()) {
part.diffuseFilename = diffuseFilename; part.diffuseTexture = diffuseTexture;
} }
if (!normalFilename.isNull()) { if (!normalTexture.filename.isNull()) {
part.normalFilename = normalFilename; part.normalTexture = normalTexture;
} }
} }
} }
materialIndex++; materialIndex++;
} else if (textureFilenames.contains(childID)) { } else if (textureFilenames.contains(childID)) {
QByteArray filename = textureFilenames.value(childID); FBXTexture texture = getTexture(childID, textureFilenames, textureContent);
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
if (extracted.partMaterialTextures.at(j).second == textureIndex) { if (extracted.partMaterialTextures.at(j).second == textureIndex) {
extracted.mesh.parts[j].diffuseFilename = filename; extracted.mesh.parts[j].diffuseTexture = texture;
} }
} }
textureIndex++; textureIndex++;

View file

@ -103,6 +103,14 @@ public:
glm::mat4 inverseBindMatrix; glm::mat4 inverseBindMatrix;
}; };
/// A texture map in an FBX document.
class FBXTexture {
public:
QByteArray filename;
QByteArray content;
};
/// A single part of a mesh (with the same material). /// A single part of a mesh (with the same material).
class FBXMeshPart { class FBXMeshPart {
public: public:
@ -114,8 +122,8 @@ public:
glm::vec3 specularColor; glm::vec3 specularColor;
float shininess; float shininess;
QByteArray diffuseFilename; FBXTexture diffuseTexture;
QByteArray normalFilename; FBXTexture normalTexture;
}; };
/// A single mesh (with optional blendshapes) extracted from an FBX document. /// A single mesh (with optional blendshapes) extracted from an FBX document.