Search Unity

Skinning

Discussion in 'Shaders' started by felipin, Sep 19, 2019.

  1. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    49
    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:
    upload_2019-9-18_22-55-33.png

    SkinningDebug.cs
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class SkinningDebug : MonoBehaviour
    4. {
    5.    Matrix4x4[] m_Bones = new Matrix4x4[100];
    6.  
    7.    public Material Material;
    8.  
    9.    SkinnedMeshRenderer m_SkinnedMeshRenderer;
    10.  
    11.    void Awake()
    12.    {
    13.       m_SkinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
    14.       var Mesh = m_SkinnedMeshRenderer.sharedMesh;
    15.  
    16.       var GameObject = new GameObject("GameObject", typeof(MeshRenderer), typeof(MeshFilter));
    17.  
    18.       var MeshFilter = GameObject.GetComponent<MeshFilter>();
    19.       MeshFilter.sharedMesh = Mesh;
    20.  
    21.       var MeshRenderer = GameObject.GetComponent<MeshRenderer>();
    22.       MeshRenderer.sharedMaterial = Material;
    23.       MeshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    24.       MeshRenderer.allowOcclusionWhenDynamic = false;
    25.       MeshRenderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
    26.  
    27.       var Bindposes = Mesh.bindposes;
    28.       Shader.SetGlobalMatrixArray("bindposes", Bindposes);
    29.  
    30.       m_Bones = new Matrix4x4[100];
    31.    }
    32.  
    33.    void FixedUpdate()
    34.    {
    35.       for (int Index = 0; Index < m_SkinnedMeshRenderer.bones.Length; Index++)
    36.          m_Bones[Index] = m_SkinnedMeshRenderer.bones[Index].localToWorldMatrix;
    37.  
    38.       Shader.SetGlobalMatrixArray("bones", m_Bones);
    39.    }
    40. }
    41.  
    DebugSkinning.shader

    Code (CSharp):
    1. Shader "Debug/Skinning"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _SkeletonIndex("Skeleton Index", Int) = 0
    7.     }
    8.  
    9.         SubShader
    10.         {
    11.             Tags { "RenderType" = "Opaque" }
    12.  
    13.             CGPROGRAM
    14.  
    15.             #pragma multi_compile_instancing
    16.             #pragma surface surf Lambert vertex:vert
    17.             #pragma target 4.5
    18.  
    19.             #include "UnityCG.cginc"
    20.             #include "FTech.Animations/Skinning.cginc"      
    21.  
    22.             struct Input
    23.             {
    24.                 float2 uv_MainTex;
    25.                 float3 customColor;
    26.             };
    27.  
    28.             struct appdata
    29.             {
    30.                 float4 vertex            : POSITION0;
    31.                 float3 normal            : NORMAL;
    32.                 float4 texcoord            : TEXCOORD0;
    33.                 float4 texcoord1        : TEXCOORD1;
    34.                 float4 texcoord2        : TEXCOORD2;
    35.                 float4 tangent            : TANGENT;
    36.                 float4 color            : COLOR;
    37.                 float4 blendedWeights    : BLENDWEIGHTS;
    38.                 int4 blendedIndices        : BLENDINDICES;
    39.  
    40.                 INTERNAL_DATA
    41.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    42.             };
    43.  
    44.             uniform float4x4 bindposes[100];
    45.             uniform float4x4 bones[100];
    46.  
    47.             sampler2D _MainTex;
    48.  
    49.             void vert(inout appdata v, out Input o)
    50.             {
    51.                 UNITY_INITIALIZE_OUTPUT(Input, o);
    52.                 UNITY_SETUP_INSTANCE_ID(v);
    53.  
    54.                 int SkeletonIndex = UNITY_ACCESS_INSTANCED_PROP(Skeleton, _SkeletonIndex);
    55.  
    56.                 float4x4 bindpose = 0;
    57.                 float4x4 bone = 0;
    58.  
    59.                 for (int i = 0; i < 4; i++)
    60.                 {
    61.                     float weight = v.blendedWeights[i];
    62.                     int index = v.blendedIndices[i];
    63.  
    64.                     bindpose += bindposes[index] * weight;
    65.                     bone += bones[index] * weight;
    66.                 }
    67.  
    68.                 float4x4 blendedMatrix = mul(bone, bindpose);
    69.  
    70.                 v.vertex = mul(blendedMatrix, v.vertex);
    71.                 v.normal = mul(blendedMatrix, v.normal).xyz;
    72.                 v.tangent = mul(blendedMatrix, v.tangent);
    73.             }
    74.  
    75.  
    76.             void surf(Input IN, inout SurfaceOutput o)
    77.             {
    78.                 o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
    79.             }
    80.             ENDCG
    81.         }
    82.         Fallback "Diffuse"
    83. }
    Anyone know what is going on?
     
    Last edited: Sep 19, 2019
    Zee-Play likes this.
  2. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    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.
     
  3. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    49
    Built-in Shaders: Internal-Skinning.compute
    Code (CSharp):
    1.  
    2. #if SKIN_BONESFORVERT == 0
    3.     uint startIndex = si.index0;
    4.     uint endIndex = inSkin[t + 1].index0;
    5.     float4x4 blendedMatrix = 0;
    6.     for (uint i = startIndex; i < endIndex; i++)
    7.     {
    8.         uint weightAndIndex = inSkin[i].index0;
    9.         float weight = float(weightAndIndex >> 16) * (1.0f / 65535.0f);
    10.         uint index = weightAndIndex & 0xFFFF;
    11.         blendedMatrix += inMatrices[index] * weight;
    12.     }
    13. #elif SKIN_BONESFORVERT == 1
    14.     const float4x4 blendedMatrix = inMatrices[si.index0];
    15. #elif SKIN_BONESFORVERT == 2
    16.     const float4x4 blendedMatrix = inMatrices[si.index0] * si.weight0 +
    17.                                    inMatrices[si.index1] * si.weight1;
    18. #elif SKIN_BONESFORVERT == 4
    19.     const float4x4 blendedMatrix = inMatrices[si.index0] * si.weight0 +
    20.                                    inMatrices[si.index1] * si.weight1 +
    21.                                    inMatrices[si.index2] * si.weight2 +
    22.                                    inMatrices[si.index3] * si.weight3;
    23. #endif
    24.  
    25.     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.
     
  4. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    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):
    1. float4 pos = 0;
    2. float4 normal = 0;
    3.  
    4. for (int i = 0; i < 4; i++)
    5. {
    6.     int index = v.blendedIndices[i];
    7.     float weight = v.blendedWeights[i];
    8.  
    9.     float4x4 blendedMatrix = mul(bones[index], bindposes[index]);
    10.  
    11.     pos += mul(blendedMatrix, v.vertex) * weight;
    12.     normal += mul(blendedMatrix, v.normal) * weight;
    13. }
    14.  
    15. v.vertex = pos;
    16. v.normal = normal;

    Or you can use your way but you have to multiply the matrices first and blend them second.
    Code (CSharp):
    1. float4x4 blendedMatrix = 0;
    2.  
    3. for (int i = 0; i < 4; i++)
    4. {
    5.     float weight = v.blendedWeights[i];
    6.     int index = v.blendedIndices[i];
    7.  
    8.     blendedMatrix += mul(bones[index], bindposes[index]) * weight;
    9. }
    10.  
    11. v.vertex = mul(blendedMatrix, v.vertex);
    12. v.normal = mul(blendedMatrix, v.normal).xyz;
    13. v.tangent = mul(blendedMatrix, v.tangent);
     
    Zee-Play and felipin like this.
  5. felipin

    felipin

    Joined:
    Nov 18, 2015
    Posts:
    49
    I can't thank you enough, it's working perfectly now :D

    upload_2019-9-19_14-59-24.png
     
  6. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    No problem. I learned something new today too.
     
    felipin likes this.