04.three官方示例+编辑器+AI快速学习webgl_animation_skinning_additive_blending

时间:2025-05-15 08:12:55
// 导入Three.js核心库和辅助工具 import * as THREE from 'three'; import Stats from 'three/addons/libs/stats.module.js'; // 性能统计工具 import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; // GUI控制面板 import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 轨道控制器 import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; // GLTF模型加载器 // 全局变量定义 let scene, renderer, camera, stats; // 场景、渲染器、相机和性能统计 let model, skeleton, mixer, clock; // 模型、骨骼辅助工具、动画混合器和时钟 const crossFadeControls = []; // 存储淡入淡出控制按钮的数组 let currentBaseAction = 'idle'; // 当前基础动画动作 const allActions = []; // 存储所有动画动作的数组 // 基础动画动作配置,包含权重信息 const baseActions = { idle: { weight: 1 }, walk: { weight: 0 }, run: { weight: 0 } }; // 叠加动画动作配置,包含权重信息 const additiveActions = { sneak_pose: { weight: 0 }, sad_pose: { weight: 0 }, agree: { weight: 0 }, headShake: { weight: 0 } }; let panelSettings, numAnimations; // 控制面板设置和动画数量 // 初始化函数 init(); function init() { // 获取渲染容器 const container = document.getElementById( 'container' ); clock = new THREE.Clock(); // 创建时钟用于计算动画时间增量 // 创建场景并设置背景和雾 scene = new THREE.Scene(); scene.background = new THREE.Color( 0xa0a0a0 ); scene.fog = new THREE.Fog( 0xa0a0a0, 10, 50 ); // 添加半球光,提供自然光照效果 const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x8d8d8d, 3 ); hemiLight.position.set( 0, 20, 0 ); scene.add( hemiLight ); // 添加方向光,用于产生阴影 const dirLight = new THREE.DirectionalLight( 0xffffff, 3 ); dirLight.position.set( 3, 10, 10 ); dirLight.castShadow = true; // 设置阴影相机参数,控制阴影范围和精度 dirLight.shadow.camera.top = 2; dirLight.shadow.camera.bottom = - 2; dirLight.shadow.camera.left = - 2; dirLight.shadow.camera.right = 2; dirLight.shadow.camera.near = 0.1; dirLight.shadow.camera.far = 40; scene.add( dirLight ); // 创建地面平面 const mesh = new THREE.Mesh( new THREE.PlaneGeometry( 100, 100 ), new THREE.MeshPhongMaterial( { color: 0xcbcbcb, depthWrite: false } ) ); mesh.rotation.x = - Math.PI / 2; // 旋转平面使其水平 mesh.receiveShadow = true; // 地面接收阴影 scene.add( mesh ); // 加载GLTF格式模型 const loader = new GLTFLoader(); loader.load( 'models/gltf/Xbot.glb', function ( gltf ) { model = gltf.scene; // 获取模型场景对象 scene.add( model ); // 将模型添加到场景中 // 遍历模型的所有对象,设置可投射阴影 model.traverse( function ( object ) { if ( object.isMesh ) object.castShadow = true; } ); // 创建骨骼辅助工具,用于可视化骨骼结构 skeleton = new THREE.SkeletonHelper( model ); skeleton.visible = false; // 默认不显示骨骼 scene.add( skeleton ); // 获取模型中的所有动画 const animations = gltf.animations; mixer = new THREE.AnimationMixer( model ); // 创建动画混合器 numAnimations = animations.length; // 记录动画数量 // 处理所有动画 for ( let i = 0; i !== numAnimations; ++ i ) { let clip = animations[ i ]; // 获取当前动画片段 const name = clip.name; // 获取动画名称 if ( baseActions[ name ] ) { // 如果是基础动画 // 创建动画动作并激活 const action = mixer.clipAction( clip ); activateAction( action ); baseActions[ name ].action = action; // 存储动画动作 allActions.push( action ); // 将动作添加到所有动作数组 } else if ( additiveActions[ name ] ) { // 如果是叠加动画 // 将动画设置为叠加模式 THREE.AnimationUtils.makeClipAdditive( clip ); // 如果是姿态类动画,提取特定帧作为姿态 if ( clip.name.endsWith( '_pose' ) ) { clip = THREE.AnimationUtils.subclip( clip, clip.name, 2, 3, 30 ); } // 创建动画动作并激活 const action = mixer.clipAction( clip ); activateAction( action ); additiveActions[ name ].action = action; // 存储动画动作 allActions.push( action ); // 将动作添加到所有动作数组 } } // 创建控制面板 createPanel(); // 设置渲染循环 renderer.setAnimationLoop( animate ); } ); // 初始化WebGL渲染器 renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); // 设置像素比,适配高DPI屏幕 renderer.setSize( window.innerWidth, window.innerHeight ); // 设置渲染尺寸 renderer.shadowMap.enabled = true; // 启用阴影渲染 container.appendChild( renderer.domElement ); // 将渲染器DOM元素添加到容器中 // 设置相机 camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 100 ); camera.position.set( - 1, 2, 3 ); // 添加轨道控制器,允许用户旋转和缩放场景 const controls = new OrbitControls( camera, renderer.domElement ); controls.enablePan = false; // 禁用平移 controls.enableZoom = false; // 禁用缩放 controls.target.set( 0, 1, 0 ); // 设置控制器目标点 controls.update(); // 更新控制器状态 // 添加性能统计面板 stats = new Stats(); container.appendChild( stats.dom ); // 添加窗口大小变化事件监听,调整相机和渲染器 window.addEventListener( 'resize', onWindowResize ); } // 创建控制面板函数 function createPanel() { // 创建GUI面板 const panel = new GUI( { width: 310 } ); // 创建不同功能的折叠面板 const folder1 = panel.addFolder( 'Base Actions' ); // 基础动画控制面板 const folder2 = panel.addFolder( 'Additive Action Weights' ); // 叠加动画权重控制面板 const folder3 = panel.addFolder( 'General Speed' ); // 全局速度控制面板 // 初始化控制面板设置 panelSettings = { 'modify time scale': 1.0 // 动画全局速度设置 }; // 获取所有基础动画名称,并添加"None"选项 const baseNames = [ 'None', ...Object.keys( baseActions ) ]; // 为每个基础动画添加控制按钮 for ( let i = 0, l = baseNames.length; i !== l; ++ i ) { const name = baseNames[ i ]; const settings = baseActions[ name ]; // 为每个基础动画按钮设置点击事件 panelSettings[ name ] = function () { // 获取当前和目标动画动作 const currentSettings = baseActions[ currentBaseAction ]; const currentAction = currentSettings ? currentSettings.action : null; const action = settings ? settings.action : null; // 如果当前动画和目标动画不同,则执行过渡 if ( currentAction !== action ) { prepareCrossFade( currentAction, action, 0.35 ); } }; // 将控制按钮添加到面板并存储到控制数组 crossFadeControls.push( folder1.add( panelSettings, name ) ); } // 为每个叠加动画添加权重控制滑块 for ( const name of Object.keys( additiveActions ) ) { const settings = additiveActions[ name ]; // 添加滑块并监听变化 panelSettings[ name ] = settings.weight; folder2.add( panelSettings, name, 0.0, 1.0, 0.01 ).listen().onChange( function ( weight ) { setWeight( settings.action, weight ); // 设置动画权重 settings.weight = weight; // 更新设置中的权重值 } ); } // 添加全局速度控制滑块 folder3.add( panelSettings, 'modify time scale', 0.0, 1.5, 0.01 ).onChange( modifyTimeScale ); // 默认打开所有折叠面板 folder1.open(); folder2.open(); folder3.open(); // 为控制按钮添加激活/禁用状态样式 crossFadeControls.forEach( function ( control ) { // 设置按钮为非活动状态 control.setInactive = function () { control.domElement.classList.add( 'control-inactive' ); }; // 设置按钮为活动状态 control.setActive = function () { control.domElement.classList.remove( 'control-inactive' ); }; // 根据初始权重设置按钮状态 const settings = baseActions[ control.property ]; if ( ! settings || ! settings.weight ) { control.setInactive(); } } ); } // 激活动画动作的函数 function activateAction( action ) { // 获取动画片段和对应的设置 const clip = action.getClip(); const settings = baseActions[ clip.name ] || additiveActions[ clip.name ]; // 设置动画权重并播放 setWeight( action, settings.weight ); action.play(); } // 修改动画全局速度的函数 function modifyTimeScale( speed ) { mixer.timeScale = speed; // 设置动画混合器的时间缩放 } // 准备动画过渡的函数 function prepareCrossFade( startAction, endAction, duration ) { // 如果当前动画是"idle",或者没有起始或目标动画,立即执行过渡 if ( currentBaseAction === 'idle' || ! startAction || ! endAction ) { executeCrossFade( startAction, endAction, duration ); } else { // 否则等待当前动画完成当前循环后再执行过渡 synchronizeCrossFade( startAction, endAction, duration ); } // 更新控制按钮状态 if ( endAction ) { // 如果有目标动画,更新当前基础动画名称 const clip = endAction.getClip(); currentBaseAction = clip.name; } else { // 否则设置为"None" currentBaseAction = 'None'; } // 更新所有控制按钮的激活/禁用状态 crossFadeControls.forEach( function ( control ) { const name = control.property; if ( name === currentBaseAction ) { control.setActive(); // 设置为活动状态 } else { control.setInactive(); // 设置为非活动状态 } } ); } // 同步动画过渡的函数,确保在动画循环结束时进行过渡 function synchronizeCrossFade( startAction, endAction, duration ) { // 添加循环结束事件监听 mixer.addEventListener( 'loop', onLoopFinished ); function onLoopFinished( event ) { if ( event.action === startAction ) { // 当起始动画完成一个循环 mixer.removeEventListener( 'loop', onLoopFinished ); // 移除事件监听 executeCrossFade( startAction, endAction, duration ); // 执行过渡 } } } // 执行动画过渡的函数 function executeCrossFade( startAction, endAction, duration ) { if ( endAction ) { // 如果有目标动画 // 设置目标动画权重为1,并重置时间 setWeight( endAction, 1 ); endAction.time = 0; if ( startAction ) { // 从起始动画过渡到目标动画(带warping效果) startAction.crossFadeTo( endAction, duration, true ); } else { // 淡入目标动画(没有起始动画) endAction.fadeIn( duration ); } } else { // 如果没有目标动画 // 淡出起始动画 startAction.fadeOut( duration ); } } // 设置动画权重的函数 function setWeight( action, weight ) { action.enabled = true; // 启用动画 action.setEffectiveTimeScale( 1 ); // 设置时间缩放为1 action.setEffectiveWeight( weight ); // 设置动画权重 } // 窗口大小变化事件处理函数 function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; // 更新相机宽高比 camera.updateProjectionMatrix(); // 更新相机投影矩阵 renderer.setSize( window.innerWidth, window.innerHeight ); // 更新渲染器尺寸 } // 动画循环函数,每一帧都会被调用 function animate() { // 更新所有动画的权重信息 for ( let i = 0; i !== numAnimations; ++ i ) { const action = allActions[ i ]; const clip = action.getClip(); // 更新基础动画或叠加动画的权重 const settings = baseActions[ clip.name ] || additiveActions[ clip.name ]; settings.weight = action.getEffectiveWeight(); } // 获取自上一帧以来的时间增量,用于更新动画混合器 const mixerUpdateDelta = clock.getDelta(); // 更新动画混合器,渲染场景,并更新性能统计 mixer.update( mixerUpdateDelta ); renderer.render( scene, camera ); stats.update(); }