forked from Mirrors/openclonk
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
commit
4639ce1675
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -68,6 +68,8 @@ enum C4SS_Uniforms
|
|||
C4SSU_AmbientTransform, // C4SSC_LIGHT
|
||||
C4SSU_AmbientBrightness, // C4SSC_LIGHT
|
||||
|
||||
C4SSU_Bones, // for meshes
|
||||
|
||||
C4SSU_Count
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue