什麼是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/