Drafting the materials for FBXReader

This commit is contained in:
Sam Gateau 2015-08-25 22:12:51 -07:00
parent e06e50cb5c
commit 1e9fce2a61
13 changed files with 816 additions and 467 deletions

View file

@ -38,7 +38,7 @@
//#define DEBUG_FBXREADER //#define DEBUG_FBXREADER
using namespace std; using namespace std;
/*
struct TextureParam { struct TextureParam {
glm::vec2 UVTranslation; glm::vec2 UVTranslation;
glm::vec2 UVScaling; glm::vec2 UVScaling;
@ -80,22 +80,27 @@ struct TextureParam {
{} {}
}; };
*/
bool FBXMesh::hasSpecularTexture() const { bool FBXMesh::hasSpecularTexture() const {
foreach (const FBXMeshPart& part, parts) { // TODO fix that in model.cpp / payload
/* foreach (const FBXMeshPart& part, parts) {
if (!part.specularTexture.filename.isEmpty()) { if (!part.specularTexture.filename.isEmpty()) {
return true; return true;
} }
} }
*/
return false; return false;
} }
bool FBXMesh::hasEmissiveTexture() const { bool FBXMesh::hasEmissiveTexture() const {
// TODO Fix that in model cpp / payload
/*
foreach (const FBXMeshPart& part, parts) { foreach (const FBXMeshPart& part, parts) {
if (!part.emissiveTexture.filename.isEmpty()) { if (!part.emissiveTexture.filename.isEmpty()) {
return true; return true;
} }
} }
*/
return false; return false;
} }
@ -184,325 +189,7 @@ static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
static int fbxAnimationFrameMetaTypeId = qRegisterMetaType<FBXAnimationFrame>(); static int fbxAnimationFrameMetaTypeId = qRegisterMetaType<FBXAnimationFrame>();
static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<FBXAnimationFrame> >(); static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<FBXAnimationFrame> >();
template<class T> int streamSize() {
return sizeof(T);
}
template<bool> int streamSize() {
return 1;
}
template<class T> QVariant readBinaryArray(QDataStream& in, int& position) {
quint32 arrayLength;
quint32 encoding;
quint32 compressedLength;
in >> arrayLength;
in >> encoding;
in >> compressedLength;
position += sizeof(quint32) * 3;
QVector<T> values;
const unsigned int DEFLATE_ENCODING = 1;
if (encoding == DEFLATE_ENCODING) {
// preface encoded data with uncompressed length
QByteArray compressed(sizeof(quint32) + compressedLength, 0);
*((quint32*)compressed.data()) = qToBigEndian<quint32>(arrayLength * sizeof(T));
in.readRawData(compressed.data() + sizeof(quint32), compressedLength);
position += compressedLength;
QByteArray uncompressed = qUncompress(compressed);
QDataStream uncompressedIn(uncompressed);
uncompressedIn.setByteOrder(QDataStream::LittleEndian);
uncompressedIn.setVersion(QDataStream::Qt_4_5); // for single/double precision switch
for (quint32 i = 0; i < arrayLength; i++) {
T value;
uncompressedIn >> value;
values.append(value);
}
} else {
for (quint32 i = 0; i < arrayLength; i++) {
T value;
in >> value;
position += streamSize<T>();
values.append(value);
}
}
return QVariant::fromValue(values);
}
QVariant parseBinaryFBXProperty(QDataStream& in, int& position) {
char ch;
in.device()->getChar(&ch);
position++;
switch (ch) {
case 'Y': {
qint16 value;
in >> value;
position += sizeof(qint16);
return QVariant::fromValue(value);
}
case 'C': {
bool value;
in >> value;
position++;
return QVariant::fromValue(value);
}
case 'I': {
qint32 value;
in >> value;
position += sizeof(qint32);
return QVariant::fromValue(value);
}
case 'F': {
float value;
in >> value;
position += sizeof(float);
return QVariant::fromValue(value);
}
case 'D': {
double value;
in >> value;
position += sizeof(double);
return QVariant::fromValue(value);
}
case 'L': {
qint64 value;
in >> value;
position += sizeof(qint64);
return QVariant::fromValue(value);
}
case 'f': {
return readBinaryArray<float>(in, position);
}
case 'd': {
return readBinaryArray<double>(in, position);
}
case 'l': {
return readBinaryArray<qint64>(in, position);
}
case 'i': {
return readBinaryArray<qint32>(in, position);
}
case 'b': {
return readBinaryArray<bool>(in, position);
}
case 'S':
case 'R': {
quint32 length;
in >> length;
position += sizeof(quint32) + length;
return QVariant::fromValue(in.device()->read(length));
}
default:
throw QString("Unknown property type: ") + ch;
}
}
FBXNode parseBinaryFBXNode(QDataStream& in, int& position) {
qint32 endOffset;
quint32 propertyCount;
quint32 propertyListLength;
quint8 nameLength;
in >> endOffset;
in >> propertyCount;
in >> propertyListLength;
in >> nameLength;
position += sizeof(quint32) * 3 + sizeof(quint8);
FBXNode node;
const int MIN_VALID_OFFSET = 40;
if (endOffset < MIN_VALID_OFFSET || nameLength == 0) {
// use a null name to indicate a null node
return node;
}
node.name = in.device()->read(nameLength);
position += nameLength;
for (quint32 i = 0; i < propertyCount; i++) {
node.properties.append(parseBinaryFBXProperty(in, position));
}
while (endOffset > position) {
FBXNode child = parseBinaryFBXNode(in, position);
if (child.name.isNull()) {
return node;
} else {
node.children.append(child);
}
}
return node;
}
class Tokenizer {
public:
Tokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { }
enum SpecialToken {
NO_TOKEN = -1,
NO_PUSHBACKED_TOKEN = -1,
DATUM_TOKEN = 0x100
};
int nextToken();
const QByteArray& getDatum() const { return _datum; }
void pushBackToken(int token) { _pushedBackToken = token; }
void ungetChar(char ch) { _device->ungetChar(ch); }
private:
QIODevice* _device;
QByteArray _datum;
int _pushedBackToken;
};
int Tokenizer::nextToken() {
if (_pushedBackToken != NO_PUSHBACKED_TOKEN) {
int token = _pushedBackToken;
_pushedBackToken = NO_PUSHBACKED_TOKEN;
return token;
}
char ch;
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace()) {
continue; // skip whitespace
}
switch (ch) {
case ';':
_device->readLine(); // skip the comment
break;
case ':':
case '{':
case '}':
case ',':
return ch; // special punctuation
case '\"':
_datum = "";
while (_device->getChar(&ch)) {
if (ch == '\"') { // end on closing quote
break;
}
if (ch == '\\') { // handle escaped quotes
if (_device->getChar(&ch) && ch != '\"') {
_datum.append('\\');
}
}
_datum.append(ch);
}
return DATUM_TOKEN;
default:
_datum = "";
_datum.append(ch);
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace() || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == ',' || ch == '\"') {
ungetChar(ch); // read until we encounter a special character, then replace it
break;
}
_datum.append(ch);
}
return DATUM_TOKEN;
}
}
return NO_TOKEN;
}
FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
FBXNode node;
if (tokenizer.nextToken() != Tokenizer::DATUM_TOKEN) {
return node;
}
node.name = tokenizer.getDatum();
if (tokenizer.nextToken() != ':') {
return node;
}
int token;
bool expectingDatum = true;
while ((token = tokenizer.nextToken()) != Tokenizer::NO_TOKEN) {
if (token == '{') {
for (FBXNode child = parseTextFBXNode(tokenizer); !child.name.isNull(); child = parseTextFBXNode(tokenizer)) {
node.children.append(child);
}
return node;
}
if (token == ',') {
expectingDatum = true;
} else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) {
QByteArray datum = tokenizer.getDatum();
if ((token = tokenizer.nextToken()) == ':') {
tokenizer.ungetChar(':');
tokenizer.pushBackToken(Tokenizer::DATUM_TOKEN);
return node;
} else {
tokenizer.pushBackToken(token);
node.properties.append(datum);
expectingDatum = false;
}
} else {
tokenizer.pushBackToken(token);
return node;
}
}
return node;
}
FBXNode parseFBX(QIODevice* device) {
// verify the prolog
const QByteArray BINARY_PROLOG = "Kaydara FBX Binary ";
if (device->peek(BINARY_PROLOG.size()) != BINARY_PROLOG) {
// parse as a text file
FBXNode top;
Tokenizer tokenizer(device);
while (device->bytesAvailable()) {
FBXNode next = parseTextFBXNode(tokenizer);
if (next.name.isNull()) {
return top;
} else {
top.children.append(next);
}
}
return top;
}
QDataStream in(device);
in.setByteOrder(QDataStream::LittleEndian);
in.setVersion(QDataStream::Qt_4_5); // for single/double precision switch
// see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation
// of the FBX binary format
// skip the rest of the header
const int HEADER_SIZE = 27;
in.skipRawData(HEADER_SIZE);
int position = HEADER_SIZE;
// parse the top-level node
FBXNode top;
while (device->bytesAvailable()) {
FBXNode next = parseBinaryFBXNode(in, position);
if (next.name.isNull()) {
return top;
} else {
top.children.append(next);
}
}
return top;
}
QVector<glm::vec4> createVec4Vector(const QVector<double>& doubleVector) { QVector<glm::vec4> createVec4Vector(const QVector<double>& doubleVector) {
QVector<glm::vec4> values; QVector<glm::vec4> values;
@ -693,7 +380,7 @@ public:
glm::vec3 rotationMax; // radians glm::vec3 rotationMax; // radians
}; };
glm::mat4 getGlobalTransform(const QMultiHash<QString, QString>& parentMap, glm::mat4 getGlobalTransform(const QMultiHash<QString, QString>& _connectionParentMap,
const QHash<QString, FBXModel>& models, QString nodeID, bool mixamoHack) { const QHash<QString, FBXModel>& models, QString nodeID, bool mixamoHack) {
glm::mat4 globalTransform; glm::mat4 globalTransform;
while (!nodeID.isNull()) { while (!nodeID.isNull()) {
@ -704,7 +391,7 @@ glm::mat4 getGlobalTransform(const QMultiHash<QString, QString>& parentMap,
// there's something weird about the models from Mixamo Fuse; they don't skin right with the full transform // there's something weird about the models from Mixamo Fuse; they don't skin right with the full transform
return globalTransform; return globalTransform;
} }
QList<QString> parentIDs = parentMap.values(nodeID); QList<QString> parentIDs = _connectionParentMap.values(nodeID);
nodeID = QString(); nodeID = QString();
foreach (const QString& parentID, parentIDs) { foreach (const QString& parentID, parentIDs) {
if (models.contains(parentID)) { if (models.contains(parentID)) {
@ -738,17 +425,6 @@ void printNode(const FBXNode& node, int indentLevel) {
} }
} }
class Material {
public:
glm::vec3 diffuse;
glm::vec3 specular;
glm::vec3 emissive;
float shininess;
float opacity;
QString id;
model::MaterialPointer _material;
};
class Cluster { class Cluster {
public: public:
QVector<int> indices; QVector<int> indices;
@ -756,19 +432,19 @@ public:
glm::mat4 transformLink; glm::mat4 transformLink;
}; };
void appendModelIDs(const QString& parentID, const QMultiHash<QString, QString>& childMap, void appendModelIDs(const QString& parentID, const QMultiHash<QString, QString>& _connectionChildMap,
QHash<QString, FBXModel>& models, QSet<QString>& remainingModels, QVector<QString>& modelIDs) { QHash<QString, FBXModel>& models, QSet<QString>& remainingModels, QVector<QString>& modelIDs) {
if (remainingModels.contains(parentID)) { if (remainingModels.contains(parentID)) {
modelIDs.append(parentID); modelIDs.append(parentID);
remainingModels.remove(parentID); remainingModels.remove(parentID);
} }
int parentIndex = modelIDs.size() - 1; int parentIndex = modelIDs.size() - 1;
foreach (const QString& childID, childMap.values(parentID)) { foreach (const QString& childID, _connectionChildMap.values(parentID)) {
if (remainingModels.contains(childID)) { if (remainingModels.contains(childID)) {
FBXModel& model = models[childID]; FBXModel& model = models[childID];
if (model.parentIndex == -1) { if (model.parentIndex == -1) {
model.parentIndex = parentIndex; model.parentIndex = parentIndex;
appendModelIDs(childID, childMap, models, remainingModels, modelIDs); appendModelIDs(childID, _connectionChildMap, models, remainingModels, modelIDs);
} }
} }
} }
@ -1235,11 +911,11 @@ void addBlendshapes(const ExtractedBlendshape& extracted, const QList<WeightedIn
} }
} }
QString getTopModelID(const QMultiHash<QString, QString>& parentMap, QString getTopModelID(const QMultiHash<QString, QString>& _connectionParentMap,
const QHash<QString, FBXModel>& models, const QString& modelID) { const QHash<QString, FBXModel>& models, const QString& modelID) {
QString topID = modelID; QString topID = modelID;
forever { forever {
foreach (const QString& parentID, parentMap.values(topID)) { foreach (const QString& parentID, _connectionParentMap.values(topID)) {
if (models.contains(parentID)) { if (models.contains(parentID)) {
topID = parentID; topID = parentID;
goto outerContinue; goto outerContinue;
@ -1303,10 +979,10 @@ FBXTexture getTexture(const QString& textureID,
return texture; return texture;
} }
bool checkMaterialsHaveTextures(const QHash<QString, Material>& materials, bool checkMaterialsHaveTextures(const QHash<QString, FBXMaterial>& materials,
const QHash<QString, QByteArray>& textureFilenames, const QMultiHash<QString, QString>& childMap) { const QHash<QString, QByteArray>& textureFilenames, const QMultiHash<QString, QString>& _connectionChildMap) {
foreach (const QString& materialID, materials.keys()) { foreach (const QString& materialID, materials.keys()) {
foreach (const QString& childID, childMap.values(materialID)) { foreach (const QString& childID, _connectionChildMap.values(materialID)) {
if (textureFilenames.contains(childID)) { if (textureFilenames.contains(childID)) {
return true; return true;
} }
@ -1530,29 +1206,21 @@ QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) {
return filename; return filename;
} }
FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) {
const FBXNode& node = _fbxNode;
QHash<QString, ExtractedMesh> meshes; QHash<QString, ExtractedMesh> meshes;
QHash<QString, QString> modelIDsToNames; QHash<QString, QString> modelIDsToNames;
QHash<QString, int> meshIDsToMeshIndices; QHash<QString, int> meshIDsToMeshIndices;
QHash<QString, QString> ooChildToParent; QHash<QString, QString> ooChildToParent;
QVector<ExtractedBlendshape> blendshapes; QVector<ExtractedBlendshape> blendshapes;
QMultiHash<QString, QString> parentMap;
QMultiHash<QString, QString> childMap;
QHash<QString, FBXModel> models; QHash<QString, FBXModel> models;
QHash<QString, Cluster> clusters; QHash<QString, Cluster> clusters;
QHash<QString, AnimationCurve> animationCurves; QHash<QString, AnimationCurve> animationCurves;
QHash<QString, QString> textureNames;
QHash<QString, QByteArray> textureFilenames;
QHash<QString, TextureParam> textureParams;
QHash<QByteArray, QByteArray> textureContent;
QHash<QString, Material> materials;
QHash<QString, QString> typeFlags; QHash<QString, QString> typeFlags;
QHash<QString, QString> diffuseTextures;
QHash<QString, QString> bumpTextures;
QHash<QString, QString> specularTextures;
QHash<QString, QString> emissiveTextures;
QHash<QString, QString> ambientTextures;
QHash<QString, QString> localRotations; QHash<QString, QString> localRotations;
QHash<QString, QString> xComponents; QHash<QString, QString> xComponents;
QHash<QString, QString> yComponents; QHash<QString, QString> yComponents;
@ -1580,8 +1248,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
QString jointLeftToeID; QString jointLeftToeID;
QString jointRightToeID; QString jointRightToeID;
float lightmapOffset = 0.0f;
QVector<QString> humanIKJointNames; QVector<QString> humanIKJointNames;
for (int i = 0;; i++) { for (int i = 0;; i++) {
QByteArray jointName = HUMANIK_JOINTS[i]; QByteArray jointName = HUMANIK_JOINTS[i];
@ -1618,6 +1285,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
FBXGeometry* geometryPtr = new FBXGeometry; FBXGeometry* geometryPtr = new FBXGeometry;
FBXGeometry& geometry = *geometryPtr; FBXGeometry& geometry = *geometryPtr;
geometry._asset.reset(new model::Asset());
float unitScaleFactor = 1.0f; float unitScaleFactor = 1.0f;
glm::vec3 ambientColor; glm::vec3 ambientColor;
QString hifiGlobalNodeID; QString hifiGlobalNodeID;
@ -1942,8 +1611,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
textureContent.insert(filename, content); 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), glm::vec3(), 96.0f, 1.0f, FBXMaterial material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(), glm::vec2(0.f, 1.0f), 96.0f, 1.0f,
QString(""), model::MaterialPointer(NULL)}; QString(""), model::MaterialTable::INVALID_ID};
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
bool properties = false; bool properties = false;
QByteArray propertyName; QByteArray propertyName;
@ -1962,13 +1631,13 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
foreach (const FBXNode& property, subobject.children) { foreach (const FBXNode& property, subobject.children) {
if (property.name == propertyName) { if (property.name == propertyName) {
if (property.properties.at(0) == "DiffuseColor") { if (property.properties.at(0) == "DiffuseColor") {
material.diffuse = getVec3(property.properties, index); material.diffuseColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "SpecularColor") { } else if (property.properties.at(0) == "SpecularColor") {
material.specular = getVec3(property.properties, index); material.specularColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "Emissive") { } else if (property.properties.at(0) == "Emissive") {
material.emissive = getVec3(property.properties, index); material.emissiveColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "Shininess") { } else if (property.properties.at(0) == "Shininess") {
material.shininess = property.properties.at(index).value<double>(); material.shininess = property.properties.at(index).value<double>();
@ -1999,25 +1668,9 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
} }
#endif #endif
} }
material.id = getID(object.properties); material.materialID = getID(object.properties);
material._material = make_shared<model::Material>(); _fbxMaterials.insert(material.materialID, material);
material._material->setEmissive(material.emissive);
if (glm::all(glm::equal(material.diffuse, glm::vec3(0.0f)))) {
material._material->setDiffuse(material.diffuse);
} else {
material._material->setDiffuse(material.diffuse);
}
material._material->setMetallic(glm::length(material.specular));
material._material->setGloss(material.shininess);
if (material.opacity <= 0.0f) {
material._material->setOpacity(1.0f);
} else {
material._material->setOpacity(material.opacity);
}
materials.insert(material.id, material);
} else if (object.name == "NodeAttribute") { } else if (object.name == "NodeAttribute") {
#if defined(DEBUG_FBXREADER) #if defined(DEBUG_FBXREADER)
@ -2106,11 +1759,11 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
if (!hifiGlobalNodeID.isEmpty() && (parentID == hifiGlobalNodeID)) { if (!hifiGlobalNodeID.isEmpty() && (parentID == hifiGlobalNodeID)) {
std::map< QString, FBXLight >::iterator lit = lights.find(childID); std::map< QString, FBXLight >::iterator lit = lights.find(childID);
if (lit != lights.end()) { if (lit != lights.end()) {
lightmapLevel = (*lit).second.intensity; _lightmapLevel = (*lit).second.intensity;
if (lightmapLevel <= 0.0f) { if (_lightmapLevel <= 0.0f) {
loadLightmaps = false; _loadLightmaps = false;
} }
lightmapOffset = glm::clamp((*lit).second.color.x, 0.f, 1.f); _lightmapOffset = glm::clamp((*lit).second.color.x, 0.f, 1.f);
} }
} }
} }
@ -2144,18 +1797,18 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
} else if (type.contains("shininess")) { } else if (type.contains("shininess")) {
counter++; counter++;
} else if (loadLightmaps && type.contains("emissive")) { } else if (_loadLightmaps && type.contains("emissive")) {
emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); emissiveTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (loadLightmaps && type.contains("ambient")) { } else if (_loadLightmaps && type.contains("ambient")) {
ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else { } else {
QString typenam = type.data(); QString typenam = type.data();
counter++; counter++;
} }
} }
parentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); _connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2));
childMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); _connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} }
} }
} }
@ -2184,15 +1837,15 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
if (!lights.empty()) { if (!lights.empty()) {
if (hifiGlobalNodeID.isEmpty()) { if (hifiGlobalNodeID.isEmpty()) {
std::map< QString, FBXLight >::iterator l = lights.begin(); std::map< QString, FBXLight >::iterator l = lights.begin();
lightmapLevel = (*l).second.intensity; _lightmapLevel = (*l).second.intensity;
} }
} }
// assign the blendshapes to their corresponding meshes // assign the blendshapes to their corresponding meshes
foreach (const ExtractedBlendshape& extracted, blendshapes) { foreach (const ExtractedBlendshape& extracted, blendshapes) {
QString blendshapeChannelID = parentMap.value(extracted.id); QString blendshapeChannelID = _connectionParentMap.value(extracted.id);
QString blendshapeID = parentMap.value(blendshapeChannelID); QString blendshapeID = _connectionParentMap.value(blendshapeChannelID);
QString meshID = parentMap.value(blendshapeID); QString meshID = _connectionParentMap.value(blendshapeID);
addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]);
} }
@ -2209,23 +1862,23 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
QSet<QString> remainingModels; QSet<QString> remainingModels;
for (QHash<QString, FBXModel>::const_iterator model = models.constBegin(); model != models.constEnd(); model++) { for (QHash<QString, FBXModel>::const_iterator model = models.constBegin(); model != models.constEnd(); model++) {
// models with clusters must be parented to the cluster top // models with clusters must be parented to the cluster top
foreach (const QString& deformerID, childMap.values(model.key())) { foreach (const QString& deformerID, _connectionChildMap.values(model.key())) {
foreach (const QString& clusterID, childMap.values(deformerID)) { foreach (const QString& clusterID, _connectionChildMap.values(deformerID)) {
if (!clusters.contains(clusterID)) { if (!clusters.contains(clusterID)) {
continue; continue;
} }
QString topID = getTopModelID(parentMap, models, childMap.value(clusterID)); QString topID = getTopModelID(_connectionParentMap, models, _connectionChildMap.value(clusterID));
childMap.remove(parentMap.take(model.key()), model.key()); _connectionChildMap.remove(_connectionParentMap.take(model.key()), model.key());
parentMap.insert(model.key(), topID); _connectionParentMap.insert(model.key(), topID);
goto outerBreak; goto outerBreak;
} }
} }
outerBreak: outerBreak:
// make sure the parent is in the child map // make sure the parent is in the child map
QString parent = parentMap.value(model.key()); QString parent = _connectionParentMap.value(model.key());
if (!childMap.contains(parent, model.key())) { if (!_connectionChildMap.contains(parent, model.key())) {
childMap.insert(parent, model.key()); _connectionChildMap.insert(parent, model.key());
} }
remainingModels.insert(model.key()); remainingModels.insert(model.key());
} }
@ -2236,8 +1889,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
first = id; first = id;
} }
} }
QString topID = getTopModelID(parentMap, models, first); QString topID = getTopModelID(_connectionParentMap, models, first);
appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs); appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs);
} }
// figure the number of animation frames from the curves // figure the number of animation frames from the curves
@ -2298,7 +1951,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
joint.inverseBindRotation = joint.inverseDefaultRotation; joint.inverseBindRotation = joint.inverseDefaultRotation;
joint.name = model.name; joint.name = model.name;
foreach (const QString& childID, childMap.values(modelID)) { foreach (const QString& childID, _connectionChildMap.values(modelID)) {
QString type = typeFlags.value(childID); QString type = typeFlags.value(childID);
if (!type.isEmpty()) { if (!type.isEmpty()) {
geometry.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); geometry.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton"));
@ -2350,8 +2003,11 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
geometry.bindExtents.reset(); geometry.bindExtents.reset();
geometry.meshExtents.reset(); geometry.meshExtents.reset();
// Create the Material Library
consolidateFBXMaterials();
// see if any materials have texture children // see if any materials have texture children
bool materialsHaveTextures = checkMaterialsHaveTextures(materials, textureFilenames, childMap); bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, textureFilenames, _connectionChildMap);
for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) { for (QHash<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
ExtractedMesh& extracted = it.value(); ExtractedMesh& extracted = it.value();
@ -2359,8 +2015,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
extracted.mesh.meshExtents.reset(); extracted.mesh.meshExtents.reset();
// accumulate local transforms // accumulate local transforms
QString modelID = models.contains(it.key()) ? it.key() : parentMap.value(it.key()); QString modelID = models.contains(it.key()) ? it.key() : _connectionParentMap.value(it.key());
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID, geometry.applicationName == "mixamo.com"); glm::mat4 modelTransform = getGlobalTransform(_connectionParentMap, models, modelID, geometry.applicationName == "mixamo.com");
// compute the mesh extents from the transformed vertices // compute the mesh extents from the transformed vertices
foreach (const glm::vec3& vertex, extracted.mesh.vertices) { foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
@ -2374,14 +2030,19 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
} }
// look for textures, material properties // look for textures, material properties
// allocate the Part material library
int materialIndex = 0; int materialIndex = 0;
int textureIndex = 0; int textureIndex = 0;
bool generateTangents = false; bool generateTangents = false;
QList<QString> children = childMap.values(modelID); QList<QString> children = _connectionChildMap.values(modelID);
for (int i = children.size() - 1; i >= 0; i--) { for (int i = children.size() - 1; i >= 0; i--) {
const QString& childID = children.at(i); const QString& childID = children.at(i);
if (materials.contains(childID)) { if (_fbxMaterials.contains(childID)) {
Material material = materials.value(childID); // the pure material associated with this part
FBXMaterial material = _fbxMaterials.value(childID);
bool detectDifferentUVs = false; bool detectDifferentUVs = false;
FBXTexture diffuseTexture; FBXTexture diffuseTexture;
QString diffuseTextureID = diffuseTextures.value(childID); QString diffuseTextureID = diffuseTextures.value(childID);
@ -2389,7 +2050,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams); diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams);
// 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, _connectionChildMap.values(diffuseTextureID)) {
if (textureFilenames.contains(childTextureID)) { if (textureFilenames.contains(childTextureID)) {
diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams); diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams);
} }
@ -2420,12 +2081,12 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
FBXTexture emissiveTexture; FBXTexture emissiveTexture;
glm::vec2 emissiveParams(0.f, 1.f); glm::vec2 emissiveParams(0.f, 1.f);
emissiveParams.x = lightmapOffset; emissiveParams.x = _lightmapOffset;
emissiveParams.y = lightmapLevel; emissiveParams.y = _lightmapLevel;
QString emissiveTextureID = emissiveTextures.value(childID); QString emissiveTextureID = emissiveTextures.value(childID);
QString ambientTextureID = ambientTextures.value(childID); QString ambientTextureID = ambientTextures.value(childID);
if (loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) { if (_loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) {
if (!emissiveTextureID.isNull()) { if (!emissiveTextureID.isNull()) {
emissiveTexture = getTexture(emissiveTextureID, textureNames, textureFilenames, textureContent, textureParams); emissiveTexture = getTexture(emissiveTextureID, textureNames, textureFilenames, textureContent, textureParams);
@ -2447,10 +2108,10 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
if (extracted.partMaterialTextures.at(j).first == materialIndex) { if (extracted.partMaterialTextures.at(j).first == materialIndex) {
FBXMeshPart& part = extracted.mesh.parts[j]; FBXMeshPart& part = extracted.mesh.parts[j];
part._material = material._material; /* part._material = material._material;
part.diffuseColor = material.diffuse; part.diffuseColor = material.diffuseColor;
part.specularColor = material.specular; part.specularColor = material.specularColor;
part.emissiveColor = material.emissive; part.emissiveColor = material.emissiveColor;
part.shininess = material.shininess; part.shininess = material.shininess;
part.opacity = material.opacity; part.opacity = material.opacity;
if (!diffuseTexture.filename.isNull()) { if (!diffuseTexture.filename.isNull()) {
@ -2466,8 +2127,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
part.emissiveTexture = emissiveTexture; part.emissiveTexture = emissiveTexture;
} }
part.emissiveParams = emissiveParams; part.emissiveParams = emissiveParams;
*/
part.materialID = material.id; part.materialID = material.materialID;
} }
} }
materialIndex++; materialIndex++;
@ -2477,7 +2138,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
int partTexture = extracted.partMaterialTextures.at(j).second; int partTexture = extracted.partMaterialTextures.at(j).second;
if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) {
extracted.mesh.parts[j].diffuseTexture = texture; // extracted.mesh.parts[j].diffuseTexture = texture;
} }
} }
textureIndex++; textureIndex++;
@ -2509,8 +2170,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
// find the clusters with which the mesh is associated // find the clusters with which the mesh is associated
QVector<QString> clusterIDs; QVector<QString> clusterIDs;
foreach (const QString& childID, childMap.values(it.key())) { foreach (const QString& childID, _connectionChildMap.values(it.key())) {
foreach (const QString& clusterID, childMap.values(childID)) { foreach (const QString& clusterID, _connectionChildMap.values(childID)) {
if (!clusters.contains(clusterID)) { if (!clusters.contains(clusterID)) {
continue; continue;
} }
@ -2520,7 +2181,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
// see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion
// of skinning information in FBX // of skinning information in FBX
QString jointID = childMap.value(clusterID); QString jointID = _connectionChildMap.value(clusterID);
fbxCluster.jointIndex = modelIDs.indexOf(jointID); fbxCluster.jointIndex = modelIDs.indexOf(jointID);
if (fbxCluster.jointIndex == -1) { if (fbxCluster.jointIndex == -1) {
qCDebug(modelformat) << "Joint not in model list: " << jointID; qCDebug(modelformat) << "Joint not in model list: " << jointID;
@ -2773,5 +2434,113 @@ FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const
} }
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel); FBXReader reader;
reader._fbxNode = FBXReader::parseFBX(device);
reader._loadLightmaps = loadLightmaps;
reader._lightmapLevel = lightmapLevel;
return reader.extractFBXGeometry(mapping, url);
}
bool FBXMaterial::needTangentSpace() const {
return !normalTexture.isNull();
}
void FBXReader::consolidateFBXMaterials() {
// foreach (const QString& materialID, materials) {
for (QHash<QString, FBXMaterial>::iterator it = _fbxMaterials.begin(); it != _fbxMaterials.end(); it++) {
FBXMaterial& material = (*it);
// the pure material associated with this part
bool detectDifferentUVs = false;
FBXTexture diffuseTexture;
QString diffuseTextureID = diffuseTextures.value(material.materialID);
if (!diffuseTextureID.isNull()) {
diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach (const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) {
if (textureFilenames.contains(childTextureID)) {
diffuseTexture = getTexture(diffuseTextureID, textureNames, textureFilenames, textureContent, textureParams);
}
}
// TODO associate this per part
//diffuseTexture.texcoordSet = matchTextureUVSetToAttributeChannel(diffuseTexture.texcoordSetName, extracted.texcoordSetMap);
material.diffuseTexture = diffuseTexture;
detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity());
}
FBXTexture normalTexture;
QString bumpTextureID = bumpTextures.value(material.materialID);
if (!bumpTextureID.isNull()) {
normalTexture = getTexture(bumpTextureID, textureNames, textureFilenames, textureContent, textureParams);
// TODO Need to generate tangent space at association per part
//generateTangents = true;
// TODO at per part association time
// normalTexture.texcoordSet = matchTextureUVSetToAttributeChannel(normalTexture.texcoordSetName, extracted.texcoordSetMap);
material.normalTexture = normalTexture;
detectDifferentUVs |= (normalTexture.texcoordSet != 0) || (!normalTexture.transform.isIdentity());
}
FBXTexture specularTexture;
QString specularTextureID = specularTextures.value(material.materialID);
if (!specularTextureID.isNull()) {
specularTexture = getTexture(specularTextureID, textureNames, textureFilenames, textureContent, textureParams);
// TODO at per part association time
// specularTexture.texcoordSet = matchTextureUVSetToAttributeChannel(specularTexture.texcoordSetName, extracted.texcoordSetMap);
detectDifferentUVs |= (specularTexture.texcoordSet != 0) || (!specularTexture.transform.isIdentity());
}
FBXTexture emissiveTexture;
glm::vec2 emissiveParams(0.f, 1.f);
emissiveParams.x = _lightmapOffset;
emissiveParams.y = _lightmapLevel;
QString emissiveTextureID = emissiveTextures.value(material.materialID);
QString ambientTextureID = ambientTextures.value(material.materialID);
if (_loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) {
if (!emissiveTextureID.isNull()) {
emissiveTexture = getTexture(emissiveTextureID, textureNames, textureFilenames, textureContent, textureParams);
emissiveParams.y = 4.0f;
} else if (!ambientTextureID.isNull()) {
emissiveTexture = getTexture(ambientTextureID, textureNames, textureFilenames, textureContent, textureParams);
}
// TODO : do this at per part association
//emissiveTexture.texcoordSet = matchTextureUVSetToAttributeChannel(emissiveTexture.texcoordSetName, extracted.texcoordSetMap);
material.emissiveParams = emissiveParams;
material.emissiveTexture = emissiveTexture;
detectDifferentUVs |= (emissiveTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity());
}
// Finally create the true material representation
material._material = make_shared<model::Material>();
material._material->setEmissive(material.emissiveColor);
if (glm::all(glm::equal(material.diffuseColor, glm::vec3(0.0f)))) {
material._material->setDiffuse(material.diffuseColor);
} else {
material._material->setDiffuse(material.diffuseColor);
}
material._material->setMetallic(glm::length(material.specularColor));
material._material->setGloss(material.shininess);
if (material.opacity <= 0.0f) {
material._material->setOpacity(1.0f);
} else {
material._material->setOpacity(material.opacity);
}
}
} }

View file

@ -28,6 +28,7 @@
#include <model/Geometry.h> #include <model/Geometry.h>
#include <model/Material.h> #include <model/Material.h>
#include <model/Asset.h>
class QIODevice; class QIODevice;
class FBXNode; class FBXNode;
@ -89,6 +90,15 @@ public:
glm::mat4 inverseBindMatrix; glm::mat4 inverseBindMatrix;
}; };
// The true texture image which can be used for different textures
class FBXTextureImage {
public:
QString name;
QByteArray filename;
QByteArray content;
};
/// A texture map in an FBX document. /// A texture map in an FBX document.
class FBXTexture { class FBXTexture {
public: public:
@ -99,6 +109,8 @@ public:
Transform transform; Transform transform;
int texcoordSet; int texcoordSet;
QString texcoordSetName; QString texcoordSetName;
bool isNull() const { return name.isEmpty() && filename.isEmpty() && content.isEmpty(); }
}; };
/// A single part of a mesh (with the same material). /// A single part of a mesh (with the same material).
@ -109,7 +121,7 @@ public:
QVector<int> triangleIndices; // original indices from the FBX mesh QVector<int> triangleIndices; // original indices from the FBX mesh
mutable gpu::BufferPointer quadsAsTrianglesIndicesBuffer; mutable gpu::BufferPointer quadsAsTrianglesIndicesBuffer;
glm::vec3 diffuseColor; /* glm::vec3 diffuseColor;
glm::vec3 specularColor; glm::vec3 specularColor;
glm::vec3 emissiveColor; glm::vec3 emissiveColor;
glm::vec2 emissiveParams; glm::vec2 emissiveParams;
@ -120,15 +132,38 @@ public:
FBXTexture normalTexture; FBXTexture normalTexture;
FBXTexture specularTexture; FBXTexture specularTexture;
FBXTexture emissiveTexture; FBXTexture emissiveTexture;
*/
QString materialID; QString materialID;
model::MaterialPointer _material; // model::MaterialPointer _material;
mutable bool trianglesForQuadsAvailable = false; mutable bool trianglesForQuadsAvailable = false;
mutable int trianglesForQuadsIndicesCount = 0; mutable int trianglesForQuadsIndicesCount = 0;
gpu::BufferPointer getTrianglesForQuads() const; gpu::BufferPointer getTrianglesForQuads() const;
}; };
class FBXMaterial {
public:
glm::vec3 diffuseColor;
glm::vec3 specularColor;
glm::vec3 emissiveColor;
glm::vec2 emissiveParams;
float shininess;
float opacity;
QString materialID;
model::MaterialTable::ID _modelMaterialID;
model::MaterialPointer _material;
FBXTexture diffuseTexture;
FBXTexture opacityTexture;
FBXTexture normalTexture;
FBXTexture specularTexture;
FBXTexture emissiveTexture;
bool needTangentSpace() const;
};
/// A single mesh (with optional blendshapes) extracted from an FBX document. /// A single mesh (with optional blendshapes) extracted from an FBX document.
class FBXMesh { class FBXMesh {
public: public:
@ -220,7 +255,7 @@ public:
bool hasSkeletonJoints; bool hasSkeletonJoints;
QVector<FBXMesh> meshes; QVector<FBXMesh> meshes;
glm::mat4 offset; glm::mat4 offset;
int leftEyeJointIndex = -1; int leftEyeJointIndex = -1;
@ -266,6 +301,8 @@ public:
QString getModelNameOfMesh(int meshIndex) const; QString getModelNameOfMesh(int meshIndex) const;
QList<QString> blendshapeChannelNames; QList<QString> blendshapeChannelNames;
model::AssetPointer _asset;
}; };
Q_DECLARE_METATYPE(FBXGeometry) Q_DECLARE_METATYPE(FBXGeometry)
@ -278,4 +315,80 @@ FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const
/// \exception QString if an error occurs in parsing /// \exception QString if an error occurs in parsing
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
struct TextureParam {
glm::vec2 UVTranslation;
glm::vec2 UVScaling;
glm::vec4 cropping;
QString UVSet;
glm::vec3 translation;
glm::vec3 rotation;
glm::vec3 scaling;
uint8_t alphaSource;
uint8_t currentTextureBlendMode;
bool useMaterial;
template <typename T>
bool assign(T& ref, const T& v) {
if (ref == v) {
return false;
} else {
ref = v;
isDefault = false;
return true;
}
}
bool isDefault;
TextureParam() :
UVTranslation(0.0f),
UVScaling(1.0f),
cropping(0.0f),
UVSet("map1"),
translation(0.0f),
rotation(0.0f),
scaling(1.0f),
alphaSource(0),
currentTextureBlendMode(0),
useMaterial(true),
isDefault(true)
{}
};
class FBXReader {
public:
FBXGeometry* _fbxGeometry;
FBXNode _fbxNode;
static FBXNode parseFBX(QIODevice* device);
FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url);
QHash<QString, FBXTextureImage> _textureImages;
QHash<QString, QString> textureNames;
QHash<QString, QByteArray> textureFilenames;
QHash<QByteArray, QByteArray> textureContent;
QHash<QString, TextureParam> textureParams;
QHash<QString, QString> diffuseTextures;
QHash<QString, QString> bumpTextures;
QHash<QString, QString> specularTextures;
QHash<QString, QString> emissiveTextures;
QHash<QString, QString> ambientTextures;
QHash<QString, FBXMaterial> _fbxMaterials;
void consolidateFBXMaterials();
bool _loadLightmaps = true;
float _lightmapOffset = 0.0f;
float _lightmapLevel;
QMultiHash<QString, QString> _connectionParentMap;
QMultiHash<QString, QString> _connectionChildMap;
};
#endif // hifi_FBXReader_h #endif // hifi_FBXReader_h

View file

@ -0,0 +1,342 @@
//
// FBXReader.cpp
// interface/src/renderer
//
// Created by Andrzej Kapolka on 9/18/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <iostream>
#include <QBuffer>
#include <QDataStream>
#include <QIODevice>
#include <QStringList>
#include <QTextStream>
#include <QtDebug>
#include <QtEndian>
#include <QFileInfo>
#include "FBXReader.h"
template<class T> int streamSize() {
return sizeof(T);
}
template<bool> int streamSize() {
return 1;
}
template<class T> QVariant readBinaryArray(QDataStream& in, int& position) {
quint32 arrayLength;
quint32 encoding;
quint32 compressedLength;
in >> arrayLength;
in >> encoding;
in >> compressedLength;
position += sizeof(quint32) * 3;
QVector<T> values;
const unsigned int DEFLATE_ENCODING = 1;
if (encoding == DEFLATE_ENCODING) {
// preface encoded data with uncompressed length
QByteArray compressed(sizeof(quint32) + compressedLength, 0);
*((quint32*)compressed.data()) = qToBigEndian<quint32>(arrayLength * sizeof(T));
in.readRawData(compressed.data() + sizeof(quint32), compressedLength);
position += compressedLength;
QByteArray uncompressed = qUncompress(compressed);
QDataStream uncompressedIn(uncompressed);
uncompressedIn.setByteOrder(QDataStream::LittleEndian);
uncompressedIn.setVersion(QDataStream::Qt_4_5); // for single/double precision switch
for (quint32 i = 0; i < arrayLength; i++) {
T value;
uncompressedIn >> value;
values.append(value);
}
} else {
for (quint32 i = 0; i < arrayLength; i++) {
T value;
in >> value;
position += streamSize<T>();
values.append(value);
}
}
return QVariant::fromValue(values);
}
QVariant parseBinaryFBXProperty(QDataStream& in, int& position) {
char ch;
in.device()->getChar(&ch);
position++;
switch (ch) {
case 'Y': {
qint16 value;
in >> value;
position += sizeof(qint16);
return QVariant::fromValue(value);
}
case 'C': {
bool value;
in >> value;
position++;
return QVariant::fromValue(value);
}
case 'I': {
qint32 value;
in >> value;
position += sizeof(qint32);
return QVariant::fromValue(value);
}
case 'F': {
float value;
in >> value;
position += sizeof(float);
return QVariant::fromValue(value);
}
case 'D': {
double value;
in >> value;
position += sizeof(double);
return QVariant::fromValue(value);
}
case 'L': {
qint64 value;
in >> value;
position += sizeof(qint64);
return QVariant::fromValue(value);
}
case 'f': {
return readBinaryArray<float>(in, position);
}
case 'd': {
return readBinaryArray<double>(in, position);
}
case 'l': {
return readBinaryArray<qint64>(in, position);
}
case 'i': {
return readBinaryArray<qint32>(in, position);
}
case 'b': {
return readBinaryArray<bool>(in, position);
}
case 'S':
case 'R': {
quint32 length;
in >> length;
position += sizeof(quint32) + length;
return QVariant::fromValue(in.device()->read(length));
}
default:
throw QString("Unknown property type: ") + ch;
}
}
FBXNode parseBinaryFBXNode(QDataStream& in, int& position) {
qint32 endOffset;
quint32 propertyCount;
quint32 propertyListLength;
quint8 nameLength;
in >> endOffset;
in >> propertyCount;
in >> propertyListLength;
in >> nameLength;
position += sizeof(quint32) * 3 + sizeof(quint8);
FBXNode node;
const int MIN_VALID_OFFSET = 40;
if (endOffset < MIN_VALID_OFFSET || nameLength == 0) {
// use a null name to indicate a null node
return node;
}
node.name = in.device()->read(nameLength);
position += nameLength;
for (quint32 i = 0; i < propertyCount; i++) {
node.properties.append(parseBinaryFBXProperty(in, position));
}
while (endOffset > position) {
FBXNode child = parseBinaryFBXNode(in, position);
if (child.name.isNull()) {
return node;
} else {
node.children.append(child);
}
}
return node;
}
class Tokenizer {
public:
Tokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { }
enum SpecialToken {
NO_TOKEN = -1,
NO_PUSHBACKED_TOKEN = -1,
DATUM_TOKEN = 0x100
};
int nextToken();
const QByteArray& getDatum() const { return _datum; }
void pushBackToken(int token) { _pushedBackToken = token; }
void ungetChar(char ch) { _device->ungetChar(ch); }
private:
QIODevice* _device;
QByteArray _datum;
int _pushedBackToken;
};
int Tokenizer::nextToken() {
if (_pushedBackToken != NO_PUSHBACKED_TOKEN) {
int token = _pushedBackToken;
_pushedBackToken = NO_PUSHBACKED_TOKEN;
return token;
}
char ch;
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace()) {
continue; // skip whitespace
}
switch (ch) {
case ';':
_device->readLine(); // skip the comment
break;
case ':':
case '{':
case '}':
case ',':
return ch; // special punctuation
case '\"':
_datum = "";
while (_device->getChar(&ch)) {
if (ch == '\"') { // end on closing quote
break;
}
if (ch == '\\') { // handle escaped quotes
if (_device->getChar(&ch) && ch != '\"') {
_datum.append('\\');
}
}
_datum.append(ch);
}
return DATUM_TOKEN;
default:
_datum = "";
_datum.append(ch);
while (_device->getChar(&ch)) {
if (QChar(ch).isSpace() || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == ',' || ch == '\"') {
ungetChar(ch); // read until we encounter a special character, then replace it
break;
}
_datum.append(ch);
}
return DATUM_TOKEN;
}
}
return NO_TOKEN;
}
FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
FBXNode node;
if (tokenizer.nextToken() != Tokenizer::DATUM_TOKEN) {
return node;
}
node.name = tokenizer.getDatum();
if (tokenizer.nextToken() != ':') {
return node;
}
int token;
bool expectingDatum = true;
while ((token = tokenizer.nextToken()) != Tokenizer::NO_TOKEN) {
if (token == '{') {
for (FBXNode child = parseTextFBXNode(tokenizer); !child.name.isNull(); child = parseTextFBXNode(tokenizer)) {
node.children.append(child);
}
return node;
}
if (token == ',') {
expectingDatum = true;
} else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) {
QByteArray datum = tokenizer.getDatum();
if ((token = tokenizer.nextToken()) == ':') {
tokenizer.ungetChar(':');
tokenizer.pushBackToken(Tokenizer::DATUM_TOKEN);
return node;
} else {
tokenizer.pushBackToken(token);
node.properties.append(datum);
expectingDatum = false;
}
} else {
tokenizer.pushBackToken(token);
return node;
}
}
return node;
}
FBXNode FBXReader::parseFBX(QIODevice* device) {
// verify the prolog
const QByteArray BINARY_PROLOG = "Kaydara FBX Binary ";
if (device->peek(BINARY_PROLOG.size()) != BINARY_PROLOG) {
// parse as a text file
FBXNode top;
Tokenizer tokenizer(device);
while (device->bytesAvailable()) {
FBXNode next = parseTextFBXNode(tokenizer);
if (next.name.isNull()) {
return top;
} else {
top.children.append(next);
}
}
return top;
}
QDataStream in(device);
in.setByteOrder(QDataStream::LittleEndian);
in.setVersion(QDataStream::Qt_4_5); // for single/double precision switch
// see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation
// of the FBX binary format
// skip the rest of the header
const int HEADER_SIZE = 27;
in.skipRawData(HEADER_SIZE);
int position = HEADER_SIZE;
// parse the top-level node
FBXNode top;
while (device->bytesAvailable()) {
FBXNode next = parseBinaryFBXNode(in, position);
if (next.name.isNull()) {
return top;
} else {
top.children.append(next);
}
}
return top;
}

View file

@ -121,21 +121,21 @@ glm::vec2 OBJTokenizer::getVec2() {
void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID) { void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID) {
meshPart.diffuseColor = glm::vec3(1, 1, 1); /* meshPart.diffuseColor = glm::vec3(1, 1, 1);
meshPart.specularColor = glm::vec3(1, 1, 1); meshPart.specularColor = glm::vec3(1, 1, 1);
meshPart.emissiveColor = glm::vec3(0, 0, 0); meshPart.emissiveColor = glm::vec3(0, 0, 0);
meshPart.emissiveParams = glm::vec2(0, 1); meshPart.emissiveParams = glm::vec2(0, 1);
meshPart.shininess = 40; meshPart.shininess = 40;
meshPart.opacity = 1; meshPart.opacity = 1;*/
meshPart.materialID = materialID; meshPart.materialID = materialID;
meshPart.opacity = 1.0; /* meshPart.opacity = 1.0;
meshPart._material = std::make_shared<model::Material>(); meshPart._material = std::make_shared<model::Material>();
meshPart._material->setDiffuse(glm::vec3(1.0, 1.0, 1.0)); meshPart._material->setDiffuse(glm::vec3(1.0, 1.0, 1.0));
meshPart._material->setOpacity(1.0); meshPart._material->setOpacity(1.0);
meshPart._material->setMetallic(0.0); meshPart._material->setMetallic(0.0);
meshPart._material->setGloss(96.0); meshPart._material->setGloss(96.0);
meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0)); meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0));*/
} }
// OBJFace // OBJFace
@ -486,7 +486,10 @@ FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping,
} }
if (!groupMaterialName.isEmpty()) { if (!groupMaterialName.isEmpty()) {
OBJMaterial* material = &materials[groupMaterialName]; OBJMaterial* material = &materials[groupMaterialName];
// The code behind this is in transition. Some things are set directly in the FXBMeshPart...
// TODO Fix this once the transision is understood
/*// The code behind this is in transition. Some things are set directly in the FXBMeshPart...
meshPart.materialID = groupMaterialName; meshPart.materialID = groupMaterialName;
meshPart.diffuseTexture.filename = material->diffuseTextureFilename; meshPart.diffuseTexture.filename = material->diffuseTextureFilename;
meshPart.specularTexture.filename = material->specularTextureFilename; meshPart.specularTexture.filename = material->specularTextureFilename;
@ -495,6 +498,7 @@ FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping,
meshPart._material->setMetallic(glm::length(material->specularColor)); meshPart._material->setMetallic(glm::length(material->specularColor));
meshPart._material->setGloss(material->shininess); meshPart._material->setGloss(material->shininess);
meshPart._material->setOpacity(material->opacity); meshPart._material->setOpacity(material->opacity);
*/
} }
// qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count(); // qCDebug(modelformat) << "OBJ Reader part:" << meshPartCount << "name:" << leadFace.groupName << "material:" << groupMaterialName << "diffuse:" << meshPart._material->getDiffuse() << "faces:" << faceGroup.count() << "triangle indices will start with:" << mesh.vertices.count();
foreach(OBJFace face, faceGroup) { foreach(OBJFace face, faceGroup) {
@ -576,15 +580,18 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) {
foreach (FBXMeshPart meshPart, mesh.parts) { foreach (FBXMeshPart meshPart, mesh.parts) {
qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count();
qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count();
/*
qCDebug(modelformat) << " diffuseColor =" << meshPart.diffuseColor << "mat =" << meshPart._material->getDiffuse(); qCDebug(modelformat) << " diffuseColor =" << meshPart.diffuseColor << "mat =" << meshPart._material->getDiffuse();
qCDebug(modelformat) << " specularColor =" << meshPart.specularColor << "mat =" << meshPart._material->getMetallic(); qCDebug(modelformat) << " specularColor =" << meshPart.specularColor << "mat =" << meshPart._material->getMetallic();
qCDebug(modelformat) << " emissiveColor =" << meshPart.emissiveColor << "mat =" << meshPart._material->getEmissive(); qCDebug(modelformat) << " emissiveColor =" << meshPart.emissiveColor << "mat =" << meshPart._material->getEmissive();
qCDebug(modelformat) << " emissiveParams =" << meshPart.emissiveParams; qCDebug(modelformat) << " emissiveParams =" << meshPart.emissiveParams;
qCDebug(modelformat) << " gloss =" << meshPart.shininess << "mat =" << meshPart._material->getGloss(); qCDebug(modelformat) << " gloss =" << meshPart.shininess << "mat =" << meshPart._material->getGloss();
qCDebug(modelformat) << " opacity =" << meshPart.opacity << "mat =" << meshPart._material->getOpacity(); qCDebug(modelformat) << " opacity =" << meshPart.opacity << "mat =" << meshPart._material->getOpacity();
*/
qCDebug(modelformat) << " materialID =" << meshPart.materialID; qCDebug(modelformat) << " materialID =" << meshPart.materialID;
qCDebug(modelformat) << " diffuse texture =" << meshPart.diffuseTexture.filename; /* qCDebug(modelformat) << " diffuse texture =" << meshPart.diffuseTexture.filename;
qCDebug(modelformat) << " specular texture =" << meshPart.specularTexture.filename; qCDebug(modelformat) << " specular texture =" << meshPart.specularTexture.filename;
*/
} }
qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count();
foreach (FBXCluster cluster, mesh.clusters) { foreach (FBXCluster cluster, mesh.clusters) {

View file

@ -25,14 +25,29 @@ public:
typedef std::vector< T > Vector; typedef std::vector< T > Vector;
typedef int ID; typedef int ID;
const ID INVALID_ID = 0; static const ID INVALID_ID = 0;
typedef size_t Index;
enum Version { enum Version {
DRAFT = 0, DRAFT = 0,
FINAL, FINAL,
NUM_VERSIONS, NUM_VERSIONS,
}; };
static Version evalVersionFromID(ID id) {
if (ID <= 0) {
return DRAFT;
} else (ID > 0) {
return FINAL;
}
}
static Index evalIndexFromID(ID id) {
return Index(id < 0 ? -id : id) - 1;
}
static ID evalID(Index index, Version version) {
return (version == DRAFT ? -int(index + 1) : int(index + 1));
}
Table() { Table() {
for (auto e : _elements) { for (auto e : _elements) {
e.resize(0); e.resize(0);
@ -40,29 +55,54 @@ public:
} }
~Table() {} ~Table() {}
ID add(const T& element, Version v = FINAL) { Index getNumElements() const {
switch (v) { return _elements[DRAFT].size();
case DRAFT: { }
_elements[DRAFT].push_back(element);
return ID(-(_elements[DRAFT].size() - 1)); ID add(const T& element) {
break; for (auto e : _elements) {
e.push_back(element);
} }
case FINAL: { return evalID(_elements[DRAFT].size(), DRAFT);
_elements[FINAL].push_back(element); }
return ID(_elements[FINAL].size() - 1);
break; void set(ID id, const T& element) {
Index index = evalIndexFromID(id);
if (index < getNumElements()) {
_elements[DRAFT][index] = element;
} }
}
const T& get(ID id, const T& element) const {
Index index = evalIndexFromID(id);
if (index < getNumElements()) {
return _elements[DRAFT][index];
} }
return INVALID_ID; return _default;
} }
protected: protected:
Vector _elements[NUM_VERSIONS]; Vector _elements[NUM_VERSIONS];
T _default;
}; };
typedef Table< MaterialPointer > MaterialTable; typedef Table< MaterialPointer > MaterialTable;
typedef Table< TextureChannelPointer > TextureChannelTable;
typedef Table< MeshPointer > MeshTable; typedef Table< MeshPointer > MeshTable;
class Shape {
public:
MeshTable::ID _meshID{ MeshTable::INVALID_ID };
int _partID = 0;
MaterialTable::ID _materialID{ MaterialTable::INVALID_ID };
};
typedef Table< Shape > ShapeTable;
class Asset { class Asset {
public: public:
@ -76,10 +116,14 @@ public:
MaterialTable& editMaterials() { return _materials; } MaterialTable& editMaterials() { return _materials; }
const MaterialTable& getMaterials() const { return _materials; } const MaterialTable& getMaterials() const { return _materials; }
ShapeTable& editShapes() { return _shapes; }
const ShapeTable& getShapes() const { return _shapes; }
protected: protected:
MeshTable _meshes; MeshTable _meshes;
MaterialTable _materials; MaterialTable _materials;
ShapeTable _shapes;
}; };

View file

@ -198,6 +198,15 @@ public:
}; };
}; };
class TextureChannel {
public:
TextureChannel() {}
gpu::TextureView _texture;
};
typedef std::shared_ptr< TextureChannel > TextureChannelPointer;
class Material { class Material {
public: public:
typedef gpu::BufferView UniformBufferView; typedef gpu::BufferView UniformBufferView;

View file

@ -25,20 +25,18 @@ uniform materialBuffer {
Material getMaterial() { Material getMaterial() {
return _mat; return _mat;
} }
/*
float componentSRGBToLinear(float cs) { float componentSRGBToLinear(float cs) {
/* sRGB to linear conversion // sRGB to linear conversion
{ cs / 12.92, cs <= 0.04045 // { cs / 12.92, cs <= 0.04045
cl = { // cl = {
{ ((cs + 0.055)/1.055)^2.4, cs > 0.04045 // { ((cs + 0.055)/1.055)^2.4, cs > 0.04045
// constants:
constants: // T = 0.04045
T = 0.04045 // A = 1 / 1.055 = 0.94786729857
A = 1 / 1.055 = 0.94786729857 // B = 0.055 * A = 0.05213270142
B = 0.055 * A = 0.05213270142 // C = 1 / 12.92 = 0.0773993808
C = 1 / 12.92 = 0.0773993808 // G = 2.4
G = 2.4
*/
const float T = 0.04045; const float T = 0.04045;
const float A = 0.947867; const float A = 0.947867;
const float B = 0.052132; const float B = 0.052132;
@ -55,9 +53,10 @@ float componentSRGBToLinear(float cs) {
vec3 SRGBToLinear(vec3 srgb) { vec3 SRGBToLinear(vec3 srgb) {
return vec3(componentSRGBToLinear(srgb.x),componentSRGBToLinear(srgb.y),componentSRGBToLinear(srgb.z)); return vec3(componentSRGBToLinear(srgb.x),componentSRGBToLinear(srgb.y),componentSRGBToLinear(srgb.z));
} }
vec3 getMaterialDiffuse(Material m) { return (gl_FragCoord.x < 800 ? SRGBToLinear(m._diffuse.rgb) : m._diffuse.rgb); }
*/
float getMaterialOpacity(Material m) { return m._diffuse.a; } float getMaterialOpacity(Material m) { return m._diffuse.a; }
vec3 getMaterialDiffuse(Material m) { return (gl_FragCoord.x > 800 ? SRGBToLinear(m._diffuse.rgb) : m._diffuse.rgb); } vec3 getMaterialDiffuse(Material m) { return m._diffuse.rgb; }
vec3 getMaterialSpecular(Material m) { return m._specular.rgb; } vec3 getMaterialSpecular(Material m) { return m._specular.rgb; }
float getMaterialShininess(Material m) { return m._specular.a; } float getMaterialShininess(Material m) { return m._specular.a; }

View file

@ -412,8 +412,9 @@ void Resource::handleReplyFinished() {
ResourceCache::requestCompleted(this); ResourceCache::requestCompleted(this);
finishedLoading(true); finishedLoading(true);
emit loaded(*reply);
downloadFinished(reply); downloadFinished(reply);
// Signal the VERY end of loading AND processing a resource, at this point even the specialized class is finalized
emit loaded(*reply);
} }

View file

@ -1723,7 +1723,8 @@ void GeometryReader::run() {
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
_url(url), _url(url),
_mapping(mapping), _mapping(mapping),
_textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) { _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url),
_asset() {
if (delayLoad) { if (delayLoad) {
_state = DelayState; _state = DelayState;
@ -1910,6 +1911,9 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas
int totalIndices = 0; int totalIndices = 0;
bool checkForTexcoordLightmap = false; bool checkForTexcoordLightmap = false;
// process material parts
foreach (const FBXMaterial& mat, ) {
// process network parts // process network parts
foreach (const FBXMeshPart& part, mesh.parts) { foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart* networkPart = new NetworkMeshPart(); NetworkMeshPart* networkPart = new NetworkMeshPart();
@ -2051,11 +2055,14 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas
void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) { void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) {
// assume owner ship of geometry pointer // assume owner ship of geometry pointer
_geometry.reset(geometry); _geometry.reset(geometry);
_asset = _geometry->_asset;
foreach(const FBXMesh& mesh, _geometry->meshes) { foreach(const FBXMesh& mesh, _geometry->meshes) {
_meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl)); _meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl));
} }
foreach(const FBXMaterial& material, _geometry->
_state = SuccessState; _state = SuccessState;
emit onSuccess(*this, *_geometry.get()); emit onSuccess(*this, *_geometry.get());

View file

@ -25,9 +25,12 @@
#include <gpu/Stream.h> #include <gpu/Stream.h>
#include <model/Asset.h>
class NetworkGeometry; class NetworkGeometry;
class NetworkMesh; class NetworkMesh;
class NetworkTexture; class NetworkTexture;
class NetworkMaterial;
typedef glm::vec3 Vec3Key; typedef glm::vec3 Vec3Key;
@ -329,6 +332,7 @@ public:
// WARNING: only valid when isLoaded returns true. // WARNING: only valid when isLoaded returns true.
const FBXGeometry& getFBXGeometry() const { return *_geometry; } const FBXGeometry& getFBXGeometry() const { return *_geometry; }
const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; } const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; }
const model::AssetPointer getAsset() const { return _asset; }
void setTextureWithNameToURL(const QString& name, const QUrl& url); void setTextureWithNameToURL(const QString& name, const QUrl& url);
QStringList getTextureNames() const; QStringList getTextureNames() const;
@ -357,6 +361,8 @@ protected slots:
void modelParseSuccess(FBXGeometry* geometry); void modelParseSuccess(FBXGeometry* geometry);
void modelParseError(int error, QString str); void modelParseError(int error, QString str);
void oneTextureLoaded();
protected: protected:
void attemptRequestInternal(); void attemptRequestInternal();
void requestMapping(const QUrl& url); void requestMapping(const QUrl& url);
@ -378,6 +384,9 @@ protected:
std::unique_ptr<FBXGeometry> _geometry; std::unique_ptr<FBXGeometry> _geometry;
std::vector<std::unique_ptr<NetworkMesh>> _meshes; std::vector<std::unique_ptr<NetworkMesh>> _meshes;
// The model asset created from this NetworkGeometry
model::AssetPointer _asset;
// cache for isLoadedWithTextures() // cache for isLoadedWithTextures()
mutable bool _isLoadedWithTextures = false; mutable bool _isLoadedWithTextures = false;
}; };
@ -413,6 +422,15 @@ public:
bool isTranslucent() const; bool isTranslucent() const;
}; };
class NetworkMaterial {
public:
model::MaterialTable::ID _materialID;
typedef std::map<model::MaterialKey::FlagBit, model::TextureChannelTable::ID> TextureChannelIDs;
TextureChannelIDs _textureChannelIDs;
};
/// The state associated with a single mesh. /// The state associated with a single mesh.
class NetworkMesh { class NetworkMesh {
public: public:

View file

@ -768,6 +768,16 @@ public:
QUrl url; QUrl url;
int meshIndex; int meshIndex;
int partIndex; int partIndex;
// Core definition of a Shape = transform + model/mesh/part + material
model::AssetPointer _asset;
model::ShapeTable::ID _shapeID;
Transform _transform;
model::MeshPointer _mesh;
int _part;
model::MaterialPointer _material;
}; };
namespace render { namespace render {

View file

@ -28,14 +28,12 @@ void main(void) {
// Fetch diffuse map // Fetch diffuse map
vec4 diffuse = texture(diffuseMap, _texCoord0); vec4 diffuse = texture(diffuseMap, _texCoord0);
vec3 vertexColor = (gl_FragCoord.y > 400 ? SRGBToLinear(_color.rgb) : _color.rgb);
Material mat = getMaterial(); Material mat = getMaterial();
packDeferredFragment( packDeferredFragment(
normalize(_normal.xyz), normalize(_normal.xyz),
evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a),
getMaterialDiffuse(mat) * diffuse.rgb * vertexColor, getMaterialDiffuse(mat) * diffuse.rgb * _color,
getMaterialSpecular(mat), getMaterialSpecular(mat),
getMaterialShininess(mat)); getMaterialShininess(mat));
} }

View file

@ -17,6 +17,36 @@
<$declareStandardTransform()$> <$declareStandardTransform()$>
/*
float componentSRGBToLinear(float cs) {
// sRGB to linear conversion
// { cs / 12.92, cs <= 0.04045
// cl = {
// { ((cs + 0.055)/1.055)^2.4, cs > 0.04045
// constants:
// T = 0.04045
// A = 1 / 1.055 = 0.94786729857
// B = 0.055 * A = 0.05213270142
// C = 1 / 12.92 = 0.0773993808
// G = 2.4
const float T = 0.04045;
const float A = 0.947867;
const float B = 0.052132;
const float C = 0.077399;
const float G = 2.4;
if (cs > T) {
return pow((cs * A + B), G);
} else {
return cs * C;
}
}
vec3 SRGBToLinear(vec3 srgb) {
return vec3(componentSRGBToLinear(srgb.x),componentSRGBToLinear(srgb.y),componentSRGBToLinear(srgb.z));
}
*/
const int MAX_TEXCOORDS = 2; const int MAX_TEXCOORDS = 2;
uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; uniform mat4 texcoordMatrices[MAX_TEXCOORDS];
@ -30,7 +60,9 @@ void main(void) {
// pass along the diffuse color // pass along the diffuse color
_color = inColor.xyz; _color = inColor.xyz;
// _color = SRGBToLinear(inColor.xyz);
// and the texture coordinates // and the texture coordinates
_texCoord0 = (texcoordMatrices[0] * vec4(inTexCoord0.st, 0.0, 1.0)).st; _texCoord0 = (texcoordMatrices[0] * vec4(inTexCoord0.st, 0.0, 1.0)).st;