I'm trying to understand how skeleton/skinning works, so I've decided to write a shader to reproduce skinning (w/ bone matrices from a SkinnedMeshRenderer), but it isn't working properly: SkinningDebug.cs Code (CSharp): using UnityEngine; public class SkinningDebug : MonoBehaviour { Matrix4x4[] m_Bones = new Matrix4x4[100]; public Material Material; SkinnedMeshRenderer m_SkinnedMeshRenderer; void Awake() { m_SkinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>(); var Mesh = m_SkinnedMeshRenderer.sharedMesh; var GameObject = new GameObject("GameObject", typeof(MeshRenderer), typeof(MeshFilter)); var MeshFilter = GameObject.GetComponent<MeshFilter>(); MeshFilter.sharedMesh = Mesh; var MeshRenderer = GameObject.GetComponent<MeshRenderer>(); MeshRenderer.sharedMaterial = Material; MeshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; MeshRenderer.allowOcclusionWhenDynamic = false; MeshRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; var Bindposes = Mesh.bindposes; Shader.SetGlobalMatrixArray("bindposes", Bindposes); m_Bones = new Matrix4x4[100]; } void FixedUpdate() { for (int Index = 0; Index < m_SkinnedMeshRenderer.bones.Length; Index++) m_Bones[Index] = m_SkinnedMeshRenderer.bones[Index].localToWorldMatrix; Shader.SetGlobalMatrixArray("bones", m_Bones); } } DebugSkinning.shader Code (CSharp): Shader "Debug/Skinning" { Properties { _MainTex("Texture", 2D) = "white" {} _SkeletonIndex("Skeleton Index", Int) = 0 } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma multi_compile_instancing #pragma surface surf Lambert vertex:vert #pragma target 4.5 #include "UnityCG.cginc" #include "FTech.Animations/Skinning.cginc" struct Input { float2 uv_MainTex; float3 customColor; }; struct appdata { float4 vertex : POSITION0; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 tangent : TANGENT; float4 color : COLOR; float4 blendedWeights : BLENDWEIGHTS; int4 blendedIndices : BLENDINDICES; INTERNAL_DATA UNITY_VERTEX_INPUT_INSTANCE_ID }; uniform float4x4 bindposes[100]; uniform float4x4 bones[100]; sampler2D _MainTex; void vert(inout appdata v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input, o); UNITY_SETUP_INSTANCE_ID(v); int SkeletonIndex = UNITY_ACCESS_INSTANCED_PROP(Skeleton, _SkeletonIndex); float4x4 bindpose = 0; float4x4 bone = 0; for (int i = 0; i < 4; i++) { float weight = v.blendedWeights[i]; int index = v.blendedIndices[i]; bindpose += bindposes[index] * weight; bone += bones[index] * weight; } float4x4 blendedMatrix = mul(bone, bindpose); v.vertex = mul(blendedMatrix, v.vertex); v.normal = mul(blendedMatrix, v.normal).xyz; v.tangent = mul(blendedMatrix, v.tangent); } void surf(Input IN, inout SurfaceOutput o) { o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb; } ENDCG } Fallback "Diffuse" } Anyone know what is going on?
Blending matrices makes no mathematical sense. You have to blend the results. Multiply every vertex with all four matrices and blend the resulting vectors instead. Also, since you won't be modifiing the matrices you can premultiply bindpose and bone matrices outside of the shader.
Built-in Shaders: Internal-Skinning.compute Code (CSharp): #if SKIN_BONESFORVERT == 0 uint startIndex = si.index0; uint endIndex = inSkin[t + 1].index0; float4x4 blendedMatrix = 0; for (uint i = startIndex; i < endIndex; i++) { uint weightAndIndex = inSkin[i].index0; float weight = float(weightAndIndex >> 16) * (1.0f / 65535.0f); uint index = weightAndIndex & 0xFFFF; blendedMatrix += inMatrices[index] * weight; } #elif SKIN_BONESFORVERT == 1 const float4x4 blendedMatrix = inMatrices[si.index0]; #elif SKIN_BONESFORVERT == 2 const float4x4 blendedMatrix = inMatrices[si.index0] * si.weight0 + inMatrices[si.index1] * si.weight1; #elif SKIN_BONESFORVERT == 4 const float4x4 blendedMatrix = inMatrices[si.index0] * si.weight0 + inMatrices[si.index1] * si.weight1 + inMatrices[si.index2] * si.weight2 + inMatrices[si.index3] * si.weight3; #endif oPos = mul(blendedMatrix, float4(vPos, 1)).xyz; Unity GPU Skinning blends bone matrices. This code is just to undersand skinning and skeleton transformation, indeed. It didn't work.
Interesting. I guess it works in this particular case. As long as the sum of all weights is exactly one. You can make a skin that violates that rule but I guess unity normalizes the weights during import. Anyway, you can either do it the way I suggested. I just checked it does work: Code (CSharp): float4 pos = 0; float4 normal = 0; for (int i = 0; i < 4; i++) { int index = v.blendedIndices[i]; float weight = v.blendedWeights[i]; float4x4 blendedMatrix = mul(bones[index], bindposes[index]); pos += mul(blendedMatrix, v.vertex) * weight; normal += mul(blendedMatrix, v.normal) * weight; } v.vertex = pos; v.normal = normal; Or you can use your way but you have to multiply the matrices first and blend them second. Code (CSharp): float4x4 blendedMatrix = 0; for (int i = 0; i < 4; i++) { float weight = v.blendedWeights[i]; int index = v.blendedIndices[i]; blendedMatrix += mul(bones[index], bindposes[index]) * weight; } v.vertex = mul(blendedMatrix, v.vertex); v.normal = mul(blendedMatrix, v.normal).xyz; v.tangent = mul(blendedMatrix, v.tangent);