mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 09:48:44 +02:00
use FBXReader/FBXWriter for texture baking in FBXBaker
This commit is contained in:
parent
7214f57376
commit
b153d1e177
8 changed files with 87 additions and 65 deletions
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue