unity gpu instance skin mesh骨骼动画

 运行效果图如上

原理很简单,

1 先对动画进行采样(利用Animator StartRecording/Update/StopRecording这三个接口)

2 然后回放(利用Animator StartPlayback/Update/playbackTime这三个接口)

3 从SkinnedMeshRenderer里取出mesh数据(该mesh数据应该是包含bone index的)

对每个bone(就是transform)计算变换矩阵

4 将生成的矩阵数据存入纹理

5 将纹理传递给shader,根据当前动画播放到哪一帧,计算出纹理采样索引(利用shader参数BLENDINDICES,该参数在mesh有bone idx数据情况下,会由unity自动传递给shader)

如上图所示,观察mesh数据,有红框里这样的,就是包含bone idx的

计算骨骼矩阵:

原理很简单

骨骼有根节占, 每个骨骼节点有父子关系,子节点相对父节点有相对的旋转平移,记录为<矩阵M>

根据矩阵的结合律,把子节点依次按父节点回朔,对应的<矩阵M>依次相乘,就可以得到该节点

在世界空间中的新的变换矩阵

上代码,计算子节点的变换矩阵:

            for (int bone_idx = 0; bone_idx < bones.Length; ++bone_idx)
            {
                {
                    Transform tmp_bone = bones[bone_idx];
                    List<Transform> lst_parent = new List<Transform>();
                    lst_parent.Add(tmp_bone);
                    while (tmp_bone.name != gdata.root_name)
                    {
                        tmp_bone = tmp_bone.parent;
                        if (!tmp_bone)
                            break;
                        lst_parent.Add(tmp_bone);
                    }

                    Matrix4x4 tmp_mtx = bindposes[bone_idx];
                    foreach (var tnode in lst_parent)
                    {
                        Matrix4x4 mat = Matrix4x4.TRS(tnode.transform.localPosition, tnode.transform.localRotation, tnode.transform.localScale);
                        Matrix4x4 tm = tmp_mtx;
                        tmp_mtx = mat * tm;
                    }

                    for (int row_idx = 0; row_idx < 4; ++row_idx)
                    {
                        var row = tmp_mtx.GetRow(row_idx);

                        tex_clr_identity[texel_index_identity++] = new Color(row.x, row.y, row.z, row.w);
                    }
                    Debug.Log(bone_idx + ":" + bones[bone_idx].name + " " + tmp_mtx);
                }
            }

假设动画有10帧,骨骼有20个,那么生成的动画矩阵个数为200个,一个矩阵16个浮点数,占4个像素

要显示某一帧动画,在shader根据 帧数*(20*4) +(bone_idx*4)索引到该节点的矩阵

shader代码

Shader "lsc/gpu_instancing"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //启用gpu instancing
            #pragma multi_compile_instancing 

            //开启主灯光的阴影
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile_fragment _ _SHADOWS_SOFT
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            //自定义数组,保存每个实例的颜色
            StructuredBuffer<float4> _instancing_color;
            StructuredBuffer<int> _ani_matrix_index;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                uint4 vBones : BLENDINDICES;
                float4 vWeights : BLENDWEIGHTS;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;

                float3 positionWS               : TEXCOORD2;
                float4 positionCS               : SV_POSITION;

                uint4 vBones : BLENDINDICES;
                float4 vWeights : BLENDWEIGHTS;

                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            TEXTURE2D_FLOAT(_tex_ani);
            SAMPLER(sampler_PointClamp);

            int _tex_ani_side_length;
            float _tex_ani_unit_size;


            float4x4 cal_bone_mtx(const uint bone_idx)
            {
#ifdef UNITY_INSTANCING_ENABLED
                uint idx_1dx = _ani_matrix_index[unity_InstanceID] + bone_idx * 4;
#else
                uint idx_1dx = bone_idx * 4;
#endif

                uint x = idx_1dx % _tex_ani_side_length;
                uint y = idx_1dx / _tex_ani_side_length;

                float2 ani_xy = float2(0, 0);
                ani_xy.x = (float)x / _tex_ani_side_length;
                ani_xy.y = (float)y / _tex_ani_side_length;
                float4 mat1 = SAMPLE_TEXTURE2D_LOD(_tex_ani, sampler_PointClamp, ani_xy + float2(0, 0), 0);
                float4 mat2 = SAMPLE_TEXTURE2D_LOD(_tex_ani, sampler_PointClamp, ani_xy + float2(1.0 / _tex_ani_side_length, 0), 0);
                float4 mat3 = SAMPLE_TEXTURE2D_LOD(_tex_ani, sampler_PointClamp, ani_xy + float2(2.0 / _tex_ani_side_length, 0), 0);
                float4 mat4 = SAMPLE_TEXTURE2D_LOD(_tex_ani, sampler_PointClamp, ani_xy + float2(3.0 / _tex_ani_side_length, 0), 0);


                float4x4 mtx_x = float4x4(
                    mat1,
                    mat2,
                    mat3,
                    float4(0, 0, 0, 1)
                    );

                return mtx_x;
            }

            v2f vert (appdata v)
            {
                v2f o;
                //给unity_InstanceID赋值,使urp的内部函数会自动调用unity_Builtins0数组中的属性
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);

                float3 obj_vertex = v.vertex.xyz;

                float4x4 mtx_1 = cal_bone_mtx(v.vBones.x);
                float4x4 mtx_2 = cal_bone_mtx(v.vBones.y);
                float4x4 mtx_3 = cal_bone_mtx(v.vBones.z);
                float4x4 mtx_4 = cal_bone_mtx(v.vBones.w);

                float4x4 mtx =
                    mtx_1 * v.vWeights.x +
                    mtx_2 * v.vWeights.y +
                    mtx_3 * v.vWeights.z +
                    mtx_4 * v.vWeights.w;

                obj_vertex = mul(mtx_1, float4(v.vertex.xyz, 1.0f)).xyz;

                //内部会自动取实例的变幻矩阵,在不同的位置画出实例
                //VertexPositionInputs vertexInput = GetVertexPositionInputs(obj_vertex);
                //o.positionWS = vertexInput.positionWS;
                //o.positionCS = vertexInput.positionCS;

                o.positionWS = mul(UNITY_MATRIX_M,  float4(obj_vertex, 1.0)).xyz;
                o.positionCS = mul(UNITY_MATRIX_VP, float4(o.positionWS, 1.0));
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.vBones = v.vBones;
                o.vWeights = v.vWeights;
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);

                half4 col = tex2D(_MainTex, i.uv);
                //计算主光与阴影
                float4 shadow_coord = TransformWorldToShadowCoord(i.positionWS);
                Light mainLight = GetMainLight(shadow_coord);
                half3 light_col = mainLight.color * mainLight.shadowAttenuation;


#ifdef UNITY_INSTANCING_ENABLED
                col.rgb = light_col * col.rgb * _instancing_color[unity_InstanceID];
#else
                col.rgb = light_col * col.rgb;
#endif

                return col;
            }
            ENDHLSL
        }

        Pass //产生阴景 todo 根据ShadowCasterPass.hlsl修改成gpu skin,从而能产生正确的阴影
        {
            Name "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}

            ZWrite On
            ZTest LEqual
            ColorMask 0
            Cull[_Cull]

            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            // -------------------------------------
            // Material Keywords
            #pragma shader_feature_local_fragment _ALPHATEST_ON
            #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A

            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing
            #pragma multi_compile _ DOTS_INSTANCING_ON

            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment

            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
            ENDHLSL
		}

        Pass //写入深度 todo 修改成gpu skin
        {
            Name "DepthOnly"
            Tags{"LightMode" = "DepthOnly"}

            ZWrite On
            ColorMask 0
            Cull[_Cull]

            HLSLPROGRAM
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment

            // -------------------------------------
            // Material Keywords
            #pragma shader_feature_local_fragment _ALPHATEST_ON
            #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A

            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing
            #pragma multi_compile _ DOTS_INSTANCING_ON

            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }
    }
}

对应的cs代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using System.IO;
using UnityEditor;

//此脚本挂在任意物体(空物体也可)


public class gpu_instancing : MonoBehaviour
{
    public int gobj_line_count_ = 20;
    public int gobj_count_perline_ = 20;

    public GameObject gobj_gpu_instancing_;
    //public AnimationClip ani_clip_;
    public Texture2D tex_surface_;

    public Texture2D tex_ani_;
    public Texture2D tex_ani_tmp_;
    public int tex_ani_side_length_ = 0;
    public int ani_len_ = 0;
    public int bone_count = 0;
    public float ani_rate_ = 1.0f;

    const int PIXEL_PER_MATRIX = 4;

    int ani_idx_ = 0;

    public string ani_prefix_name_;



    //网格数据
    //public Mesh instance_mesh_;
    //gpu instancing shader
    public Material mtrl_instance_;
    public Material mtrl_work_;

    ComputeBuffer cb_color_;
    List<float4> lst_color_;

    ComputeBuffer cb_ani_matrix_index_;
    List<int> lst_ani_matrix_index_;

    void Start()
    {
        tex_ani_tmp_ = tex_ani_;

        int total_count = gobj_line_count_ * gobj_count_perline_;

        cb_color_ = new ComputeBuffer(total_count, sizeof(float) * 4);
        lst_color_ = new List<float4>();

        cb_ani_matrix_index_ = new ComputeBuffer(total_count, sizeof(int));
        lst_ani_matrix_index_ = new List<int>();

        mtrl_work_ = new Material(mtrl_instance_);
        mtrl_work_.mainTexture = tex_surface_;

        gen_ani_tex_by_AnimationClip();
    }

    //根据动画剪辑生成骨骼变换矩阵纹理,mesh的顶点属性应有bones index, bones weight属性
    void gen_ani_tex_by_AnimationClip()
    {
        if (!gobj_gpu_instancing_)
            return;

        var tmp_gobj = GameObject.Instantiate(gobj_gpu_instancing_, new Vector3(0, 0, 0), Quaternion.identity);

        gpu_instancing_data gdata = tmp_gobj.GetComponent<gpu_instancing_data>();
        if (!gdata)
        {
            Debug.Log("NO gpu instancing data com");
            return;
        }

        if (!gdata.gobj_skinmesh_render_)
            return;

        //从预制体中取出mesh与骨骼动画
        SkinnedMeshRenderer skin_render = null;
        skin_render = gdata.gobj_skinmesh_render_.GetComponent<SkinnedMeshRenderer>();
        if (!skin_render)
            return;
        var bindposes = skin_render.sharedMesh.bindposes;
        var bones = skin_render.bones;
        var bone_weight = skin_render.sharedMesh.boneWeights;
        this.bone_count = bones.Length;

        var animator = tmp_gobj.GetComponent<Animator>();
        if (!animator)
            return;

        //从动画状态机里取出动画剪辑
        RuntimeAnimatorController ani_ctrl = animator.runtimeAnimatorController;
        if (!ani_ctrl)
            return;
        if (ani_ctrl.animationClips.Length < 1)
            return;
        var tmp_ani_clip = ani_ctrl.animationClips[0];

        int frame_count = (int)(tmp_ani_clip.frameRate * tmp_ani_clip.length);
        ani_len_ = frame_count;
        //记录动画帧率
        ani_rate_ = 1.0f / tmp_ani_clip.frameRate;

        //录制动画
        {
            animator.applyRootMotion = false;//避免物体跑来跑去
            animator.Rebind();
            animator.recorderStartTime = 0;
            animator.StartRecording(frame_count);
            for (int i = 0; i < frame_count; ++i)
            {
                animator.Update(1.0f / tmp_ani_clip.frameRate);
            }
            animator.StopRecording();
            animator.StartPlayback();
        }

        int max_matrix_count = frame_count * bones.Length;
        int pixel_count = max_matrix_count * PIXEL_PER_MATRIX;
        int tex_width = (int)Mathf.Ceil(Mathf.Sqrt(pixel_count)), tex_height = 1;
        while (tex_height < tex_width)
            tex_height = tex_height << 1;
        this.tex_ani_side_length_ = tex_width = tex_height;

        //生成一个临时纹理,记录骨骼动画矩阵
        tex_ani_tmp_ = new Texture2D(tex_width, tex_height, TextureFormat.RGBAHalf, false);
        var tex_clr_identity = tex_ani_tmp_.GetPixels();
        int texel_index_identity = 0;

        for (int j = 0; j < frame_count; j++)
        {
            float t = (j / (float)frame_count) * tmp_ani_clip.length;

            //回放动画
            animator.playbackTime = t;
            animator.Update(0);

            for (int bone_idx = 0; bone_idx < bones.Length; ++bone_idx)
            {
                {
                    Transform tmp_bone = bones[bone_idx];
                    List<Transform> lst_parent = new List<Transform>();
                    lst_parent.Add(tmp_bone);
                    while (tmp_bone.name != gdata.root_name)
                    {
                        tmp_bone = tmp_bone.parent;
                        if (!tmp_bone)
                            break;
                        lst_parent.Add(tmp_bone);
                    }

                    Matrix4x4 tmp_mtx = bindposes[bone_idx];
                    foreach (var tnode in lst_parent)
                    {
                        Matrix4x4 mat = Matrix4x4.TRS(tnode.transform.localPosition, tnode.transform.localRotation, tnode.transform.localScale);
                        Matrix4x4 tm = tmp_mtx;
                        tmp_mtx = mat * tm;
                    }

                    for (int row_idx = 0; row_idx < 4; ++row_idx)
                    {
                        var row = tmp_mtx.GetRow(row_idx);

                        tex_clr_identity[texel_index_identity++] = new Color(row.x, row.y, row.z, row.w);
                    }
                    Debug.Log(bone_idx + ":" + bones[bone_idx].name + " " + tmp_mtx);
                }
            }
        }

        tex_ani_tmp_.SetPixels(tex_clr_identity);
        tex_ani_tmp_.Apply();

        DestroyImmediate(tmp_gobj);
    }

    void Update()
    {
        if (!gobj_gpu_instancing_)
        {
            Debug.Log("NO gpu game obj");
            return;
        }

        gpu_instancing_data gdata = gobj_gpu_instancing_.GetComponent<gpu_instancing_data>();
        if (!gdata)
        {
            Debug.Log("NO gpu instancing data com");
            return;
        }
        Mesh instance_mesh = gdata.gobj_skinmesh_render_.GetComponent<SkinnedMeshRenderer>().sharedMesh;

        //绘制gpu instancing 100个小熊
        lst_color_.Clear();
        lst_ani_matrix_index_.Clear();
        Vector3 pos = this.transform.position;
        var matrices = new Matrix4x4[gobj_line_count_ * gobj_count_perline_];
        for (int i = 0; i < gobj_line_count_; i++)
        {
            for (int j = 0; j < gobj_count_perline_; j++)
            {
                Vector3 tmppos = pos;
                tmppos.z = pos.z + 2.0f * i;
                tmppos.x = pos.x + 2.0f * j;

                var scale = new Vector3(1.0f, 1.0f, 1.0f);
                Quaternion q = Quaternion.AngleAxis(30, Vector3.up);
                if (i == 0 && (j < 2))
                    q = Quaternion.identity;

                var matrix = Matrix4x4.TRS(tmppos, q, scale);

                matrices[i * gobj_count_perline_ + j] = matrix;

                //每个实例颜色赋值
                lst_color_.Add(new float4((float)i / gobj_line_count_, (float)j / gobj_count_perline_, 0.0f, 1.0f));
                int a_idx = (ani_idx_ + i * 50 * gobj_line_count_ + j * 50 * gobj_count_perline_) % ani_len_;
                lst_ani_matrix_index_.Add(a_idx * bone_count * 4);
            }
        }
        
        ani_idx_ = (int)(Time.time / ani_rate_) % ani_len_;

        cb_color_.SetData(lst_color_);
        cb_ani_matrix_index_.SetData(lst_ani_matrix_index_);

        //将生成的动画矩阵纹理传给shader
        mtrl_work_.SetTexture(Shader.PropertyToID("_tex_ani"), tex_ani_tmp_);
        mtrl_work_.SetInt(Shader.PropertyToID("_tex_ani_side_length"), tex_ani_side_length_);
        mtrl_work_.SetFloat(Shader.PropertyToID("_tex_ani_unit_size"), 1.0f / tex_ani_side_length_);
        //每个实例的颜色数组
        mtrl_work_.SetBuffer(Shader.PropertyToID("_instancing_color"), cb_color_);
        mtrl_work_.SetBuffer(Shader.PropertyToID("_ani_matrix_index"), cb_ani_matrix_index_);

        if (mtrl_work_)
            Graphics.DrawMeshInstanced(instance_mesh, 0, mtrl_work_, matrices, gobj_line_count_ * gobj_count_perline_);//materices是变换矩阵
    }

工程链接:

unity/Assets/script/gpu_instancing at master · lsccsl/unity · GitHubContribute to lsccsl/unity development by creating an account on GitHub.https://github.com/lsccsl/unity/tree/master/Assets/script/gpu_instancing

猜你喜欢

转载自blog.csdn.net/lsccsl/article/details/120726554