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; 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) slice(position)
{ {
#if BONE_COUNT == 0
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 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) slice(texcoord)
@ -12,5 +46,21 @@ slice(texcoord)
slice(normal) slice(normal)
{ {
#if BONE_COUNT == 0
normalDir = normalize(gl_NormalMatrix * gl_Normal); 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_AmbientTex] = "ambientTex";
uniformNames[C4SSU_AmbientTransform] = "ambientTransform"; uniformNames[C4SSU_AmbientTransform] = "ambientTransform";
uniformNames[C4SSU_AmbientBrightness] = "ambientBrightness"; uniformNames[C4SSU_AmbientBrightness] = "ambientBrightness";
uniformNames[C4SSU_Bones] = "bones";
uniformNames[C4SSU_Count] = NULL; uniformNames[C4SSU_Count] = NULL;
// Clear previous content // Clear previous content

View File

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

View File

@ -461,9 +461,29 @@ namespace
assert(material.BestTechniqueIndex != -1); assert(material.BestTechniqueIndex != -1);
const StdMeshMaterialTechnique& technique = material.Techniques[material.BestTechniqueIndex]; const StdMeshMaterialTechnique& technique = material.Techniques[material.BestTechniqueIndex];
bool using_shared_vertices = instance.GetVertices().empty(); bool using_shared_vertices = instance.GetSubMesh().GetVertices().empty();
GLuint vbo = mesh_instance.GetVBO(); GLuint vbo = mesh_instance.GetMesh().GetVBO();
size_t buffer_offset = using_shared_vertices ? 0 : instance.GetOffsetInBuffer(); 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 // Render each pass
for (unsigned int i = 0; i < technique.Passes.size(); ++i) for (unsigned int i = 0; i < technique.Passes.size(); ++i)
@ -532,13 +552,6 @@ namespace
glBlendFunc(OgreBlendTypeToGL(pass.SceneBlendFactors[0]), GL_ONE); 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); glMatrixMode(GL_TEXTURE);
assert(pass.Program.get() != NULL); assert(pass.Program.get() != NULL);
@ -551,6 +564,27 @@ namespace
C4ShaderCall call(shader); C4ShaderCall call(shader);
call.Start(); 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) for (unsigned int j = 0; j < pass.TextureUnits.size(); ++j)
{ {
const StdMeshMaterialTextureUnit& texunit = pass.TextureUnits[j]; const StdMeshMaterialTextureUnit& texunit = pass.TextureUnits[j];
@ -655,9 +689,15 @@ namespace
} }
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glDrawElements(GL_TRIANGLES, instance.GetNumFaces()*3, GL_UNSIGNED_INT, instance.GetFaces()); size_t vertex_count = 3 * instance.GetNumFaces();
call.Finish(); glDrawElements(GL_TRIANGLES, vertex_count, GL_UNSIGNED_INT, instance.GetFaces());
glBindBuffer(GL_ARRAY_BUFFER, 0); 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) if(!pass.DepthCheck)
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);

View File

@ -336,6 +336,12 @@ bool C4Shader::Init(const char *szWhat, const char **szUniforms)
hProg = glCreateProgramObjectARB(); hProg = glCreateProgramObjectARB();
glAttachObjectARB(hProg, hVert); glAttachObjectARB(hProg, hVert);
glAttachObjectARB(hProg, hFrag); 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); glLinkProgramARB(hProg);
// Link successful? // Link successful?

View File

@ -75,6 +75,23 @@ private:
GLint *pUniforms; GLint *pUniforms;
public: 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; } bool Initialised() const { return hVert != 0; }

View File

@ -529,12 +529,12 @@ std::vector<int> StdMeshSkeleton::GetMatchingBones(const StdMeshSkeleton& child_
return MatchedBoneInParentSkeleton; return MatchedBoneInParentSkeleton;
} }
StdSubMesh::StdSubMesh(): StdSubMesh::StdSubMesh() :
Material(NULL) 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.x1 = BoundingBox.y1 = BoundingBox.z1 = 0.0f;
BoundingBox.x2 = BoundingBox.y2 = BoundingBox.z2 = 0.0f; BoundingBox.x2 = BoundingBox.y2 = BoundingBox.z2 = 0.0f;
@ -543,21 +543,71 @@ StdMesh::StdMesh() : Skeleton(new StdMeshSkeleton)
StdMesh::~StdMesh() StdMesh::~StdMesh()
{ {
if (vbo)
glDeleteBuffers(1, &vbo);
} }
void StdMesh::PostInit() void StdMesh::PostInit()
{ {
// Order submeshes so that opaque submeshes come before non-opaque ones // Order submeshes so that opaque submeshes come before non-opaque ones
std::sort(SubMeshes.begin(), SubMeshes.end(), StdMeshSubMeshVisibilityCmpPred()); std::sort(SubMeshes.begin(), SubMeshes.end(), StdMeshSubMeshVisibilityCmpPred());
UpdateVBO();
} }
StdSubMeshInstance::StdSubMeshInstance(StdMeshInstance& instance, const StdSubMesh& submesh, float completion): void StdMesh::UpdateVBO()
Vertices(submesh.GetNumVertices()), {
Material(NULL), CurrentFaceOrdering(FO_Fixed) // 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); LoadFacesForCompletion(instance, submesh, completion);
SetMaterial(submesh.GetMaterial()); 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 // 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. // Y in Clonk, so we are fine without additional transformation.
const StdMeshVertex* vertices; const StdMeshVertex* vertices;
if(GetNumVertices() > 0) if(submesh.GetNumVertices() > 0)
vertices = &GetVertices()[0]; vertices = &submesh.GetVertices()[0];
else else
vertices = &instance.GetSharedVertices()[0]; vertices = &instance.GetSharedVertices()[0];
SortFacesArray(vertices, Faces, FO_FarthestToNearest, StdMeshMatrix::Identity()); 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[] = static const StdEnumEntry<NodeType> NodeTypes[] =
{ {
@ -961,15 +1011,11 @@ void StdMeshInstance::AttachedMesh::MapBonesOfChildToParent(const StdMeshSkeleto
} }
StdMeshInstance::StdMeshInstance(const StdMesh& mesh, float completion): 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()), BoneTransforms(Mesh->GetSkeleton().GetNumBones(), StdMeshMatrix::Identity()),
SubMeshInstances(Mesh->GetNumSubMeshes()), AttachParent(NULL), SubMeshInstances(Mesh->GetNumSubMeshes()), AttachParent(NULL),
BoneTransformsDirty(false) BoneTransformsDirty(false)
{ {
// Copy initial shared vertices
for (unsigned int i = 0; i < SharedVertices.size(); ++i)
SharedVertices[i] = mesh.GetSharedVertices()[i];
// Create submesh instances // Create submesh instances
for (unsigned int i = 0; i < Mesh->GetNumSubMeshes(); ++i) 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. // copy, order is fine at the moment since only default materials are used.
SubMeshInstancesOrdered = SubMeshInstances; SubMeshInstancesOrdered = SubMeshInstances;
// Initialize VBOs for non-animated mesh instances
UpdateVBO();
} }
StdMeshInstance::~StdMeshInstance() StdMeshInstance::~StdMeshInstance()
@ -1003,9 +1046,6 @@ StdMeshInstance::~StdMeshInstance()
{ {
delete SubMeshInstances[i]; delete SubMeshInstances[i];
} }
if (vbo)
glDeleteBuffers(1, &vbo);
} }
void StdMeshInstance::SetFaceOrdering(FaceOrdering ordering) void StdMeshInstance::SetFaceOrdering(FaceOrdering ordering)
@ -1301,6 +1341,14 @@ const StdMeshMatrix& StdMeshInstance::GetBoneTransform(size_t i) const
return BoneTransforms[i]; 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 StdMeshInstance::UpdateBoneTransforms()
{ {
bool was_dirty = BoneTransformsDirty; bool was_dirty = BoneTransformsDirty;
@ -1345,22 +1393,6 @@ bool StdMeshInstance::UpdateBoneTransforms()
if (parent) BoneTransforms[i] = BoneTransforms[parent->Index] * BoneTransforms[i]; 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. // Update attachment's attach transformations. Note this is done recursively.
@ -1399,58 +1431,6 @@ bool StdMeshInstance::UpdateBoneTransforms()
return was_dirty; 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) void StdMeshInstance::ReorderFaces(StdMeshMatrix* global_trans)
{ {
for (unsigned int i = 0; i < SubMeshInstances.size(); ++i) 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) if(inst.Faces.size() > 0 && inst.CurrentFaceOrdering != StdSubMeshInstance::FO_Fixed)
{ {
const StdMeshVertex* vertices; const StdMeshVertex* vertices;
if(inst.GetNumVertices() > 0) if(inst.GetSubMesh().GetNumVertices() > 0)
vertices = &inst.GetVertices()[0]; vertices = &inst.GetSubMesh().GetVertices()[0];
else else
vertices = &GetSharedVertices()[0]; vertices = &GetSharedVertices()[0];
SortFacesArray(vertices, inst.Faces, inst.CurrentFaceOrdering, global_trans ? *global_trans : StdMeshMatrix::Identity()); SortFacesArray(vertices, inst.Faces, inst.CurrentFaceOrdering, global_trans ? *global_trans : StdMeshMatrix::Identity());
@ -1759,32 +1739,6 @@ bool StdMeshInstance::ExecuteAnimationNode(AnimationNode* node)
return true; 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) void StdMeshInstance::SetBoneTransformsDirty(bool value)
{ {
BoneTransformsDirty = value; BoneTransformsDirty = value;

View File

@ -149,12 +149,7 @@ class StdSubMesh
friend class StdMeshLoader; friend class StdMeshLoader;
friend class StdMeshMaterialUpdate; friend class StdMeshMaterialUpdate;
public: public:
// Remember bone assignments for vertices typedef StdMeshVertex Vertex;
class Vertex: public StdMeshVertex
{
public:
std::vector<StdMeshVertexBoneAssignment> BoneAssignments;
};
const std::vector<Vertex>& GetVertices() const { return Vertices; } const std::vector<Vertex>& GetVertices() const { return Vertices; }
const Vertex& GetVertex(size_t i) const { return Vertices[i]; } const Vertex& GetVertex(size_t i) const { return Vertices[i]; }
@ -165,11 +160,15 @@ public:
const StdMeshMaterial& GetMaterial() const { return *Material; } 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: private:
StdSubMesh(); StdSubMesh();
std::vector<Vertex> Vertices; // Empty if we use shared vertices std::vector<Vertex> Vertices; // Empty if we use shared vertices
std::vector<StdMeshFace> Faces; std::vector<StdMeshFace> Faces;
size_t buffer_offset;
const StdMeshMaterial* Material; const StdMeshMaterial* Material;
}; };
@ -198,7 +197,11 @@ public:
void PostInit(); void PostInit();
const GLuint GetVBO() const { return vbo; }
private: private:
GLuint vbo;
void UpdateVBO();
StdMesh(const StdMesh& other); // non-copyable StdMesh(const StdMesh& other); // non-copyable
StdMesh& operator=(const StdMesh& other); // non-assignable StdMesh& operator=(const StdMesh& other); // non-assignable
@ -230,18 +233,12 @@ public:
void CompileFunc(StdCompiler* pComp); 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, // 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 // with the exception that they are differently ordered, depending on the
// current FaceOrdering. See FaceOrdering in StdMeshInstance. // current FaceOrdering. See FaceOrdering in StdMeshInstance.
const StdMeshFace* GetFaces() const { return Faces.size() > 0 ? &Faces[0] : 0; } const StdMeshFace* GetFaces() const { return Faces.size() > 0 ? &Faces[0] : 0; }
size_t GetNumFaces() const { return Faces.size(); } 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; } 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; } 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 SetFaceOrdering(const StdSubMesh& submesh, FaceOrdering ordering);
void SetFaceOrderingForClrModulation(const StdSubMesh& submesh, uint32_t clrmod); void SetFaceOrderingForClrModulation(const StdSubMesh& submesh, uint32_t clrmod);
// Vertices transformed according to current animation const StdSubMesh *base;
// Faces sorted according to current face ordering // 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. 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; const StdMeshMaterial* Material;
@ -424,7 +416,7 @@ public:
ValueProvider* GetWeightProvider() { assert(Type == LinearInterpolationNode); return LinearInterpolation.Weight; } ValueProvider* GetWeightProvider() { assert(Type == LinearInterpolationNode); return LinearInterpolation.Weight; }
C4Real GetWeight() const { assert(Type == LinearInterpolationNode); return LinearInterpolation.Weight->Value; } 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 DenumeratePointers();
void ClearPointers(class C4Object* pObj); void ClearPointers(class C4Object* pObj);
@ -499,6 +491,9 @@ public:
void DenumeratePointers(); void DenumeratePointers();
bool ClearPointers(class C4Object* pObj); bool ClearPointers(class C4Object* pObj);
unsigned int GetParentBone() const { return ParentBone; }
unsigned int GetChildBone() const { return ChildBone; }
private: private:
unsigned int ParentBone; unsigned int ParentBone;
unsigned int ChildBone; unsigned int ChildBone;
@ -521,8 +516,8 @@ public:
void SetFaceOrdering(FaceOrdering ordering); void SetFaceOrdering(FaceOrdering ordering);
void SetFaceOrderingForClrModulation(uint32_t clrmod); void SetFaceOrderingForClrModulation(uint32_t clrmod);
const std::vector<StdMeshVertex>& GetSharedVertices() const { return SharedVertices; } const std::vector<StdMeshVertex>& GetSharedVertices() const { return Mesh->GetSharedVertices(); }
size_t GetNumSharedVertices() const { return SharedVertices.size(); } size_t GetNumSharedVertices() const { return GetSharedVertices().size(); }
// Set completion of the mesh. For incompleted meshes not all faces will be available. // Set completion of the mesh. For incompleted meshes not all faces will be available.
void SetCompletion(float completion); void SetCompletion(float completion);
@ -569,6 +564,7 @@ public:
void SetMaterial(size_t i, const StdMeshMaterial& material); void SetMaterial(size_t i, const StdMeshMaterial& material);
const StdMeshMatrix& GetBoneTransform(size_t i) const; const StdMeshMatrix& GetBoneTransform(size_t i) const;
size_t GetBoneCount() const;
// Update bone transformation matrices, vertex positions and final attach transformations of attached children. // 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 // 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 DenumeratePointers();
void ClearPointers(class C4Object* pObj); void ClearPointers(class C4Object* pObj);
const StdMesh& GetMesh() const { assert(Mesh != NULL); return *Mesh; } const StdMesh& GetMesh() const { return *Mesh; }
GLuint GetVBO() const { return vbo; }
protected: protected:
typedef std::vector<AnimationNode*> AnimationNodeList; 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 ApplyBoneTransformToVertices(const std::vector<StdSubMesh::Vertex>& mesh_vertices, std::vector<StdMeshVertex>& instance_vertices);
void SetBoneTransformsDirty(bool value); void SetBoneTransformsDirty(bool value);
const StdMesh* Mesh; const StdMesh *Mesh;
float Completion; // NoSave float Completion; // NoSave
std::vector<StdMeshVertex> SharedVertices;
GLuint vbo;
void UpdateVBO();
AnimationNodeList AnimationNodes; // for simple lookup of animation nodes by their unique number AnimationNodeList AnimationNodes; // for simple lookup of animation nodes by their unique number
AnimationNodeList AnimationStack; // contains top level nodes only, ordered by slot number AnimationNodeList AnimationStack; // contains top level nodes only, ordered by slot number
std::vector<StdMeshMatrix> BoneTransforms; std::vector<StdMeshMatrix> BoneTransforms;

View File

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

View File

@ -2,7 +2,7 @@
* OpenClonk, http://www.openclonk.org * OpenClonk, http://www.openclonk.org
* *
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * 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 * Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details. * "COPYING" for details.
@ -228,11 +228,22 @@ void StdMeshLoader::StdMeshXML::LoadBoneAssignments(StdMesh& mesh, std::vector<S
// bone_lookup[mesh->GetSkeleton().GetBone(i).ID] = i; // 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]; StdSubMesh::Vertex& vertex = vertices[VertexIndex];
vertex.BoneAssignments.push_back(StdMeshVertexBoneAssignment()); // Check quickly if all weight slots are used
StdMeshVertexBoneAssignment& assignment = vertex.BoneAssignments.back(); if (vertex.bone_weight[StdMeshVertex::MaxBoneWeightCount - 1] != 0)
assignment.BoneIndex = bone->Index; {
assignment.Weight = weight; 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 // 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) for (unsigned int i = 0; i < vertices.size(); ++i)
{ {
StdSubMesh::Vertex& vertex = vertices[i]; StdSubMesh::Vertex& vertex = vertices[i];
float sum = 0.0f; float sum = 0.0;
for (unsigned int j = 0; j < vertex.BoneAssignments.size(); ++j) for (float weight : vertex.bone_weight)
sum += vertex.BoneAssignments[j].Weight; sum += weight;
for (unsigned int j = 0; j < vertex.BoneAssignments.size(); ++j) if (sum != 0)
vertex.BoneAssignments[j].Weight /= sum; 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_AmbientTex] = "ambientTex";
uniformNames[C4SSU_AmbientTransform] = "ambientTransform"; uniformNames[C4SSU_AmbientTransform] = "ambientTransform";
uniformNames[C4SSU_AmbientBrightness] = "ambientBrightness"; uniformNames[C4SSU_AmbientBrightness] = "ambientBrightness";
uniformNames[C4SSU_Bones] = "bones";
for (unsigned int i = 0; i < ParameterNames.size(); ++i) for (unsigned int i = 0; i < ParameterNames.size(); ++i)
uniformNames[C4SSU_Count + i] = ParameterNames[i].getData(); uniformNames[C4SSU_Count + i] = ParameterNames[i].getData();
uniformNames[C4SSU_Count + ParameterNames.size()] = NULL; uniformNames[C4SSU_Count + ParameterNames.size()] = NULL;

View File

@ -2,7 +2,7 @@
* OpenClonk, http://www.openclonk.org * OpenClonk, http://www.openclonk.org
* *
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * 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 * Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details. * "COPYING" for details.
@ -29,15 +29,30 @@ struct StdMeshVector
static StdMeshVector Cross(const StdMeshVector& lhs, const StdMeshVector& rhs); static StdMeshVector Cross(const StdMeshVector& lhs, const StdMeshVector& rhs);
}; };
struct StdMeshVertex struct StdMeshVertex
{ {
static const size_t MaxBoneWeightCount = 8;
// Match GL_T2F_N3F_V3F // Match GL_T2F_N3F_V3F
float u, v; float u, v;
float nx, ny, nz; float nx, ny, nz;
float x, y, z; 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(); //void Normalize();
}; };
static_assert((sizeof(StdMeshVertex) & 31) == 0, "StdMeshVertex should be a multiple of 32 bytes");
struct StdMeshQuaternion struct StdMeshQuaternion
{ {