use FBXReader/FBXWriter for texture baking in FBXBaker

This commit is contained in:
Stephen Birarda 2017-09-07 18:38:29 -07:00
parent 7214f57376
commit b153d1e177
8 changed files with 87 additions and 65 deletions

View file

@ -35,9 +35,6 @@
#include "FBXBaker.h" #include "FBXBaker.h"
std::once_flag onceFlag;
FBXSDKManagerUniquePointer FBXBaker::_sdkManager { nullptr };
FBXBaker::FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter, FBXBaker::FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter,
const QString& bakedOutputDir, const QString& originalOutputDir) : const QString& bakedOutputDir, const QString& originalOutputDir) :
_fbxURL(fbxURL), _fbxURL(fbxURL),
@ -45,12 +42,7 @@ FBXBaker::FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGet
_originalOutputDir(originalOutputDir), _originalOutputDir(originalOutputDir),
_textureThreadGetter(textureThreadGetter) _textureThreadGetter(textureThreadGetter)
{ {
std::call_once(onceFlag, [](){
// create the static FBX SDK manager
_sdkManager = FBXSDKManagerUniquePointer(FbxManager::Create(), [](FbxManager* manager){
manager->Destroy();
});
});
} }
void FBXBaker::bake() { void FBXBaker::bake() {
@ -216,8 +208,12 @@ void FBXBaker::importScene() {
return; return;
} }
qCDebug(model_baking) << "Imported" << _fbxURL << "to FbxScene"; FBXReader reader;
_rootNode = FBXReader::parseFBX(&fbxFile);
qCDebug(model_baking) << "Parsing" << _fbxURL;
_rootNode = reader._rootNode = reader.parseFBX(&fbxFile);
_geometry = *reader.extractFBXGeometry({}, _fbxURL.toString());
_textureContent = reader._textureContent;
} }
QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) {
@ -254,7 +250,7 @@ QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) {
return bakedTextureFileName; return bakedTextureFileName;
} }
QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* fileTexture) { QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName) {
QUrl urlToTexture; QUrl urlToTexture;
if (textureFileInfo.exists() && textureFileInfo.isFile()) { if (textureFileInfo.exists() && textureFileInfo.isFile()) {
@ -264,7 +260,6 @@ QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* f
// external texture that we'll need to download or find // external texture that we'll need to download or find
// first check if it the RelativePath to the texture in the FBX was relative // first check if it the RelativePath to the texture in the FBX was relative
QString relativeFileName = fileTexture->GetRelativeFileName();
auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/"));
// this is a relative file path which will require different handling // this is a relative file path which will require different handling
@ -336,31 +331,44 @@ image::TextureUsage::Type textureTypeForMaterialProperty(FbxProperty& property,
} }
void FBXBaker::rewriteAndBakeSceneTextures() { void FBXBaker::rewriteAndBakeSceneTextures() {
using namespace image::TextureUsage;
QHash<QString, image::TextureUsage::Type> textureTypes;
// enumerate the surface materials to find the textures used in the scene // enumerate the materials in the extracted geometry so we can determine the texture type for each texture ID
int numMaterials = _scene->GetMaterialCount(); for (const auto& material : _geometry.materials) {
for (int i = 0; i < numMaterials; i++) { if (material.normalTexture.isBumpmap) {
FbxSurfaceMaterial* material = _scene->GetMaterial(i); textureTypes[material.normalTexture.id] = BUMP_TEXTURE;
} else {
textureTypes[material.normalTexture.id] = NORMAL_TEXTURE;
}
if (material) { textureTypes[material.albedoTexture.id] = ALBEDO_TEXTURE;
// enumerate the properties of this material to see what texture channels it might have textureTypes[material.glossTexture.id] = GLOSS_TEXTURE;
FbxProperty property = material->GetFirstProperty(); textureTypes[material.roughnessTexture.id] = ROUGHNESS_TEXTURE;
textureTypes[material.specularTexture.id] = SPECULAR_TEXTURE;
textureTypes[material.metallicTexture.id] = METALLIC_TEXTURE;
textureTypes[material.emissiveTexture.id] = EMISSIVE_TEXTURE;
textureTypes[material.occlusionTexture.id] = OCCLUSION_TEXTURE;
textureTypes[material.lightmapTexture.id] = LIGHTMAP_TEXTURE;
}
while (property.IsValid()) { // enumerate the children of the root node
// first check if this property has connected textures, if not we don't need to bother with it here for (FBXNode& rootChild : _rootNode.children) {
if (property.GetSrcObjectCount<FbxTexture>() > 0) {
// figure out the type of texture from the material property if (rootChild.name == "Objects") {
auto textureType = textureTypeForMaterialProperty(property, material);
if (textureType != image::TextureUsage::UNUSED_TEXTURE) { // enumerate the objects
int numTextures = property.GetSrcObjectCount<FbxFileTexture>(); auto object = rootChild.children.begin();
while (object != rootChild.children.end()) {
if (object->name == "Texture") {
for (int j = 0; j < numTextures; j++) { // enumerate the texture children
FbxFileTexture* fileTexture = property.GetSrcObject<FbxFileTexture>(j); for (FBXNode& textureChild : object->children) {
if (textureChild.name == "RelativeFilename") {
// use QFileInfo to easily split up the existing texture filename into its components // use QFileInfo to easily split up the existing texture filename into its components
QString fbxTextureFileName { fileTexture->GetFileName() }; QString fbxTextureFileName { textureChild.properties.at(0).toByteArray() };
QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") }; QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") };
// make sure this texture points to something and isn't one we've already re-mapped // make sure this texture points to something and isn't one we've already re-mapped
@ -383,38 +391,50 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
}; };
_outputFiles.push_back(bakedTextureFilePath); _outputFiles.push_back(bakedTextureFilePath);
qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() qCDebug(model_baking).noquote() << "Re-mapping" << fbxTextureFileName
<< "to" << bakedTextureFilePath; << "to" << bakedTextureFileName;
// figure out the URL to this texture, embedded or external // figure out the URL to this texture, embedded or external
auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); auto urlToTexture = getTextureURL(textureFileInfo, fbxTextureFileName);
// write the new filename into the FBX scene // write the new filename into the FBX scene
fileTexture->SetFileName(bakedTextureFilePath.toUtf8().data()); textureChild.properties[0] = bakedTextureFileName.toLocal8Bit();
// write the relative filename to be the baked texture file name since it will
// be right beside the FBX
fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit().constData());
if (!_bakingTextures.contains(urlToTexture)) { if (!_bakingTextures.contains(urlToTexture)) {
// grab the ID for this texture so we can figure out the
// texture type from the loaded materials
QString textureID { object->properties[0].toByteArray() };
auto textureType = textureTypes[textureID];
// check if this was an embedded texture we have already have in-memory content for
auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit());
// bake this texture asynchronously // bake this texture asynchronously
bakeTexture(urlToTexture, textureType, _bakedOutputDir); bakeTexture(urlToTexture, textureType, _bakedOutputDir, textureContent);
} }
} }
} }
} }
}
property = material->GetNextProperty(property); ++object;
} else if (object->name == "Video") {
// this is an embedded texture, we need to remove it from the FBX
object = rootChild.children.erase(object);
} else {
++object;
}
} }
} }
} }
} }
void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir) { void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType,
const QDir& outputDir, const QByteArray& textureContent) {
// start a bake for this texture and add it to our list to keep track of // start a bake for this texture and add it to our list to keep track of
QSharedPointer<TextureBaker> bakingTexture { QSharedPointer<TextureBaker> bakingTexture {
new TextureBaker(textureURL, textureType, outputDir), new TextureBaker(textureURL, textureType, outputDir, textureContent),
&TextureBaker::deleteLater &TextureBaker::deleteLater
}; };
@ -464,7 +484,7 @@ void FBXBaker::handleBakedTexture() {
if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) {
qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName()
<< "for" << _fbxURL; << "for" << _fbxURL;
} else { } else {
handleError("Could not save original external texture " + originalTextureFile.fileName() handleError("Could not save original external texture " + originalTextureFile.fileName()
+ " for " + _fbxURL.toString()); + " for " + _fbxURL.toString());

View file

@ -26,15 +26,7 @@
#include <FBX.h> #include <FBX.h>
namespace fbxsdk {
class FbxManager;
class FbxProperty;
class FbxScene;
class FbxFileTexture;
}
static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
using FBXSDKManagerUniquePointer = std::unique_ptr<fbxsdk::FbxManager, std::function<void (fbxsdk::FbxManager *)>>;
using TextureBakerThreadGetter = std::function<QThread*()>; using TextureBakerThreadGetter = std::function<QThread*()>;
@ -73,13 +65,16 @@ private:
void checkIfTexturesFinished(); void checkIfTexturesFinished();
QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QString createBakedTextureFileName(const QFileInfo& textureFileInfo);
QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName);
void bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir); void bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir,
const QByteArray& textureContent = QByteArray());
QUrl _fbxURL; QUrl _fbxURL;
FBXNode _rootNode; FBXNode _rootNode;
FBXGeometry _geometry;
QHash<QByteArray, QByteArray> _textureContent;
QString _bakedFBXFilePath; QString _bakedFBXFilePath;
@ -91,9 +86,6 @@ private:
QDir _tempDir; QDir _tempDir;
QString _originalFBXFilePath; QString _originalFBXFilePath;
static FBXSDKManagerUniquePointer _sdkManager;
fbxsdk::FbxScene* _scene { nullptr };
QMultiHash<QUrl, QSharedPointer<TextureBaker>> _bakingTextures; QMultiHash<QUrl, QSharedPointer<TextureBaker>> _bakingTextures;
QHash<QString, int> _textureNameMatchCount; QHash<QString, int> _textureNameMatchCount;

View file

@ -25,8 +25,10 @@
const QString BAKED_TEXTURE_EXT = ".ktx"; const QString BAKED_TEXTURE_EXT = ".ktx";
TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDirectory) : TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
const QDir& outputDirectory, const QByteArray& textureContent) :
_textureURL(textureURL), _textureURL(textureURL),
_originalTexture(textureContent),
_textureType(textureType), _textureType(textureType),
_outputDirectory(outputDirectory) _outputDirectory(outputDirectory)
{ {
@ -39,8 +41,13 @@ void TextureBaker::bake() {
// once our texture is loaded, kick off a the processing // once our texture is loaded, kick off a the processing
connect(this, &TextureBaker::originalTextureLoaded, this, &TextureBaker::processTexture); connect(this, &TextureBaker::originalTextureLoaded, this, &TextureBaker::processTexture);
// first load the texture (either locally or remotely) if (_originalTexture.isEmpty()) {
loadTexture(); // first load the texture (either locally or remotely)
loadTexture();
} else {
// we already have a texture passed to us, use that
emit originalTextureLoaded();
}
} }
const QStringList TextureBaker::getSupportedFormats() { const QStringList TextureBaker::getSupportedFormats() {

View file

@ -27,7 +27,8 @@ class TextureBaker : public Baker {
Q_OBJECT Q_OBJECT
public: public:
TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDirectory); TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
const QDir& outputDirectory, const QByteArray& textureContent = QByteArray());
static const QStringList getSupportedFormats(); static const QStringList getSupportedFormats();

View file

@ -113,6 +113,7 @@ const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048;
/// A texture map in an FBX document. /// A texture map in an FBX document.
class FBXTexture { class FBXTexture {
public: public:
QString id;
QString name; QString name;
QByteArray filename; QByteArray filename;
QByteArray content; QByteArray content;

View file

@ -939,7 +939,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
QByteArray content; QByteArray content;
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") { if (subobject.name == "RelativeFilename") {
filepath= subobject.properties.at(0).toByteArray(); filepath = subobject.properties.at(0).toByteArray();
filepath = filepath.replace('\\', '/'); filepath = filepath.replace('\\', '/');
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) { } else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {

View file

@ -92,6 +92,7 @@ FBXTexture FBXReader::getTexture(const QString& textureID) {
texture.filename = filepath; texture.filename = filepath;
} }
texture.id = textureID;
texture.name = _textureNames.value(textureID); texture.name = _textureNames.value(textureID);
texture.transform.setIdentity(); texture.transform.setIdentity();
texture.texcoordSet = 0; texture.texcoordSet = 0;

View file

@ -119,7 +119,7 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) {
case QMetaType::QString: case QMetaType::QString:
{ {
auto& bytes = prop.toString().toUtf8(); auto bytes = prop.toString().toUtf8();
out << 'S'; out << 'S';
out << bytes.length(); out << bytes.length();
out << bytes; out << bytes;
@ -130,7 +130,7 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) {
case QMetaType::QByteArray: case QMetaType::QByteArray:
{ {
auto& bytes = prop.toByteArray(); auto bytes = prop.toByteArray();
out.device()->write("S", 1); out.device()->write("S", 1);
out << (int32_t)bytes.size(); out << (int32_t)bytes.size();
out.writeRawData(bytes, bytes.size()); out.writeRawData(bytes, bytes.size());
@ -140,7 +140,7 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) {
// TODO Delete? Do we ever use QList instead of QVector? // TODO Delete? Do we ever use QList instead of QVector?
case QVariant::Type::List: case QVariant::Type::List:
{ {
auto& list = prop.toList(); auto list = prop.toList();
auto listType = prop.userType(); auto listType = prop.userType();
switch (listType) { switch (listType) {