Unity | 从建模到蒙皮动画的整个过程

news2024/11/26 23:36:56

目录

一、顶点数组、索引数组及UV数组

二、Mesh、MeshFilter、MeshRenderer及SkinnedMeshRenderer

1. Mesh

2. MeshFilter

3. MeshRenderer

4. MeshRenderer与SkinnedMeshRenderer(蒙皮网格)

三、Unity中相关组件

1. mesh和material

2. sharedMesh和sharedMaterial

3. materials和sharedMaterials

四、蒙皮骨骼动画

1. 骨骼点

2. 制作蒙皮动画步骤

3. 关键帧

五、网格数据从制作到渲染的过程

六、动态实现从建模到蒙皮动画的整个过程

1. 代码

2. 相关数据

(1)Curve属性效果

(2)mesh数据

3. 整体效果

4. 注意细节


一、顶点数组、索引数组及UV数组

        顶点索引在程序中的表现为,把所有顶点放进一个数组里(顶点数组),再用另一个整数数组作为索引来表达三角形的组成(整数代表顶点数组里的index下标)。 

        有几个索引数据,就有几个UV坐标,它们由两个浮点数组成,这两个浮点数的范围是0到1,0表示贴图的左上角起始位置,1表示贴图的最大偏移位置,也就是右下角,一听UV两个字母我们就应该知道说的是图片上的坐标。

        在绘制3D模型时,除了顶点和索引数组外,还有个数组叫UV数组,这个UV数组是用于存储UV坐标而存在的。由于已经有了索引来表达三角形的三个顶点,所以UV数组不需要再用索引来表达了,只需要按照顶点索引形成的三角形来定制UV的顺序即可。

  • 4个顶点: [(0,0,0),(0,1,0),(1,1,0),(1,0,0)]
  • 顶点索引,表示2个三角形:[0,1,2,2,3,0]
  • UV数组,表示两个三角形上贴图的绘制范围:[(0,0),(0,1),(1,1),(1,1),(1,0),(0,0)]

二、Mesh、MeshFilter、MeshRenderer及SkinnedMeshRenderer

1. Mesh

        网格(Mesh)是数据资源,它可以有自己的资源文件,比如XXX.FBX。网格里存储了顶点、UV、顶点颜色、三角形、切线、法线、骨骼、骨骼权重等提供渲染所必要的数据。

2. MeshFilter

        MeshFilter是承载网格数据的类,网格被实例化后存储在MeshFilter类中。MeshFilter包含两种类型,即实例型和共享型的变量(mesh和sharedMesh),对mesh进行操作将生成新的mesh实例,而对sharedMesh进行操作将改变与其他模型共同拥有的那个指定的网格数据实例。

3. MeshRenderer

        MeshRenderer是绘制网格的类,具有渲染功能,它会提取MeshFilter中的网格数据,结合自身的materials或sharedMaterials进行渲染。

4. MeshRenderer与SkinnedMeshRenderer(蒙皮网格)

        MeshRenderer与SkinnedMeshRenderer这两个组件分别用于渲染3D模型和3D模型动画,它们的模型数据都存储在MeshFilter中,因此它们都依赖于MeshFilter组件。其中,MeshRenderer只负责渲染模型,我们也可以称它为普通网格渲染组件,它从MeshFilter中提取网格顶点数据。而SkinnedMeshRenderer(蒙皮网格)虽然也渲染模型,也从MeshFilter中提取模型网格顶点数据,但蒙皮网格主要用于渲染动画服务,所以蒙皮网格除了3D模型数据外,还有骨骼数据及顶点权重数据。

        如果蒙皮网格上没有存储任何骨骼数据,那么它与普通网格MeshRender的作用没有任何区别,渲染的都是没有动画的3D模型。

        总之,MeshRenderer适用于静态不变形的网格渲染,而SkinnedMeshRenderer适用于需要骨骼动画和变形的网格渲染。在游戏中,常见的角色模型通常会使用SkinnedMeshRenderer来实现骨骼动画,而环境模型和其他静态物体通常会使用MeshRenderer。

三、Unity中相关组件

1. mesh和material

        mesh(见上文)定义了模型的形状和顶点数据,而material定义了模型的外观属性,如颜色、纹理和光照效果。

        mesh和material都是实例型的变量,对mesh和material执行任何操作,都是额外复制一份后再重新赋值,即使只是get操作,也同样会执行复制操作。也就是说,对mesh和material进行操作后,就会变成另外一个实例,虽然看上去一样,但其实已是不同的实例了。

2. sharedMesh和sharedMaterial

        sharedMesh和sharedMaterial与前面两个变量不同,它们是共享型的。多个3D模型可以共用同一个指定的sharedMesh和sharedMaterial,当修改sharedMesh或sharedMaterial里面的参数时,指向同一个sharedMesh和sharedMaterial的多个模型就会同时改变效果。也就是说,sharedMesh和sharedMaterial发生改变后,所有使用sharedMesh和sharedMaterial资源的3D模型都会表现出相同的效果。

3. materials和sharedMaterials

        与material和sharedMaterial一样,materials是实例型的,sharedMaterials是共享型的,只不过现在它们变成了数组形式。

        无论对materials进行什么操作,都会复制一份一模一样的来替换,sharedMaterials操作后,指向这个材质球的所有模型都会改变效果。materials&sharedMaterials和material&sharedMaterial的区别是,materials和sharedMaterials可以针对不同的子网格,material和sharedMaterial只针对主网格。也就是说,material和sharedMaterial等于materials[0]和sharedMaterials[0]。

四、蒙皮骨骼动画

        3D模型要做动作,首先是模型网格上的点、线、面要动起来,只有点、线、面动起来了,每帧渲染的时候才能渲染出不同的网格形状,从而才有看起来会动的画面。那么怎么让点、线、面动起来呢?

        主要有两种方法:一种是用一种算法来改变顶点位置,我们通常称之为顶点动画;另一种是用骨骼的方式去影响网格顶点,我们称之为骨骼动画。这两种动画方式都是通过在每一帧里偏移模型网格上的各个顶点,让模型变形,从而形成动画的效果的。每一帧模型网格的形状不一样,播放时就形成了动画,两种方法虽然方式不同,但都遵循同一个原理。

        刚性层级式动画

        起初3D模型动画只有刚性层级式动画(rigid hierarchical animation),它将整个模型拆分成多个部位,然后按照层级节点的方式安装上去。

        这样,模型以层级的方式布置在节点上,当父节点移动、旋转、缩放时,子节点也随之而动。刚性层级式动画的问题很多,其中比较严重的是关节连接位置常产生“裂缝”,因为它们并不是由一个模型衔接而成的,而是由多个模型拼凑起来的。

        变形目标动画

        变形目标动画(morph target animation)的方法常使用在脸部动画中,它将动画制作成几个固定的极端姿势的模型,然后在两个模型的每个顶点之间做线性插值,脸部动作大约需要50组肌肉驱动,这种复杂细微程度的动画用两个网格顶点之间的线性插值来表现会比较合适。

1. 骨骼点

        骨骼动画由骨骼点组成,骨骼点可以认为是带有相对空间坐标点的数据实体,骨骼动画中可以有许多个骨骼点,但根节点只有一个。(在现代手机游戏中,每个人物骨骼动画的数量一般为30个左右,PC单机游戏中可达75个左右。骨骼数量越多,动画就越有动感,但同时也会消耗掉更多的运算量。)

        骨骼点为树形结构,一个骨骼可以有很多个子骨骼,子骨骼存在于父骨骼的相对空间下,子骨骼与父骨骼拥有相同的功能,由于子骨骼在父骨骼的空间下,因此,当父骨骼移动、旋转、缩放时,子骨骼也随着父骨骼一起移动、旋转、缩放,它们的相对位置、相对角度、相对比例不变。

        在Unity3D的蒙皮网格组件中,bones变量用于存储所有骨骼点,骨骼点在蒙皮网格中是以Transform数组的形式存储的,这一点可以从bones变量就是Transform[]数组类型得知。

        骨骼点可以影响周围一定范围内的顶点,单一顶点也可以受到多个骨骼的影响。除了骨骼数据,模型中的每个顶点都有对其顶点本身影响最多的4个骨骼的权重值,Unity3D对这4个骨骼的权重值进行了存储,将它们存放在BoneWeight的Struct结构中,每个SkinMeshRender类都有一个boneWeights数组变量,用于记录所有顶点的骨骼权重值,那些没有骨骼动画的网格,就没有这些数据。

        从Unity3D的图形质量设置(Quality setting)中,我们可以看到,Blend Weights参数可用于设置一个顶点能被多少骨骼影响。其中有1 Bone、2 Bones、4 Bones等参数,表达的意思分别是一个顶点能被1个骨骼影响,或者被2个骨骼影响,或者被4个骨骼影响。被影响的骨骼数越多,CPU消耗在骨骼计算蒙皮上的时间就越长,消耗量越大。

         骨骼动画是以顶点的骨骼权重数据来决定顶点受哪些骨骼点的影响的,每个顶点都可以受到骨骼点的影响。在Unity3D中,每个顶点最多被4个骨骼点影响,这些数据被存储在BoneWeight实例里,该实例用于描述当前顶点分别受到哪4个骨骼点的影响,它们分别占有多少权重。

        当骨骼点移动时,引擎就会使用这些顶点权重值来计算顶点的旋转度、偏移量和缩放度。 简单来说就是,用顶点上的骨骼权重数据确定该点受到哪些骨骼点的影响,影响的程度有多大。

2. 制作蒙皮动画步骤

        我们制作蒙皮动画通常分为三步:

        第一步是使用3DMax、Maya等3D模型软件在几何模型上构建一系列的骨骼点(bones),并计算出几何模型的每个顶点受这些骨骼点影响的权重值(BoneWeight)。

        第二步是动画师通过3D模型软件制作一系列动画,这些动画都是通过骨骼点的偏移、旋转、缩放来完成的,每一帧都有可能发生变化,关键帧与关键帧之间会补间一些非关键帧的动画。制作完毕后,导出引擎专有的动画文件格式。在Unity3D中,我们以.fbx作为专有格式文件。

        第三步则是在Unity3D中导入并播放动画,播放动画时就已经存储了动画师制作的骨骼点位每帧发生变化的数据,动画序列帧会根据每帧的动画数据来持续改变一系列骨骼点,骨骼点的变化又会导致几何模型网格上的顶点发生相应的变化。

3. 关键帧

        通常我们使用的都是关键帧动画,就是Unity3D里的Animation文件。在某个时间点上对需要改变的骨骼做关键帧,而不是在每帧上都执行关键帧的操作。使用关键帧作为骨骼的旋转位移点,好处是不需要为每帧都设置骨骼点的位置变化,在关键帧与关键帧之间,骨骼位置可以由Animation组件做平滑插值计算,这样可以大大减少数据量,相当于关键帧之间做了补间动画。补间动画的目的就是对需要改变的骨骼做平滑的位移、旋转、缩放的插值计算,从而实时得到相应的结果,以减少数据的使用量。

        由于4×4矩阵能够完整地表达点位的偏移、缩放和旋转等操作,也能通过连续右乘法计算出从根节点到父节点再到子节点上的具体方位,因此4×4矩阵是骨骼点必要的数据,它表达了相对空间的偏移量,即骨骼节点变化矩阵=根节点矩阵×父父父节点矩阵1×父父节点矩阵×父节点矩阵×骨骼节点矩阵。

五、网格数据从制作到渲染的过程

        首先,美术人员制作3D模型并导出成Unity3D能够识别的格式,即.fbx文件,其中已经包含了顶点和索引数据。

        然后在程序中将.fbx实例化成Unity3D的GameObject,它们身上附带的MeshFilter组件存储了网格的顶点数据和索引数据(我们也可以自己创建顶点数组和索引数组,以手动的方式输入顶点数据和索引数据)。

        MeshFilter可用于存储顶点和索引数据,MeshRender或SkinMeshRender可用于渲染模型,这些顶点数据通常都会与材质球结合,在渲染时一起送入图形卡,其中与我们预想的不一样的是,在送入时并不会由索引数据送入,而是由三个顶点一组组成的三角形顶点送入图形卡。接着由图形卡负责处理我们送入的数据,然后渲染帧缓存,并输出到屏幕。

六、动态实现从建模到蒙皮动画的整个过程

1. 代码

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

public class TestMesh : MonoBehaviour
{
    public SkinnedMeshRenderer rend;
    public Animation anim;
    public AnimationCurve curve;
    public AnimationClip clip;
    public string clipName = "test";
    // Start is called before the first frame update
    void Start()
    {
        // 新建一个动画组件和蒙皮组件
        gameObject.AddComponent<Animation>();
        gameObject.AddComponent<SkinnedMeshRenderer>();
        rend = GetComponent<SkinnedMeshRenderer>();
        anim = GetComponent<Animation>();

        // 新建一个网格组件,并编入4个顶点形成一个矩形形状的网格
        Mesh mesh = new Mesh();
        mesh.vertices = new Vector3[] { new Vector3(-1, 0, 0), new Vector3(1, 0, 0), new Vector3(-1, 5, 0), new Vector3(1, 5, 0) };
        mesh.uv = new Vector2[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1) };
        mesh.triangles = new int[] { 0, 1, 2, 1, 3, 2 };
        mesh.RecalculateNormals();

        // 新建一个漫反射的材质球
        rend.material = new Material(Shader.Find("Diffuse"));

        // 为每个顶点定制相应的骨骼权重
        BoneWeight[] weights = new BoneWeight[4];
        weights[0].boneIndex0 = 0;
        weights[0].weight0 = 1;
        weights[1].boneIndex0 = 0;
        weights[1].weight0 = 1;
        weights[2].boneIndex0 = 1;
        weights[2].weight0 = 1;
        weights[3].boneIndex0 = 1;
        weights[3].weight0 = 1;

        // 将骨骼权重赋值给网格组件
        mesh.boneWeights = weights;

        // 创建新的骨骼点,设置骨骼点的位置、父骨骼点和位移旋转矩阵
        Transform[] bones = new Transform[2];
        Matrix4x4[] bindPoses = new Matrix4x4[2];

        bones[0] = new GameObject("Lower").transform;
        bones[0].parent = transform;
        bones[0].localRotation = Quaternion.identity;
        bones[0].localPosition = Vector3.zero;
        bindPoses[0] = bones[0].worldToLocalMatrix * transform.localToWorldMatrix;

        bones[1] = new GameObject("Upper").transform;
        bones[1].parent = transform;
        bones[1].localRotation = Quaternion.identity;
        bones[1].localPosition = new Vector3(0, 5, 0);
        bindPoses[1] = bones[1].worldToLocalMatrix * transform.localToWorldMatrix;

        mesh.bindposes = bindPoses;

        // 将骨骼点和网格赋值给蒙皮组件
        rend.bones = bones;
        rend.sharedMesh = mesh;

        // 定制几个关键帧
        curve = new AnimationCurve();
        curve.keys = new Keyframe[] { new Keyframe(0, 3, 0, 0), new Keyframe(2, -3, 0, 0), new Keyframe(4, 3, 0, 0) };

        // 创建帧动画
        clip = new AnimationClip();
        clip.legacy = true;     //正确
        clip.SetCurve("Lower", typeof(Transform), "m_LocalPosition.z", curve);
        //clip.legacy = true;   //错误,应先设置legacy为true,再SetCurve,否则发布exe后报错
        clip.wrapMode = WrapMode.Loop;

        // 将帧动画赋值给动画组件,并播放动画
        anim.AddClip(clip, clipName);
        anim.playAutomatically = true;
        anim.clip = clip;
        anim.Play(clipName);
        Debug.Log("play");
    }

    // Update is called once per frame
    void Update()
    {

    }
}

2. 相关数据

(1)Curve属性效果

(2)mesh数据

3. 整体效果

4. 注意细节

        clip.legacy的设置要在SetCurve之前,否则发布exe后会报错:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/724370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【微信小程序创作之路】- 小程序项目组成(初创小程序)

【微信小程序创作之路】- 小程序项目组成&#xff08;初创小程序&#xff09; 提示&#xff1a;第二章 初创小程序&#xff0c;讲解小程序项目组成 文章目录 【微信小程序创作之路】- 小程序项目组成&#xff08;初创小程序&#xff09;前言一、下载安装小程序微信开发者工具二…

Python multiprocessing 多进程

在multiporcessing中&#xff0c;通过新建Process对象创建进程&#xff0c;然后用start()方法调用它。Process与threading.Thread类似。如下是一个简单的例子&#xff1a; from multiprocessing import Processdef f(name):print(hello ,name)if __name__ __main__:p Proces…

2D和3D双管齐下才是王道?KAUST联合Snap、VGG提出单张图像三维重建的Magic123框架

在最近的AIGC社区中&#xff0c;3D视觉生成领域越来越受到广泛的关注&#xff0c;以神经辐射场&#xff08;NeRFs&#xff09; 为基础的深度渲染网络向大家展示了非常惊艳的三维效果。可是&#xff0c;NeRFs需要大量的多视角图片作为监督&#xff0c;因而从单张2D图像进行3D重建…

昆仑万维的“天工”能否改变人工智能应用的格局?

在人工智能领域&#xff0c;昆仑万维公司近日引发了广泛关注&#xff0c;发布了名为“天工”的大语言模型。这一新的技术突破引发了人们对于人工智能应用格局是否会被改变的讨论。随着昆仑万维进军大型语言模型市场&#xff0c;人们开始思考&#xff0c;“天工”能否成为人工智…

面部表情动画数据捕捉利器——AH表情捕捉头盔

在游戏制作、电视广告、影视动画制作中&#xff0c;逐渐开始采用面部捕捉头盔进行采集表情面部数据。广州虚拟动力推出的AH表情捕捉头盔&#xff0c;能够轻松创建实时逼真的面部动画&#xff0c;搭配广州虚拟动力的动作捕捉系统VDMocap Studio Plus&#xff0c;能够自动运算出表…

第八章:怎样远程连接+远程连接常用工具+Teamviewer详解

目录 一、什么是远程连接 二、为什么需要远程连接 三、常用的远程连接工具 四、Teamviewer 一、什么是远程连接 远程连接就是在远程连接另外一台计算机。当某台计算机开启了远程桌面连接功能后我们就可以在网络的另一端控制这台计算机了&#xff0c;通过远程桌面功能我们可…

基于Java校园二手物品交易平台设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

hive函数dayofweek的奇怪用法

hive函数dayofweek的奇怪用法 1.单独使用 select dayofweek(2023-07-03); 周一 2 select dayofweek(2023-07-04); 周二 3 select dayofweek(2023-07-05); 周三 4 select dayofweek(2023-07-06); 周四 5 select dayofweek(2023-07-07); 周五 6 select dayofweek(2023-07-08); …

VIO在ARM上的加速(2)- Neon

VIO在ARM上的加速&#xff1a; VIO在ARM上的加速&#xff08;1&#xff09;- ARM加速基础 VIO在ARM上的加速&#xff08;2&#xff09;- Neon VIO在ARM上的加速&#xff08;3&#xff09;- Neon在VIO中的应用 1 NEON的概述 ARM 处理器中使用的高级 SIMD 扩展的实现称为 NE…

【数据结构与算法】魔王语言解释(C/C++)

实践要求 1. 问题描述 有一个魔王总是使用自己的一种非常精炼而抽象的语言讲话&#xff0c;没有人能听懂。但他的语言是可以逐步解释成人能懂的语言的&#xff0c;因为他的语言是由以下两种形式的规则由人的语言逐步抽象上去的: 形式一 α → β 1 β 2 . . . β m \alpha \…

无线耳机推荐的品牌有哪些?八款无线蓝牙耳机推荐

无线蓝牙耳机无疑是当前最受欢迎的数码产品之一&#xff0c;平听闲暇时刻听听歌或者是运动健身&#xff0c;常常能看到蓝牙耳机的身影&#xff0c;作为一个热衷于听歌的精致boy&#xff0c;佩戴过的蓝牙耳机数不胜数&#xff0c;现在&#xff0c;除了手机品牌会开发无线蓝牙耳机…

西电_矩阵论_学习笔记

文章目录 【 第一章 线性空间 】【 第二章 范数 】【 第三章 矩阵函数 】【 第四章 矩阵分解 】【 第五章 矩阵特征值估计 】【 第六章 广义逆 】【 考试重点内容总结 】 这是博主2023春季西电所学矩阵论的思维导图&#xff08;软件是幕布&#xff09;&#xff0c;供大家参考&a…

ROS:工作空间覆盖

目录 一、概念二、示例2.1操作2.2原因 三、存在的问题 一、概念 所谓工作空间覆盖&#xff0c;是指不同工作空间中&#xff0c;存在重名的功能包的情形。 ROS 开发中&#xff0c;会自定义工作空间且自定义工作空间可以同时存在多个&#xff0c;可能会出现一种情况: 虽然特定工…

【数学建模】 灰色预测模型

数学建模——预测模型简介 https://www.cnblogs.com/somedayLi/p/9542835.html 灰色预测模型 https://blog.csdn.net/qq_39798423/article/details/89283000?ops_request_misc&request_id&biz_id102&utm_term%E7%81%B0%E8%89%B2%E9%A2%84%E6%B5%8B%E6%A8%…

开放式耳机哪个好?2023开放式耳机排行榜推荐

​耳机成为了当代青年必不可少的一款数码单品&#xff0c;无论在什么时间、哪个地点总能看到很多人戴着耳机。耳机也分有很多类型&#xff0c;就比如市面上大火的开放式耳机&#xff0c;很多人还不清楚开放式耳机如何挑选的&#xff0c;下面我来推荐几款很不错的开放式耳机&…

【一】部署Zabbix监控详解

Zabbix监控 1.Zabbix监控概述1.1 zabbix是什么1.2 zabbix监控原理1.3 Zabbix 6.0新特性1.4 Zabbix 6.0功能组件1.5 Zabbix与prometheus区别对比 2. 部署Zabbix6.02.1 安装NginxPHP2.2 部署Mariadb数据库2.3 安装zabbix Server服务端2.4 部署Web前端&#xff0c;进行访问2.5 部署…

软件测试中的二八定律到底是什么?

目录 前言&#xff1a; 一、80%的软件缺陷&#xff0c;集聚在软件20%的模块中 二、软件测试工作尽早介入 三、反映在软件测试的自动化方面 四、80%的缺陷&#xff0c;集中在某20%的开发工程师代码中&#xff1b; 一、缺陷是解决不完的 二、是不可能发现100%缺陷的 三、…

Web APls-day04

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 日期对象 日期对象&#xff1a;用来表示时间的对象 作用&#xff1a;可以得到当前系统时间 实例化 在代码中发现…

【设计模式】第九章:外观模式(门面模式)详解及应用案例

系列文章 【设计模式】七大设计原则 【设计模式】第一章&#xff1a;单例模式 【设计模式】第二章&#xff1a;工厂模式 【设计模式】第三章&#xff1a;建造者模式 【设计模式】第四章&#xff1a;原型模式 【设计模式】第五章&#xff1a;适配器模式 【设计模式】第六章&…

17 MFC进程通信

文章目录 剪切板管道匿名管道父进程写入数据子进程读出数据 命名管道 邮槽邮槽服务器邮槽客户端 剪切板 设置界面 发送 //设置剪切板数据 void CClipboardDlg::OnBnClickedBtnSend() {UpdateData(TRUE);if (m_strSend.IsEmpty()){MessageBox(L"请输入需要设置的文本&quo…