HY-Motion 1.0开发者案例Three.js网页端嵌入3D动作播放器1. 引言当3D动画创作遇上AI想象一下这个场景你正在为一个游戏角色设计一套待机动作或者为一个虚拟主播构思一段开场舞。传统的流程是什么你需要动画师在Maya或Blender里一帧一帧地K帧耗时耗力灵感还容易枯竭。现在情况变了。你只需要在网页上输入一句“一个角色在悠闲地散步”几秒钟后一段流畅、自然的3D骨骼动画就生成了并且可以直接在你的网页项目里预览和播放。这不是科幻而是HY-Motion 1.0结合Three.js带来的真实开发体验。HY-Motion 1.0是一个基于流匹配技术的3D动作生成大模型。简单来说它能把你的文字描述直接变成3D角色的骨骼动画数据。而Three.js则是那个能让这些数据在浏览器里“活”起来的强大WebGL库。今天我们就来聊聊如何把这两者结合起来打造一个能直接在网页里生成和播放3D动画的“魔法播放器”。无论你是前端开发者、游戏制作人还是对交互式内容感兴趣的创意者这篇文章都将带你一步步实现这个酷炫的功能。2. 核心思路从文本到网页动画的桥梁要把HY-Motion生成的动画用在网页里我们需要搭建一座“桥梁”。这座桥连接了AI模型输出的数据和Three.js能理解的世界。整个流程可以拆解成三个关键步骤生成数据用HY-Motion模型把你的文字描述比如“挥手打招呼”变成一套标准的3D骨骼动作数据。处理数据模型生成的数据格式可能比较“原始”我们需要把它转换成Three.js骨骼动画系统能直接使用的格式。渲染播放在Three.js场景里加载一个3D角色模型然后把处理好的动画数据“喂”给它让它动起来。听起来有点复杂别担心我们接下来会用一个完整的代码示例把每一步都讲清楚。你会发现借助现有的工具链这个过程比想象中要简单得多。3. 环境准备与项目搭建在开始写代码之前我们需要准备好“工具箱”。这里假设你已经有一个可以运行HY-Motion模型的后端服务例如通过Gradio接口或API它能接收文本并返回动画数据。我们的重点在前端Three.js部分的集成。首先创建一个新的项目文件夹并用你喜欢的包管理工具初始化mkdir hymotion-threejs-player cd hymotion-threejs-player npm init -y然后安装我们核心的依赖库npm install three # 如果需要加载glTF等格式的模型通常还需要加载器 npm install types/three --save-dev # 如果你使用TypeScript我们的项目结构会很简单hymotion-threejs-player/ ├── index.html # 主页面 ├── style.css # 样式文件 ├── main.js # Three.js主逻辑 ├── package.json └── (模型文件比如 character.glb)index.html是入口它引入了Three.js库和我们的主逻辑文件。!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleHY-Motion Three.js 动作播放器/title style body { margin: 0; overflow: hidden; } #info { position: absolute; top: 10px; left: 10px; color: white; font-family: monospace; z-index: 100; } /style /head body !-- 用于显示提示信息 -- div idinfo等待输入指令.../div !-- Three.js渲染器将把画面输出到这个canvas里 -- canvas idcanvas/canvas !-- 引入Three.js库 -- script srchttps://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js/script !-- 引入GLTF加载器从CDN或使用本地构建的版本 -- script srchttps://cdn.jsdelivr.net/npm/three0.128.0/examples/js/loaders/GLTFLoader.js/script !-- 引入我们自己的主逻辑 -- script srcmain.js/script /body /html4. 构建Three.js动画播放器核心现在进入最核心的部分编写main.js。我们将在这里创建场景、加载角色、并最终驱动它播放HY-Motion生成的动画。4.1 初始化Three.js世界首先搭建一个基础的Three.js场景包括渲染器、相机和灯光。// main.js let scene, camera, renderer, character, mixer, currentAction; function init() { // 1. 创建场景 scene new THREE.Scene(); scene.background new THREE.Color(0x333333); // 2. 创建相机透视相机模拟人眼 camera new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 1.5, 5); // 把相机放在角色前方 // 3. 创建渲染器 const canvas document.getElementById(canvas); renderer new THREE.WebGLRenderer({ canvas, antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled true; // 启用阴影 // 4. 添加灯光让角色有立体感 const ambientLight new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 10, 7); directionalLight.castShadow true; scene.add(directionalLight); // 5. 添加一个简单的地面 const groundGeometry new THREE.PlaneGeometry(10, 10); const groundMaterial new THREE.MeshStandardMaterial({ color: 0x666666 }); const ground new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x -Math.PI / 2; ground.receiveShadow true; scene.add(ground); // 6. 加载角色模型 loadCharacterModel(); // 7. 处理窗口大小变化 window.addEventListener(resize, onWindowResize); // 8. 开始动画循环 animate(); } function onWindowResize() { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); // 如果存在动画混合器需要每帧更新它动画才能播放 if (mixer) { mixer.update(0.016); // 假设60帧每帧间隔约0.016秒 } renderer.render(scene, camera); } // 初始化 init();4.2 加载角色模型并准备动画系统接下来我们需要加载一个带骨骼的3D角色模型。glTF格式.glb或.gltf是Three.js社区推荐的标准它完美支持骨骼动画。// 在 main.js 中继续添加 function loadCharacterModel() { const loader new THREE.GLTFLoader(); // 使用GLTF加载器 // 假设我们的角色模型文件叫 character.glb放在同一目录下 loader.load( ./character.glb, // 模型路径 function (gltf) { character gltf.scene; scene.add(character); // 调整角色位置和大小 character.position.y 0; character.scale.set(1, 1, 1); // 关键步骤创建动画混合器AnimationMixer // 它是Three.js中播放动画的核心对象 mixer new THREE.AnimationMixer(character); // 模型自带的动画如果有会存储在 gltf.animations 数组里 // 这里我们先清空因为我们要播放HY-Motion生成的动画 // gltf.animations.forEach((clip) { // const action mixer.clipAction(clip); // action.play(); // }); console.log(角色模型加载完成等待HY-Motion动画数据。); updateInfo(角色就绪。请输入动作描述并生成动画。); // 这里可以触发一个UI让用户输入文本 setupUI(); }, function (xhr) { // 加载进度回调 console.log((xhr.loaded / xhr.total * 100) % loaded); }, function (error) { // 加载错误回调 console.error(模型加载失败:, error); updateInfo(模型加载失败请检查控制台。); } ); } function updateInfo(text) { document.getElementById(info).textContent text; }4.3 设计用户交互界面我们需要一个简单的界面让用户输入动作描述并触发生成请求。// 在 main.js 中继续添加 function setupUI() { // 创建一个简单的输入框和按钮实际项目中可以用更漂亮的UI库 const container document.createElement(div); container.style.position absolute; container.style.top 50px; container.style.left 10px; container.style.backgroundColor rgba(0,0,0,0.7); container.style.padding 10px; container.style.borderRadius 5px; container.style.color white; container.style.fontFamily monospace; const input document.createElement(input); input.type text; input.placeholder 输入英文动作描述如a person walking; input.style.width 300px; input.style.padding 5px; input.value a person walking; // 默认示例 const button document.createElement(button); button.textContent 生成并播放动画; button.style.marginLeft 10px; button.style.padding 5px 10px; const status document.createElement(p); status.id status; status.textContent 就绪; container.appendChild(input); container.appendChild(button); container.appendChild(status); document.body.appendChild(container); // 按钮点击事件调用HY-Motion后端并播放动画 button.addEventListener(click, async () { const prompt input.value.trim(); if (!prompt) { alert(请输入动作描述); return; } status.textContent 正在生成动画...; try { // 调用我们的后端服务 const animationData await generateMotionFromHYMotion(prompt); // 播放接收到的动画数据 playAnimation(animationData); status.textContent 正在播放: ${prompt}; } catch (error) { console.error(生成或播放动画失败:, error); status.textContent 失败: error.message; } }); }5. 关键集成连接HY-Motion与Three.js这是最核心的一步。generateMotionFromHYMotion函数负责与HY-Motion后端通信而playAnimation函数负责将返回的数据转换成Three.js动画。5.1 获取HY-Motion动画数据假设你的HY-Motion模型已经部署为一个API服务它接收文本返回骨骼动画数据例如每帧每个关节的旋转和平移数据。// 在 main.js 中继续添加 async function generateMotionFromHYMotion(prompt) { // 注意这是一个示例URL你需要替换成你自己部署的HY-Motion服务地址 const apiUrl http://your-hymotion-server:port/generate; // 替换为你的API端点 const response await fetch(apiUrl, { method: POST, headers: { Content-Type: application/json, }, body: JSON.stringify({ text: prompt, // 可能还有其他参数如动作长度、种子等参考HY-Motion文档 num_frames: 60, // 例如生成60帧 fps: 30, // 帧率 }), }); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const data await response.json(); // 假设API返回的数据结构包含骨骼名称和每帧的变换数据 // 例如{ bones: [Hips, Spine, ...], frames: [ {frame1_data}, {frame2_data} ] } return data; }重要提示你需要根据HY-Motion模型实际的输出格式来调整这个函数。模型可能输出为.npy文件、包含旋转四元数的数组或是其他3D动画通用格式如.bvh。后端服务需要负责将这些数据转换成前端易于处理的JSON格式。5.2 将数据转换为Three.js动画并播放现在我们有了动画数据需要将其绑定到Three.js的骨骼系统上。// 在 main.js 中继续添加 function playAnimation(motionData) { if (!mixer || !character) { console.error(角色或动画混合器未就绪); return; } // 1. 停止当前正在播放的动画 if (currentAction) { currentAction.stop(); } // 2. 根据motionData创建Three.js的动画片段(AnimationClip) // 这是最关键的一步需要将你的数据格式映射到角色的骨骼上。 const clip createClipFromMotionData(motionData, character); if (!clip) { updateInfo(无法从数据创建动画片段。); return; } // 3. 用混合器创建并播放这个动画片段 currentAction mixer.clipAction(clip); currentAction.reset(); // 重置到初始状态 currentAction.setLoop(THREE.LoopRepeat); // 设置为循环播放 currentAction.clampWhenFinished false; // 播放结束后不钳制 currentAction.play(); updateInfo(动画播放中: ${clip.name}); } function createClipFromMotionData(motionData, targetObject) { // 这是一个高度简化的示例函数。 // 实际实现取决于1. HY-Motion的输出格式 2. 你角色模型的骨骼结构 // 两者必须匹配骨骼名称、层级关系。 const tracks []; // 用于存放所有关键帧轨道 // 假设 motionData 的结构是 // { // bones: [mixamorig:Hips, mixamorig:Spine, ...], // frames: [ // { mixamorig:Hips: { position: [x,y,z], quaternion: [x,y,z,w] }, ... }, // ... // 第2帧 // ] // } const boneNames motionData.bones; const frames motionData.frames; const fps motionData.fps || 30; const duration frames.length / fps; // 遍历每一根需要动画的骨骼 for (const boneName of boneNames) { // 在Three.js场景图中找到对应的骨骼节点 const boneNode targetObject.getObjectByName(boneName); if (!boneNode) { console.warn(在角色模型中未找到骨骼: ${boneName}); continue; // 跳过不存在的骨骼 } // 创建位置关键帧轨道 (VectorKeyframeTrack) const positionTimes []; const positionValues []; // 创建旋转关键帧轨道 (QuaternionKeyframeTrack) const rotationTimes []; const rotationValues []; for (let i 0; i frames.length; i) { const frameData frames[i][boneName]; const time i / fps; positionTimes.push(time); positionValues.push(...(frameData.position || [0, 0, 0])); // 假设数据是[x,y,z] rotationTimes.push(time); rotationValues.push(...(frameData.quaternion || [0, 0, 0, 1])); // 假设数据是[x,y,z,w]四元数 } // 创建轨道。轨道名称格式为节点名称.属性 // 例如mixamorig:Hips.position 和 mixamorig:Hips.quaternion if (positionValues.length 0) { const posTrack new THREE.VectorKeyframeTrack( ${boneName}.position, positionTimes, positionValues ); tracks.push(posTrack); } if (rotationValues.length 0) { const rotTrack new THREE.QuaternionKeyframeTrack( ${boneName}.quaternion, rotationTimes, rotationValues ); tracks.push(rotTrack); } } if (tracks.length 0) { console.error(未能创建任何动画轨道。); return null; } // 使用轨道创建动画片段 const clip new THREE.AnimationClip(HYMotion_Generated, duration, tracks); return clip; }请注意createClipFromMotionData函数是概念性的。实际集成中最大的挑战在于数据格式的桥接。你需要确保HY-Motion输出的骨骼名称与你角色模型中的骨骼名称完全一致。数据的坐标系如Y轴向上还是Z轴向上、旋转顺序四元数还是欧拉角与Three.js兼容。通常你需要一个“重定向”或“适配”层来处理这些差异这可能需要在后端或前端进行额外的数据处理。一个更稳健的做法是让HY-Motion后端直接输出Three.js的AnimationClip可序列化的JSON格式或者通用的.glb动画文件前端只需加载即可。6. 总结与展望通过上面的步骤我们完成了一个基础的HY-Motion Three.js网页端动画播放器。它实现了从文本输入到3D动画在浏览器中实时播放的完整流程。回顾一下我们做了什么搭建了基础环境创建了Three.js场景加载了带骨骼的角色模型。建立了通信机制通过前端界面调用HY-Motion后端API获取文本生成的动画数据。实现了数据驱动编写了将原始动画数据转换为Three.jsAnimationClip的核心函数并驱动角色模型播放。下一步可以优化的方向数据格式标准化与模型团队协作定义或采用一种前后端通用的动画数据交换格式如扩展的glTF简化集成。UI/UX增强提供更友好的界面如动作列表、播放控制暂停、循环、速度调节、多视角观察等。性能优化对于长序列动画考虑流式加载和播放。对动画数据进行压缩。角色与动画解耦实现动画重定向让同一套HY-Motion生成的动画能应用到多个不同比例、骨骼的角色模型上。离线与缓存缓存生成过的动画减少对后端服务的重复请求。HY-Motion 1.0这样的AI模型正在极大地降低3D内容创作的门槛。而Three.js这样的Web技术则让成果的分享和体验变得无比便捷。将它们结合我们就能在浏览器这个最普及的平台上打造出极具想象力的交互式3D应用。希望这个案例能为你打开一扇新的大门期待看到你创造出更精彩的作品。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。