【计算机图形学】3D渲染 Three.js主要组件 + VRM面部/身体姿态跟踪与表情驱动库Kalidokit + 官方数字分身&虚拟主播demo解释
1. Three.js:JavaScript 3D库
1.1 3D渲染原理 & OpenGL & Three.js
- Three.js是基于WebGL开发的3D图形库,WebGL是OpenGL ES的Web端实现(浏览器原生规范):
OpenGL(桌面端标准)→ OpenGL ES(嵌入式/移动端精简版)→ WebGL(浏览器端规范)→ Three.js(WebGL的封装库)
-
OpenGL 3D渲染原理可阅:【计算机图形学】3D渲染 OpenGL :LearnOpenGL笔记
-
两者的设计目标、使用方式、学习成本差异极大:
| 特性 | OpenGL | Three.js |
|---|---|---|
| 层级/类型 | 跨平台3D图形底层API标准 | 基于WebGL的JavaScript 3D库 |
| 开发语言 | C/C++(桌面)、GLSL(着色器) | JavaScript + GLSL(可选) |
| 核心工作 | 手动操作顶点、纹理、着色器、渲染管线,需要自己实现3D基础逻辑(如相机、模型、光照) | 内置相机、几何体、光照、材质、渲染器等所有3D基础组件,直接调用API即可 |
| 学习成本 | 极高,需要深入理解计算机图形学(如矩阵变换、深度测试、光栅化) | 极低,无需精通图形学,入门即可做3D效果 |
| 运行环境 | 桌面(Windows/Linux/Mac)、移动端、嵌入式(需结合对应语言框架) | 所有支持WebGL的浏览器(几乎所有现代浏览器),无需额外安装环境 |
| 渲染管线 | 支持固定管线(旧版)和可编程管线(核心,需手动写顶点/片元着色器) | 封装了可编程管线,内置海量材质/着色器,也支持自定义GLSL拓展 |
- 注:Three.js不仅封装了基础3D能力,还拥有海量生态(如加载各种3D模型格式glTF/FBX、物理引擎ammo.js、后期处理库postprocessing),能满足绝大多数Web 3D开发需求。
1.2 Three.js主要组件(完整可运行代码实现立方体,约20行)
-
核心优势:无需写GLSL(除非自定义效果)、无需处理顶点数据、无需编译着色器、无需手动矩阵变换,零基础也能快速实现3D效果,这就是封装的价值。
-
Three.js封装了所有底层操作,内置场景、相机、渲染器、几何体、材质、网格等组件,直接调用即可,无需关注WebGL/OpenGL的底层细节:
// 1. 引入Three.js(CDN/模块化均可)
import * as THREE from 'three';
// 2. 创建核心三要素:场景、相机、渲染器(Three.js内置)
const scene = new THREE.Scene(); // 3D场景容器
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); // 透视相机
const renderer = new THREE.WebGLRenderer(); // WebGL渲染器(自动封装WebGL)
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 3. 创建立方体:几何体+材质+网格(Three.js内置所有基础组件)
const geometry = new THREE.BoxGeometry(1, 1, 1); // 立方体几何体(自动生成顶点数据)
const material = new THREE.MeshBasicMaterial({ color: 0xff7f50 }); // 基础材质(自动封装着色器)
const cube = new THREE.Mesh(geometry, material); // 网格(几何体+材质结合)
scene.add(cube); // 添加到场景
// 4. 设置相机位置
camera.position.z = 5;
// 5. 渲染循环(自动处理重绘、矩阵变换)
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01; // 立方体旋转
cube.rotation.y += 0.01;
renderer.render(scene, camera); // 自动渲染(底层调用WebGL/OpenGL)
}
animate();
2. Kalidokit:面部/身体姿态跟踪与表情驱动库
2.1 Kalidokit简介&整体架构流程
- Kalidokit是基于JavaScript/TypeScript开发的轻量型姿态解析工具,核心作用是把摄像头捕捉的人体/面部关键点数据,转换成VRM模型可直接驱动的骨骼旋转、表情权重,是Web端开发VRM实时动捕数字人的核心依赖库之一。
- 流程步骤
摄像头采集画面 → MediaPipe识别关键点 → Kalidokit解析转换 → 输出VRM骨骼/表情数据 → three-vrm驱动模型渲染
┌────────────────────┐
│ 用户摄像头 Webcam │
└─────────┬──────────┘
│
│ 摄像头视频流
▼
┌────────────────────┐
│ <video> HTML 元素 │
│ input_video │
└─────────┬──────────┘
│
│ 每一帧画面
▼
┌────────────────────────────┐
│ MediaPipe Holistic (AI) │
│ │
│ • 人脸关键点 Face │
│ • 姿态骨骼 Pose │
│ • 手部关键点 Hands │
└─────────┬──────────────────┘
│
│ 原始关键点数据
▼
┌────────────────────────────┐
│ Kalidokit │
│ (运动学解算器) │
│ │
│ • 头部旋转 Head Rotation │
│ • 表情参数 Face Blendshape │
│ • 身体骨骼 Pose Rotation │
│ • 手指关节 Hand Rotation │
└─────────┬──────────────────┘
│
│ VRM 可用的数据
▼
┌────────────────────────────┐
│ Three.js + three-vrm │
│ │
│ • 加载 VRM 模型 │
│ • 设置骨骼旋转 │
│ • 设置表情权重 │
└─────────┬──────────────────┘
│
│ 实时动画
▼
┌────────────────────┐
│ WebGL Canvas │
│ 浏览器 3D 画面 │
└────────────────────┘
2.2 Kalidokit运行依赖
-
VRM 深度适配:内置了VRM 1.0/0.0规范的骨骼映射规则和表情规范(VRM BlendShape),无需手动定义骨骼与姿态的映射关系,解析后的数据可直接对接three-vrm、@pixiv/three-vrm等VRM渲染库;
-
运行环境:纯前端浏览器运行(基于WebGL/Canvas),无需后端服务,支持Chrome/Firefox等现代浏览器,是Web端VRM数字人实时动捕的首选工具。
-
核心依赖:Kalidokit本身不做摄像头识别,而是解析MediaPipe(Google开源的视觉识别框架)输出的关键点数据(面部468个关键点、身体33个姿态点、手部21个关键点),做二次处理和坐标/角度转换;
| 模块类别 | 组件/库名称 | 作用 |
|---|---|---|
| 3D 渲染基础 | Three.js | 基于 WebGL 的 3D 渲染引擎,用于在浏览器中创建和显示 3D 场景与模型。 |
| 3D 模型加载 | GLTFLoader | 加载 .gltf 或 .glb 格式的 3D 模型;VRM 模型基于 GLTF 格式,因此也适用。 |
| 相机交互控制 | OrbitControls | 实现鼠标拖拽旋转、滚轮缩放、平移等操作,提升用户与 3D 场景的交互体验。 |
| VRM 虚拟角色支持 | three-vrm (@pixiv/three-vrm) | 专门用于加载和控制 VRM 格式 的虚拟人物模型,支持骨骼动画、BlendShape 表情等。 |
| 人体/人脸 AI 捕捉 | MediaPipe Holistic | Google 提供的 AI 模型,通过摄像头实时检测: • 人脸关键点(表情、眼嘴) • 全身姿态(骨骼) • 双手关节(21 点/手) |
- VRM = GLTF + 扩展:VRM 是基于 GLTF 2.0 的扩展格式,专为虚拟人设计,因此需
GLTFLoader+three-vrm协同工作。 - MediaPipe Holistic 运行在浏览器端,无需服务器,直接调用摄像头即可实现实时动作捕捉。
2.3 Kalidokit 核心功能模块
Kalidokit将动捕解析分为4大核心模块,完全覆盖VRM数字人的头部、面部、身体、手部驱动需求,每个模块都能输出VRM适配的标准化数据:
2.3.1. 面部表情解析(Face)
- 解析MediaPipe Face Mesh的468个面部关键点,计算出VRM标准BlendShape表情权重(0~1之间);
- 内置支持VRM规范的基础表情:开心(Joy)、生气(Anger)、悲伤(Sorrow)、惊讶(Fun)、闭眼(Blink)、张嘴(Aah)、挑眉、嘴角拉伸等,也支持自定义表情映射;
import * as Kalidokit from 'kalidokit';
import { VRM } from '@pixiv/three-vrm';
// 假设已通过MediaPipe获取面部关键点数据faceMeshResults
// 假设已加载并实例化VRM模型:const vrm: VRM;
// Kalidokit解析面部数据(输出头部姿态+表情权重)
const faceResult = Kalidokit.Face.solve(faceMeshResults, {
runtime: 'mediapipe', // 识别引擎:mediapipe/tfjs
video: videoElement, // 摄像头视频元素(用于计算相对坐标)
faceDetectConfidence: 0.8, // 面部检测置信度阈值
meshConfidence: 0.8, // 关键点识别置信度阈值
});
// 驱动VRM表情(BlendShape权重赋值,0~1)
vrm.blendShapeProxy.setValue('Joy', faceResult.expressions.Joy); // 开心
vrm.blendShapeProxy.setValue('Aah', faceResult.expressions.Aah); // 张嘴
vrm.blendShapeProxy.setValue('Blink_L', faceResult.expressions.Blink); // 左眼闭眼
vrm.blendShapeProxy.setValue('Blink_R', faceResult.expressions.Blink); // 右眼闭眼
vrm.blendShapeProxy.update(); // 更新表情
- 额外支持头部姿态跟踪:解析头部的偏航(左右转)、俯仰(上下抬)、侧倾(左右歪)角度,转换成VRM头部骨骼的旋转数据。
// 驱动VRM头部姿态(四元数赋值)
if (vrm.humanoid.getBoneNode('head')) {
vrm.humanoid.getBoneNode('head').quaternion.set(
faceResult.head.x, // 侧倾
faceResult.head.y, // 偏航
faceResult.head.z, // 俯仰
faceResult.head.w // 四元数w分量
);
}
2.3.2. 身体姿态解析(Pose)
- 解析MediaPipe Pose的33个人体姿态关键点,计算出全身骨骼的旋转角度(胯部、腰部、胸部、肩膀、手臂、腿部等);
- 针对VRM骨骼做了反向运动学(IK)简化,无需复杂的IK解算,直接输出欧拉角/四元数,可直接赋值给VRM骨骼;
- 支持下半身根节点锁定(避免数字人因姿态偏移原地移动),适配VRM数字人“站定动捕”的常见需求。
2.3.3. 手部姿态解析(Hand)
- 解析MediaPipe Hands的左右手各21个关键点,计算出手指各关节、手腕的旋转角度;
- 适配VRM的手部骨骼规范,支持五指的独立弯曲、张开、旋转驱动,还原精细的手部动作(如比心、点赞、握拳)。
2.3.4. 数据平滑与优化
-
内置插值平滑算法,解决摄像头识别的“抖动问题”(如面部关键点轻微跳动导致数字人表情僵硬);
-
支持角度限制,可自定义骨骼旋转的最大范围,避免VRM模型出现骨骼穿模、姿态扭曲的情况;
-
输出数据支持欧拉角(Euler)、四元数(Quaternion),兼容three.js等3D引擎的骨骼旋转赋值规范。
-
Kalidokit 工具函数(数学辅助):
// 从 Kalidokit 中导入工具函数
const remap = Kalidokit.Utils.remap; // 数值区间映射
const clamp = Kalidokit.Utils.clamp; // 限制数值范围
const lerp = Kalidokit.Vector.lerp; // 线性插值(平滑过渡)
- remap:把一个范围的值映射到另一个范围
- clamp:防止数值超出合理范围
- lerp:让动画“慢慢过渡”,避免抖动
3. 官方示例
- three-vrm集成示例:https://github.com/pixiv/three-vrm/tree/main/examples(含Kalidokit联动示例);
3.1. HTML部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- HTML Meta Tags -->
<title>KalidoKit - Face and Body Rig Solver</title>
<meta
name="description"
content="Track face and body rigs just your browser webcam!"
/>
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content="kit.kalidoface.com" />
<meta property="twitter:url" content="https://kit.kalidoface.com/" />
<meta name="twitter:title" content="KalidoKit - Face and Body Rig Solver" />
<meta
name="twitter:description"
content="Track face and body rigs just your browser webcam!"
/>
<meta
name="twitter:image"
content="https://cdn.glitch.com/239c5934-4d83-4c5c-bef6-44dcdb04c8fb%2Fkalidoface-meta.jpg?v=1630110302224"
/>
<meta
name="viewport"
content="viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"
/>
<link
rel="shortcut icon"
href="https://yeemachine.github.io/kalidoface-live2d-models/Icons/icon-circle.svg"
/>
<!-- Basic Three.js -->
<script src="https://unpkg.com/three@0.133.0/build/three.js"></script>
<!-- GLTF Loader for Three.js -->
<script src="https://unpkg.com/three@0.133.0/examples/js/loaders/GLTFLoader.js"></script>
<!-- Orbit Controls for Three.js -->
<script src="https://unpkg.com/three@0.133.0/examples/js/controls/OrbitControls.js"></script>
<!-- VRM Loader for Three.js -->
<script src="https://unpkg.com/@pixiv/three-vrm@0.6.7/lib/three-vrm.js"></script>
<!-- Mediapipe or Tensorflow.js -->
<script
src="https://cdn.jsdelivr.net/npm/@mediapipe/holistic@0.5.1635989137/holistic.js"
crossorigin="anonymous"
></script>
<!-- Mediapipe Drawing Tools -->
<script
src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
crossorigin="anonymous"
></script>
<!-- Mediapipe Camera Tools -->
<script
src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"
crossorigin="anonymous"
></script>
<!-- Kalidokit Kinematic Solver -->
<script src="https://cdn.jsdelivr.net/npm/kalidokit@1.1/dist/kalidokit.umd.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="preview">
<video class="input_video" width="1280px" height="720px" autoplay muted playsinline></video>
<canvas class="guides"></canvas>
<section>
<a href="https://kalidokit-live2d.glitch.me"><p>Live2D</p></a>
<a class="current" href="https://glitch.com/edit/#!/kalidokit"
><p>VRM</p></a
>
</section>
</div>
<h1 class="notranslate">
<a href="https://3d.kalidoface.com">kalidoface</a>
</h1>
<nav>
<a href="https://www.npmjs.com/package/kalidokit">
<img
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Fnpm.png?v=1635133318451"
/>
</a>
<a href="https://github.com/yeemachine/kalidokit">
<img
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Fgithub.png?v=1635133310517"
/>
</a>
<a href="https://twitter.com/yeemachine">
<img
src="https://cdn.glitch.me/447b6603-7eae-4da6-957d-73ee30c8e731%2Ftwitter.png?v=1635133322561"
/>
</a>
</nav>
<p class="linkOut">
Visit
<a id="full" href="https://kalidoface.com">the full Vtuber App</a>!
</p>
<script src="script.js"></script>
</body>
</html>
3.1.1 画布&容器
<div class="preview">
<video class="input_video" width="1280px" height="720px" autoplay muted playsinline></video> //`muted`:防止自动播放被浏览器拦截,`playsinline`:iOS 不全屏
<canvas class="guides"></canvas>
</div>
3.1.2 video 和 canvas 的关系(简短说明):
-
video.input_video:作为媒体源,播放摄像头流(原始图像帧)。
-
canvas.guides:覆盖在 video 之上,用来绘制检测到的关键点/骨架/调试指示(不会改变视频本身)。
-
脚本中把 video 作为输入送给 Mediapipe/kalidokit,拿到 landmark 后在 canvas 上用 2D context 绘制(每帧清除 canvas,然后绘制新的 guides,3D类似且更简单)。保证两者尺寸一致并通过 CSS 用 position/z-index 叠放。
-
简短说明:
- videoElement (.input_video) 作为摄像头媒体源,传给 Mediapipe(Camera + holistic.send)。
- guideCanvas (canvas.guides) 用于在每帧上绘制关键点/连接,尺寸通过 videoElement.videoWidth/Height 同步。
-
每一帧更新数据,实现实时动捕驱动并在2d中就是绘制关键点连接:
// 获取元素
const video = document.querySelector('.input_video');
const canvas = document.querySelector('.guides');
const ctx = canvas.getContext('2d');
// 每帧渲染(回调示例)
function onFrame(landmarks) {
// 保证 canvas 大小与 video 一致
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// 清除并绘制视频(可选,如果 video 在页面下层则不必画)
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ctx.drawImage(video, 0, 0, canvas.width, canvas.height);// 将 <video> 元素当前播放的画面绘制到画布上
// 用 landmarks 绘制点/线
ctx.fillStyle = 'red';
landmarks.forEach(pt => {
ctx.beginPath();
ctx.arc(pt.x * canvas.width, pt.y * canvas.height, 3, 0, Math.PI * 2);
ctx.fill();
});
}
// 在 Mediapipe / camera 回调中调用 onFrame(landmarks)
3.2 脚本部分
3.2.1 加载脚本
- 所有脚本需按顺序加载(如先加载
three.js,再加载GLTFLoader和OrbitControls)。 - 辅助工具
drawing_utils:画人脸/骨骼点到 Canvascamera_utils:简化摄像头调用
| 流程步骤 | 对应代码 / 库 |
|---|---|
| 摄像头 | <video class="input_video"> |
| 摄像头控制 | camera_utils.js |
| 人体识别 | MediaPipe Holistic |
| 画关键点 | drawing_utils.js |
| 数据转换 | Kalidokit |
| 3D 渲染 | Three.js |
| VRM 支持 | three-vrm |
| 主逻辑 | script.js |
3.2.2 完整版运行流程
- 3DVRM的驱动代码(2D类似):
/**************************************************
* 一、从 Kalidokit 中导入常用工具函数
**************************************************/
// Kalidokit控制函数
const remap = Kalidokit.Utils.remap;// remap:把一个数值从一个区间映射到另一个区间
const clamp = Kalidokit.Utils.clamp;// clamp:限制数值在最小值和最大值之间
const lerp = Kalidokit.Vector.lerp;// lerp:线性插值,用于让动画平滑过渡
/**************************************************
* 二、Three.js 世界初始化
**************************************************/
// 当前加载并正在使用的 VRM 模型
let currentVrm;
/* ========== 1. 创建渲染器 Renderer ========== */
// 创建 WebGL 渲染器,alpha=true 表示背景透明
const renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);// 设置渲染尺寸为窗口大小
renderer.setPixelRatio(window.devicePixelRatio);// 设置像素比,避免高清屏模糊
document.body.appendChild(renderer.domElement);// 把渲染画布插入到页面 body 中;创建渲染器时,Three.js 在内存里生成<canvas> 标签(即renderer.domElement)。
/* ========== 2. 创建相机 Camera ========== */
// 创建透视相机
// 参数:视野角度、宽高比、近裁剪面、远裁剪面
const orbitCamera = new THREE.PerspectiveCamera(
35,
window.innerWidth / window.innerHeight,
0.1,
1000
);
// 设置相机位置(前方 + 稍微高一点)
orbitCamera.position.set(0.0, 1.4, 0.7);
/* ========== 3. 轨道控制器 OrbitControls ========== */
// 用鼠标控制相机旋转 / 缩放
const orbitControls = new THREE.OrbitControls(
orbitCamera,
renderer.domElement
);
orbitControls.screenSpacePanning = true;// 允许屏幕空间平移
orbitControls.target.set(0.0, 1.4, 0.0);// 相机始终看向角色头部附近
orbitControls.update();// 更新控制器状态
/* ========== 4. 场景 Scene ========== */
// Three.js 的场景容器
const scene = new THREE.Scene();
/* ========== 5. 光源 Light ========== */
// 创建平行光
const light = new THREE.DirectionalLight(0xffffff);
light.position.set(1.0, 1.0, 1.0).normalize();// 设置光源方向并归一化
scene.add(light);// 把光源加入场景
/* ========== 6. 主渲染循环 ========== */
// 用于获取每一帧的时间差(deltaTime)
const clock = new THREE.Clock();
// 主动画循环函数
function animate() {
// 请求浏览器下一帧
requestAnimationFrame(animate);
// 如果 VRM 已加载,更新 VRM 物理(头发、裙子等)
if (currentVrm) {
currentVrm.update(clock.getDelta());
}
// 渲染场景
renderer.render(scene, orbitCamera);
}
// 启动渲染循环
animate();
/**************************************************
* 三、加载 VRM 角色模型
**************************************************/
// 创建 GLTF 加载器(VRM 本质是 GLTF)
const loader = new THREE.GLTFLoader();
// 允许跨域加载模型
loader.crossOrigin = "anonymous";
// 从 URL 加载 VRM 模型
loader.load(
"https://cdn.glitch.com/29e07830-2317-4b15-a044-135e73c7f840%2FAshtra.vrm?v=1630342336981",
// 加载成功回调
gltf => {
// 移除 VRM 中不必要的骨骼,提高性能
THREE.VRMUtils.removeUnnecessaryJoints(gltf.scene);
// 将 GLTF 转换为 VRM 对象
THREE.VRM.from(gltf).then(vrm => {
// 把模型加入场景
scene.add(vrm.scene);
// 保存当前 VRM 引用
currentVrm = vrm;
// 旋转 180 度,让模型面向摄像头
currentVrm.scene.rotation.y = Math.PI;
});
},
// 加载进度回调
progress => {
console.log(
"Loading model...",
100.0 * (progress.loaded / progress.total),
"%"
);
},
// 加载错误回调
error => console.error(error)
);
/**************************************************
* 四、骨骼动画辅助函数
**************************************************/
/* ========== 1. 骨骼旋转函数 ========== */
// name:骨骼名称(VRM 标准名)
// rotation:旋转角度(x y z)
// dampener:动作幅度缩放
// lerpAmount:插值平滑程度
const rigRotation = (
name,
rotation = { x: 0, y: 0, z: 0 },
dampener = 1,
lerpAmount = 0.3
) => {
// 如果模型还没加载,直接返回
if (!currentVrm) return;
// 根据骨骼名获取骨骼节点
const Part = currentVrm.humanoid.getBoneNode(
THREE.VRMSchema.HumanoidBoneName[name]
);
// 如果该骨骼不存在,返回
if (!Part) return;
// 创建欧拉角
let euler = new THREE.Euler(
rotation.x * dampener,
rotation.y * dampener,
rotation.z * dampener
);
// 欧拉角转四元数
let quaternion = new THREE.Quaternion().setFromEuler(euler);
// 使用 slerp 进行平滑旋转
Part.quaternion.slerp(quaternion, lerpAmount);
};
/* ========== 2. 骨骼位移函数 ========== */
const rigPosition = (
name,
position = { x: 0, y: 0, z: 0 },
dampener = 1,
lerpAmount = 0.3
) => {
if (!currentVrm) return;
const Part = currentVrm.humanoid.getBoneNode(
THREE.VRMSchema.HumanoidBoneName[name]
);
if (!Part) return;
// 创建位移向量
let vector = new THREE.Vector3(
position.x * dampener,
position.y * dampener,
position.z * dampener
);
// 平滑插值移动
Part.position.lerp(vector, lerpAmount);
};
/**************************************************
* 五、面部驱动(头部 / 表情 / 眼睛)
**************************************************/
// 保存上一帧的眼球方向,防止抖动
let oldLookTarget = new THREE.Euler();
const rigFace = (riggedFace) => {
if (!currentVrm) return;
/* ========== 1. 头部旋转 ========== */
rigRotation("Neck", riggedFace.head, 0.7);
/* ========== 2. 表情(BlendShape) ========== */
const Blendshape = currentVrm.blendShapeProxy;
const PresetName = THREE.VRMSchema.BlendShapePresetName;
// 计算左右眼眨眼(VRM:1=闭眼,0=睁眼)
riggedFace.eye.l = lerp(
clamp(1 - riggedFace.eye.l, 0, 1),
Blendshape.getValue(PresetName.Blink),
0.5
);
riggedFace.eye.r = lerp(
clamp(1 - riggedFace.eye.r, 0, 1),
Blendshape.getValue(PresetName.Blink),
0.5
);
// 使用 Kalidokit 稳定眨眼
riggedFace.eye = Kalidokit.Face.stabilizeBlink(
riggedFace.eye,
riggedFace.head.y
);
// 设置眨眼表情
Blendshape.setValue(PresetName.Blink, riggedFace.eye.l);
/* ========== 3. 嘴型(A I U E O) ========== */
Blendshape.setValue(PresetName.I,
lerp(riggedFace.mouth.shape.I, Blendshape.getValue(PresetName.I), 0.5)
);
Blendshape.setValue(PresetName.A,
lerp(riggedFace.mouth.shape.A, Blendshape.getValue(PresetName.A), 0.5)
);
Blendshape.setValue(PresetName.E,
lerp(riggedFace.mouth.shape.E, Blendshape.getValue(PresetName.E), 0.5)
);
Blendshape.setValue(PresetName.O,
lerp(riggedFace.mouth.shape.O, Blendshape.getValue(PresetName.O), 0.5)
);
Blendshape.setValue(PresetName.U,
lerp(riggedFace.mouth.shape.U, Blendshape.getValue(PresetName.U), 0.5)
);
/* ========== 4. 眼球注视方向 ========== */
let lookTarget = new THREE.Euler(
lerp(oldLookTarget.x, riggedFace.pupil.y, 0.4),
lerp(oldLookTarget.y, riggedFace.pupil.x, 0.4),
0,
"XYZ"
);
oldLookTarget.copy(lookTarget);
// 应用眼球朝向
currentVrm.lookAt.applyer.lookAt(lookTarget);
};
/**************************************************
* 六、VRM 总动画入口(核心)
**************************************************/
const animateVRM = (vrm, results) => {
if (!vrm) return;
let riggedPose, riggedLeftHand, riggedRightHand, riggedFace;
// MediaPipe 输出数据
const faceLandmarks = results.faceLandmarks;
const pose3DLandmarks = results.ea;
const pose2DLandmarks = results.poseLandmarks;
const leftHandLandmarks = results.rightHandLandmarks;
const rightHandLandmarks = results.leftHandLandmarks;
/* ========== 面部动画 ========== */
if (faceLandmarks) {
riggedFace = Kalidokit.Face.solve(faceLandmarks, {
runtime: "mediapipe",
video: videoElement
});
rigFace(riggedFace);
}
/* ========== 身体动画 ========== */
if (pose2DLandmarks && pose3DLandmarks) {
riggedPose = Kalidokit.Pose.solve(
pose3DLandmarks,
pose2DLandmarks,
{
runtime: "mediapipe",
video: videoElement
}
);
// 髋部旋转 + 位移
rigRotation("Hips", riggedPose.Hips.rotation, 0.7);
rigPosition(
"Hips",
{
x: -riggedPose.Hips.position.x,
y: riggedPose.Hips.position.y + 1,
z: -riggedPose.Hips.position.z
},
1,
0.07
);
// 脊柱
rigRotation("Chest", riggedPose.Spine, 0.25, 0.3);
rigRotation("Spine", riggedPose.Spine, 0.45, 0.3);
// 四肢
rigRotation("RightUpperArm", riggedPose.RightUpperArm);
rigRotation("RightLowerArm", riggedPose.RightLowerArm);
rigRotation("LeftUpperArm", riggedPose.LeftUpperArm);
rigRotation("LeftLowerArm", riggedPose.LeftLowerArm);
rigRotation("LeftUpperLeg", riggedPose.LeftUpperLeg);
rigRotation("LeftLowerLeg", riggedPose.LeftLowerLeg);
rigRotation("RightUpperLeg", riggedPose.RightUpperLeg);
rigRotation("RightLowerLeg", riggedPose.RightLowerLeg);
}
/* ========== 手部动画(左右手) ========== */
if (leftHandLandmarks) {
riggedLeftHand = Kalidokit.Hand.solve(leftHandLandmarks, "Left");
rigRotation("LeftHand", {
z: riggedPose.LeftHand.z,
y: riggedLeftHand.LeftWrist.y,
x: riggedLeftHand.LeftWrist.x
});
}
if (rightHandLandmarks) {
riggedRightHand = Kalidokit.Hand.solve(rightHandLandmarks, "Right");
rigRotation("RightHand", {
z: riggedPose.RightHand.z,
y: riggedRightHand.RightWrist.y,
x: riggedRightHand.RightWrist.x
});
}
};
/**************************************************
* 七、MediaPipe Holistic 初始化
**************************************************/
let videoElement = document.querySelector(".input_video");
let guideCanvas = document.querySelector("canvas.guides");
// MediaPipe 每帧回调
const onResults = (results) => {
drawResults(results); // 画调试点
animateVRM(currentVrm, results); // 驱动 VRM
};
// 创建 Holistic 实例
const holistic = new Holistic({
locateFile: file =>
`https://cdn.jsdelivr.net/npm/@mediapipe/holistic@0.5.1635989137/${file}`
});
// 设置识别参数
holistic.setOptions({
modelComplexity: 1,
smoothLandmarks: true,
minDetectionConfidence: 0.7,
minTrackingConfidence: 0.7,
refineFaceLandmarks: true
});
// 注册回调
holistic.onResults(onResults);
/**************************************************
* 八、启动摄像头
**************************************************/
const camera = new Camera(videoElement, {
onFrame: async () => {
// 把当前视频帧送入 MediaPipe
await holistic.send({ image: videoElement });
},
width: 640,
height: 480
});
// 启动摄像头
camera.start();
3.2.3 测试效果

CG
- 官方仓库:https://github.com/yeemachine/kalidokit(含核心API文档、基础示例);
- 官方VRM示例:https://kalidokit.yeemachine.com/(在线演示,可直接摄像头体验VRM动捕);
- Blender基础使用:【计算机图形学】3D渲染 Blender :简单总结 建模(视图、移动旋转放缩、视图分割、细分、挤出、分割、合并、填充、曲线建模)+修改器+材质(渲染引擎设置、材质、BSDF、纹理)
- 模型生成相关:【计算机图形学】3D渲染 从模型生成到VRM:AI(混元3D、Rodi、Meshy)生成模型+Blender模型优化+模型导出为VRM
- 表情控制相关:【计算机图形学】3D渲染 Blender : 形态键(Shape Key) +BlendShape表情控制+VRM预设形态+镜像操作
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)