unity - Blend Shape - 变形器 - 实践

文章目录

  • 目的
  • Blend Shape 逐顶点 多个混合思路
  • Blender
  • 3Ds max
  • Unity 中使用
  • Project


目的

拾遗,备份


Blend Shape 逐顶点 多个混合思路

blend shape 基于: vertex number, vertex sn 相同,才能正常混合、播放
也就是 vertex buffer 的顶点数量一样,还有 triangles 的 index 要一致

这样 blend shape 才能逐个顶点计算

计算公式:使用一张大佬整理的图,大佬的文章:BlendShapes基础与拓展练习(面捕与物体变形)
在这里插入图片描述


Blender

Shift+A 新建一个 sphere
在这里插入图片描述

选中

在这里插入图片描述

Tab 进入 Editor Mode,并且在 Data 页签属性的 Shape Keys 添加对应的 blend shape 状态
在这里插入图片描述
在这里插入图片描述

调整好每一个 Shape Keys (或是叫:blend shape) 的顶点位置
然后再 Object Mode 下,我们可以选中对应的 Shape Keys 然后调整 value 查看变形结果
请添加图片描述

最终我们尝试 K帧动画来查看 多个 shape keys 混合控制的情况
请添加图片描述


3Ds max

interlude _1 : working on Yui-chan’s face morphing. - 3Ds max 中的演示 二次元 脸部表情 FFD


Unity 中使用

先在 blender 导出 fbx
在这里插入图片描述

将 fbx 模型拖拽到 hierarchy
在这里插入图片描述

尝试拖拉 inspector 中的 blend shape 拉杆,即可查看效果
请添加图片描述

所以我们写脚本控制 blend shape 混合拉杆即可达到我们各种表情的混合控制
请添加图片描述

在原来 blendshape_1 基础上在混合 blendshape_2
请添加图片描述

blendshape_1 和 blendshape_2 一起播放
请添加图片描述

然后可以尝试看一下 blendshape_1 和 blendshape_2 不同速率的控制混合的情况
这里使用 pingpong 算法

请添加图片描述

下面是测试 csharp 脚本

// jave.lin 2023/10/07 测试 blend shape

using System.Collections;
using UnityEngine;

public class TestingBlendShape : MonoBehaviour
{
    public SkinnedMeshRenderer skinnedMeshRenderer;
    private int _BlendShape_1_IDX = -1;
    private int _BlendShape_2_IDX = -1;

    private IEnumerator _couroutine_blendShape1;
    private IEnumerator _couroutine_blendShape2;
    private IEnumerator _couroutine_pingpong;

    private void OnDestroy()
    {
        StopAllCoroutines();
    }

    private void Refresh()
    {
        if (skinnedMeshRenderer != null)
        {
            _BlendShape_1_IDX = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex("BlendShape_1");
            _BlendShape_2_IDX = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex("BlendShape_2");
        }
    }

    public void ToBlendShape_1()
    {
        Refresh();

        if (_couroutine_blendShape1 != null)
        {
            StopCoroutine(_couroutine_blendShape1);
        }
        StartCoroutine(_couroutine_blendShape1 = Play_ToBlendShape(_BlendShape_1_IDX, 100.0f));
    }

    public void ToBlendShape_2()
    {
        Refresh();

        if (_couroutine_blendShape2 != null)
        {
            StopCoroutine(_couroutine_blendShape2);
        }
        StartCoroutine(_couroutine_blendShape2 = Play_ToBlendShape(_BlendShape_2_IDX, 100.0f));
    }

    public void ToBlendShape_1_2()
    {
        Refresh();

        if (_couroutine_blendShape1 != null)
        {
            StopCoroutine(_couroutine_blendShape1);
        }
        StartCoroutine(_couroutine_blendShape1 = Play_ToBlendShape(_BlendShape_1_IDX, 100.0f));
        if (_couroutine_blendShape2 != null)
        {
            StopCoroutine(_couroutine_blendShape2);
        }
        StartCoroutine(_couroutine_blendShape2 = Play_ToBlendShape(_BlendShape_2_IDX, 100.0f));
    }

    public void ToPingPong_BlendShape_1_2()
    {
        Refresh();

        if (_couroutine_pingpong != null)
        {
            StopCoroutine(_couroutine_pingpong);
        }
        StartCoroutine(_couroutine_pingpong = PingPong_BlendShape());
    }

    public void StopAll()
    {
        StopAllCoroutines();
    }

    public void ResetAll()
    {
        if (skinnedMeshRenderer != null)
        {
            var count = skinnedMeshRenderer.sharedMesh.blendShapeCount;
            for (int i = 0; i < count; i++)
            {
                skinnedMeshRenderer.SetBlendShapeWeight(i, 0.0f);
            }
        }
    }

    private IEnumerator Play_ToBlendShape(int idx, float to_val)
    {
        to_val = Mathf.Clamp(to_val, 0.0f, 100.0f);
        var start_val = skinnedMeshRenderer.GetBlendShapeWeight(idx);
        var cur_val = start_val;

        while (cur_val < to_val)
        {
            cur_val = Mathf.MoveTowards(cur_val, to_val, (to_val - start_val) * Time.deltaTime);
            skinnedMeshRenderer.SetBlendShapeWeight(idx, cur_val);
            yield return null;
        }

        Debug.Log($"play to blend shape [{idx}] : [{to_val}] complete!");
    }

    private IEnumerator PingPong_BlendShape()
    {
        var now_time = Time.time;

        while (true)
        {
            var _time = Time.time - now_time;
            var weight1 = Mathf.PingPong(_time * 200f, 100f);
            var weight2 = Mathf.PingPong(_time * 50f, 100f);
            skinnedMeshRenderer.SetBlendShapeWeight(_BlendShape_1_IDX, weight1);
            skinnedMeshRenderer.SetBlendShapeWeight(_BlendShape_2_IDX, weight2);
            yield return null;
        }
    }
}


Project

个人备份用

  • blender 工程: TestingBlenderShapeKeys.blend
  • unity 工程: TestingBlendShape_BRP_2020.3.37f1.rar

  • 百人计划-BlendShapes基础(物体变形与面捕应用)
  • google : how to implementing the blend shape in blender
  • google : how to create shape keys animation in blender
    • Shape Key / Blendshape Hacks to easily create expressions and actions for your avatar
    • Blender 2.8 Shapekeys and Morphing
    • How to Add Shape Keys in Blender
  • 形态键
  • 在Unity中实现BlendShape表情和骨骼动画混合的实践 - 讲得挺不错
  • BlendShapes基础与拓展练习(面捕与物体变形) - 讲得挺不错
  • GDC2011: Fast and Efficient Facial Rigging
  • GDC jeremy_ernst_fastandefficietfacialrigging.pdf
  • 技术美术百人计划-美术 3.5 BlendShape基础 笔记
  • 3.5 BlendShapes基础
  • 百人计划-BlendShapes基础(物体变形与面捕应用)
  • 【技术美术百人计划】美术 3.5 BlendShape基础
  • Unity通过导入器优化动画关键帧数据 - 删除仅仅只有 blend shape 的动画的其他骨骼信息,优化性能
  • interlude _1 : working on Yui-chan’s face morphing. - 3Ds max 中的演示 二次元 脸部表情 FFD

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】类型转换 | IO流 | 空间配置器

C语言类型转换 C语言总共有两种形式的类型转换&#xff1a;隐式类型转换 和 显示类型转换。 C语言的转换格式虽然很简单&#xff0c;但也存在不少缺陷&#xff1a; 隐式类型转换有些情况下可能会引发意料之外的结果&#xff0c;比如数据精度丢失。显示类型转换的可视性比较差…

matlab中实现画函数图像添加坐标轴

大家好&#xff0c;我是带我去滑雪&#xff01; 主函数matlab代码&#xff1a; function PlotAxisAtOrigin(x,y); if nargin 2 plot(x,y);hold on; elsedisplay( Not 2D Data set !) end; Xget(gca,Xtick); Yget(gca,Ytick); XLget(gca,XtickLabel); YLget(gca,YtickLabel)…

基于SpringBoot的SSMP整合案例(开启日志与分页查询条件查询功能实现)

开启事务 导入Mybatis-Plus框架后&#xff0c;我们可以使用Mybatis-Plus自带的事务&#xff0c;只需要在配置文件中配置即可 使用配置方式开启日志&#xff0c;设置日志输出方式为标准输出mybatis-plus:global-config:db-config:table-prefix: tb_id-type: autoconfiguration:…

【C语言 | 预处理】C语言预处理详解(三)——内存对齐、手把手带你计算结构体大小

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

可视化技术专栏100例教程导航帖—学习可视化技术的指南宝典

&#x1f389;&#x1f38a;&#x1f389; 你的技术旅程将在这里启航&#xff01; &#x1f680;&#x1f680; 本文专栏&#xff1a;可视化技术专栏100例 可视化技术专栏100例领略各种先进的可视化技术&#xff0c;包括但不限于大屏可视化、图表可视化等等。订阅专栏用户在文章…

linux中的工程管理工具makefile

makefile文件:Linux上的工程管理工具,可以实现自动化编译; 工程中的源文件不计其数,可以根据模块,功能等存储在不同的目录中; makefile可以提高编译效率,使用make命令每次只会编译那些修改了的或者依赖修改了的这些文件,没有修改的文件不会重新编译. VS底层就有自己的makefile文…

【蓝桥杯选拔赛真题65】Scratch水下探险 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析

目录 scratch水下探险 一、题目要求 编程实现 二、案例分析 1、角色分析

酷柚易汛ERP-账户管理操作指南

1、应用场景 对账户进行管理&#xff0c;可设置账户当前余额、期初余额和设置是否为默认账户。 2、主要操作 2.1 新增支付账户 打开【资料】-【账款管理】&#xff0c;点击【新增】添加账户类别&#xff0c;输入相关信息并保存&#xff0c;账户编号和名称为必录项。&#x…

【陈老板赠书活动 - 17期】- 她以亲身经历证明,程序员借助AI做副业可以有多成功!

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f468;‍&am…

Linux使用gdb进行代码调试

1.debug版本 在编译阶段会加入某些调试信息; 调试信息是在编译的过程中加入到中间文件.o文件的;gcc -c main.c -g:生成包含调试信息的中间文件 gcc -o main main.o一步执行:gcc -o main main.c -g 2.release版本 发行版本,没有调试信息; gcc默认生成release版本; 3.gdb基础命…

从Hadoop到对象存储,抛弃Hadoop,数据湖才能重获新生?

Hadoop与数据湖的关系 1、Hadoop时代的落幕2、Databricks和Snowflake做对了什么3、Hadoop与对象存储&#xff08;OSD&#xff09;4、Databricks与Snowflake为什么选择对象存储5、对象存储面临的挑战 1、Hadoop时代的落幕 十几年前&#xff0c;Hadoop是解决大规模数据分析的“白…

关于session的不断变化问题

今天在帮同学解决一个小问题&#xff0c;差点阴沟翻船。 问题再现&#xff1a;他从github上拉了一个项目下来跑&#xff0c;结果发生跑不通问题出现在验证码一直不对。 我一看项目源码&#xff0c;验证码生成后存储再session中了&#xff0c;等用户发送请求验证的时候sessionI…

AI系统开发源码搭建的专业深度思考:从零到一的技术探索之路

【内容摘要】 随着人工智能技术的飞速发展&#xff0c;AI系统开发源码搭建已成为越来越多开发者关注的焦点。本文将从专业角度探讨AI系统开发源码搭建的过程&#xff0c;从需求分析、设计、开发、测试到部署&#xff0c;深入挖掘其中的关键技术和挑战。同时&#xff0c;我们将…

MySQL查询时间处理相关函数与方法实践笔记

1. 实践案例 在查询mysql数据库获取数据时&#xff0c;有这样一个需求&#xff1a;按每30分钟分组获取电量数据&#xff0c;形成1天48个数据点。 方法一&#xff1a; select hour(a.CreateTime) 时点,case when MINUTE(a.CreateTime)<30 then 1 else 2 end 半小时,sum(a…

无人驾驶智能:两车居然可以“交流”

导读“这些智能车看着个子小小的&#xff0c;却有大用途&#xff0c;可以说是无人驾驶车的雏形……”昨日&#xff0c;在重庆大学光电工程学院内&#xff0c;记者看到了几辆个头不大的智能小车&#xff0c;是大学生自主设计的无人驾驶车的雏形。据悉&#xff0c;它们在8月26日结…

【树与二叉树的转换,哈夫曼树的基本概念】

文章目录 树与二叉树的转换将二叉树转化为树森林与二叉树的转化&#xff08;二叉树与多棵树之间的关系&#xff09;二叉树转换为森林森林的先序遍历1&#xff09;先序遍历2&#xff09;后序遍历 哈夫曼树的基本概念森林转换成二叉树&#xff08;二叉树与多棵树的关系&#xff0…

【dbeaver】添加mysql高低版本选择驱动

添加mysql高低版本选择驱动 连接到数据库->全部->查询mysql MySQL 版本驱动 8.0 MySQL 5 版本驱动 5.7.x 其他需要就&#xff1a;https://downloads.mysql.com/archives/c-j/ 密码查看 项目设置密码&#xff1a; File -> Project security ->设置密码 It i…

C语言、c++史上最全最全爱心代码大全,彩色闪动、字符填充,附源码

第一种&#xff1a;红色爱心代码 直接上代码&#xff1a; #include<stdio.h> #include<Windows.h> int main() {system(" color 0c");//设计程序颜色 printf("遇见你是一件很开心的事情,爱你哟&#xff01;&#xff01;&#xff01;\n");//打…

【JavaEE初阶】 TCP滑动窗口与流量控制和拥塞控制

文章目录 &#x1f384;为什么出现滑动窗口&#x1f38b;滑动窗口丢包问题&#x1f6a9;情况一&#xff1a;数据包已经抵达&#xff0c;ACK被丢了。&#x1f6a9;情况二&#xff1a;数据包就直接丢了 &#x1f332;流量控制&#xff08;安全机制&#xff09;&#x1f333;拥塞控…

JVM之jmap java内存映射工具

jmap java内存映射工具 1、jmap jdk安装后会自带一些小工具&#xff0c;jmap命令(Memory Map for Java)是其中之一。主要用于打印指定Java进程(或核 心文件、远程调试服务器)的共享对象内存映射或堆内存细节。 jmap命令可以获得运行中的jvm的堆的快照&#xff0c;从而可以离…