AI聊天角色3D化:用Three.js实现对话驱动的虚拟形象
2026/9/8 5:33:44 网站建设 项目流程

在过去很长一段时间里,做 AI 聊天角色交互和做 3D 角色展示是两套完全独立的开发流程。一边是后端接口返回一段文本,另一边是前端加载一个 3D 模型,两者各管各,互不通信。真正想把“陪你聊天的 AI 角色”升级成一个“看得见、有表情、会做动作”的 3D 形象时,很多人会卡在技术选型、模型加载、对话数据如何驱动 3D 角色这些环节上。

这篇文章会把这套流程完整拆开讲:从 AI 对话接口接入,到 Three.js 搭建 3D 场景,再到用对话结果驱动角色表情和动作,最后给出常见报错和工程建议。无论你是刚接触前端 3D 的新手,还是想给现有聊天机器人加一个可视化形象的后端开发者,都可以按这里的步骤落地。

1. 背景与核心概念

1.1 什么是“AI 角色 + 3D 形象”方案

先解释一个容易混淆的概念:AI 角色聊天是“大脑”,3D 形象是“身体”。大脑负责理解用户输入、生成回复文本;身体负责把回复内容用可视化方式表达出来。

如果只做聊天,用户看到的是黑底白字的对话窗口,体验相对单薄。如果给 AI 角色加上 3D 形象,聊天就不再只是文本往返,而是一次包含语气、表情、动作的互动体验。用户说“今天好累”,AI 角色可以在回复文字的同时做出一个擦汗或叹气的动作;用户说“太棒了”,角色可以跳起来或者张开双臂。这种体验比单纯的文字回复更有代入感。

在技术上,这套方案通常由三部分组成:

  • 对话服务:调用大模型 API,或本地部署开源模型,接收用户消息并返回回复。
  • 3D 渲染端:使用 Three.js、Babylon.js 或 Unity WebGL 渲染角色模型。
  • 中间数据层:把 AI 回复中的情绪、动作提示词转换成 3D 角色可识别的指令。

1.2 为什么是 Three.js

常见的 3D 方案有 Unity、Unreal、Blender 渲染器等,但如果你做的是网页端产品,Three.js 是门槛最低、社区最活跃的选择。

Three.js 基于 WebGL,能够在浏览器中直接渲染 3D 场景。它不需要用户安装任何插件,也不需要单独的 3D 编辑器。对于 AI 聊天角色这种场景,Three.js 支持 GLB/GLTF 格式的角色模型加载,也能用内置几何体快速搭一个卡通角色原型,非常适合快速验证功能。

此外,Three.js 的动画系统可以直接控制骨骼、表情混合形态,也就是说,AI 返回的情绪标签可以映射成角色的动作和表情,不需要手动逐帧做动画。

1.3 这套方案能解决什么问题

  • 解决“聊天只有文字”的问题:给 AI 对话增加可视化反馈。
  • 解决“3D 角色不会动”的问题:通过表情、动作、镜头变化让角色更生动。
  • 解决“每次换角色都要重写前端”的问题:把角色模型与对话逻辑解耦,模型可替换。
  • 解决“情绪无法可视化”的问题:在 AI 回复中提取情绪标签,驱动 3D 角色做出对应表现。

2. 技术思路与方案选型

在写代码之前,先想清楚整体流程。下图是核心链路:

用户输入文本 ↓ 前端发送请求到对话服务 ↓ 对话服务调用大模型接口得到回复内容和情绪标签 ↓ 前端把文本显示在聊天窗口 前端把情绪标签映射为 3D 角色动作/表情 ↓ Three.js 播放动画并渲染形象

从这个链路可以看出,AI 角色 3D 化的关键不在 3D 渲染本身,而是如何让对话结果“驱动”角色表现。在后面的实战中,我会把情绪标签设计成几个固定枚举值:happy、sad、angry、surprised、neutral,方便前端映射。

2.1 AI 对话服务选型

在实际项目中,AI 对话服务有两种选型方式:

  • 使用云端大模型 API:开发简单、回复质量高,但需要考虑调用成本和数据隐私。
  • 使用本地部署开源模型:数据不出内网,适合企业内部场景,但需要 GPU 资源。

本文示例采用“服务端代理大模型接口”的方式,前端不直接暴露 API Key。这样做的原因是:云厂商的 API 密钥属于敏感信息,放在浏览器端很容易被窃取,而且存在跨域和配额限制。让后端做一个统一的代理接口,前端只请求自己的后端,既安全又便于后续扩展。

2.2 3D 角色来源选型

构建 3D 角色形象,一般有三种途径:

方式优点缺点适用场景
使用现成 GLB 模型效果精细,有现成骨骼动画模型版权需确认,文件较大产品上线阶段
Three.js 程序化建模代码生成,体积小,加载快形象简单,细节有限原型验证、Demo、教程
AI 生成 3D 模型快捷,创意空间大生成结果质量不稳,需要后处理设计师辅助创作

在本文的第 4 节,我会先使用 Three.js 程序化方式搭建一个简单卡通角色,因为这种方式不需要外部模型文件,所有几何体都由代码生成,复制即可运行。到了第 5 节,再介绍如何换成更精细的 GLB 模型。

2.3 3D 角色与 AI 对话的交互方式

AI 角色 3D 形象不只是“静态展示”,它需要根据对话内容做出反应。目前比较现实的交互方式有如下三种:

  1. 文本情绪驱动:AI 返回结果里附带一个情绪字段,前端根据字段切换对应动画。
  2. 语音情绪驱动:通过语音识别和声学特征判断用户情绪,再驱动角色。
  3. 用户行为驱动:通过摄像头识别用户面部表情,让 AI 角色模仿或回应。

第一种最简单,也是最容易上手的方案。本文实战部分采用这一方式。

3. 环境准备与项目结构

3.1 环境说明

本文的示例环境如下:

  • Node.js 16 或更高版本
  • npm 8 或更高版本
  • 现代浏览器(Chrome、Edge、Firefox)
  • 推荐的编辑器:VS Code

由于 Three.js 的版本更新比较频繁,不同的 API 可能存在差异。本文以常见的 Three.js 0.150.0 以上版本为例进行演示。如果你使用的是更早或更晚的版本,个别 API 名称可能略有不同,请以你实际安装版本的官方文档为准。

3.2 创建项目并安装依赖

先新建一个空目录,并初始化项目:

mkdir ai-avatar-chat cd ai-avatar-chat npm init -y

然后安装依赖:

npm install express three npm install -D nodemon

这里解释一下依赖的作用:

  • express:用于搭建一个本地静态服务器,同时提供简单的后端接口。
  • three:浏览器端 3D 渲染引擎。
  • nodemon:开发模式下监听文件变化并自动重启服务。

项目目录结构规划如下:

ai-avatar-chat/ ├── package.json ├── server.js └── public/ ├── index.html ├── style.css └── main.js

server.js负责启动服务和代理 AI 对话接口,public/index.html是页面入口,public/main.js是前端逻辑,包括聊天交互和 Three.js 渲染。

4. 完整实战:从文本聊天到 3D 角色形象

4.1 创建后端服务

先在项目根目录创建server.js

// server.js const express = require('express'); const path = require('path'); const app = express(); const PORT = 3000; // 解析 JSON 请求体 app.use(express.json()); // 静态资源托管 app.use(express.static(path.join(__dirname, 'public'))); // 模拟 AI 对话接口(后续可替换为大模型接口) app.post('/api/chat', async (req, res) => { const { message } = req.body; if (!message) { return res.status(400).json({ error: 'message is required' }); } const reply = `你说的是:“${message}”。我是你的 AI 伙伴,很高兴见到你!`; const emotion = detectEmotion(message); res.json({ reply, emotion }); }); // 简单的情绪识别规则 function detectEmotion(text) { if (/开心|高兴|太好了|棒|喜欢/.test(text)) return 'happy'; if (/难过|伤心|哭|失望/.test(text)) return 'sad'; if (/生气|愤怒|讨厌|烦/.test(text)) return 'angry'; if (/惊讶|真的吗|哇|不会吧/.test(text)) return 'surprised'; return 'neutral'; } app.listen(PORT, () => { console.log(`Server is running at http://localhost:${PORT}`); });

在这个示例中,/api/chat接口接收前端传来的message字段,返回两个字段:

  • reply:AI 角色的回复文本。
  • emotion:从用户输入中识别出的情绪标签。

这里的detectEmotion只是简单的关键词规则,用于演示。实际项目中,更推荐让大模型在生成回复时同时输出一个结构化 JSON,包含replyemotion两个字段,这样情绪判断会更准确。

4.2 创建页面结构

public/index.html包含聊天面板和 3D 渲染区域。

<!-- public/index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>AI 角色 3D 形象聊天</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <div id="three-container"></div> <div class="chat-panel"> <div class="chat-header">AI 角色聊天</div> <div id="chat-box" class="chat-box"></div> <div class="input-row"> <input type="text" id="message-input" placeholder="输入你想说的话..."> <button id="send-btn">发送</button> </div> </div> </div> <script type="module" src="main.js"></script> </body> </html>

注意,main.js使用了type="module",这样可以直接使用 ES Module 语法导入 Three.js。

4.3 编写基础样式

public/style.css用来把页面划分成左右两块区域。

/* public/style.css */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "Microsoft YaHei", sans-serif; background: #1a1a2e; color: #fff; overflow: hidden; } .container { display: flex; width: 100vw; height: 100vh; } #three-container { flex: 2; height: 100vh; } .chat-panel { flex: 1; display: flex; flex-direction: column; background: #16213e; border-left: 1px solid #0f3460; } .chat-header { padding: 16px; font-size: 18px; font-weight: bold; border-bottom: 1px solid #0f3460; } .chat-box { flex: 1; padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; } .message { max-width: 80%; padding: 10px 14px; border-radius: 12px; line-height: 1.6; font-size: 14px; } .message.user { align-self: flex-end; background: #0f3460; border-bottom-right-radius: 2px; } .message.ai { align-self: flex-start; background: #533483; border-bottom-left-radius: 2px; } .input-row { display: flex; gap: 10px; padding: 16px; border-top: 1px solid #0f3460; } .input-row input { flex: 1; padding: 10px; border: none; border-radius: 8px; background: #0f3460; color: #fff; outline: none; } .input-row button { padding: 10px 20px; border: none; border-radius: 8px; background: #e94560; color: #fff; cursor: pointer; } .input-row button:hover { background: #c23152; }

4.4 用 Three.js 程序化创建 3D 角色

接下来是核心部分。public/main.js中先完成 3D 场景和角色的搭建。

// public/main.js import * as THREE from 'three'; // 创建场景、相机、渲染器 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, 2, 0.1, 100); camera.position.set(0, 1.5, 5); camera.lookAt(0, 1, 0); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(window.innerWidth * 2 / 3, window.innerHeight); renderer.shadowMap.enabled = true; document.getElementById('three-container').appendChild(renderer.domElement); // 添加环境光和平行光 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(2, 4, 3); directionalLight.castShadow = true; scene.add(directionalLight); // 创建一个简单的舞台平面 const floorGeometry = new THREE.CircleGeometry(2, 32); const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x2a2a4a }); const floor = new THREE.Mesh(floorGeometry, floorMaterial); floor.rotation.x = -Math.PI / 2; floor.position.y = 0; floor.receiveShadow = true; scene.add(floor); // 创建 3D 角色:由球体 + 圆柱体组成的卡通人形 const character = new THREE.Group(); // 头 const headMaterial = new THREE.MeshStandardMaterial({ color: 0xffccaa }); const head = new THREE.Mesh(new THREE.SphereGeometry(0.4, 32, 32), headMaterial); head.position.y = 1.6; character.add(head); // 身体 const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0x4a90d9 }); const body = new THREE.Mesh(new THREE.CylinderGeometry(0.35, 0.45, 0.8, 16), bodyMaterial); body.position.y = 1.0; character.add(body); // 左眼 const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 }); const leftEye = new THREE.Mesh(new THREE.SphereGeometry(0.05, 16, 16), eyeMaterial); leftEye.position.set(-0.15, 1.68, 0.33); character.add(leftEye); // 右眼 const rightEye = leftEye.clone(); rightEye.position.set(0.15, 1.68, 0.33); character.add(rightEye); // 嘴巴 const mouthMaterial = new THREE.MeshStandardMaterial({ color: 0xcc4444 }); const mouth = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.28, 8), mouthMaterial); mouth.rotation.z = Math.PI / 2; mouth.position.set(0, 1.5, 0.38); character.add(mouth); // 左手 const armMaterial = new THREE.MeshStandardMaterial({ color: 0x4a90d9 }); const leftArm = new THREE.Mesh(new THREE.CapsuleGeometry(0.08, 0.4, 4, 8), armMaterial); leftArm.position.set(-0.55, 1.2, 0); character.add(leftArm); // 右手 const rightArm = leftArm.clone(); rightArm.position.set(0.55, 1.2, 0); character.add(rightArm); // 把角色加入场景 scene.add(character); // 动画循环 function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate();

这里的关键点在于:

  • PerspectiveCamera的近裁面和远裁面分别设置为 0.1 和 100,避免因为裁剪面问题导致模型看不到。
  • alpha: true让渲染器背景透明,方便叠加页面背景色。
  • 程序化角色由球体、圆柱体、胶囊体组合而成,适合原型验证。
  • 每个部位使用position调整位置,整体放在一个Group中,方便后续整体旋转或缩放。

4.5 实现聊天交互与情绪驱动

3D 角色已经出现了,但还不会“反应”。现在把聊天接口和 3D 角色动作绑定起来。

main.js中继续补充代码,把发送消息后的情绪映射到角色动作上。

// 继续在 public/main.js 中添加 // 记录角色原始手部位置 const leftArmOrigin = new THREE.Vector3(-0.55, 1.2, 0); const rightArmOrigin = new THREE.Vector3(0.55, 1.2, 0); // DOM 元素 const messageInput = document.getElementById('message-input'); const sendBtn = document.getElementById('send-btn'); const chatBox = document.getElementById('chat-box'); // 发送消息 async function sendMessage() { const message = messageInput.value.trim(); if (!message) return; appendMessage('user', message); messageInput.value = ''; try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }) }); const data = await response.json(); appendMessage('ai', data.reply); handleEmotion(data.emotion); } catch (err) { console.error('请求失败', err); appendMessage('ai', '抱歉,我暂时无法回复你。'); } } function appendMessage(sender, text) { const div = document.createElement('div'); div.className = `message ${sender}`; div.textContent = text; chatBox.appendChild(div); chatBox.scrollTop = chatBox.scrollHeight; } // 根据情绪执行 3D 动作 function handleEmotion(emotion) { switch (emotion) { case 'happy': playHappy(); break; case 'sad': playSad(); break; case 'angry': playAngry(); break; case 'surprised': playSurprised(); break; default: playNeutral(); break; } } function playHappy() { // 角色跳起来 animateCharacter(() => { character.position.y = 0.5; }, () => { character.position.y = 0; }, 600); // 双手张开 animateArm(leftArm, leftArmOrigin, new THREE.Vector3(-0.8, 1.1, 0)); animateArm(rightArm, rightArmOrigin, new THREE.Vector3(0.8, 1.1, 0)); } function playSad() { // 低头 animateHeadRotation(-0.3, 0); animateArm(leftArm, leftArmOrigin, new THREE.Vector3(-0.5, 0.9, 0)); animateArm(rightArm, rightArmOrigin, new THREE.Vector3(0.5, 0.9, 0)); } function playAngry() { // 双手举起 animateArm(leftArm, leftArmOrigin, new THREE.Vector3(-0.6, 1.6, 0)); animateArm(rightArm, rightArmOrigin, new THREE.Vector3(0.6, 1.6, 0)); } function playSurprised() { // 头部后仰,嘴巴张开 animateHeadRotation(0.2, 0); mouth.scale.set(1.5, 0.6, 1); setTimeout(() => { mouth.scale.set(1, 1, 1); }, 800); } function playNeutral() { // 角色回到初始位置 character.position.y = 0; head.rotation.x = 0; leftArm.position.copy(leftArmOrigin); rightArm.position.copy(rightArmOrigin); } // 简单的动画工具函数 function animateCharacter(before, after, duration) { before(); setTimeout(() => { after(); }, duration); } function animateArm(arm, from, to) { arm.position.copy(from); setTimeout(() => { arm.position.copy(to); }, 50); setTimeout(() => { arm.position.copy(from); }, 800); } function animateHeadRotation(x, z) { head.rotation.x = x; head.rotation.z = z; setTimeout(() => { head.rotation.x = 0; head.rotation.z = 0; }, 800); } sendBtn.addEventListener('click', sendMessage); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); });

这里用了几种非常直观的动作控制方式:

  • 改变character.position.y模拟跳跃。
  • 改变手臂的position模拟张开、放下的动作。
  • 改变head.rotation.x模拟点头和低头。
  • 改变mouth.scale模拟张嘴。

这些方式虽然简单,但已经足以表达基础情绪。实际项目中如果使用带骨骼的 GLB 模型,动作可以由骨骼动画实现,效果会更自然。

4.6 运行与验证

在终端启动服务:

node server.js

如果使用 nodemon:

npx nodemon server.js

浏览器访问http://localhost:3000,你应该能看到:

  • 右侧是聊天面板。
  • 左侧场景中有一个蓝色身体、白色头的卡通角色。
  • 输入“今天太开心了”,角色会跳起来并张开双手。
  • 输入“我好难过”,角色会低头,手臂下垂。
  • 输入“你让我很生气”,角色会举起双手。

这套演示说明 AI 对话与 3D 形象之间的数据链路已经打通。

5. 进阶:使用 GLB 模型替换程序化角色

程序化角色适合演示,但如果你希望角色看起来更精细,建议使用现成的 GLB/GLTF 模型。

5.1 加载 GLB 模型

Three.js 需要使用GLTFLoader来加载.glb文件。首先安装依赖:

npm install three/examples/jsm/loaders/GLTFLoader.js

如果你的 Three.js 版本是最新版本,可以直接这样导入:

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

然后在场景中加载模型:

const loader = new GLTFLoader(); loader.load('/models/my-avatar.glb', (gltf) => { const model = gltf.scene; model.scale.set(1, 1, 1); scene.add(model); }, undefined, (error) => { console.error('模型加载失败', error); });

5.2 GLB 模型的动作播放

带骨骼动画的 GB 模型通常会在gltf.animations中自带多个动画片段。你可以通过AnimationMixer播放这些动画。

import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; let mixer; const loader = new GLTFLoader(); loader.load('/models/my-avatar.glb', (gltf) => { const model = gltf.scene; scene.add(model); mixer = new THREE.AnimationMixer(model); const clips = gltf.animations; if (clips && clips.length > 0) { const action = mixer.clipAction(clips[0]); action.play(); } }, undefined, (error) => { console.error('模型加载失败', error); }); // 在动画循环中更新 mixer function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); if (mixer) mixer.update(delta); renderer.render(scene, camera); }

情绪驱动时,可以根据情绪名称找到对应动画片段并切换播放:

function playAnimationByName(name) { const clips = gltf.animations; const clip = clips.find((c) => c.name.includes(name)); if (clip) { mixer.clipAction(clip).play(); } }

5.3 模型来源与版权提醒

使用现成模型时,需要注意版权问题。建议优先使用以下来源:

  • 模型作者明确标注了允许商用。
  • 来自 CC0 或 MIT 协议的开源模型库。
  • 企业自建的模型资产库。

不要随意下载未标注使用协议的模型用于商业产品,这是很多项目在后期容易踩的合规坑。

6. 常见问题与排查思路

在开发 AI 角色 3D 形象的过程中,最容易遇到下面几类问题。

6.1 页面空白,控制台报错

问题现象常见原因解决思路
页面空白,没有 3D 场景Three.js 渲染器尺寸为 0检查容器是否设置了宽高
页面空白,控制台报错找不到模块模块导入路径错误确认three的导入路径和版本
模型不显示相机朝向不对调整camera.lookAt目标点
模型颜色太暗光照不足增加环境光或平行光强度

其中,渲染器尺寸为 0 是最常见的问题。如果使用renderer.setSize(width, height)时,容器还没有完成布局,宽高拿到的是 0,就会导致画面不显示。推荐使用固定尺寸或者延迟到window.onload后再初始化。

6.2 聊天接口跨域报错

如果前端页面和后端服务不在同一个端口,比如前端在 5500,后端在 3000,浏览器会拦截跨域请求。

解决方案有三种:

  • 使用 Nginx 反向代理,把/api请求转发到后端。
  • 后端添加 CORS 中间件。
  • 前端和后端部署在同源环境下。

在开发阶段,最简单的方式是利用 Express 托管静态资源,让前端页面和后端接口同源,这也是本文示例采用的方式。

6.3 模型加载慢或卡顿

GLB 模型文件如果过大,会影响首屏加载速度。优化建议:

优化手段说明
压缩模型文件使用 glTF Pipeline 或 Draco 压缩
使用 LOD 多级模型远距离显示低模,近距离显示高模
延迟加载角色出现后再异步加载 GLB
减少材质数量合并材质,减少 Draw Call

6.4 情绪判断不准确

简单关键词规则覆盖的场景有限。比如用户说“今天被领导骂了”,文本里没有“难过”这个词,但语义明显是负向情绪。这时建议用大模型返回结构化结果。

请求体可以这样设计:

{ "message": "今天被领导骂了" }

后端拿到内容后,要求大模型返回:

{ "reply": "听起来你现在有些委屈,愿意和我聊聊发生了什么吗?", "emotion": "sad" }

这样的情绪识别准确率远高于关键词规则。

6.5 动画播放抖动

如果使用setTimeout控制动作,多个动作快速切换时容易出现状态混乱。建议使用一个全局的状态标志,当上一个动作未完成时,忽略新的动作请求,或者立即终止上一个动作。

let isAnimating = false; function playHappy() { if (isAnimating) return; isAnimating = true; // 执行动作 setTimeout(() => { isAnimating = false; }, 800); }

7. 最佳实践与工程建议

7.1 把后端对话服务和 3D 渲染解耦

不要让前端直接访问大模型 API。正确做法是:

  • 前端只请求自己的后端/api/chat
  • 后端负责调用大模型,处理密钥、限流、日志。
  • 后端返回统一的 JSON 结构,包含replyemotionaction等字段。
  • 前端只关心如何根据emotion渲染 3D 表现。

这样做的目的是后续替换模型厂商时,前端代码完全不需要改。

7.2 设计统一的“AI 回复协议”

推荐使用下面的 JSON 结构作为 AI 回复的标准协议:

{ "reply": "回复文本内容", "emotion": "happy | sad | angry | surprised | neutral", "action": "wave | jump | bow", "duration": 1200 }

其中action是可选的,用于指定特殊动作;duration控制动作持续时间。这个协议可以在不同角色、不同项目中重复使用。

7.3 3D 资源按需加载

不要把 3D 角色模型放在首屏必须加载的资源列表里。建议在页面主体内容渲染完成后再加载。如果模型超过 5MB,还需要考虑加载进度提示,以免用户误以为页面卡死。

7.4 性能优化

3D 场景渲染消耗的是 GPU 资源,低端设备上需要特别注意:

优化项建议
角色面数单角色控制在 5 万面以内
纹理尺寸单张纹理不超过 1024x1024
实时阴影移动端尽量关闭
抗锯齿低端设备关闭 antialias
帧率监控通过renderer.setAnimationLoop控制渲染频率

7.5 情绪引擎的可扩展设计

如果把情绪限定为少数枚举值,会限制角色的表现力。工程实践上,可以把“情绪”设计成一个分数空间,比如:

const emotionState = { happy: 0, sad: 0, angry: 0, surprised: 0 };

每次 AI 返回情绪后,累积更新分数,再由插值函数驱动面部表情和动作幅度。这种方式更适合需要长期陪伴感的聊天角色,情绪变化会显得更自然。

7.6 安全与合规建议

  • 对话接口必须做用户输入长度限制,避免超大文本攻击。
  • 后端增加简单的内容过滤,防止不适宜内容直接进入 3D 角色交互。
  • 大模型 API Key 存放在环境变量或配置中心,不要提交到代码仓库。
  • 涉及用户数据时,需要遵循最小化采集原则,并对日志中的敏感信息脱敏。

8. 总结与下一步

本文围绕“陪你聊天的 AI 角色,可以直接做出 3D 形象”这一场景,完整演示了从后端对话接口、前端聊天面板、Three.js 3D 角色渲染,到情绪驱动角色动作的全流程。

核心收获有三点:

  • 3D 角色形象的本质是渲染 + 动画,AI 部分只需要提供结构化的回复和情绪字段。
  • 程序化建模适合快速 Demo,正式产品建议使用 GLB 模型并配合 AnimationMixer 播放动画。
  • 前后端通过一个统一的 JSON 协议解耦,业务扩展时只需要更换后端模型或前端展示层。

接下来你可以继续探索:

  • 用语音合成让 AI 角色开口说话。
  • 加入 WebRTC 和摄像头,实现更真实的互动。
  • 把动作映射从简单的位置偏移升级为骨骼动画混合。
  • 接入真实的大模型接口,使用提示词约束模型输出结构化 JSON。

如果你在做这个方向时遇到了其他问题,或者有更好的实现思路,欢迎在评论区交流。本文的示例代码可以直接复制到本地跑起来,先跑通再优化,是最快的学习路径。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询