Browse Source

Skeleton: More work

Panagiotis Christopoulos Charitos 8 years ago
parent
commit
8bf2bcd0e0

+ 26 - 20
programs/GBufferGeneric.ankiprog

@@ -16,6 +16,7 @@ http://www.anki3d.org/LICENSE
 		<mutator name="NORMAL_TEX" values="0 1"/>
 		<mutator name="PARALLAX" values="0 1"/>
 		<mutator name="EMISSIVE_TEX" values="0 1"/>
+		<mutator name="BONES" values="0 1"/>
 	</mutators>
 
 	<shaders>
@@ -36,16 +37,21 @@ http://www.anki3d.org/LICENSE
 			</inputs>
 
 			<source><![CDATA[#include "shaders/GBufferCommonVert.glsl"
+
 void main() 
 {
+#if BONES
+	skinning();
+#endif
+
 #if PASS == PASS_GB_FS
 	positionUvNormalTangent(mvp, normalMat);
 	
-	#if PARALLAX
-		parallax(modelViewMat);
-	#endif
+#	if PARALLAX
+	parallax(modelViewMat);
+#	endif
 #else
-	gl_Position = mvp * vec4(in_position, 1.0);
+	gl_Position = mvp * vec4(g_position, 1.0);
 #endif
 }
 			]]>
@@ -149,37 +155,37 @@ void main()
 void main()
 {
 #if PASS == PASS_GB_FS
-	#if heightTex_DEFINED
+#	if heightTex_DEFINED
 		vec2 uv =  computeTextureCoordParallax(heightTex, in_uv, heightMapScale);
-	#else
+#	else
 		vec2 uv = in_uv;
-	#endif
+#	endif
 
-	#if diffTex_DEFINED
+#	if diffTex_DEFINED
 		vec3 diffColor = texture(diffTex, uv).rgb;
-	#endif
+#	endif
 
-	#if specTex_DEFINED
+#	if specTex_DEFINED
 		vec3 specColor = texture(specTex, uv).rgb;
-	#endif
+#	endif
 
-	#if roughnessTex_DEFINED
+#	if roughnessTex_DEFINED
 		float roughness = texture(roughnessTex, uv).r;
-	#endif
+#	endif
 
-	#if metalTex_DEFINED
+#	if metalTex_DEFINED
 		float metallic = texture(metalTex, uv).r;
-	#endif
+#	endif
 
-	#if normalTex_DEFINED
+#	if normalTex_DEFINED
 		vec3 normal = readNormalFromTexture(normalTex, uv);
-	#else
+#	else
 		vec3 normal = normalize(in_normal);
-	#endif
+#	endif
 
-	#if emissiveTex_DEFINED
+#	if emissiveTex_DEFINED
 		vec3 emission = texture(emissiveTex, uv).rgb;
-	#endif
+#	endif
 
 	writeRts(diffColor, normal, specColor, roughness, subsurface, emission, metallic);
 #elif PASS == PASS_EZ

+ 2 - 0
shaders/Common.glsl

@@ -41,6 +41,8 @@ const uint MAX_U32 = 0xFFFFFFFFu;
 #define TEXTURE_COORDINATE_LOCATION 1
 #define NORMAL_LOCATION 2
 #define TANGENT_LOCATION 3
+#define BONE_WEIGHTS_LOCATION 4
+#define BONE_INDICES_LOCATION 5
 #define SCALE_LOCATION 1
 #define ALPHA_LOCATION 2
 

+ 64 - 14
shaders/GBufferCommonVert.glsl

@@ -8,16 +8,31 @@
 
 #include "shaders/Common.glsl"
 
+//
+// Uniforms
+//
+#if BONES
+layout(ANKI_SS_BINDING(0, 0), row_major) readonly buffer ss00_
+{
+	mat4 u_boneTransforms[];
+};
+#endif
+
 //
 // Input
 //
 layout(location = POSITION_LOCATION) in highp vec3 in_position;
 #if PASS == PASS_GB_FS
 layout(location = TEXTURE_COORDINATE_LOCATION) in highp vec2 in_uv;
-layout(location = NORMAL_LOCATION) in mediump vec4 in_normal;
+layout(location = NORMAL_LOCATION) in mediump vec3 in_normal;
 layout(location = TANGENT_LOCATION) in mediump vec4 in_tangent;
 #endif
 
+#if BONES
+layout(location = BONE_WEIGHTS_LOCATION) in mediump vec4 in_boneWeights;
+layout(location = BONE_INDICES_LOCATION) in uvec4 in_boneIndices;
+#endif
+
 //
 // Output
 //
@@ -36,7 +51,17 @@ layout(location = 3) out mediump vec3 out_bitangent;
 layout(location = 4) out mediump vec3 out_vertPosViewSpace;
 layout(location = 5) out mediump vec3 out_eyeTangentSpace; // Parallax
 layout(location = 6) out mediump vec3 out_normalTangentSpace; // Parallax
-#endif // PASS == PASS_GB_FS
+#endif
+
+//
+// Globals
+//
+vec3 g_position = in_position;
+#if PASS == PASS_GB_FS
+highp vec2 g_uv = in_uv;
+mediump vec3 g_normal = in_normal;
+mediump vec4 g_tangent = in_tangent;
+#endif
 
 //
 // Functions
@@ -44,23 +69,18 @@ layout(location = 6) out mediump vec3 out_normalTangentSpace; // Parallax
 #if PASS == PASS_GB_FS
 void positionUvNormalTangent(mat4 mvp, mat3 normalMat)
 {
-	out_uv = in_uv;
-	gl_Position = mvp * vec4(in_position, 1.0);
+	out_uv = g_uv;
+	gl_Position = mvp * vec4(g_position, 1.0);
 
-	out_normal = normalMat * in_normal.xyz;
-	out_tangent.xyz = normalMat * in_tangent.xyz;
-	out_tangent.w = in_tangent.w;
+	out_normal = normalMat * g_normal.xyz;
+	out_tangent.xyz = normalMat * g_tangent.xyz;
+	out_tangent.w = g_tangent.w;
 
 #if CALC_BITANGENT_IN_VERT
-	out_bitangent = cross(out_normal, out_tangent.xyz) * in_tangent.w;
+	out_bitangent = cross(out_normal, out_tangent.xyz) * out_tangent.w;
 #endif
 }
 
-void vertPosViewSpace(in mat4 modelViewMat)
-{
-	out_vertPosViewSpace = vec3(modelViewMat * vec4(in_position, 1.0));
-}
-
 void parallax(in mat4 modelViewMat)
 {
 	vec3 n = out_normal;
@@ -72,11 +92,41 @@ void parallax(in mat4 modelViewMat)
 #endif
 	mat3 invTbn = transpose(mat3(t, b, n));
 
-	vertPosViewSpace(modelViewMat);
+	out_vertPosViewSpace = vec3(modelViewMat * vec4(g_position, 1.0));
 
 	out_eyeTangentSpace = invTbn * out_vertPosViewSpace;
 	out_normalTangentSpace = invTbn * n;
 }
 #endif // PASS == PASS_GB_FS
 
+/// Will compute new position, normal and tangent
+#if BONES
+void skinning()
+{
+	vec3 position = vec3(0.0);
+	vec3 normal = vec3(0.0);
+	vec3 tangent = vec3(0.0);
+	for(uint i = 0; i < 4; ++i)
+	{
+		uint boneIdx = in_boneIndices[i];
+		if(boneIdx < 0xFFFF)
+		{
+			float boneWeight = in_boneWeights[i];
+
+			position += (u_boneTransforms[boneIdx] * vec4(g_position * boneWeight, 1.0)).xyz;
+#if PASS == PASS_GB_FS
+			normal += (u_boneTransforms[boneIdx] * vec4(g_normal * boneWeight, 0.0)).xyz;
+			tangent += (u_boneTransforms[boneIdx] * vec4(g_tangent.xyz * boneWeight, 0.0)).xyz;
+#endif
+		}
+	}
+
+	g_position = position;
+#if PASS == PASS_GB_FS
+	g_tangent.xyz = tangent;
+	g_normal = normal;
+#endif
+}
+#endif
+
 #endif

+ 0 - 18
src/anki/core/Trace.h

@@ -168,40 +168,22 @@ using TraceManagerSingleton = Singleton<TraceManager>;
 
 /// @name Trace macros.
 /// @{
-
 #if ANKI_ENABLE_TRACE
-
 #define ANKI_TRACE_START_EVENT(name_) TraceManagerSingleton::get().startEvent()
-
 #define ANKI_TRACE_STOP_EVENT(name_) TraceManagerSingleton::get().stopEvent(TraceEventType::name_)
-
 #define ANKI_TRACE_SCOPED_EVENT(name_) \
 	ScopedTraceManagerEvent _tse##name_(&TraceManagerSingleton::get(), TraceEventType::name_)
-
 #define ANKI_TRACE_INC_COUNTER(name_, val_) TraceManagerSingleton::get().incCounter(TraceCounterType::name_, val_)
-
 #define ANKI_TRACE_START_FRAME() TraceManagerSingleton::get().startFrame()
-
 #define ANKI_TRACE_STOP_FRAME() TraceManagerSingleton::get().stopFrame()
-
 #else
-
 #define ANKI_TRACE_START_EVENT(name_) ((void)0)
 #define ANKI_TRACE_STOP_EVENT(name_) ((void)0)
 #define ANKI_TRACE_SCOPED_EVENT(name_) ((void)0)
 #define ANKI_TRACE_INC_COUNTER(name_, val_) ((void)0)
 #define ANKI_TRACE_START_FRAME() ((void)0)
 #define ANKI_TRACE_STOP_FRAME() ((void)0)
-
 #endif
-
-#define ANKI_TRACE_START_STOP_EVENT(execute, event) \
-	do                                              \
-	{                                               \
-		ANKI_TRACE_START_EVENT(event);              \
-		execute;                                    \
-		ANKI_TRACE_STOP_EVENT(event);               \
-	} while(0)
 /// @}
 
 } // end namespace anki

+ 9 - 1
src/anki/gr/gl/Common.cpp

@@ -182,6 +182,12 @@ void convertVertexFormat(const PixelFormat& fmt, U& compCount, GLenum& type, Boo
 		type = GL_UNSIGNED_BYTE;
 		normalized = true;
 	}
+	else if(fmt == PixelFormat(ComponentFormat::R16G16B16A16, TransformFormat::UINT))
+	{
+		compCount = 4;
+		type = GL_UNSIGNED_SHORT;
+		normalized = false;
+	}
 	else
 	{
 		ANKI_ASSERT(0 && "TODO");
@@ -410,7 +416,9 @@ void convertTextureInformation(const PixelFormat& pf,
 		}
 		else if(pf.m_transform == TransformFormat::UINT)
 		{
-			ANKI_ASSERT(!"TODO");
+			format = GL_RGBA;
+			internalFormat = GL_RGBA16UI;
+			type = GL_UNSIGNED_SHORT;
 		}
 		else
 		{

+ 36 - 5
src/anki/resource/Material.cpp

@@ -114,7 +114,7 @@ Error Material::parseMutators(XmlElement mutatorsEl)
 			return ErrorCode::USER_DATA;
 		}
 
-		if(mutatorName == "INSTANCE_COUNT" || mutatorName == "PASS" || mutatorName == "LOD")
+		if(mutatorName == "INSTANCE_COUNT" || mutatorName == "PASS" || mutatorName == "LOD" || mutatorName == "BONES")
 		{
 			ANKI_RESOURCE_LOGE("Cannot list builtin mutator \"%s\"", &mutatorName[0]);
 			return ErrorCode::USER_DATA;
@@ -222,6 +222,27 @@ Error Material::parseMutators(XmlElement mutatorsEl)
 		++builtinMutatorCount;
 	}
 
+	m_bonesMutator = m_prog->tryFindMutator("BONES");
+	if(m_bonesMutator)
+	{
+		if(m_bonesMutator->getValues().getSize() != 2)
+		{
+			ANKI_RESOURCE_LOGE("Mutator BONES should have 2 values in the program");
+			return ErrorCode::USER_DATA;
+		}
+
+		for(U i = 0; i < m_bonesMutator->getValues().getSize(); ++i)
+		{
+			if(m_bonesMutator->getValues()[i] != I(i))
+			{
+				ANKI_RESOURCE_LOGE("Values of the BONES mutator in the program are not the expected");
+				return ErrorCode::USER_DATA;
+			}
+		}
+
+		++builtinMutatorCount;
+	}
+
 	if(m_mutations.getSize() + builtinMutatorCount != m_prog->getMutators().getSize())
 	{
 		ANKI_RESOURCE_LOGE("Some mutatators are unacounted for");
@@ -456,7 +477,7 @@ Error Material::parseInputs(XmlElement inputsEl, Bool async)
 
 	if(nonConstInputCount != 0)
 	{
-		ANKI_RESOURCE_LOGE("Forgot to list %u shader program variables in this material", nonConstInputCount);
+		ANKI_RESOURCE_LOGE("Forgot to list %u shader program inputs in this material", nonConstInputCount);
 		return ErrorCode::USER_DATA;
 	}
 
@@ -469,7 +490,7 @@ Error Material::parseInputs(XmlElement inputsEl, Bool async)
 	return ErrorCode::NONE;
 }
 
-const MaterialVariant& Material::getOrCreateVariant(const RenderingKey& key_) const
+const MaterialVariant& Material::getOrCreateVariant(const RenderingKey& key_, Bool skinned) const
 {
 	RenderingKey key = key_;
 	key.m_lod = min<U>(m_lodCount - 1, key.m_lod);
@@ -479,15 +500,18 @@ const MaterialVariant& Material::getOrCreateVariant(const RenderingKey& key_) co
 		ANKI_ASSERT(key.m_instanceCount == 1);
 	}
 
+	ANKI_ASSERT(!skinned || m_bonesMutator);
+
 	key.m_instanceCount = 1 << getInstanceGroupIdx(key.m_instanceCount);
 
-	MaterialVariant& variant = m_variantMatrix[U(key.m_pass)][key.m_lod][getInstanceGroupIdx(key.m_instanceCount)];
+	MaterialVariant& variant =
+		m_variantMatrix[U(key.m_pass)][key.m_lod][getInstanceGroupIdx(key.m_instanceCount)][skinned];
 	LockGuard<SpinLock> lock(m_variantMatrixMtx);
 
 	if(variant.m_variant == nullptr)
 	{
 		const U mutatorCount = m_mutations.getSize() + ((m_instanceMutator) ? 1 : 0) + ((m_passMutator) ? 1 : 0)
-			+ ((m_lodMutator) ? 1 : 0);
+			+ ((m_lodMutator) ? 1 : 0) + ((m_bonesMutator) ? 1 : 0);
 
 		DynamicArrayAuto<ShaderProgramResourceMutation> mutations(getTempAllocator());
 		mutations.create(mutatorCount);
@@ -518,6 +542,13 @@ const MaterialVariant& Material::getOrCreateVariant(const RenderingKey& key_) co
 			++count;
 		}
 
+		if(m_bonesMutator)
+		{
+			mutations[count].m_mutator = m_bonesMutator;
+			mutations[count].m_value = skinned != 0;
+			++count;
+		}
+
 		m_prog->getOrCreateVariant(
 			WeakArray<const ShaderProgramResourceMutation>(mutations.getSize() ? &mutations[0] : nullptr, count),
 			WeakArray<const ShaderProgramResourceConstantValue>(

+ 3 - 2
src/anki/resource/Material.h

@@ -233,7 +233,7 @@ public:
 		return m_prog->isInstanced();
 	}
 
-	const MaterialVariant& getOrCreateVariant(const RenderingKey& key) const;
+	const MaterialVariant& getOrCreateVariant(const RenderingKey& key, Bool skinned = false) const;
 
 	const DynamicArray<MaterialVariable>& getVariables() const
 	{
@@ -261,11 +261,12 @@ private:
 	const ShaderProgramResourceMutator* m_lodMutator = nullptr;
 	const ShaderProgramResourceMutator* m_passMutator = nullptr;
 	const ShaderProgramResourceMutator* m_instanceMutator = nullptr;
+	const ShaderProgramResourceMutator* m_bonesMutator = nullptr;
 
 	DynamicArray<ShaderProgramResourceMutation> m_mutations;
 
 	/// Matrix of variants.
-	mutable Array3d<MaterialVariant, U(Pass::COUNT), MAX_LOD_COUNT, MAX_INSTANCE_GROUPS> m_variantMatrix;
+	mutable Array4d<MaterialVariant, U(Pass::COUNT), MAX_LOD_COUNT, MAX_INSTANCE_GROUPS, 2> m_variantMatrix;
 	mutable SpinLock m_variantMatrixMtx;
 
 	DynamicArray<MaterialVariable> m_vars; ///< Non-const vars.

+ 7 - 4
src/anki/resource/Model.cpp

@@ -36,7 +36,7 @@ void ModelPatch::getRenderingDataSub(
 		RenderingKey mtlKey = key;
 		mtlKey.m_lod = min<U>(key.m_lod, m_mtl->getLodCount() - 1);
 
-		const MaterialVariant& variant = m_mtl->getOrCreateVariant(mtlKey);
+		const MaterialVariant& variant = m_mtl->getOrCreateVariant(mtlKey, m_model->getSkeleton().isCreated());
 
 		inf.m_program = variant.getShaderProgram();
 	}
@@ -49,7 +49,6 @@ void ModelPatch::getRenderingDataSub(
 		vertBuffBinding.m_offset = 0;
 		vertBuffBinding.m_stride = mesh.getVertexSize();
 
-		inf.m_vertexAttributeCount = 4;
 		auto& attribs = inf.m_vertexAttributes;
 
 		U relativeOffset = 0;
@@ -64,6 +63,8 @@ void ModelPatch::getRenderingDataSub(
 		attribs[1].m_relativeOffset = relativeOffset;
 		relativeOffset += sizeof(U16) * 2;
 
+		inf.m_vertexAttributeCount = 2;
+
 		if(key.m_pass == Pass::GB_FS)
 		{
 			attribs[2].m_bufferBinding = 0;
@@ -75,11 +76,11 @@ void ModelPatch::getRenderingDataSub(
 			attribs[3].m_format = PixelFormat(ComponentFormat::R10G10B10A2, TransformFormat::SNORM);
 			attribs[3].m_relativeOffset = relativeOffset;
 			relativeOffset += sizeof(U32);
+
+			inf.m_vertexAttributeCount = 4;
 		}
 		else
 		{
-			inf.m_vertexAttributeCount = 1;
-
 			relativeOffset += sizeof(U32) * 2;
 		}
 
@@ -94,6 +95,8 @@ void ModelPatch::getRenderingDataSub(
 			attribs[5].m_format = PixelFormat(ComponentFormat::R16G16B16A16, TransformFormat::UINT);
 			attribs[5].m_relativeOffset = relativeOffset;
 			relativeOffset += sizeof(U16) * 4;
+
+			inf.m_vertexAttributeCount = 6;
 		}
 
 		ANKI_ASSERT(relativeOffset == mesh.getVertexSize());

+ 5 - 0
src/anki/resource/Model.h

@@ -177,6 +177,11 @@ public:
 		return m_visibilityShape;
 	}
 
+	SkeletonResourcePtr getSkeleton() const
+	{
+		return m_skeleton;
+	}
+
 	ANKI_USE_RESULT Error load(const ResourceFilename& filename, Bool async);
 
 private:

+ 26 - 6
src/anki/scene/ModelNode.cpp

@@ -6,6 +6,7 @@
 #include <anki/scene/ModelNode.h>
 #include <anki/scene/SceneGraph.h>
 #include <anki/scene/BodyComponent.h>
+#include <anki/scene/SkinComponent.h>
 #include <anki/scene/Misc.h>
 #include <anki/resource/Model.h>
 #include <anki/resource/ResourceManager.h>
@@ -137,7 +138,7 @@ public:
 	{
 		updated = false;
 
-		const MoveComponent& move = node.getComponentAt<MoveComponent>(0);
+		const MoveComponent& move = node.getComponent<MoveComponent>();
 		if(move.getTimestamp() == node.getGlobalTimestamp())
 		{
 			ModelNode& mnode = static_cast<ModelNode&>(node);
@@ -207,6 +208,10 @@ Error ModelNode::init(const CString& modelFname)
 
 		m_mergeKey = m_model->getUuid();
 
+		if(m_model->getSkeleton().isCreated())
+		{
+			newComponent<SkinComponent>(this, m_model->getSkeleton());
+		}
 		newComponent<MoveComponent>(this);
 		newComponent<MoveFeedbackComponent>(this);
 		newComponent<SpatialComponent>(this, &m_obb);
@@ -236,7 +241,7 @@ void ModelNode::onMoveComponentUpdate(const MoveComponent& move)
 	{
 		m_obb = m_model->getModelPatches()[0]->getBoundingShape().getTransformed(move.getWorldTransform());
 
-		SpatialComponent& sp = getComponentAt<SpatialComponent>(2);
+		SpatialComponent& sp = getComponent<SpatialComponent>();
 		sp.markForUpdate();
 		sp.setSpatialOrigin(move.getWorldTransform().getOrigin());
 	}
@@ -270,7 +275,10 @@ void ModelNode::drawCallback(RenderQueueDrawContext& ctx, WeakArray<const void*>
 		for(U i = 0; i < modelInf.m_vertexAttributeCount; ++i)
 		{
 			const VertexAttributeInfo& attrib = modelInf.m_vertexAttributes[i];
-			cmdb->setVertexAttribute(i, attrib.m_bufferBinding, attrib.m_format, attrib.m_relativeOffset);
+			if(attrib.m_format.m_components != ComponentFormat::NONE)
+			{
+				cmdb->setVertexAttribute(i, attrib.m_bufferBinding, attrib.m_format, attrib.m_relativeOffset);
+			}
 		}
 
 		// Set vertex buffers
@@ -285,18 +293,30 @@ void ModelNode::drawCallback(RenderQueueDrawContext& ctx, WeakArray<const void*>
 
 		// Uniforms
 		Array<Mat4, MAX_INSTANCES> trfs;
-		trfs[0] = Mat4(self.getComponentAt<MoveComponent>(0).getWorldTransform());
+		trfs[0] = Mat4(self.getComponent<MoveComponent>().getWorldTransform());
 		for(U i = 1; i < userData.getSize(); ++i)
 		{
 			const ModelNode& self2 = *static_cast<const ModelNode*>(userData[i]);
-			trfs[i] = Mat4(self2.getComponentAt<MoveComponent>(0).getWorldTransform());
+			trfs[i] = Mat4(self2.getComponent<MoveComponent>().getWorldTransform());
 		}
 
-		self.getComponentAt<RenderComponent>(3).allocateAndSetupUniforms(patch->getMaterial()->getDescriptorSetIndex(),
+		self.getComponent<RenderComponent>().allocateAndSetupUniforms(patch->getMaterial()->getDescriptorSetIndex(),
 			ctx,
 			WeakArray<const Mat4>(&trfs[0], userData.getSize()),
 			*ctx.m_stagingGpuAllocator);
 
+		// Bones storage
+		if(self.m_model->getSkeleton())
+		{
+			const SkinComponent& skinc = self.getComponentAt<SkinComponent>(0);
+			StagingGpuMemoryToken token;
+			void* trfs = ctx.m_stagingGpuAllocator->allocateFrame(
+				skinc.getBoneTransforms().getSize() * sizeof(Mat4), StagingGpuMemoryType::STORAGE, token);
+			memcpy(trfs, &skinc.getBoneTransforms()[0], skinc.getBoneTransforms().getSize() * sizeof(Mat4));
+
+			cmdb->bindStorageBuffer(0, 0, token.m_buffer, token.m_offset, token.m_range);
+		}
+
 		// Draw
 		cmdb->drawElements(PrimitiveTopology::TRIANGLES,
 			modelInf.m_indicesCountArray[0],

+ 4 - 0
src/anki/scene/SkinComponent.cpp

@@ -16,6 +16,10 @@ SkinComponent::SkinComponent(SceneNode* node, SkeletonResourcePtr skeleton)
 	, m_skeleton(skeleton)
 {
 	m_boneTrfs.create(getAllocator(), m_skeleton->getBones().getSize());
+	for(Mat4& trf : m_boneTrfs)
+	{
+		trf.setIdentity();
+	}
 }
 
 SkinComponent::~SkinComponent()

+ 5 - 0
src/anki/scene/SkinComponent.h

@@ -31,6 +31,11 @@ public:
 
 	void playAnimation(U track, AnimationResourcePtr anim, F64 startTime, Bool repeat);
 
+	const DynamicArray<Mat4>& getBoneTransforms() const
+	{
+		return m_boneTrfs;
+	}
+
 private:
 	class Track
 	{

+ 15 - 0
tools/scene/ExporterMesh.cpp

@@ -396,6 +396,21 @@ void Exporter::exportMesh(const aiMesh& mesh, const aiMatrix4x4* transform, unsi
 		// Write
 		if(hasBoneWeights)
 		{
+			// Normalize weights
+			float totalWeight = 0;
+			for(unsigned j = 0; j < weights[i].m_boneCount; ++j)
+			{
+				totalWeight += weights[i].m_weights[j];
+			}
+
+			if(totalWeight > 0.0)
+			{
+				for(unsigned j = 0; j < weights[i].m_boneCount; ++j)
+				{
+					weights[i].m_weights[j] /= totalWeight;
+				}
+			}
+
 			for(unsigned j = 0; j < weights[i].m_boneCount; ++j)
 			{
 				vert.m_boneIndices[j] = weights[i].m_boneIndices[j];