什么是live2D技术?可以用来做什么?
请点击看效果:http://ashuai.work:8890/#/16
简而言之:
- 可以用来创建虚拟角色、数字人的技术
- 达到类似于动漫、插画、游戏中的人物效果
- 可动作交互、语音发声
- 可以用到的平台很多,比如Web、Native、Unity、游戏引擎、JAVA等平台
- 就前端而言,3D项目使用threejs,2D项目使用pixijs
- 所以,pixijs搭配live2D就可以在我们的页面上实现一个虚拟角色、数字人、或者说看板娘技术
附上维基百科的介绍:https://zh.wikipedia.org/wiki/Live2D
如下图:
或
或(类似博客园的看板娘)
live2d的应用之live2d-render插件
- live2d技术实现的一个个人物、动物模型
- 所以,首先,我们要搞到一些人物的模型数据文件
关于live2d的模型数据文件
- live2d模型数据就是由绘画师提前通过live2d的官方制作建模软件搞出来的
- 绘画师提前制作出的人物、动物
- 给人物添加外型、轮廓、衣服
- 再把人物数据导出一个文件夹,文件夹中主要是一个个文件(JSON文件为主)
- github或者哔哩哔哩上有不少以往的模型文件
- 当然,官方也提供了一些模型文件给我们学习使用
- 这里,要注意他们的Licence 的使用范围
- 官方模型免费下载:https://www.live2d.com/zh-CHS/learn/sample/
- 如下图:
- 模型下载解压以后,得到对应文件夹中的模型数据
上述文件夹文件,分别是代表人物模型的数据意思为:
- kei\_basic\_free.2048 人物衣服材质素材
- motions 预设的人物动作等
- sounds 人物自带默认音效
- .moc3文件是核心模型数据(二进制)
- .motionsync3.json是动态同步设定文件
- 等...
- 我们要关注kei\_basic\_free.model3.json这个文件
- 这个是引入此模型文件的入口文件
这个模型文件比较少一些,有些丰富人物的模型,还有expressions表情文件夹(存放的预设好的喜怒哀乐等数据...)
live2d-render插件
官方的live2d相关的api比较繁杂,上手成本高一些,笔者简单调研后,找到了两个还不错的封装库
先说简单一些的live2d-render插件的使用
第0步,下载live2d的sdk
- 下载地址:https://www.live2d.com/zh-CHS/sdk/download/web/
- 因为是web项目,所以下载这个版本
- 打开页面,滑动到最底下
- 下载图示
第1步,把下载好的live2d的sdk存放在public文件夹中,再在index.html文件中使用script引入
第2步,下载live2d-render插件
npm install live2d-render --save
- 笔者用的是这个版本
"live2d-render": "^0.0.5"
第3步,搞一个.vue文件,把下列代码复制粘贴进去
<template>
<div>
<h1>live2d-render</h1>
<button @click="ccc">表情切换</button>
</div>
</template>
<script setup>
import { onMounted } from "vue";
import * as live2d from "live2d-render"; // 引入插件
onMounted(() => {
init();
});
const init = async () => {
await live2d.initializeLive2D({
// live2d 所在区域的背景颜色
BackgroundRGBA: [0.0, 0.0, 0.0, 0.0],
// live2d 的 model3.json 文件的相对 根目录 的路径
// ResourcesPath: "/live2d/Haru/Haru.model3.json", // 成熟御姐
// ResourcesPath: "/live2d/Hiyori/Hiyori.model3.json", // 可爱萝莉
ResourcesPath: "/live2d/Mao/Mao.model3.json", // 魔法女巫
// ResourcesPath: "/live2d/Mark/Mark.model3.json", // 大眼睛呆萌男孩
// ResourcesPath: "/live2d/Natori/Natori.model3.json", // 高挑帅气西装男
// ResourcesPath: "/live2d/Rice/Rice.model3.json", // 白裙水手服女
// ResourcesPath: "/live2d/Wanko/Wanko.model3.json", // 碗里面有小狗
// live2d 的大小
CanvasSize: {
height: 600,
width: 400,
},
// 展示工具箱(可以控制 live2d 的展出隐藏,使用特定表情)
ShowToolBox: false,
// 是否使用 indexDB 进行缓存优化,这样下一次载入就不会再发起网络请求了
LoadFromCache: true,
});
};
const ccc = () => {
live2d.setRandomExpression(); // 随机切换表情
};
</script>
第4步,效果出来了哦
效果图:
插件文档地址:https://document.kirigaya.cn/docs/live2d-render/vue-install.html
上述代码中,笔者提到了一些模型,包括code,都在笔者的github中。届时直接去github上pull代码即可,github仓库地址在文末
接下来,我们使用pixi-live2d-display来做一个可以根据音频说话的虚拟数字人角色
live2d的应用之pixi-live2d-display插件
关于pixi-live2d-display
- github: https://github.com/guansss/pixi-live2d-display
- 可惜少了点中文文档,不过问题不大
- 相对而言,这个库的使用量还可以,更加推荐
- 需要搭配pixi.js
需要注意版本:pixi.js 不能超过6
如下安装:
npm i pixi-live2d-display@0.4.0 --save
npm i pixi.js@6.5.10 --save
关于pixi.js的中文文档,可以看这里:https://pixijs.huashengweilai.com/
简单版代码
不赘述,很简单
<template>
<div class="canvasWrap">
<canvas id="myCanvas" />
</div>
</template>
<script setup>
import { onMounted, onBeforeUnmount } from "vue";
import * as PIXI from "pixi.js";
import { Live2DModel } from "pixi-live2d-display/cubism4"; // 只需要 Cubism 4
window.PIXI = PIXI; // 为了pixi-live2d-display内部调用
let app; // 为了存储pixi实例
let model; // 为了存储live2d实例
onMounted(() => {
init();
});
onBeforeUnmount(() => {
app = null;
model = null;
});
const init = async () => {
// 创建PIXI实例
app = new PIXI.Application({
// 指定PixiJS渲染器使用的HTML <canvas> 元素
view: document.querySelector("#myCanvas"),
// 响应式设计
resizeTo: document.querySelector("#myCanvas"),
// 设置渲染器背景的透明度 0(完全透明)到1(完全不透明)
backgroundAlpha: 0,
});
// 引入live2d模型文件
model = await Live2DModel.from("/live2d/Haru/Haru.model3.json", {
autoInteract: false, // 关闭眼睛自动跟随功能
});
// 调整live2d模型文件缩放比例(文件过大,需要缩小)
model.scale.set(0.12);
// 调整x轴和y轴坐标使模型文件居中
model.y = 0;
model.x = -24;
// 把模型添加到舞台上
app.stage.addChild(model);
};
</script>
<style lang="less" scoped>
#myCanvas {
width: 240px;
height: 360px;
}
</style>
效果图:
让人物说话——嘴唇动弹
- 使用库提供的api
model.internalModel.coreModel.setParameterValueById("ParamMouthOpenY", n)
- 这里的
n
介于0\~1之间,表示嘴唇开合的幅度 - 所以我们可以这样写:
- 点击按钮后,让人物嘴唇不断变化
<button @click="mouthFn">嘴型变换</button>
const mouthFn = () => {
setInterval(() => {
let n = Math.random();
console.log("随机数0~1控制嘴巴Y轴高度-->", n);
model.internalModel.coreModel.setParameterValueById("ParamMouthOpenY", n);
}, 100);
};
效果图:
踩坑之模型嘴唇不变化
- 官方提供的第一个模型,是不会出现这个问题的
- 但是,有些模型有原本预设的嘴唇变化幅度逻辑,或其他情况,导致会出现这个语句不生效的情况
model.internalModel.coreModel.setParameterValueById("ParamMouthOpenY", n)
- 以haru模型为例:
- 这个时候,我们需要在motions/haru\_g\_idle.motion3.json文件中做如下更改
- 这样就能够解决问题了
至此,数字人已经可以张嘴说话了,不过还没有声音,接下来让声音播放即可。笔者翻阅资料,找到了一个方案如下
使用AudioContext播放声音,并转成音频频谱做分析,再转成音量大小,从而控制嘴唇的开合大小
如下代码:
<button @click="speakFn">人物说话</button>
const speakFn = async () => {
// 请求加载一个音频文件
const response = await fetch(audioFile);
// 将音频读取为原始的二进制数据缓冲区(ArrayBuffer)。音频本身是二进制格式,要先将其加载为 ArrayBuffer 才能进一步处理
const audioData = await response.arrayBuffer();
// 将 ArrayBuffer 格式的音频数据解码成 AudioBuffer 对象,可以直接用于播放或处理音频数据。
const audioBuffer = await audioContext.decodeAudioData(audioData);
// 创建一个音频源节点(AudioBufferSourceNode),该节点用于播放音频数据
const source = audioContext.createBufferSource();
// 创建一个音频分析节点。这个节点用于实时分析音频数据,提供诸如频谱分析、波形分析等功能
const analyser = audioContext.createAnalyser();
// 将之前解码得到的 audioBuffer(即音频数据)赋值给 source 节点的 buffer 属性。这样就将加载的音频文件与 source 节点绑定,准备播放。
source.buffer = audioBuffer;
// 将 音频分析节点 连接到音频上下文的最终目标(即扬声器)
analyser.connect(audioContext.destination);
// 音频分析节点 将能够分析通过音频源流动的音频数据,并提供频谱或其他音频信息。
source.connect(analyser);
// 监听音频播放完毕
let requestId = null;
source.onended = function () {
cancelAnimationFrame(requestId); // 清除请求动画帧
model.internalModel.coreModel.setParameterValueById("ParamMouthOpenY", 0); // 闭上嘴巴
};
/**
* 启动音频源的播放,从头开始(这样的话,页面就能够听到声音了)
* 接下来需要让人物嘴巴更新动弹,即有声音的同时,且能够说话
* 即为:updateMouth函数
* */
source.start(0);
/**
* 这个 updateMouth 函数通过从 analyser 获取音频数据并计算音量,动态地更新一个模型的嘴巴张开程度。它的实现方式是每帧都更新一次,
* 通过音频的音量强度来决定嘴巴的开合程度,从而实现与音频的实时互动。
* */
const updateMouth = () => {
// analyser.frequencyBinCount 表示音频频谱的 bin(频率段)的数量。它是一个整数,表示从频率数据中可以获取多少个频率段的值
// 使用 analyser 对象的 getByteFrequencyData 方法填充 dataArray 数组。
// getByteFrequencyData 将音频的频率数据转化为 0-255 范围内的字节值,并存储在 dataArray 中。这个数据表示了音频信号在不同频率范围内的强度。
// 该方法会将频谱分析的结果填充到 dataArray 数组中,每个元素代表一个频率段的音量强度。
// 使用 reduce 方法计算 dataArray 数组的所有值的总和,并通过除以数组长度来求得平均值。这个平均值表示音频信号的总体“强度”或“音量”。
// 这里的 a + b 累加所有音频频段的强度值,最终计算出一个平均值。
// dataArray.length 是频率数据的总数,通常它等于 analyser.frequencyBinCount。
// 将计算出的 volume 除以 50,以缩放它到一个合适的范围,得到一个表示“嘴巴张开程度”的值。volume 越大,mouthOpen 越大。
// 使用 Math.min(1, volume / 50) 保证 mouthOpen 的值不会超过 1,也就是说嘴巴张开程度的最大值是 1。
// 这意味着,如果音量足够大,mouthOpen 会接近 1,表示嘴巴完全张开;如果音量较小,mouthOpen 会接近 0,表示嘴巴几乎没张开。
const dataArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(dataArray);
const volume = dataArray.reduce((a, b) => a + b) / dataArray.length;
const mouthOpen = Math.min(1, volume / 50);
// 通过调用 setParameterValueById 方法,将 mouthOpen 的值传递给 model 的内部模型(控制嘴巴大小张开幅度)
model.internalModel.coreModel.setParameterValueById("ParamMouthOpenY", mouthOpen);
requestId = requestAnimationFrame(updateMouth);
};
requestId = requestAnimationFrame(updateMouth);
};
这样的话,数字人就能够张口说话了,话说完,再让数字人闭嘴即可
踩坑之数字人动作自动切换导致人物不张嘴说话了
- 笔者的理解是:
- 数字人有的预设好几个动作
- 在一段时间内自动循环播放动作切换
- 当A动作切换到B动作时候
- 会导致处于A动作开口动作被覆盖
- 这里需要设置动作的权重(pixi-live2d-display文档中也有,需要自行探索)
- 笔者是直接删除那个多余的动作,只保留一个动作
- 就不会出现这个问题了😅😅😅
- 如下图示,修改.model3.json文件即可
github仓库
- 仓库代码:https://github.com/shuirongshuifu/vue3-echarts5-example
- 此仓库会写一些实用性高点的demo,欢迎大家给个star哦😄
参考资料文档博客
- 二次元live2d看板娘效果中的web前端技术:https://www.zhangxinxu.com/wordpress/2018/05/live2d-web-webgl...
- 视频资源:https://www.bilibili.com/opus/829185827836788806
- 语音口型同步:https://github.com/itorr/itorr/issues/7
- 模型下载方式:https://blog.csdn.net/qq_36303853/article/details/142284330
- 一个模型很多的库:https://imuncle.github.io/live2d/live2d_3/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。