Merge GPU skinning

Doing skinning on the GPU shows a noticeable performance improvement in
pretty much any situation, but especially so in scenes with lots of
animated objects with high polygon counts.
stable-6.1
Nicolas Hake 2015-03-04 12:24:02 +01:00
commit 4639ce1675
12 changed files with 284 additions and 183 deletions

View File

@ -1,8 +1,42 @@
varying vec3 normalDir;
uniform mat4 bones[128];
// For more performance, this should be set by the engine, and this shader
// should be compiled three times: with BONE_COUNT set to 0, 4, and 8,
// respectively. (Or we could split it even further.)
#define BONE_COUNT 8
attribute vec4 oc_BoneIndices0;
attribute vec4 oc_BoneWeights0;
#if BONE_COUNT > 4
attribute vec4 oc_BoneIndices1;
attribute vec4 oc_BoneWeights1;
#endif
vec4 merge_bone(vec4 vertex, vec4 original, mat4 bone, float weight)
{
return (bone * original) * weight + vertex;
}
slice(position)
{
#if BONE_COUNT == 0
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
#else
vec4 vertex = vec4(0, 0, 0, 0);
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices0.x)], oc_BoneWeights0.x);
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices0.y)], oc_BoneWeights0.y);
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices0.z)], oc_BoneWeights0.z);
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices0.w)], oc_BoneWeights0.w);
#if BONE_COUNT > 4
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices1.x)], oc_BoneWeights1.x);
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices1.y)], oc_BoneWeights1.y);
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices1.z)], oc_BoneWeights1.z);
vertex = merge_bone(vertex, gl_Vertex, bones[int(oc_BoneIndices1.w)], oc_BoneWeights1.w);
#endif
gl_Position = gl_ModelViewProjectionMatrix * vertex;
#endif
}
slice(texcoord)
@ -12,5 +46,21 @@ slice(texcoord)
slice(normal)
{
#if BONE_COUNT == 0
normalDir = normalize(gl_NormalMatrix * gl_Normal);
#else
vec4 base_normal = vec4(gl_Normal, 0.0);
vec4 normal = base_normal;
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices0.x)], oc_BoneWeights0.x);
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices0.y)], oc_BoneWeights0.y);
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices0.z)], oc_BoneWeights0.z);
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices0.w)], oc_BoneWeights0.w);
#if BONE_COUNT > 4
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices1.x)], oc_BoneWeights1.x);
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices1.y)], oc_BoneWeights1.y);
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices1.z)], oc_BoneWeights1.z);
normal = merge_bone(normal, base_normal, bones[int(oc_BoneIndices1.w)], oc_BoneWeights1.w);
#endif
normalDir = normalize(gl_NormalMatrix * normal.xyz);
#endif
}

View File

@ -570,6 +570,7 @@ bool CStdGL::CreateSpriteShader(C4Shader& shader, const char* name, int ssc, C4G
uniformNames[C4SSU_AmbientTex] = "ambientTex";
uniformNames[C4SSU_AmbientTransform] = "ambientTransform";
uniformNames[C4SSU_AmbientBrightness] = "ambientBrightness";
uniformNames[C4SSU_Bones] = "bones";
uniformNames[C4SSU_Count] = NULL;
// Clear previous content

View File

@ -68,6 +68,8 @@ enum C4SS_Uniforms
C4SSU_AmbientTransform, // C4SSC_LIGHT
C4SSU_AmbientBrightness, // C4SSC_LIGHT
C4SSU_Bones, // for meshes
C4SSU_Count
};

View File

@ -461,9 +461,29 @@ namespace
assert(material.BestTechniqueIndex != -1);
const StdMeshMaterialTechnique& technique = material.Techniques[material.BestTechniqueIndex];
bool using_shared_vertices = instance.GetVertices().empty();
GLuint vbo = mesh_instance.GetVBO();
size_t buffer_offset = using_shared_vertices ? 0 : instance.GetOffsetInBuffer();
bool using_shared_vertices = instance.GetSubMesh().GetVertices().empty();
GLuint vbo = mesh_instance.GetMesh().GetVBO();
size_t buffer_offset = using_shared_vertices ? 0 : instance.GetSubMesh().GetOffsetInBuffer();
// Cook the bone transform matrixes into something that OpenGL can use. This could be moved into RenderMeshImpl.
// Or, even better, we could upload them into a UBO, but Intel doesn't support them prior to Sandy Bridge.
struct BoneTransform
{
float m[4][4];
};
std::vector<BoneTransform> bones;
bones.reserve(mesh_instance.GetBoneCount());
for (size_t bone_index = 0; bone_index < mesh_instance.GetBoneCount(); ++bone_index)
{
const StdMeshMatrix &bone = mesh_instance.GetBoneTransform(bone_index);
BoneTransform cooked_bone = {
bone(0, 0), bone(0, 1), bone(0, 2), bone(0, 3),
bone(1, 0), bone(1, 1), bone(1, 2), bone(1, 3),
bone(2, 0), bone(2, 1), bone(2, 2), bone(2, 3),
0, 0, 0, 1
};
bones.push_back(cooked_bone);
}
// Render each pass
for (unsigned int i = 0; i < technique.Passes.size(); ++i)
@ -532,13 +552,6 @@ namespace
glBlendFunc(OgreBlendTypeToGL(pass.SceneBlendFactors[0]), GL_ONE);
}
#define VERTEX_OFFSET(field) reinterpret_cast<const uint8_t *>(offsetof(StdMeshVertex, field))
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glTexCoordPointer(2, GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(u));
glVertexPointer(3, GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(x));
glNormalPointer(GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(nx));
#undef VERTEX_OFFSET
glMatrixMode(GL_TEXTURE);
assert(pass.Program.get() != NULL);
@ -551,6 +564,27 @@ namespace
C4ShaderCall call(shader);
call.Start();
// Upload the current bone transformation matrixes (if there are any)
if (!bones.empty())
call.SetUniformMatrix4x4fv(C4SSU_Bones, bones.size(), &bones[0].m[0][0]);
// Bind the vertex data of the mesh
#define VERTEX_OFFSET(field) reinterpret_cast<const uint8_t *>(offsetof(StdMeshVertex, field))
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glTexCoordPointer(2, GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(u));
glVertexPointer(3, GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(x));
glNormalPointer(GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(nx));
for (int attrib_index = 0; attrib_index <= C4Shader::VAI_BoneIndicesMax - C4Shader::VAI_BoneIndices; ++attrib_index)
{
glVertexAttribPointer(C4Shader::VAI_BoneWeights + attrib_index, 4, GL_FLOAT, GL_FALSE, sizeof(StdMeshVertex),
buffer_offset + VERTEX_OFFSET(bone_weight) + sizeof(std::remove_all_extents<decltype(StdMeshVertex::bone_weight)>::type) * 4 * attrib_index);
glEnableVertexAttribArray(C4Shader::VAI_BoneWeights + attrib_index);
glVertexAttribPointer(C4Shader::VAI_BoneIndices + attrib_index, 4, GL_SHORT, GL_FALSE, sizeof(StdMeshVertex),
buffer_offset + VERTEX_OFFSET(bone_index) + sizeof(std::remove_all_extents<decltype(StdMeshVertex::bone_index)>::type) * 4 * attrib_index);
glEnableVertexAttribArray(C4Shader::VAI_BoneIndices + attrib_index);
}
#undef VERTEX_OFFSET
for (unsigned int j = 0; j < pass.TextureUnits.size(); ++j)
{
const StdMeshMaterialTextureUnit& texunit = pass.TextureUnits[j];
@ -655,9 +689,15 @@ namespace
}
glMatrixMode(GL_MODELVIEW);
glDrawElements(GL_TRIANGLES, instance.GetNumFaces()*3, GL_UNSIGNED_INT, instance.GetFaces());
call.Finish();
size_t vertex_count = 3 * instance.GetNumFaces();
glDrawElements(GL_TRIANGLES, vertex_count, GL_UNSIGNED_INT, instance.GetFaces());
glBindBuffer(GL_ARRAY_BUFFER, 0);
for (int attrib_index = 0; attrib_index <= C4Shader::VAI_BoneIndicesMax - C4Shader::VAI_BoneIndices; ++attrib_index)
{
glDisableVertexAttribArray(C4Shader::VAI_BoneIndices + attrib_index);
glDisableVertexAttribArray(C4Shader::VAI_BoneWeights + attrib_index);
}
call.Finish();
if(!pass.DepthCheck)
glEnable(GL_DEPTH_TEST);

View File

@ -336,6 +336,12 @@ bool C4Shader::Init(const char *szWhat, const char **szUniforms)
hProg = glCreateProgramObjectARB();
glAttachObjectARB(hProg, hVert);
glAttachObjectARB(hProg, hFrag);
// Bind all input variables
for (int i = 0; i <= VAI_BoneWeightsMax - VAI_BoneWeights; ++i)
{
glBindAttribLocation(hProg, VAI_BoneWeights + i, FormatString("oc_BoneWeights%d", i).getData());
glBindAttribLocation(hProg, VAI_BoneIndices + i, FormatString("oc_BoneIndices%d", i).getData());
}
glLinkProgramARB(hProg);
// Link successful?

View File

@ -75,6 +75,23 @@ private:
GLint *pUniforms;
public:
enum VertexAttribIndex
{
// These correspond to the locations nVidia uses for the
// respective gl_* attributes, so make sure whatever you
// use for custom ones doesn't conflict with these UNLESS
// you're not using the pre-defined ones in your shader
VAI_Vertex = 0,
VAI_Normal = 2,
VAI_Color = 3,
VAI_TexCoord0 = 8, // and upwards through TexCoord7 = 15
// Make sure you move these if we implement multitexturing
VAI_BoneWeights,
VAI_BoneWeightsMax = VAI_BoneWeights + 1,
VAI_BoneIndices,
VAI_BoneIndicesMax = VAI_BoneIndices + VAI_BoneWeightsMax - VAI_BoneWeights
};
bool Initialised() const { return hVert != 0; }

View File

@ -529,12 +529,12 @@ std::vector<int> StdMeshSkeleton::GetMatchingBones(const StdMeshSkeleton& child_
return MatchedBoneInParentSkeleton;
}
StdSubMesh::StdSubMesh():
Material(NULL)
StdSubMesh::StdSubMesh() :
Material(NULL), buffer_offset(0)
{
}
StdMesh::StdMesh() : Skeleton(new StdMeshSkeleton)
StdMesh::StdMesh() : Skeleton(new StdMeshSkeleton), vbo(0)
{
BoundingBox.x1 = BoundingBox.y1 = BoundingBox.z1 = 0.0f;
BoundingBox.x2 = BoundingBox.y2 = BoundingBox.z2 = 0.0f;
@ -543,21 +543,71 @@ StdMesh::StdMesh() : Skeleton(new StdMeshSkeleton)
StdMesh::~StdMesh()
{
if (vbo)
glDeleteBuffers(1, &vbo);
}
void StdMesh::PostInit()
{
// Order submeshes so that opaque submeshes come before non-opaque ones
std::sort(SubMeshes.begin(), SubMeshes.end(), StdMeshSubMeshVisibilityCmpPred());
UpdateVBO();
}
StdSubMeshInstance::StdSubMeshInstance(StdMeshInstance& instance, const StdSubMesh& submesh, float completion):
Vertices(submesh.GetNumVertices()),
Material(NULL), CurrentFaceOrdering(FO_Fixed)
void StdMesh::UpdateVBO()
{
// We're only uploading vertices once, so there shouldn't be a VBO so far
assert(vbo == 0);
if (vbo != 0)
glDeleteBuffers(1, &vbo);
glGenBuffers(1, &vbo);
// Calculate total number of vertices
size_t total_vertices = SharedVertices.size();
for (auto &submesh : SubMeshes)
{
total_vertices += submesh.GetNumVertices();
}
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// Unmapping the buffer may fail for certain reasons, in which case we need to try again.
do
{
// Allocate VBO backing memory. If this mesh's skeleton has no animations
// defined, we assume that the VBO will not change frequently.
glBufferData(GL_ARRAY_BUFFER, total_vertices * sizeof(StdMeshVertex), NULL, GL_STATIC_DRAW);
void *map = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
uint8_t *buffer = static_cast<uint8_t*>(map);
uint8_t *cursor = buffer;
// Add shared vertices to buffer
if (!SharedVertices.empty())
{
size_t shared_vertices_size = SharedVertices.size() * sizeof(SharedVertices[0]);
std::memcpy(cursor, &SharedVertices[0], shared_vertices_size);
cursor += shared_vertices_size;
}
// Add all submeshes to buffer
for (auto &submesh : SubMeshes)
{
// Store the offset, so the render code can use it later
submesh.buffer_offset = cursor - buffer;
if (submesh.Vertices.empty()) continue;
size_t vertices_size = sizeof(submesh.Vertices[0]) * submesh.Vertices.size();
std::memcpy(cursor, &submesh.Vertices[0], vertices_size);
cursor += vertices_size;
}
} while (glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE);
// Unbind the buffer so following rendering calls do not use it
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
StdSubMeshInstance::StdSubMeshInstance(StdMeshInstance& instance, const StdSubMesh& submesh, float completion):
base(&submesh), Material(NULL), CurrentFaceOrdering(FO_Fixed)
{
// Copy initial Vertices/Faces
for (unsigned int i = 0; i < submesh.GetNumVertices(); ++i)
Vertices[i] = submesh.GetVertex(i);
LoadFacesForCompletion(instance, submesh, completion);
SetMaterial(submesh.GetMaterial());
@ -577,8 +627,8 @@ void StdSubMeshInstance::LoadFacesForCompletion(StdMeshInstance& instance, const
// At this point, all vertices are in the OGRE coordinate frame, and Z in OGRE equals
// Y in Clonk, so we are fine without additional transformation.
const StdMeshVertex* vertices;
if(GetNumVertices() > 0)
vertices = &GetVertices()[0];
if(submesh.GetNumVertices() > 0)
vertices = &submesh.GetVertices()[0];
else
vertices = &instance.GetSharedVertices()[0];
SortFacesArray(vertices, Faces, FO_FarthestToNearest, StdMeshMatrix::Identity());
@ -753,7 +803,7 @@ bool StdMeshInstance::AnimationNode::GetBoneTransform(unsigned int bone, StdMesh
}
}
void StdMeshInstance::AnimationNode::CompileFunc(StdCompiler* pComp, const StdMesh* Mesh)
void StdMeshInstance::AnimationNode::CompileFunc(StdCompiler* pComp, const StdMesh *Mesh)
{
static const StdEnumEntry<NodeType> NodeTypes[] =
{
@ -961,15 +1011,11 @@ void StdMeshInstance::AttachedMesh::MapBonesOfChildToParent(const StdMeshSkeleto
}
StdMeshInstance::StdMeshInstance(const StdMesh& mesh, float completion):
Mesh(&mesh), SharedVertices(mesh.GetSharedVertices().size()), vbo(0), Completion(completion),
Mesh(&mesh), Completion(completion),
BoneTransforms(Mesh->GetSkeleton().GetNumBones(), StdMeshMatrix::Identity()),
SubMeshInstances(Mesh->GetNumSubMeshes()), AttachParent(NULL),
BoneTransformsDirty(false)
{
// Copy initial shared vertices
for (unsigned int i = 0; i < SharedVertices.size(); ++i)
SharedVertices[i] = mesh.GetSharedVertices()[i];
// Create submesh instances
for (unsigned int i = 0; i < Mesh->GetNumSubMeshes(); ++i)
{
@ -979,9 +1025,6 @@ StdMeshInstance::StdMeshInstance(const StdMesh& mesh, float completion):
// copy, order is fine at the moment since only default materials are used.
SubMeshInstancesOrdered = SubMeshInstances;
// Initialize VBOs for non-animated mesh instances
UpdateVBO();
}
StdMeshInstance::~StdMeshInstance()
@ -1003,9 +1046,6 @@ StdMeshInstance::~StdMeshInstance()
{
delete SubMeshInstances[i];
}
if (vbo)
glDeleteBuffers(1, &vbo);
}
void StdMeshInstance::SetFaceOrdering(FaceOrdering ordering)
@ -1301,6 +1341,14 @@ const StdMeshMatrix& StdMeshInstance::GetBoneTransform(size_t i) const
return BoneTransforms[i];
}
size_t StdMeshInstance::GetBoneCount() const
{
if ((AttachParent != NULL) && (AttachParent->GetFlags() & AM_MatchSkeleton))
return AttachParent->MatchedBoneInParentSkeleton.size();
else
return BoneTransforms.size();
}
bool StdMeshInstance::UpdateBoneTransforms()
{
bool was_dirty = BoneTransformsDirty;
@ -1345,22 +1393,6 @@ bool StdMeshInstance::UpdateBoneTransforms()
if (parent) BoneTransforms[i] = BoneTransforms[parent->Index] * BoneTransforms[i];
}
}
// Compute transformation for each vertex. We could later think about
// doing this on the GPU using a vertex shader. This would then probably
// need to go to CStdGL::PerformMesh.
// But first, we need to move vertex data to the GPU.
if(!Mesh->GetSharedVertices().empty())
ApplyBoneTransformToVertices(Mesh->GetSharedVertices(), SharedVertices);
for (unsigned int i = 0; i < SubMeshInstances.size(); ++i)
{
const StdSubMesh& submesh = Mesh->GetSubMesh(i);
if(!submesh.GetVertices().empty())
ApplyBoneTransformToVertices(submesh.GetVertices(), SubMeshInstances[i]->Vertices);
}
// Vertexes have moved, update VBO
UpdateVBO();
}
// Update attachment's attach transformations. Note this is done recursively.
@ -1399,58 +1431,6 @@ bool StdMeshInstance::UpdateBoneTransforms()
return was_dirty;
}
void StdMeshInstance::UpdateVBO()
{
bool has_animation = Mesh->GetSkeleton().IsAnimated();
// Create VBO on demand
if (vbo == 0)
{
glGenBuffers(1, &vbo);
}
// Calculate total number of vertices
size_t total_vertices = GetNumSharedVertices();
for (auto &submesh : SubMeshInstances)
{
total_vertices += submesh->GetNumVertices();
}
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// Unmapping the buffer may fail for certain reasons, in which case we need to try again.
do
{
// Allocate VBO backing memory. If this mesh's skeleton has no animations
// defined, we assume that the VBO will not change frequently.
glBufferData(GL_ARRAY_BUFFER, total_vertices * sizeof(StdMeshVertex), NULL, has_animation ? GL_STREAM_DRAW : GL_STATIC_DRAW);
void *map = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
uint8_t *buffer = static_cast<uint8_t*>(map);
uint8_t *cursor = buffer;
// Add shared vertices to buffer
if (!SharedVertices.empty())
{
size_t shared_vertices_size = GetNumSharedVertices() * sizeof(SharedVertices[0]);
std::memcpy(cursor, &SharedVertices[0], shared_vertices_size);
cursor += shared_vertices_size;
}
// Add all submeshes to buffer
for (auto &submesh : SubMeshInstances)
{
// Store the offset, so the render code can use it later
submesh->buffer_offset = cursor - buffer;
if (submesh->Vertices.empty()) continue;
size_t vertices_size = sizeof(submesh->Vertices[0]) * submesh->Vertices.size();
std::memcpy(cursor, &submesh->Vertices[0], vertices_size);
cursor += vertices_size;
}
} while (glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE);
// Unbind the buffer so following rendering calls do not use it
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void StdMeshInstance::ReorderFaces(StdMeshMatrix* global_trans)
{
for (unsigned int i = 0; i < SubMeshInstances.size(); ++i)
@ -1461,8 +1441,8 @@ void StdMeshInstance::ReorderFaces(StdMeshMatrix* global_trans)
if(inst.Faces.size() > 0 && inst.CurrentFaceOrdering != StdSubMeshInstance::FO_Fixed)
{
const StdMeshVertex* vertices;
if(inst.GetNumVertices() > 0)
vertices = &inst.GetVertices()[0];
if(inst.GetSubMesh().GetNumVertices() > 0)
vertices = &inst.GetSubMesh().GetVertices()[0];
else
vertices = &GetSharedVertices()[0];
SortFacesArray(vertices, inst.Faces, inst.CurrentFaceOrdering, global_trans ? *global_trans : StdMeshMatrix::Identity());
@ -1759,32 +1739,6 @@ bool StdMeshInstance::ExecuteAnimationNode(AnimationNode* node)
return true;
}
void StdMeshInstance::ApplyBoneTransformToVertices(const std::vector<StdSubMesh::Vertex>& mesh_vertices, std::vector<StdMeshVertex>& instance_vertices)
{
assert(mesh_vertices.size() == instance_vertices.size());
for (unsigned int j = 0; j < instance_vertices.size(); ++j)
{
const StdSubMesh::Vertex& vertex = mesh_vertices[j];
StdMeshVertex& instance_vertex = instance_vertices[j];
if (!vertex.BoneAssignments.empty())
{
instance_vertex.x = instance_vertex.y = instance_vertex.z = 0.0f;
instance_vertex.nx = instance_vertex.ny = instance_vertex.nz = 0.0f;
instance_vertex.u = vertex.u; instance_vertex.v = vertex.v;
for (unsigned int k = 0; k < vertex.BoneAssignments.size(); ++k)
{
const StdMeshVertexBoneAssignment& assignment = vertex.BoneAssignments[k];
instance_vertex += assignment.Weight * (GetBoneTransform(assignment.BoneIndex) * vertex);
}
}
else
{
instance_vertex = vertex;
}
}
}
void StdMeshInstance::SetBoneTransformsDirty(bool value)
{
BoneTransformsDirty = value;

View File

@ -149,12 +149,7 @@ class StdSubMesh
friend class StdMeshLoader;
friend class StdMeshMaterialUpdate;
public:
// Remember bone assignments for vertices
class Vertex: public StdMeshVertex
{
public:
std::vector<StdMeshVertexBoneAssignment> BoneAssignments;
};
typedef StdMeshVertex Vertex;
const std::vector<Vertex>& GetVertices() const { return Vertices; }
const Vertex& GetVertex(size_t i) const { return Vertices[i]; }
@ -165,11 +160,15 @@ public:
const StdMeshMaterial& GetMaterial() const { return *Material; }
// Return the offset into the backing vertex buffer where this SubMesh's data starts
size_t GetOffsetInBuffer() const { return buffer_offset; }
private:
StdSubMesh();
std::vector<Vertex> Vertices; // Empty if we use shared vertices
std::vector<StdMeshFace> Faces;
size_t buffer_offset;
const StdMeshMaterial* Material;
};
@ -198,7 +197,11 @@ public:
void PostInit();
const GLuint GetVBO() const { return vbo; }
private:
GLuint vbo;
void UpdateVBO();
StdMesh(const StdMesh& other); // non-copyable
StdMesh& operator=(const StdMesh& other); // non-assignable
@ -230,18 +233,12 @@ public:
void CompileFunc(StdCompiler* pComp);
// Get vertex of instance, with current animation applied. This needs to
// go elsewhere if/when we want to calculate this on the hardware.
const std::vector<StdMeshVertex>& GetVertices() const { return Vertices; }
size_t GetNumVertices() const { return Vertices.size(); }
// Return the offset into the backing vertex buffer where this SubMesh's data starts
size_t GetOffsetInBuffer() const { return buffer_offset; }
// Get face of instance. The instance faces are the same as the mesh faces,
// with the exception that they are differently ordered, depending on the
// current FaceOrdering. See FaceOrdering in StdMeshInstance.
const StdMeshFace* GetFaces() const { return Faces.size() > 0 ? &Faces[0] : 0; }
size_t GetNumFaces() const { return Faces.size(); }
const StdSubMesh &GetSubMesh() const { return *base; }
unsigned int GetTexturePhase(size_t pass, size_t texunit) const { return PassData[pass].TexUnits[texunit].Phase; }
double GetTexturePosition(size_t pass, size_t texunit) const { return PassData[pass].TexUnits[texunit].Position; }
@ -254,14 +251,9 @@ protected:
void SetFaceOrdering(const StdSubMesh& submesh, FaceOrdering ordering);
void SetFaceOrderingForClrModulation(const StdSubMesh& submesh, uint32_t clrmod);
// Vertices transformed according to current animation
const StdSubMesh *base;
// Faces sorted according to current face ordering
// TODO: We can skip these if we decide to either
// a) recompute Vertex positions each frame or
// b) compute them on the GPU
std::vector<StdMeshVertex> Vertices;
std::vector<StdMeshFace> Faces; // TODO: Indices could also be stored on GPU in a vbo (element index array). Should be done in a next step if at all.
size_t buffer_offset;
const StdMeshMaterial* Material;
@ -424,7 +416,7 @@ public:
ValueProvider* GetWeightProvider() { assert(Type == LinearInterpolationNode); return LinearInterpolation.Weight; }
C4Real GetWeight() const { assert(Type == LinearInterpolationNode); return LinearInterpolation.Weight->Value; }
void CompileFunc(StdCompiler* pComp, const StdMesh* Mesh);
void CompileFunc(StdCompiler* pComp, const StdMesh *Mesh);
void DenumeratePointers();
void ClearPointers(class C4Object* pObj);
@ -499,6 +491,9 @@ public:
void DenumeratePointers();
bool ClearPointers(class C4Object* pObj);
unsigned int GetParentBone() const { return ParentBone; }
unsigned int GetChildBone() const { return ChildBone; }
private:
unsigned int ParentBone;
unsigned int ChildBone;
@ -521,8 +516,8 @@ public:
void SetFaceOrdering(FaceOrdering ordering);
void SetFaceOrderingForClrModulation(uint32_t clrmod);
const std::vector<StdMeshVertex>& GetSharedVertices() const { return SharedVertices; }
size_t GetNumSharedVertices() const { return SharedVertices.size(); }
const std::vector<StdMeshVertex>& GetSharedVertices() const { return Mesh->GetSharedVertices(); }
size_t GetNumSharedVertices() const { return GetSharedVertices().size(); }
// Set completion of the mesh. For incompleted meshes not all faces will be available.
void SetCompletion(float completion);
@ -569,6 +564,7 @@ public:
void SetMaterial(size_t i, const StdMeshMaterial& material);
const StdMeshMatrix& GetBoneTransform(size_t i) const;
size_t GetBoneCount() const;
// Update bone transformation matrices, vertex positions and final attach transformations of attached children.
// This is called recursively for attached children, so there is no need to call it on attached children only
@ -593,9 +589,7 @@ public:
void DenumeratePointers();
void ClearPointers(class C4Object* pObj);
const StdMesh& GetMesh() const { assert(Mesh != NULL); return *Mesh; }
GLuint GetVBO() const { return vbo; }
const StdMesh& GetMesh() const { return *Mesh; }
protected:
typedef std::vector<AnimationNode*> AnimationNodeList;
@ -606,14 +600,10 @@ protected:
void ApplyBoneTransformToVertices(const std::vector<StdSubMesh::Vertex>& mesh_vertices, std::vector<StdMeshVertex>& instance_vertices);
void SetBoneTransformsDirty(bool value);
const StdMesh* Mesh;
const StdMesh *Mesh;
float Completion; // NoSave
std::vector<StdMeshVertex> SharedVertices;
GLuint vbo;
void UpdateVBO();
AnimationNodeList AnimationNodes; // for simple lookup of animation nodes by their unique number
AnimationNodeList AnimationStack; // contains top level nodes only, ordered by slot number
std::vector<StdMeshMatrix> BoneTransforms;

View File

@ -1,7 +1,7 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010-2013, The OpenClonk Team and contributors
* Copyright (c) 2010-2015, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
@ -518,26 +518,39 @@ StdMesh *StdMeshLoader::LoadMeshBinary(const char *sourcefile, size_t length, co
// Read bone assignments
std::vector<Ogre::Mesh::BoneAssignment> &boneAssignments = (csm.hasSharedVertices ? cmesh.boneAssignments : csm.boneAssignments);
assert(!csm.hasSharedVertices || csm.boneAssignments.empty());
BOOST_FOREACH(const Ogre::Mesh::BoneAssignment &ba, boneAssignments)
for(const auto &ba : boneAssignments)
{
if (ba.vertex >= sm.GetNumVertices())
throw Ogre::Mesh::VertexNotFound();
if (bone_lookup.find(ba.bone) == bone_lookup.end())
throw Ogre::Skeleton::BoneNotFound();
StdMeshVertexBoneAssignment assignment;
assignment.BoneIndex = bone_lookup[ba.bone];
assignment.Weight = ba.weight;
sm.Vertices[ba.vertex].BoneAssignments.push_back(assignment);
size_t bone_index = bone_lookup[ba.bone];
// Check quickly if all weight slots are used
StdSubMesh::Vertex &vertex = sm.Vertices[ba.vertex];
if (vertex.bone_weight[StdMeshVertex::MaxBoneWeightCount - 1] != 0)
{
throw Ogre::Mesh::NotImplemented("Vertex is influenced by too many bones");
}
for (size_t weight_index = 0; weight_index < StdMeshVertex::MaxBoneWeightCount; ++weight_index)
{
if (vertex.bone_weight[weight_index] == 0)
{
vertex.bone_weight[weight_index] = ba.weight;
vertex.bone_index[weight_index] = bone_index;
break;
}
}
}
// Normalize bone assignments
BOOST_FOREACH(StdSubMesh::Vertex &vertex, sm.Vertices)
for(StdSubMesh::Vertex &vertex : sm.Vertices)
{
float sum = 0;
BOOST_FOREACH(StdMeshVertexBoneAssignment &ba, vertex.BoneAssignments)
sum += ba.Weight;
BOOST_FOREACH(StdMeshVertexBoneAssignment &ba, vertex.BoneAssignments)
ba.Weight /= sum;
for (float weight : vertex.bone_weight)
sum += weight;
if (sum != 0)
for (float &weight : vertex.bone_weight)
weight /= sum;
}
}
return mesh.release();

View File

@ -2,7 +2,7 @@
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
* Copyright (c) 2009-2015, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
@ -228,11 +228,22 @@ void StdMeshLoader::StdMeshXML::LoadBoneAssignments(StdMesh& mesh, std::vector<S
// bone_lookup[mesh->GetSkeleton().GetBone(i).ID] = i;
//}
// Find first bone assignment with a zero weight (i.e. is unused)
StdSubMesh::Vertex& vertex = vertices[VertexIndex];
vertex.BoneAssignments.push_back(StdMeshVertexBoneAssignment());
StdMeshVertexBoneAssignment& assignment = vertex.BoneAssignments.back();
assignment.BoneIndex = bone->Index;
assignment.Weight = weight;
// Check quickly if all weight slots are used
if (vertex.bone_weight[StdMeshVertex::MaxBoneWeightCount - 1] != 0)
{
Error(FormatString("Vertex %d is influenced by more than %d bones", VertexIndex, StdMeshVertex::MaxBoneWeightCount), vertexboneassignment_elem);
}
for (size_t weight_index = 0; weight_index < StdMeshVertex::MaxBoneWeightCount; ++weight_index)
{
if (vertex.bone_weight[weight_index] == 0)
{
vertex.bone_weight[weight_index] = weight;
vertex.bone_index[weight_index] = bone->Index;
break;
}
}
}
// Normalize vertex bone assignment weights (this is not guaranteed in the
@ -240,11 +251,12 @@ void StdMeshLoader::StdMeshXML::LoadBoneAssignments(StdMesh& mesh, std::vector<S
for (unsigned int i = 0; i < vertices.size(); ++i)
{
StdSubMesh::Vertex& vertex = vertices[i];
float sum = 0.0f;
for (unsigned int j = 0; j < vertex.BoneAssignments.size(); ++j)
sum += vertex.BoneAssignments[j].Weight;
for (unsigned int j = 0; j < vertex.BoneAssignments.size(); ++j)
vertex.BoneAssignments[j].Weight /= sum;
float sum = 0.0;
for (float weight : vertex.bone_weight)
sum += weight;
if (sum != 0)
for (float &weight : vertex.bone_weight)
weight /= sum;
}
}

View File

@ -860,6 +860,7 @@ bool StdMeshMaterialProgram::CompileShader(StdMeshMaterialLoader& loader, C4Shad
uniformNames[C4SSU_AmbientTex] = "ambientTex";
uniformNames[C4SSU_AmbientTransform] = "ambientTransform";
uniformNames[C4SSU_AmbientBrightness] = "ambientBrightness";
uniformNames[C4SSU_Bones] = "bones";
for (unsigned int i = 0; i < ParameterNames.size(); ++i)
uniformNames[C4SSU_Count + i] = ParameterNames[i].getData();
uniformNames[C4SSU_Count + ParameterNames.size()] = NULL;

View File

@ -2,7 +2,7 @@
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2011-2013, The OpenClonk Team and contributors
* Copyright (c) 2011-2015, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
@ -29,15 +29,30 @@ struct StdMeshVector
static StdMeshVector Cross(const StdMeshVector& lhs, const StdMeshVector& rhs);
};
struct StdMeshVertex
{
static const size_t MaxBoneWeightCount = 8;
// Match GL_T2F_N3F_V3F
float u, v;
float nx, ny, nz;
float x, y, z;
float bone_weight[MaxBoneWeightCount];
uint16_t bone_index[MaxBoneWeightCount];
char _padding[16];
StdMeshVertex() : u(0), v(0), nx(0), ny(0), nz(0), x(0), y(0), z(0)
{
std::uninitialized_fill(std::begin(bone_weight), std::end(bone_weight), 0);
std::uninitialized_fill(std::begin(bone_index), std::end(bone_index), 0);
std::uninitialized_fill(std::begin(_padding), std::end(_padding), 0);
}
//void Normalize();
};
static_assert((sizeof(StdMeshVertex) & 31) == 0, "StdMeshVertex should be a multiple of 32 bytes");
struct StdMeshQuaternion
{