GPUInstance合并人物烘培顶点动画

2023-11-07

将4个小人合成一个,使用gpu Instance批量绘制可以使用gpu Instance的特性降低批处理,但是gpu Instance只支持mesh filter所有需要将蒙皮动画烘培成顶点动画,通过MaterialPropertyBlock给shader传值区分显示的人物。

烘培人物和动画代码

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

[CustomEditor(typeof(BakingAnimMesh))]
public class BakingAnimMeshEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        BakingAnimMesh myScript = (BakingAnimMesh)target;

        if (GUILayout.Button("提取"))
        {
            myScript.SaveAsset();
        }
    }
}

public class BakingAnimMesh : MonoBehaviour
{
    public AnimationClip clip;//指定要烘培的动画
    public Animator[] animator;//有动画组件的人物
    public SkinnedMeshRenderer[] sk;//动画人物的蒙皮网格渲染器
    int pnum;//总像素点
    Texture2D texture;
    int size = 0;//图片的宽高
    int vertexCount = 0;//总顶点数
    int frameCount;//总帧数
    public string path = "mini";//导出名称

    //导出动画图片,合并后的网格,合并后的贴图
    public void SaveAsset()
    {
        //总帧数 = 动画时长 * 帧率
        frameCount = Mathf.CeilToInt(clip.length * 30);
        Debug.Log("frameCount:" + frameCount);
        //顶点个数
        int[] vertexNum = new int[sk.Length + 1];
        vertexNum[0] = 0;
        for (int i = 0; i < sk.Length; i++)
        {
            vertexCount += sk[i].sharedMesh.vertexCount;
            vertexNum[i + 1] = vertexCount;
        }
        Debug.Log("vertexCount:" + vertexCount);
        //总像素点 = 总顶点数 * 总帧数
        pnum = vertexCount * frameCount;
        Debug.Log("pnum:" + pnum);
        //将像素点数转换成2的倍数的贴图宽高
        size = Mathf.NextPowerOfTwo(Mathf.CeilToInt(Mathf.Sqrt(pnum)));
        Debug.Log("size:" + size);
        texture = new Texture2D(size, size, TextureFormat.RGBAFloat, false, true);
        //计算顶点的最大最小值
        _CalculateVertexMinAndMax(out float min, out float max);
        Debug.Log("min:" + min);
        Debug.Log("max:" + max);
        //烘培图片
        _BakeAnimationClip(max, min);
        //合并导出网格
        Mesh mesh = new Mesh();
        //合并网格贴图
        List<Texture2D> textures = new List<Texture2D>();
        for (int i = 0; i < sk.Length; i++)
        {
            textures.Add(sk[i].sharedMaterial.mainTexture as Texture2D);
        }
        Texture2D meshtexture = new Texture2D(2048, 2048, TextureFormat.RGBAFloat, false, true);
        Rect[] rects = meshtexture.PackTextures(textures.ToArray(), 0);
        meshtexture.Apply();
        //合并网格
        List<CombineInstance> combines = new List<CombineInstance>();
        List<Vector2[]> olduvs = new List<Vector2[]>();
        for (int i = 0; i < sk.Length; i++)
        {
            CombineInstance combine = new CombineInstance();
            combine.mesh = sk[i].sharedMesh;
            combine.transform = sk[i].transform.localToWorldMatrix;

            Vector2[] olduv = sk[i].sharedMesh.uv;
            olduvs.Add(olduv);
            Vector2[] newuv = new Vector2[olduv.Length];
            for (int j = 0; j < olduv.Length; j++)
            {
                newuv[j] = new Vector2(rects[i].x + rects[i].width * olduv[j].x, rects[i].y + rects[i].height * olduv[j].y);
            }
            combine.mesh.uv = newuv;
            combines.Add(combine);
        }
        mesh.CombineMeshes(combines.ToArray(), true, true);
        for (int i = 0; i < sk.Length; i++)
        {
            sk[i].sharedMesh.uv = olduvs[i];
        }
        //导出网格
        AssetDatabase.CreateAsset(mesh, "Assets/" + path + ".asset");
        //导出贴图
        File.WriteAllBytes(Application.dataPath + "/" + path + ".png", meshtexture.EncodeToPNG());
    }

    public  void _BakeAnimationClip(float max, float min)
    {
        var mesh = new Mesh();
        //计算差值
        float vertexDiff = max - min;
        //通过差值返回0到1的一个数(简易函数,传入float返回float)
        Func<float, float> cal = (v) => (v - min) / vertexDiff;
        //顶点计数
        int currentPixelIndex = 0;
        //循环动画的所有帧
        for (int i = 0; i < frameCount; i++)
        {
            //计算每帧的时间
            float t = i * 1f / 30;
            for (int k = 0; k < animator.Length; k++)
            {
                //播放指定时间的动画
                clip.SampleAnimation(animator[k].gameObject, t);
                mesh.Clear(false);
                sk[k].BakeMesh(mesh);

                var vertices = mesh.vertices;
                //Debug.Log("vertices:" + vertices.Length);
                //循环网格的所有顶点
                for (int v = 0; v < vertices.Length; v++)
                {
                    var vertex = vertices[v];
                    //把顶点坐标转换成0到1的颜色值
                    Color c = new Color(cal(vertex.x), cal(vertex.y), cal(vertex.z));
                    //一维转二维
                    //通过顶点id计算该颜色在图片中的位置
                    int x = currentPixelIndex % size;
                    int y = currentPixelIndex / size;
                    //把颜色写入图片
                    texture.SetPixel(x, y, c);
                    currentPixelIndex++;
                }
            }
        }
        //保存图片
        texture.Apply();
        Debug.Log("currentPixelIndex:" + currentPixelIndex);
        File.WriteAllBytes(Application.dataPath + "/" + path + "Anim.png", texture.EncodeToPNG());
    }

    public void _CalculateVertexMinAndMax(out float vertexMin, out float vertexMax)
    {
        //默认为float的最大最小值
        float min = float.MaxValue;
        float max = float.MinValue;
        Mesh preCalMesh = new Mesh();
        for (int f = 0; f < frameCount; f++)
        {
            float t = f * 1f / 30;
            for (int k = 0; k < animator.Length; k++)
            {
                //播放指定时间的动画
                clip.SampleAnimation(animator[k].gameObject, t);
                //取出当前人物蒙皮的网格
                sk[k].BakeMesh(preCalMesh);
                //循环网格的所有顶点
                for (int v = 0; v < preCalMesh.vertexCount; v++)
                {
                    var vertex = preCalMesh.vertices[v];
                    //取x,y,z的最小值
                    //min = Mathf.Floor(Mathf.Min(min, vertex.x, vertex.y, vertex.z));
                    min = Mathf.Min(min, vertex.x, vertex.y, vertex.z);
                    //取x,y,z的最大值
                    //max = Mathf.Ceil(Mathf.Max(max, vertex.x, vertex.y, vertex.z));
                    max = Mathf.Max(max, vertex.x, vertex.y, vertex.z);
                }
            }
        }

        vertexMin = min;
        vertexMax = max;
    }
}

显示动画的shader

Shader "Unlit/MyGPUInstance"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _FaceTex("Face", 2D) = "white" {}
        _AnimTex("Anim", 2D) = "white" {}
        _Fmax("fmax",Float) = 87
        _VCount("vCount",Float) = 3466
        _Size("size",Float) = 1024
        _VertexMax("VertexMax",Float) = 2
        _VertexMin("VertexMin",Float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            //第一步: sharder 增加变体使用shader可以支持instance  
            #pragma multi_compile_instancing
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(int, _VMax0)//顶点最大数
                UNITY_DEFINE_INSTANCED_PROP(int, _VMin0)//顶点最小数
                UNITY_DEFINE_INSTANCED_PROP(float4, _FaceVec)//脸的贴图坐标
            UNITY_INSTANCING_BUFFER_END(Props)

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                //返回顶点id
                uint vid : SV_VertexID;
                //第二步:instancID 加入顶点着色器输入结构 
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 faceVec : VECTOR;
                int clip : INT;
                //第三步:instancID 加入顶点着色器输出结构 
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            sampler2D _MainTex;
            sampler2D _FaceTex;
            sampler2D _AnimTex;
            uint _Fmax;
            uint _VCount;
            float _Size;
            float _VertexMax;
            float _VertexMin;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                //第四步:instanceid在顶点的相关设置  
                UNITY_SETUP_INSTANCE_ID(v);
                //第五步:传递 instanceid 顶点到片元
                UNITY_TRANSFER_INSTANCE_ID(v, o);

                int vmax = UNITY_ACCESS_INSTANCED_PROP(Props, _VMax0);
                int vmin = UNITY_ACCESS_INSTANCED_PROP(Props, _VMin0);
                o.faceVec = UNITY_ACCESS_INSTANCED_PROP(Props, _FaceVec);
                //当前帧数
                uint f = fmod(ceil(_Time.y * 30), _Fmax);
                if (v.vid < vmax && v.vid > vmin)
                {
                    //当前顶点
                    uint index = v.vid + f * _VCount;
                    //计算当前顶点在图片中的xy坐标
                    uint x = index % (uint)_Size;
                    uint y = index / _Size;
                    //把xy坐标转换为0到1的uv坐标
                    float uvx = x / _Size;
                    float uvy = y / _Size;
                    //获取图片中uv坐标的颜色,赋值给顶点坐标
                    v.vertex = tex2Dlod(_AnimTex, float4(uvx, uvy, 0, 0)) * (_VertexMax - _VertexMin) + _VertexMin;
                    o.clip = 1;
                }
                else {
                    o.clip = -1;
                }

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            { 
                //第六步:instanceid在片元的相关设置
                UNITY_SETUP_INSTANCE_ID(i);
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 face = tex2D(_FaceTex, float2(i.uv.x * i.faceVec.x + i.faceVec.z,i.uv.y * i.faceVec.y + i.faceVec.w));
                col = lerp(col, face, face.a);
                clip(i.clip);
                //得到由CPU设置的颜色
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

多次创建代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreatCube : MonoBehaviour
{
    public GameObject cube;
    public int n = 10;
    // Start is called before the first frame update
    void Start()
    {
        Init();
    }
    public void Init()
    {
        //创建预制体
        for (int x = -n; x < n; x++)
        {
            for (int z = -n; z < n; z++)
            {
                if (Random.Range(0,10) < 1)
                {
                    GameObject c = Instantiate(cube, transform);
                    c.transform.position = new Vector3(x, 0, z);
                    MaterialPropertyBlock props = new MaterialPropertyBlock();
                    switch (Random.Range(0, 4))
                    {
                        case 1:
                            props.SetInt("_VMax0", 6859);
                            props.SetInt("_VMin0", 3466);
                            props.SetVector("_FaceVec", new Vector4(8, 8, -4.2f, 0));
                            c.transform.eulerAngles = new Vector3(0, Random.Range(0, 360), 0);
                            break;
                        case 2:
                            props.SetInt("_VMax0", 6859 + 3564);
                            props.SetInt("_VMin0", 6859);
                            props.SetVector("_FaceVec", new Vector4(8, 8, -0.2f, -4));
                            c.transform.eulerAngles = new Vector3(-90, Random.Range(0, 360), 0);
                            break;
                        case 3:
                            props.SetInt("_VMax0", 13581);
                            props.SetInt("_VMin0", 6859 + 3564);
                            props.SetVector("_FaceVec", new Vector4(8, 8, -4.2f, -4));
                            c.transform.eulerAngles = new Vector3(-90, Random.Range(0, 360), 0);
                            break;
                        default:
                            props.SetInt("_VMax0", 3466);
                            props.SetInt("_VMin0", 0);
                            props.SetVector("_FaceVec", new Vector4(8, 8, -0.2f, 0));
                            c.transform.eulerAngles = new Vector3(0, Random.Range(0, 360), 0);
                            break;
                    }
                    c.GetComponent<MeshRenderer>().SetPropertyBlock(props);
                }
            }
        }
    }
}

代码中的部分数据只适用我的模型

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GPUInstance合并人物烘培顶点动画 的相关文章

  • 使用stm32的ADC得到准确的电压

    一 引脚多一点的stm32单片机存在VREF 和VREF 引脚 由上面的供电图知道 如果存在VREF 和VREF 引脚 那么ADC是由这两个引脚供电的 ADC的采集电压范围为 VREF

随机推荐

  • 理解Vue插槽

    引言 在Vue开发中 我们多采用组件化开发 组件化开发最大特点就是对页面结构化划分 组件的复用 而在实际中 页面对组件的需求或许也稍有不同 那么就需要一种需求 为页面定制组件 解决的方式便是通过插槽 实例
  • Java动态执行计算表达式利器 -- ScriptEngine

    在通过配置文件对系统进行参数配置时 有时需要更好的理解参数值的具体意义 往往采用计算表达式的方式设置 例如1天换成秒数为86400 如果写成24 60 60就很清晰的表达是一天的秒数 但是这个表达式通过properties的方式获取为字符串
  • Celery ValueError: not enough values to unpack (expected 3, got 0)

    目录 1 Celery ValueError not enough values to unpack expected 3 got 0 2 AttributeError async 1 Celery ValueError not enoug
  • 使用CUDA实现零拷贝技术

    使用CUDA实现零拷贝技术 零拷贝技术是指在内存和设备之间传输数据时 不需要显式地将数据从内存复制到设备的过程 从而提高数据传输效率 本文将介绍如何使用CUDA实现零拷贝技术 并提供示例代码 在使用CUDA进行图像处理时 通常需要将数据从主
  • 【计算机视觉

    文章目录 一 SqueezeNet 二 Inception v3 三 Visual Geometry Group 19 Layer CNN 四 MobileNetV1 五 Data efficient Image Transformer 六
  • 【CentOS7】开机自启动三种方法

    有个需求 比如说我想要执行开机杀毒程序 就需要去做开机自启动相关操作 准备工作 在 usr local目录下建立killviruses sh 前提 安装病毒库 vi killviruses sh 键入以下内容 前提 已安装ClamAV cl
  • js纯ajax,纯js 的Ajax 请求

    纯js 的Ajax 请求 2018 02 24 126 var XMLHttpReq function createXMLHttpRequest try XMLHttpReq new ActiveXObject Msxml2 XMLHTTP
  • 深度学习优化算法大全系列3:NAG(Nesterov Acceleration Gradient)

    1 NAG与SGD M的区别 NAG全称为Nesterov Accelerated Gradient 是在SGD Momentum基础进一步优化所得 前面的文章我们提到过 SGD M主要是利用历史累积动量来代替当前梯度从而达到减小震荡 加速
  • python自定义assert抛出的异常

    方法一 常用于assert失败后的提示用语 assert 1 gt 4 what 异常为 AssertionError what 方法二 常用于assert失败后推断导致的报错 try assert 1 gt 4 except Except
  • 前端实现导出Excel

    一 准备文件 1 创建excel文件夹 excel Blob js Export2Excel js 2 Blob js文件夹内容 eslint disable Blob js global self unescape jslint bitw
  • python pygame 游戏实战:Maze 迷宫生成,显示和游戏(附全部代码)

    生成迷宫 maze 的算法有很多种 论坛上有很多这方面的资料可以参考 这里使用回溯法 backtracking 主要参考Build a 2 player maze game with Python Part 4 Coding TidBits
  • 深入理解nandflash之基本特性

    nandflash作为嵌入式中的 磁盘 被广泛的应用 以 K9F2G08U0B 为例 其他型号都差不多 nandflash的结构 nandflash的结构有页 page block 块 的概念 其中页是真实概念 而块儿是虚拟概念 目的是为了
  • Graphviz 可视化图形软件(python)

    目录 1 简介 2 Graphviz 工具安装 3 检查是否安装成功 4 Graphviz 库安装 5 验证安装的graphviz是否可用 6 绘制红酒数据集得到的决策树 7 问题 pycharm正常画决策树 但jupyter显示 Modu
  • 在线java编译器_五个免费在线Java编译器,轻松编译代码

    原标题 五个免费在线Java编译器 轻松编译代码 Java编译器网络版成为有用的在许多情况下 例如 假设你正在编写一个Java代码 但不在自己的计算机上 减少时间的浪费 可以无需下载和安装任何软件 使用免费的在线工具运行代码 也就很有帮助
  • 爬虫日常-cookies登录12306

    文章目录 前言 页面分析 代码设计 前言 hello兄弟们 今天没事干也不晓得更什么内容 就把上次和大家说的可以采用cookies登录12306的方法告诉大家 这个功能熟练了的话还是比较简单的 毕竟可以直接通过pickle 建议大家可以自行
  • 简单年龄换算

    function calculatePetAge birthday var userBirthday new Date birthday var now new Date var petAgeNew now getTime userBirt
  • 机器学习算法代码

    BPNN import math import random random seed 0 def rand a b return b a random random a def make matrix m n fill 0 0 mat fo
  • python将列表数据写入已有的excel文件的指定单元格

    本人结合网上资料 总结出以下两种可以将列表数据写入已有excel并不改变原文件其它内容的方法 第一种方法要用到xlwt xlutils xlrd三个第三方库 import xlwt as xw from xlutils copy impor
  • R语言数据挖掘开源工具rattle的安装

    1 R语言的环境配置 去R语言官网下载最新的R语言环境 2 安装Rstudio 去Rstudio官网下载最新的Rstudio版本安装 3 打开Rstudio安装在rattle install packages RGtk2 install p
  • GPUInstance合并人物烘培顶点动画

    将4个小人合成一个 使用gpu Instance批量绘制可以使用gpu Instance的特性降低批处理 但是gpu Instance只支持mesh filter所有需要将蒙皮动画烘培成顶点动画 通过MaterialPropertyBloc