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基础与拓展练习(面捕与物体变形)
unity - Blend Shape - 变形器 - 实践_第1张图片


Blender

Shift+A 新建一个 sphere
unity - Blend Shape - 变形器 - 实践_第2张图片

选中

unity - Blend Shape - 变形器 - 实践_第3张图片

Tab 进入 Editor Mode,并且在 Data 页签属性的 Shape Keys 添加对应的 blend shape 状态
unity - Blend Shape - 变形器 - 实践_第4张图片
unity - Blend Shape - 变形器 - 实践_第5张图片

调整好每一个 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
unity - Blend Shape - 变形器 - 实践_第6张图片

将 fbx 模型拖拽到 hierarchy
unity - Blend Shape - 变形器 - 实践_第7张图片

尝试拖拉 inspector 中的 blend shape 拉杆,即可查看效果
unity - Blend Shape - 变形器 - 实践_第8张图片

所以我们写脚本控制 blend shape 混合拉杆即可达到我们各种表情的混合控制
unity - Blend Shape - 变形器 - 实践_第9张图片

在原来 blendshape_1 基础上在混合 blendshape_2
unity - Blend Shape - 变形器 - 实践_第10张图片

blendshape_1 和 blendshape_2 一起播放
unity - Blend Shape - 变形器 - 实践_第11张图片

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

unity - Blend Shape - 变形器 - 实践_第12张图片

下面是测试 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

你可能感兴趣的:(DCC,TA,-,加油站,unity,unity,blendshape,shapekeys,变形器,FFD)