Merge pull request #1064 from ey6es/skinny

Skin the Faceshift models so that they bend at the neck rather than rotating around.  Also, fix various material parameters and apply per-pixel lighting.
This commit is contained in:
Philip Rosedale 2013-10-16 09:22:11 -07:00
commit a40156746e
12 changed files with 616 additions and 249 deletions

View file

@ -0,0 +1,29 @@
#version 120
//
// blendface.frag
// fragment shader
//
// Created by Andrzej Kapolka on 10/14/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// the diffuse texture
uniform sampler2D texture;
// the interpolated normal
varying vec4 normal;
void main(void) {
// compute the base color based on OpenGL lighting model
vec4 normalizedNormal = normalize(normal);
vec4 base = gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position));
// compute the specular component (sans exponent)
float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), normalizedNormal));
// modulate texture by base color and add specular contribution
gl_FragColor = base * texture2D(texture, gl_TexCoord[0].st) +
pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
}

View file

@ -1,10 +1,10 @@
#version 120
//
// eye.vert
// blendface.vert
// vertex shader
//
// Created by Andrzej Kapolka on 9/25/13.
// Created by Andrzej Kapolka on 10/14/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
@ -16,10 +16,6 @@ void main(void) {
// transform and store the normal for interpolation
normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0));
// compute standard diffuse lighting per-vertex
gl_FrontColor = vec4(gl_Color.rgb * (gl_LightModel.ambient.rgb + gl_LightSource[0].ambient.rgb +
gl_LightSource[0].diffuse.rgb * max(0.0, dot(normal, gl_LightSource[0].position))), gl_Color.a);
// pass along the texture coordinate
gl_TexCoord[0] = gl_MultiTexCoord0;

View file

@ -0,0 +1,38 @@
#version 120
//
// skin_blendface.vert
// vertex shader
//
// Created by Andrzej Kapolka on 10/14/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
const int MAX_CLUSTERS = 32;
const int INDICES_PER_VERTEX = 4;
uniform mat4 clusterMatrices[MAX_CLUSTERS];
attribute vec4 clusterIndices;
attribute vec4 clusterWeights;
// the interpolated normal
varying vec4 normal;
void main(void) {
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
normal = vec4(0.0, 0.0, 0.0, 0.0);
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
mat4 clusterMatrix = clusterMatrices[int(clusterIndices[i])];
float clusterWeight = clusterWeights[i];
position += clusterMatrix * gl_Vertex * clusterWeight;
normal += clusterMatrix * vec4(gl_Normal, 0.0) * clusterWeight;
}
position = gl_ModelViewProjectionMatrix * position;
normal = normalize(gl_ModelViewMatrix * normal);
// pass along the texture coordinate
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = position;
}

View file

@ -28,18 +28,33 @@ BlendFace::~BlendFace() {
deleteGeometry();
}
ProgramObject BlendFace::_eyeProgram;
ProgramObject BlendFace::_program;
ProgramObject BlendFace::_skinProgram;
int BlendFace::_clusterMatricesLocation;
int BlendFace::_clusterIndicesLocation;
int BlendFace::_clusterWeightsLocation;
void BlendFace::init() {
if (!_eyeProgram.isLinked()) {
if (!_program.isLinked()) {
switchToResourcesParentIfRequired();
_eyeProgram.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/eye.vert");
_eyeProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/iris.frag");
_eyeProgram.link();
_program.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/blendface.vert");
_program.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/blendface.frag");
_program.link();
_eyeProgram.bind();
_eyeProgram.setUniformValue("texture", 0);
_eyeProgram.release();
_program.bind();
_program.setUniformValue("texture", 0);
_program.release();
_skinProgram.addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/skin_blendface.vert");
_skinProgram.addShaderFromSourceFile(QGLShader::Fragment, "resources/shaders/blendface.frag");
_skinProgram.link();
_skinProgram.bind();
_clusterMatricesLocation = _skinProgram.uniformLocation("clusterMatrices");
_clusterIndicesLocation = _skinProgram.attributeLocation("clusterIndices");
_clusterWeightsLocation = _skinProgram.attributeLocation("clusterWeights");
_skinProgram.setUniformValue("texture", 0);
_skinProgram.release();
}
}
@ -47,7 +62,7 @@ void BlendFace::reset() {
_resetStates = true;
}
const glm::vec3 MODEL_TRANSLATION(0.0f, -120.0f, 40.0f); // temporary fudge factor
const glm::vec3 MODEL_TRANSLATION(0.0f, -60.0f, 40.0f); // temporary fudge factor
const float MODEL_SCALE = 0.0006f;
void BlendFace::simulate(float deltaTime) {
@ -59,8 +74,14 @@ void BlendFace::simulate(float deltaTime) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (_meshStates.isEmpty()) {
QVector<glm::vec3> vertices;
foreach (const FBXJoint& joint, geometry.joints) {
JointState state;
state.rotation = joint.rotation;
_jointStates.append(state);
}
foreach (const FBXMesh& mesh, geometry.meshes) {
MeshState state;
state.clusterMatrices.resize(mesh.clusters.size());
if (mesh.springiness > 0.0f) {
state.worldSpaceVertices.resize(mesh.vertices.size());
state.vertexVelocities.resize(mesh.vertices.size());
@ -71,14 +92,52 @@ void BlendFace::simulate(float deltaTime) {
_resetStates = true;
}
glm::quat orientation = _owningHead->getOrientation();
const Skeleton& skeleton = static_cast<Avatar*>(_owningHead->_owningAvatar)->getSkeleton();
glm::quat orientation = skeleton.joint[AVATAR_JOINT_NECK_BASE].absoluteRotation;
glm::vec3 scale = glm::vec3(-1.0f, 1.0f, -1.0f) * _owningHead->getScale() * MODEL_SCALE;
glm::vec3 offset = MODEL_TRANSLATION - _geometry->getFBXGeometry().neckPivot;
glm::mat4 baseTransform = glm::translate(_owningHead->getPosition()) * glm::mat4_cast(orientation) *
glm::vec3 offset = MODEL_TRANSLATION - geometry.neckPivot;
glm::mat4 baseTransform = glm::translate(skeleton.joint[AVATAR_JOINT_NECK_BASE].position) * glm::mat4_cast(orientation) *
glm::scale(scale) * glm::translate(offset);
// update the world space transforms for all joints
for (int i = 0; i < _jointStates.size(); i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = geometry.joints.at(i);
if (joint.parentIndex == -1) {
state.transform = baseTransform * geometry.offset * joint.preRotation *
glm::mat4_cast(state.rotation) * joint.postRotation;
} else {
if (i == geometry.neckJointIndex) {
// get the rotation axes in joint space and use them to adjust the rotation
glm::mat3 axes = glm::mat3_cast(orientation);
glm::mat3 inverse = glm::inverse(glm::mat3(_jointStates[joint.parentIndex].transform *
joint.preRotation * glm::mat4_cast(joint.rotation)));
state.rotation = glm::angleAxis(_owningHead->getRoll(), glm::normalize(inverse * axes[2])) *
glm::angleAxis(_owningHead->getYaw(), glm::normalize(inverse * axes[1])) *
glm::angleAxis(_owningHead->getPitch(), glm::normalize(inverse * axes[0])) * joint.rotation;
} else if (i == geometry.leftEyeJointIndex || i == geometry.rightEyeJointIndex) {
// likewise with the lookat position
glm::mat4 inverse = glm::inverse(_jointStates[joint.parentIndex].transform *
joint.preRotation * glm::mat4_cast(joint.rotation));
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getOrientation() * IDENTITY_FRONT, 0.0f));
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
_owningHead->getSaccade(), 1.0f));
state.rotation = rotationBetween(front, lookAt) * joint.rotation;
}
state.transform = _jointStates[joint.parentIndex].transform * joint.preRotation *
glm::mat4_cast(state.rotation) * joint.postRotation;
}
}
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix;
}
int vertexCount = state.worldSpaceVertices.size();
if (vertexCount == 0) {
continue;
@ -86,7 +145,7 @@ void BlendFace::simulate(float deltaTime) {
glm::vec3* destVertices = state.worldSpaceVertices.data();
glm::vec3* destVelocities = state.vertexVelocities.data();
glm::vec3* destNormals = state.worldSpaceNormals.data();
const FBXMesh& mesh = geometry.meshes.at(i);
const glm::vec3* sourceVertices = mesh.vertices.constData();
if (!mesh.blendshapes.isEmpty()) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
@ -105,14 +164,30 @@ void BlendFace::simulate(float deltaTime) {
_blendedVertices[*index] += *vertex * coefficient;
}
}
sourceVertices = _blendedVertices.constData();
}
glm::mat4 transform = baseTransform;
if (mesh.isEye) {
transform = transform * glm::translate(mesh.pivot) * glm::mat4_cast(glm::inverse(orientation) *
_owningHead->getEyeRotation(orientation * ((mesh.pivot + offset) * scale) + _owningHead->getPosition())) *
glm::translate(-mesh.pivot);
glm::mat4 transform;
if (mesh.clusters.size() > 1) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
// skin each vertex
const glm::vec4* clusterIndices = mesh.clusterIndices.constData();
const glm::vec4* clusterWeights = mesh.clusterWeights.constData();
for (int j = 0; j < vertexCount; j++) {
_blendedVertices[j] =
glm::vec3(state.clusterMatrices[clusterIndices[j][0]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][0] +
glm::vec3(state.clusterMatrices[clusterIndices[j][1]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][1] +
glm::vec3(state.clusterMatrices[clusterIndices[j][2]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][2] +
glm::vec3(state.clusterMatrices[clusterIndices[j][3]] *
glm::vec4(sourceVertices[j], 1.0f)) * clusterWeights[j][3];
}
sourceVertices = _blendedVertices.constData();
} else {
transform = state.clusterMatrices[0];
}
if (_resetStates) {
for (int j = 0; j < vertexCount; j++) {
@ -161,39 +236,19 @@ bool BlendFace::render(float alpha) {
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
_blendedVertexBufferIDs.append(id);
QVector<QSharedPointer<Texture> > dilated;
dilated.resize(mesh.parts.size());
_dilatedTextures.append(dilated);
}
// make sure we have the right number of dilated texture pointers
_dilatedTextures.resize(geometry.meshes.size());
}
glm::mat4 viewMatrix;
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&viewMatrix);
glPushMatrix();
glTranslatef(_owningHead->getPosition().x, _owningHead->getPosition().y, _owningHead->getPosition().z);
glm::quat orientation = _owningHead->getOrientation();
glm::vec3 axis = glm::axis(orientation);
glRotatef(glm::angle(orientation), axis.x, axis.y, axis.z);
glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
-_owningHead->getScale() * MODEL_SCALE);
glScalef(scale.x, scale.y, scale.z);
glm::vec3 offset = MODEL_TRANSLATION - geometry.neckPivot;
glTranslatef(offset.x, offset.y, offset.z);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
// enable normalization under the expectation that the GPU can do it faster
glEnable(GL_NORMALIZE);
glEnable(GL_TEXTURE_2D);
glDisable(GL_COLOR_MATERIAL);
// the eye shader uses the color state even though color material is disabled
glColor4f(1.0f, 1.0f, 1.0f, alpha);
for (int i = 0; i < networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = networkMeshes.at(i);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
@ -201,39 +256,31 @@ bool BlendFace::render(float alpha) {
const FBXMesh& mesh = geometry.meshes.at(i);
int vertexCount = mesh.vertices.size();
glPushMatrix();
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
// apply eye rotation if appropriate
Texture* texture = networkMesh.diffuseTexture.data();
if (mesh.isEye) {
glTranslatef(mesh.pivot.x, mesh.pivot.y, mesh.pivot.z);
glm::quat rotation = glm::inverse(orientation) * _owningHead->getEyeRotation(orientation *
((mesh.pivot + offset) * scale) + _owningHead->getPosition());
glm::vec3 rotationAxis = glm::axis(rotation);
glRotatef(glm::angle(rotation), -rotationAxis.x, rotationAxis.y, -rotationAxis.z);
glTranslatef(-mesh.pivot.x, -mesh.pivot.y, -mesh.pivot.z);
_eyeProgram.bind();
if (texture != NULL) {
texture = (_dilatedTextures[i] = static_cast<DilatableNetworkTexture*>(texture)->getDilatedTexture(
_owningHead->getPupilDilation())).data();
const MeshState& state = _meshStates.at(i);
if (state.worldSpaceVertices.isEmpty()) {
if (state.clusterMatrices.size() > 1) {
_skinProgram.bind();
glUniformMatrix4fvARB(_clusterMatricesLocation, state.clusterMatrices.size(), false,
(const float*)state.clusterMatrices.constData());
int offset = vertexCount * sizeof(glm::vec2) + (mesh.blendshapes.isEmpty() ?
vertexCount * 2 * sizeof(glm::vec3) : 0);
_skinProgram.setAttributeBuffer(_clusterIndicesLocation, GL_FLOAT, offset, 4);
_skinProgram.setAttributeBuffer(_clusterWeightsLocation, GL_FLOAT,
offset + vertexCount * sizeof(glm::vec4), 4);
_skinProgram.enableAttributeArray(_clusterIndicesLocation);
_skinProgram.enableAttributeArray(_clusterWeightsLocation);
} else {
glPushMatrix();
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
_program.bind();
}
} else {
_program.bind();
}
// apply material properties
glm::vec4 diffuse = glm::vec4(mesh.diffuseColor, alpha);
glm::vec4 specular = glm::vec4(mesh.specularColor, alpha);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, mesh.shininess);
glMultMatrixf((const GLfloat*)&mesh.transform);
glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID());
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3)));
@ -241,10 +288,7 @@ bool BlendFace::render(float alpha) {
glTexCoordPointer(2, GL_FLOAT, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
const MeshState& state = _meshStates.at(i);
if (!state.worldSpaceVertices.isEmpty()) {
glLoadMatrixf((const GLfloat*)&viewMatrix);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
@ -281,20 +325,51 @@ bool BlendFace::render(float alpha) {
glVertexPointer(3, GL_FLOAT, 0, 0);
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, mesh.quadIndices.size(), GL_UNSIGNED_INT, 0);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, mesh.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)(mesh.quadIndices.size() * sizeof(int)));
qint64 offset = 0;
for (int j = 0; j < networkMesh.parts.size(); j++) {
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
const FBXMeshPart& part = mesh.parts.at(j);
if (mesh.isEye) {
_eyeProgram.release();
// apply material properties
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, part.shininess);
Texture* texture = networkPart.diffuseTexture.data();
if (mesh.isEye) {
if (texture != NULL) {
texture = (_dilatedTextures[i][j] = static_cast<DilatableNetworkTexture*>(texture)->getDilatedTexture(
_owningHead->getPupilDilation())).data();
}
}
glBindTexture(GL_TEXTURE_2D, texture == NULL ? Application::getInstance()->getTextureCache()->getWhiteTextureID() :
texture->getID());
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
offset += part.quadIndices.size() * sizeof(int);
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)offset);
offset += part.triangleIndices.size() * sizeof(int);
}
glPopMatrix();
if (state.worldSpaceVertices.isEmpty()) {
if (state.clusterMatrices.size() > 1) {
_skinProgram.disableAttributeArray(_clusterIndicesLocation);
_skinProgram.disableAttributeArray(_clusterWeightsLocation);
_skinProgram.release();
} else {
glPopMatrix();
_program.release();
}
} else {
_program.release();
}
}
glDisable(GL_NORMALIZE);
glDisable(GL_TEXTURE_2D);
// deactivate vertex arrays after drawing
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
@ -305,8 +380,6 @@ bool BlendFace::render(float alpha) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glPopMatrix();
// restore all the default material settings
Application::getInstance()->setupWorldLight(*Application::getInstance()->getCamera());
@ -314,32 +387,19 @@ bool BlendFace::render(float alpha) {
}
bool BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright) const {
if (!isActive()) {
if (!isActive() || _jointStates.isEmpty()) {
return false;
}
glm::vec3 translation = _owningHead->getPosition();
glm::quat orientation = _owningHead->getOrientation();
if (upright) {
translation = static_cast<MyAvatar*>(_owningHead->_owningAvatar)->getUprightHeadPosition();
orientation = static_cast<Avatar*>(_owningHead->_owningAvatar)->getWorldAlignedOrientation();
}
glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
-_owningHead->getScale() * MODEL_SCALE);
bool foundFirst = false;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
foreach (const FBXMesh& mesh, geometry.meshes) {
if (mesh.isEye) {
glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) + translation;
if (foundFirst) {
secondEyePosition = position;
return true;
}
firstEyePosition = position;
foundFirst = true;
}
if (geometry.leftEyeJointIndex != -1) {
const glm::mat4& transform = _jointStates[geometry.leftEyeJointIndex].transform;
firstEyePosition = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
}
return false;
if (geometry.rightEyeJointIndex != -1) {
const glm::mat4& transform = _jointStates[geometry.rightEyeJointIndex].transform;
secondEyePosition = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
}
return geometry.leftEyeJointIndex != -1 && geometry.rightEyeJointIndex != -1;
}
glm::vec4 BlendFace::computeAverageColor() const {
@ -365,5 +425,6 @@ void BlendFace::deleteGeometry() {
glDeleteBuffers(1, &id);
}
_blendedVertexBufferIDs.clear();
_jointStates.clear();
_meshStates.clear();
}

View file

@ -58,8 +58,17 @@ private:
QSharedPointer<NetworkGeometry> _geometry;
class JointState {
public:
glm::quat rotation;
glm::mat4 transform;
};
QVector<JointState> _jointStates;
class MeshState {
public:
QVector<glm::mat4> clusterMatrices;
QVector<glm::vec3> worldSpaceVertices;
QVector<glm::vec3> vertexVelocities;
QVector<glm::vec3> worldSpaceNormals;
@ -67,13 +76,17 @@ private:
QVector<MeshState> _meshStates;
QVector<GLuint> _blendedVertexBufferIDs;
QVector<QSharedPointer<Texture> > _dilatedTextures;
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
bool _resetStates;
QVector<glm::vec3> _blendedVertices;
QVector<glm::vec3> _blendedNormals;
static ProgramObject _eyeProgram;
static ProgramObject _program;
static ProgramObject _skinProgram;
static int _clusterMatricesLocation;
static int _clusterIndicesLocation;
static int _clusterWeightsLocation;
};
#endif /* defined(__interface__BlendFace__) */

View file

@ -69,6 +69,7 @@ public:
glm::vec3 getPosition() const { return _position; }
const glm::vec3& getSkinColor() const { return _skinColor; }
const glm::vec3& getEyePosition() const { return _eyePosition; }
const glm::vec3& getSaccade() const { return _saccade; }
glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; }
glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }

View file

@ -298,28 +298,27 @@ const char* FACESHIFT_BLENDSHAPES[] = {
""
};
class Transform {
class Model {
public:
QByteArray name;
bool inheritScale;
glm::mat4 withScale;
glm::mat4 withoutScale;
glm::mat4 preRotation;
glm::quat rotation;
glm::mat4 postRotation;
int parentIndex;
};
glm::mat4 getGlobalTransform(const QMultiHash<qint64, qint64>& parentMap, const QHash<qint64, Transform>& localTransforms,
qint64 nodeID, bool forceScale = true) {
glm::mat4 getGlobalTransform(const QMultiHash<qint64, qint64>& parentMap, const QHash<qint64, Model>& models, qint64 nodeID) {
glm::mat4 globalTransform;
bool useScale = true;
while (nodeID != 0) {
const Transform& localTransform = localTransforms.value(nodeID);
globalTransform = (useScale ? localTransform.withScale : localTransform.withoutScale) * globalTransform;
useScale = (useScale && localTransform.inheritScale) || forceScale;
const Model& model = models.value(nodeID);
globalTransform = model.preRotation * glm::mat4_cast(model.rotation) * model.postRotation * globalTransform;
QList<qint64> parentIDs = parentMap.values(nodeID);
nodeID = 0;
foreach (qint64 parentID, parentIDs) {
if (localTransforms.contains(parentID)) {
if (models.contains(parentID)) {
nodeID = parentID;
break;
}
@ -354,13 +353,34 @@ public:
float shininess;
};
class Cluster {
public:
QVector<int> indices;
QVector<double> weights;
glm::mat4 transformLink;
};
void appendModelIDs(qint64 parentID, const QMultiHash<qint64, qint64>& childMap,
QHash<qint64, Model>& models, QVector<qint64>& modelIDs) {
if (parentID != 0) {
modelIDs.append(parentID);
}
int parentIndex = modelIDs.size() - 1;
foreach (qint64 childID, childMap.values(parentID)) {
if (models.contains(childID)) {
models[childID].parentIndex = parentIndex;
appendModelIDs(childID, childMap, models, modelIDs);
}
}
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash<qint64, FBXMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
QMultiHash<qint64, qint64> parentMap;
QMultiHash<qint64, qint64> childMap;
QHash<qint64, Transform> localTransforms;
QHash<qint64, glm::mat4> transformLinkMatrices;
QHash<qint64, Model> models;
QHash<qint64, Cluster> clusters;
QHash<qint64, QByteArray> textureFilenames;
QHash<qint64, Material> materials;
QHash<qint64, qint64> diffuseTextures;
@ -406,6 +426,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QVector<int> normalIndices;
QVector<glm::vec2> texCoords;
QVector<int> texCoordIndices;
QVector<int> materials;
foreach (const FBXNode& data, object.children) {
if (data.name == "Vertices") {
mesh.vertices = createVec3Vector(data.properties.at(0).value<QVector<double> >());
@ -439,6 +460,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
texCoordIndices = subdata.properties.at(0).value<QVector<int> >();
}
}
} else if (data.name == "LayerElementMaterial") {
foreach (const FBXNode& subdata, data.children) {
if (subdata.name == "Materials") {
materials = subdata.properties.at(0).value<QVector<int> >();
}
}
}
}
@ -474,25 +501,30 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
// convert the polygons to quads and triangles
int polygonIndex = 0;
for (const int* beginIndex = polygonIndices.constData(), *end = beginIndex + polygonIndices.size();
beginIndex != end; ) {
beginIndex != end; polygonIndex++) {
const int* endIndex = beginIndex;
while (*endIndex++ >= 0);
int materialIndex = (polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0;
mesh.parts.resize(max(mesh.parts.size(), materialIndex + 1));
FBXMeshPart& part = mesh.parts[materialIndex];
if (endIndex - beginIndex == 4) {
mesh.quadIndices.append(*beginIndex++);
mesh.quadIndices.append(*beginIndex++);
mesh.quadIndices.append(*beginIndex++);
mesh.quadIndices.append(-*beginIndex++ - 1);
part.quadIndices.append(*beginIndex++);
part.quadIndices.append(*beginIndex++);
part.quadIndices.append(*beginIndex++);
part.quadIndices.append(-*beginIndex++ - 1);
} else {
for (const int* nextIndex = beginIndex + 1;; ) {
mesh.triangleIndices.append(*beginIndex);
mesh.triangleIndices.append(*nextIndex++);
part.triangleIndices.append(*beginIndex);
part.triangleIndices.append(*nextIndex++);
if (*nextIndex >= 0) {
mesh.triangleIndices.append(*nextIndex);
part.triangleIndices.append(*nextIndex);
} else {
mesh.triangleIndices.append(-*nextIndex - 1);
part.triangleIndices.append(-*nextIndex - 1);
break;
}
}
@ -536,7 +568,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
glm::vec3 preRotation, rotation, postRotation;
glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f);
glm::vec3 scalePivot, rotationPivot;
Transform transform = { name, true };
Model model = { name };
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "Properties70") {
foreach (const FBXNode& property, subobject.children) {
@ -574,23 +606,19 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
} else if (property.properties.at(0) == "Lcl Scaling") {
scale = glm::vec3(property.properties.at(4).value<double>(),
property.properties.at(5).value<double>(),
property.properties.at(6).value<double>());
} else if (property.properties.at(0) == "InheritType") {
transform.inheritScale = property.properties.at(4) != 2;
property.properties.at(6).value<double>());
}
}
}
}
}
// see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html
transform.withoutScale = glm::translate(translation) * glm::translate(rotationPivot) *
glm::mat4_cast(glm::quat(glm::radians(preRotation))) *
glm::mat4_cast(glm::quat(glm::radians(rotation))) *
glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot);
transform.withScale = transform.withoutScale * glm::translate(scalePivot) * glm::scale(scale) *
glm::translate(-scalePivot);
localTransforms.insert(object.properties.at(0).value<qint64>(), transform);
model.preRotation = glm::translate(translation) * glm::translate(rotationPivot) *
glm::mat4_cast(glm::quat(glm::radians(preRotation)));
model.rotation = glm::quat(glm::radians(rotation));
model.postRotation = glm::mat4_cast(glm::quat(glm::radians(postRotation))) * glm::translate(-rotationPivot) *
glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot);
models.insert(object.properties.at(0).value<qint64>(), model);
} else if (object.name == "Texture") {
foreach (const FBXNode& subobject, object.children) {
@ -628,12 +656,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
} else if (object.name == "Deformer") {
if (object.properties.at(2) == "Cluster") {
Cluster cluster;
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "TransformLink") {
if (subobject.name == "Indexes") {
cluster.indices = subobject.properties.at(0).value<QVector<int> >();
} else if (subobject.name == "Weights") {
cluster.weights = subobject.properties.at(0).value<QVector<double> >();
} else if (subobject.name == "TransformLink") {
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
transformLinkMatrices.insert(object.properties.at(0).value<qint64>(), createMat4(values));
cluster.transformLink = createMat4(values);
}
}
clusters.insert(object.properties.at(0).value<qint64>(), cluster);
} else if (object.properties.at(2) == "BlendShapeChannel") {
QByteArray name = object.properties.at(1).toByteArray();
name = name.left(name.indexOf('\0'));
@ -675,16 +712,58 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
FBXMesh& mesh = meshes[meshID];
mesh.blendshapes.resize(max(mesh.blendshapes.size(), index.first + 1));
mesh.blendshapes[index.first] = extracted.blendshape;
// apply scale if non-unity
if (index.second != 1.0f) {
FBXBlendshape& blendshape = mesh.blendshapes[index.first];
for (int i = 0; i < blendshape.vertices.size(); i++) {
blendshape.vertices[i] *= index.second;
blendshape.normals[i] *= index.second;
}
}
}
// get offset transform from mapping
FBXGeometry geometry;
float offsetScale = mapping.value("scale", 1.0f).toFloat();
glm::mat4 offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
geometry.offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
mapping.value("tz").toFloat()) * glm::mat4_cast(glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) *
glm::scale(offsetScale, offsetScale, offsetScale);
FBXGeometry geometry;
// get the list of models in depth-first traversal order
QVector<qint64> modelIDs;
appendModelIDs(0, childMap, models, modelIDs);
// convert the models to joints
foreach (qint64 modelID, modelIDs) {
const Model& model = models[modelID];
FBXJoint joint;
joint.parentIndex = model.parentIndex;
joint.preRotation = model.preRotation;
joint.rotation = model.rotation;
joint.postRotation = model.postRotation;
if (joint.parentIndex == -1) {
joint.transform = geometry.offset * model.preRotation * glm::mat4_cast(model.rotation) * model.postRotation;
} else {
joint.transform = geometry.joints.at(joint.parentIndex).transform *
model.preRotation * glm::mat4_cast(model.rotation) * model.postRotation;
}
geometry.joints.append(joint);
}
// find our special joints
geometry.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID);
geometry.rightEyeJointIndex = modelIDs.indexOf(jointEyeRightID);
geometry.neckJointIndex = modelIDs.indexOf(jointNeckID);
// extract the translation component of the neck transform
if (geometry.neckJointIndex != -1) {
const glm::mat4& transform = geometry.joints.at(geometry.neckJointIndex).transform;
geometry.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
}
QVariantHash springs = mapping.value("spring").toHash();
QVariant defaultSpring = springs.value("default");
for (QHash<qint64, FBXMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
@ -692,48 +771,82 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
// accumulate local transforms
qint64 modelID = parentMap.value(it.key());
mesh.springiness = springs.value(localTransforms.value(modelID).name, defaultSpring).toFloat();
glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID);
mesh.springiness = springs.value(models.value(modelID).name, defaultSpring).toFloat();
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID);
// look for textures, material properties
int partIndex = 0;
foreach (qint64 childID, childMap.values(modelID)) {
if (!materials.contains(childID)) {
if (!materials.contains(childID) || partIndex >= mesh.parts.size()) {
continue;
}
Material material = materials.value(childID);
mesh.diffuseColor = material.diffuse;
mesh.specularColor = material.specular;
mesh.shininess = material.shininess;
FBXMeshPart& part = mesh.parts[mesh.parts.size() - ++partIndex];
part.diffuseColor = material.diffuse;
part.specularColor = material.specular;
part.shininess = material.shininess;
qint64 diffuseTextureID = diffuseTextures.value(childID);
if (diffuseTextureID != 0) {
mesh.diffuseFilename = textureFilenames.value(diffuseTextureID);
part.diffuseFilename = textureFilenames.value(diffuseTextureID);
}
qint64 bumpTextureID = bumpTextures.value(childID);
if (bumpTextureID != 0) {
mesh.normalFilename = textureFilenames.value(bumpTextureID);
part.normalFilename = textureFilenames.value(bumpTextureID);
}
}
// look for a limb pivot
// find the clusters with which the mesh is associated
mesh.isEye = false;
QVector<qint64> clusterIDs;
foreach (qint64 childID, childMap.values(it.key())) {
foreach (qint64 clusterID, childMap.values(childID)) {
if (!transformLinkMatrices.contains(clusterID)) {
if (!clusters.contains(clusterID)) {
continue;
}
FBXCluster fbxCluster;
const Cluster& cluster = clusters[clusterID];
clusterIDs.append(clusterID);
qint64 jointID = childMap.value(clusterID);
if (jointID == jointEyeLeftID || jointID == jointEyeRightID) {
mesh.isEye = true;
}
// see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion
// of skinning information in FBX
glm::mat4 jointTransform = offset * getGlobalTransform(parentMap, localTransforms, jointID);
mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * modelTransform;
fbxCluster.jointIndex = modelIDs.indexOf(jointID);
fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform;
mesh.clusters.append(fbxCluster);
}
}
// if we don't have a skinned joint, parent to the model itself
if (mesh.clusters.isEmpty()) {
FBXCluster cluster;
cluster.jointIndex = modelIDs.indexOf(modelID);
mesh.clusters.append(cluster);
}
// whether we're skinned depends on how many clusters are attached
if (clusterIDs.size() > 1) {
mesh.clusterIndices.resize(mesh.vertices.size());
mesh.clusterWeights.resize(mesh.vertices.size());
for (int i = 0; i < clusterIDs.size(); i++) {
qint64 clusterID = clusterIDs.at(i);
const Cluster& cluster = clusters[clusterID];
// extract translation component for pivot
glm::mat4 jointTransformScaled = offset * getGlobalTransform(parentMap, localTransforms, jointID, true);
mesh.pivot = glm::vec3(jointTransformScaled[3][0], jointTransformScaled[3][1], jointTransformScaled[3][2]);
for (int j = 0; j < cluster.indices.size(); j++) {
int index = cluster.indices.at(j);
glm::vec4& weights = mesh.clusterWeights[index];
// look for an unused slot in the weights vector
for (int k = 0; k < 4; k++) {
if (weights[k] == 0.0f) {
mesh.clusterIndices[index][k] = i;
weights[k] = cluster.weights.at(j);
break;
}
}
}
}
}
@ -742,34 +855,36 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QSet<QPair<int, int> > edges;
mesh.vertexConnections.resize(mesh.vertices.size());
for (int i = 0; i < mesh.quadIndices.size(); i += 4) {
int index0 = mesh.quadIndices.at(i);
int index1 = mesh.quadIndices.at(i + 1);
int index2 = mesh.quadIndices.at(i + 2);
int index3 = mesh.quadIndices.at(i + 3);
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
edges.insert(QPair<int, int>(qMin(index2, index3), qMax(index2, index3)));
edges.insert(QPair<int, int>(qMin(index3, index0), qMax(index3, index0)));
mesh.vertexConnections[index0].append(QPair<int, int>(index3, index1));
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index3));
mesh.vertexConnections[index3].append(QPair<int, int>(index2, index0));
}
for (int i = 0; i < mesh.triangleIndices.size(); i += 3) {
int index0 = mesh.triangleIndices.at(i);
int index1 = mesh.triangleIndices.at(i + 1);
int index2 = mesh.triangleIndices.at(i + 2);
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
edges.insert(QPair<int, int>(qMin(index2, index0), qMax(index2, index0)));
mesh.vertexConnections[index0].append(QPair<int, int>(index2, index1));
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index0));
foreach (const FBXMeshPart& part, mesh.parts) {
for (int i = 0; i < part.quadIndices.size(); i += 4) {
int index0 = part.quadIndices.at(i);
int index1 = part.quadIndices.at(i + 1);
int index2 = part.quadIndices.at(i + 2);
int index3 = part.quadIndices.at(i + 3);
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
edges.insert(QPair<int, int>(qMin(index2, index3), qMax(index2, index3)));
edges.insert(QPair<int, int>(qMin(index3, index0), qMax(index3, index0)));
mesh.vertexConnections[index0].append(QPair<int, int>(index3, index1));
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index3));
mesh.vertexConnections[index3].append(QPair<int, int>(index2, index0));
}
for (int i = 0; i < part.triangleIndices.size(); i += 3) {
int index0 = part.triangleIndices.at(i);
int index1 = part.triangleIndices.at(i + 1);
int index2 = part.triangleIndices.at(i + 2);
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
edges.insert(QPair<int, int>(qMin(index2, index0), qMax(index2, index0)));
mesh.vertexConnections[index0].append(QPair<int, int>(index2, index1));
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index0));
}
}
for (QSet<QPair<int, int> >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) {
@ -780,10 +895,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
geometry.meshes.append(mesh);
}
// extract translation component for neck pivot
glm::mat4 neckTransform = offset * getGlobalTransform(parentMap, localTransforms, jointNeckID, true);
geometry.neckPivot = glm::vec3(neckTransform[3][0], neckTransform[3][1], neckTransform[3][2]);
return geometry;
}

View file

@ -37,20 +37,31 @@ public:
QVector<glm::vec3> normals;
};
/// A single mesh (with optional blendshapes) extracted from an FBX document.
class FBXMesh {
/// A single joint (transformation node) extracted from an FBX document.
class FBXJoint {
public:
int parentIndex;
glm::mat4 preRotation;
glm::quat rotation;
glm::mat4 postRotation;
glm::mat4 transform;
};
/// A single binding to a joint in an FBX document.
class FBXCluster {
public:
int jointIndex;
glm::mat4 inverseBindMatrix;
};
/// A single part of a mesh (with the same material).
class FBXMeshPart {
public:
QVector<int> quadIndices;
QVector<int> triangleIndices;
QVector<glm::vec3> vertices;
QVector<glm::vec3> normals;
QVector<glm::vec2> texCoords;
glm::vec3 pivot;
glm::mat4 transform;
bool isEye;
glm::vec3 diffuseColor;
glm::vec3 specularColor;
@ -58,6 +69,23 @@ public:
QByteArray diffuseFilename;
QByteArray normalFilename;
};
/// A single mesh (with optional blendshapes) extracted from an FBX document.
class FBXMesh {
public:
QVector<FBXMeshPart> parts;
QVector<glm::vec3> vertices;
QVector<glm::vec3> normals;
QVector<glm::vec2> texCoords;
QVector<glm::vec4> clusterIndices;
QVector<glm::vec4> clusterWeights;
QVector<FBXCluster> clusters;
bool isEye;
QVector<FBXBlendshape> blendshapes;
@ -70,8 +98,16 @@ public:
class FBXGeometry {
public:
QVector<FBXJoint> joints;
QVector<FBXMesh> meshes;
glm::mat4 offset;
int leftEyeJointIndex;
int rightEyeJointIndex;
int neckJointIndex;
glm::vec3 neckPivot;
};

View file

@ -290,19 +290,26 @@ NetworkGeometry::~NetworkGeometry() {
glm::vec4 NetworkGeometry::computeAverageColor() const {
glm::vec4 totalColor;
int totalVertices = 0;
int totalTriangles = 0;
for (int i = 0; i < _meshes.size(); i++) {
if (_geometry.meshes.at(i).isEye) {
const FBXMesh& mesh = _geometry.meshes.at(i);
if (mesh.isEye) {
continue; // skip eyes
}
glm::vec4 color = glm::vec4(_geometry.meshes.at(i).diffuseColor, 1.0f);
if (_meshes.at(i).diffuseTexture) {
color *= _meshes.at(i).diffuseTexture->getAverageColor();
const NetworkMesh& networkMesh = _meshes.at(i);
for (int j = 0; j < mesh.parts.size(); j++) {
const FBXMeshPart& part = mesh.parts.at(j);
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
glm::vec4 color = glm::vec4(part.diffuseColor, 1.0f);
if (networkPart.diffuseTexture) {
color *= networkPart.diffuseTexture->getAverageColor();
}
int triangles = part.quadIndices.size() * 2 + part.triangleIndices.size();
totalColor += color * triangles;
totalTriangles += triangles;
}
totalColor += color * _geometry.meshes.at(i).vertices.size();
totalVertices += _geometry.meshes.at(i).vertices.size();
}
return (totalVertices == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalVertices;
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
}
void NetworkGeometry::handleModelReplyError() {
@ -351,44 +358,76 @@ void NetworkGeometry::maybeReadModelWithMapping() {
foreach (const FBXMesh& mesh, _geometry.meshes) {
NetworkMesh networkMesh;
int totalIndices = 0;
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart networkPart;
QString basePath = url.path();
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
if (!part.diffuseFilename.isEmpty()) {
url.setPath(basePath + part.diffuseFilename);
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye);
}
if (!part.normalFilename.isEmpty()) {
url.setPath(basePath + part.normalFilename);
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url);
}
networkMesh.parts.append(networkPart);
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
}
glGenBuffers(1, &networkMesh.indexBufferID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (mesh.quadIndices.size() + mesh.triangleIndices.size()) * sizeof(int),
NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mesh.quadIndices.size() * sizeof(int), mesh.quadIndices.constData());
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, mesh.quadIndices.size() * sizeof(int),
mesh.triangleIndices.size() * sizeof(int), mesh.triangleIndices.constData());
glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW);
int offset = 0;
foreach (const FBXMeshPart& part, mesh.parts) {
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
part.quadIndices.constData());
offset += part.quadIndices.size() * sizeof(int);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
part.triangleIndices.constData());
offset += part.triangleIndices.size() * sizeof(int);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenBuffers(1, &networkMesh.vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
// if we don't need to do any blending or springing, then the positions/normals can be static
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
mesh.texCoords.size() * sizeof(glm::vec2) + (mesh.clusterIndices.size() +
mesh.clusterWeights.size()) * sizeof(glm::vec4), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, mesh.vertices.size() * sizeof(glm::vec3),
mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3),
mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
mesh.texCoords.size() * sizeof(glm::vec2), mesh.clusterIndices.size() * sizeof(glm::vec4),
mesh.clusterIndices.constData());
glBufferSubData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
mesh.texCoords.size() * sizeof(glm::vec2) + mesh.clusterIndices.size() * sizeof(glm::vec4),
mesh.clusterWeights.size() * sizeof(glm::vec4), mesh.clusterWeights.constData());
// if there's no springiness, then the cluster indices/weights can be static
} else if (mesh.springiness == 0.0f) {
glBufferData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2) + (mesh.clusterIndices.size() +
mesh.clusterWeights.size()) * sizeof(glm::vec4), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
glBufferSubData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2),
mesh.clusterIndices.size() * sizeof(glm::vec4), mesh.clusterIndices.constData());
glBufferSubData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2) +
mesh.clusterIndices.size() * sizeof(glm::vec4), mesh.clusterWeights.size() * sizeof(glm::vec4),
mesh.clusterWeights.constData());
} else {
glBufferData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2),
mesh.texCoords.constData(), GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
QString basePath = url.path();
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
if (!mesh.diffuseFilename.isEmpty()) {
url.setPath(basePath + mesh.diffuseFilename);
networkMesh.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye);
}
if (!mesh.normalFilename.isEmpty()) {
url.setPath(basePath + mesh.normalFilename);
networkMesh.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url);
}
_meshes.append(networkMesh);
}
}

View file

@ -80,6 +80,14 @@ private:
QVector<NetworkMesh> _meshes;
};
/// The state associated with a single mesh part.
class NetworkMeshPart {
public:
QSharedPointer<NetworkTexture> diffuseTexture;
QSharedPointer<NetworkTexture> normalTexture;
};
/// The state associated with a single mesh.
class NetworkMesh {
public:
@ -87,8 +95,7 @@ public:
GLuint indexBufferID;
GLuint vertexBufferID;
QSharedPointer<NetworkTexture> diffuseTexture;
QSharedPointer<NetworkTexture> normalTexture;
QVector<NetworkMeshPart> parts;
};
#endif /* defined(__interface__GeometryCache__) */

View file

@ -17,14 +17,22 @@
#include "Application.h"
#include "TextureCache.h"
TextureCache::TextureCache() : _permutationNormalTextureID(0),
_primaryFramebufferObject(NULL), _secondaryFramebufferObject(NULL), _tertiaryFramebufferObject(NULL) {
TextureCache::TextureCache() :
_permutationNormalTextureID(0),
_whiteTextureID(0),
_primaryFramebufferObject(NULL),
_secondaryFramebufferObject(NULL),
_tertiaryFramebufferObject(NULL)
{
}
TextureCache::~TextureCache() {
if (_permutationNormalTextureID != 0) {
glDeleteTextures(1, &_permutationNormalTextureID);
}
if (_whiteTextureID != 0) {
glDeleteTextures(1, &_whiteTextureID);
}
foreach (GLuint id, _fileTextureIDs) {
glDeleteTextures(1, &id);
}
@ -66,6 +74,20 @@ GLuint TextureCache::getPermutationNormalTextureID() {
return _permutationNormalTextureID;
}
GLuint TextureCache::getWhiteTextureID() {
if (_whiteTextureID == 0) {
glGenTextures(1, &_whiteTextureID);
glBindTexture(GL_TEXTURE_2D, _whiteTextureID);
const char OPAQUE_WHITE[] = { 0xFF, 0xFF, 0xFF, 0xFF };
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, OPAQUE_WHITE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
}
return _whiteTextureID;
}
GLuint TextureCache::getFileTextureID(const QString& filename) {
GLuint id = _fileTextureIDs.value(filename);
if (id == 0) {
@ -85,10 +107,19 @@ GLuint TextureCache::getFileTextureID(const QString& filename) {
}
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool dilatable) {
QSharedPointer<NetworkTexture> texture = _networkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(dilatable ? new DilatableNetworkTexture(url) : new NetworkTexture(url));
_networkTextures.insert(url, texture);
QSharedPointer<NetworkTexture> texture;
if (dilatable) {
texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url));
_dilatableNetworkTextures.insert(url, texture);
}
} else {
texture = _networkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new NetworkTexture(url));
_networkTextures.insert(url, texture);
}
}
return texture;
}

View file

@ -37,6 +37,9 @@ public:
/// the second, a set of random unit vectors to be used as noise gradients.
GLuint getPermutationNormalTextureID();
/// Returns the ID of an opaque white texture (useful for a default).
GLuint getWhiteTextureID();
/// Returns the ID of a texture containing the contents of the specified file, loading it if necessary.
GLuint getFileTextureID(const QString& filename);
@ -65,11 +68,13 @@ private:
QOpenGLFramebufferObject* createFramebufferObject();
GLuint _permutationNormalTextureID;
GLuint _whiteTextureID;
QHash<QString, GLuint> _fileTextureIDs;
QHash<QUrl, QWeakPointer<NetworkTexture> > _networkTextures;
QHash<QUrl, QWeakPointer<NetworkTexture> > _dilatableNetworkTextures;
GLuint _primaryDepthTextureID;
QOpenGLFramebufferObject* _primaryFramebufferObject;
QOpenGLFramebufferObject* _secondaryFramebufferObject;