05.three官方示例+编辑器+AI快速学习three.js webgl - animation - skinning - ik

news2025/7/12 7:32:20

本实例主要讲解内容

这个Three.js示例展示了**反向运动学(Inverse Kinematics, IK)**在3D角色动画中的应用。通过加载一个角色模型,演示了如何使用IK技术实现自然的肢体运动控制,如手部抓取物体的动作。

核心技术包括:

  • CCD反向运动学求解器
  • 实时IK计算与应用
  • 角色头部跟踪
  • 镜面反射效果
  • 交互式控制器

在这里插入图片描述

完整代码注释

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - animation - skinning - ik</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<meta name="author" content="Antoine BERNIER (abernier)" />
		<link type="text/css" rel="stylesheet" href="main.css">
		<style>
		body {color:white;}
		#info a {
			color:#4d6675;
		}
		</style>
	</head>
	<body>
		<div id="info">
			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl - inverse kinematics<br />
			Character model by <a href="https://assetstore.unity.com/packages/3d/characters/humanoids/humans/kira-lowpoly-character-100303" target="_blank" rel="noopener">Aki</a>, furnitures from <a href="https://poly.pizza" target="_blank" rel="noopener">poly.pizza</a>, scene by <a href="https://abernier.name/three.js/examples/webgl_esher.html" target="_blank" rel="noopener">abernier</a>. CC0.
		</div>

		<script type="importmap">
		{
			"imports": {
				"three": "../build/three.module.js",
				"three/addons/": "./jsm/"
			}
		}
		</script>

		<script type="module">
		import * as THREE from 'three';

		import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
		import { TransformControls } from 'three/addons/controls/TransformControls.js';
		import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
		import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
		import { CCDIKSolver, CCDIKHelper } from 'three/addons/animation/CCDIKSolver.js';
		import Stats from 'three/addons/libs/stats.module.js';
		import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

		let scene, camera, renderer, orbitControls, transformControls;
		let mirrorSphereCamera;

		const OOI = {}; // 感兴趣的对象集合
		let IKSolver; // IK求解器

		let stats, gui, conf; // 统计面板、控制面板和配置对象
		const v0 = new THREE.Vector3(); // 临时向量,用于计算

		init();

		async function init() {

			conf = {
				followSphere: false, // 相机是否跟随球体
				turnHead: true, // 头部是否转向球体
				ik_solver: true, // 是否自动更新IK
				update: updateIK // 手动更新IK的函数
			};

			scene = new THREE.Scene();
			scene.fog = new THREE.FogExp2( 0xffffff, .17 ); // 设置指数雾
			scene.background = new THREE.Color( 0xffffff ); // 设置背景色为白色

			camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.001, 5000 );
			camera.position.set( 0.9728517749133652, 1.1044765132727201, 0.7316689528482836 );
			camera.lookAt( scene.position );

			// 添加环境光,照亮整个场景
			const ambientLight = new THREE.AmbientLight( 0xffffff, 8 ); // 柔和的白色光
			scene.add( ambientLight );

			// 初始化DRACO加载器,用于加载压缩的GLTF模型
			const dracoLoader = new DRACOLoader();
			dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
			const gltfLoader = new GLTFLoader();
			gltfLoader.setDRACOLoader( dracoLoader );

			// 加载GLTF模型
			const gltf = await gltfLoader.loadAsync( 'models/gltf/kira.glb' );
			gltf.scene.traverse( n => {
				// 收集感兴趣的对象,用于后续控制
				if ( n.name === 'head' ) OOI.head = n;
				if ( n.name === 'lowerarm_l' ) OOI.lowerarm_l = n;
				if ( n.name === 'Upperarm_l' ) OOI.Upperarm_l = n;
				if ( n.name === 'hand_l' ) OOI.hand_l = n;
				if ( n.name === 'target_hand_l' ) OOI.target_hand_l = n;

				if ( n.name === 'boule' ) OOI.sphere = n; // 球体对象
				if ( n.name === 'Kira_Shirt_left' ) OOI.kira = n; // 角色主体
			} );
			scene.add( gltf.scene );

			// 记录球体的初始位置,用于轨道控制器
			const targetPosition = OOI.sphere.position.clone();
			// 将球体附加到角色的左手上,使其跟随手部移动
			OOI.hand_l.attach( OOI.sphere );

			// 创建镜面球体的立方相机
			const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 1024 );
			mirrorSphereCamera = new THREE.CubeCamera( 0.05, 50, cubeRenderTarget );
			scene.add( mirrorSphereCamera );
			// 使用立方相机的渲染结果作为球体的环境贴图,实现镜面效果
			const mirrorSphereMaterial = new THREE.MeshBasicMaterial( { envMap: cubeRenderTarget.texture } );
			OOI.sphere.material = mirrorSphereMaterial;

			// 将角色的骨骼根节点添加到角色对象中
			OOI.kira.add( OOI.kira.skeleton.bones[ 0 ] );
			
			// 设置IK求解器配置
			const iks = [
				{
					target: 22, // "target_hand_l" 目标对象ID
					effector: 6, // "hand_l" 效应器ID(手)
					links: [
						{
							index: 5, // "lowerarm_l" 下臂
							rotationMin: new THREE.Vector3( 1.2, - 1.8, - .4 ), // 最小旋转角度
							rotationMax: new THREE.Vector3( 1.7, - 1.1, .3 )  // 最大旋转角度
						},
						{
							index: 4, // "Upperarm_l" 上臂
							rotationMin: new THREE.Vector3( 0.1, - 0.7, - 1.8 ),
							rotationMax: new THREE.Vector3( 1.1, 0, - 1.4 )
						},
					],
				}
			];
			// 创建CCDIK求解器,用于计算反向运动学
			IKSolver = new CCDIKSolver( OOI.kira, iks );
			// 创建IK辅助工具,可视化IK链
			const ccdikhelper = new CCDIKHelper( OOI.kira, iks, 0.01 );
			scene.add( ccdikhelper );

			// 创建控制面板
			gui = new GUI();
			gui.add( conf, 'followSphere' ).name( 'follow sphere' ); // 相机是否跟随球体
			gui.add( conf, 'turnHead' ).name( 'turn head' ); // 头部是否转向球体
			gui.add( conf, 'ik_solver' ).name( 'IK auto update' ); // 是否自动更新IK
			gui.add( conf, 'update' ).name( 'IK manual update()' ); // 手动更新IK按钮
			gui.open();

			// 初始化渲染器
			renderer = new THREE.WebGLRenderer( { antialias: true } );
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			renderer.setAnimationLoop( animate ); // 设置动画循环
			document.body.appendChild( renderer.domElement );

			// 初始化轨道控制器,允许用户旋转和缩放相机
			orbitControls = new OrbitControls( camera, renderer.domElement );
			orbitControls.minDistance = 0.2;
			orbitControls.maxDistance = 1.5;
			orbitControls.enableDamping = true; // 启用阻尼效果,使相机移动更平滑
			orbitControls.target.copy( targetPosition ); // 设置控制器目标位置

			// 初始化变换控制器,允许用户交互式移动、旋转和缩放对象
			transformControls = new TransformControls( camera, renderer.domElement );
			transformControls.size = 0.75;
			transformControls.showX = false; // 不显示X轴
			transformControls.space = 'world'; // 在世界坐标系下操作
			transformControls.attach( OOI.target_hand_l ); // 附加到左手目标对象
			scene.add( transformControls.getHelper() ); // 添加控制器辅助工具

			// 当使用变换控制器时,禁用轨道控制器
			transformControls.addEventListener( 'mouseDown', () => orbitControls.enabled = false );
			transformControls.addEventListener( 'mouseUp', () => orbitControls.enabled = true );

			// 添加性能统计面板
			stats = new Stats();
			document.body.appendChild( stats.dom );

			// 添加窗口大小变化事件监听
			window.addEventListener( 'resize', onWindowResize, false );

		}

		function animate( ) {

			if ( OOI.sphere && mirrorSphereCamera ) {
				// 更新镜面球体的反射效果
				OOI.sphere.visible = false; // 临时隐藏球体,避免反射自身
				OOI.sphere.getWorldPosition( mirrorSphereCamera.position ); // 将相机放置在球体位置
				mirrorSphereCamera.update( renderer, scene ); // 更新立方相机渲染
				OOI.sphere.visible = true; // 重新显示球体
			}

			if ( OOI.sphere && conf.followSphere ) {
				// 相机跟随球体
				OOI.sphere.getWorldPosition( v0 );
				orbitControls.target.lerp( v0, 0.1 ); // 平滑过渡到球体位置
			}

			if ( OOI.head && OOI.sphere && conf.turnHead ) {
				// 头部转向球体
				OOI.sphere.getWorldPosition( v0 );
				OOI.head.lookAt( v0 );
				// 调整头部旋转,使其看起来更自然
				OOI.head.rotation.set( OOI.head.rotation.x, OOI.head.rotation.y + Math.PI, OOI.head.rotation.z );
			}

			if ( conf.ik_solver ) {
				// 更新IK求解器
				updateIK();
			}

			orbitControls.update(); // 更新轨道控制器
			renderer.render( scene, camera ); // 渲染场景

			stats.update(); // 更新性能统计
		}

		function updateIK() {
			// 更新IK求解器
			if ( IKSolver ) IKSolver.update();

			// 重新计算所有蒙皮网格的边界球体
			scene.traverse( function ( object ) {
				if ( object.isSkinnedMesh ) object.computeBoundingSphere();
			} );
		}

		function onWindowResize() {
			// 窗口大小变化时调整相机和渲染器
			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();
			renderer.setSize( window.innerWidth, window.innerHeight );
		}
		</script>
	</body>
</html>

反向运动学原理与应用

反向运动学(IK)是计算机动画中的重要技术,与正向运动学(FK)相对。

正向运动学与反向运动学的区别
  • 正向运动学(FK):从父关节到子关节的运动传递方式。例如,当你移动肩膀时,上臂、下臂和手都会随之移动。这是传统骨骼动画的工作方式。

  • 反向运动学(IK):根据末端效应器(如手或脚)的目标位置,计算出所有关节的旋转角度。例如,当你指定手要抓住某个物体时,IK系统会自动计算出肩膀、上臂和下臂的正确角度。

CCD IK求解器

本示例使用了Three.js提供的**CCDIK(Cyclic Coordinate Descent)**求解器:

  • 工作原理:从末端效应器开始,逐个调整关节角度,使效应器逐渐接近目标位置,直到达到收敛条件或最大迭代次数。

  • 参数配置

    • target:目标位置对象ID
    • effector:末端效应器ID
    • links:关节链,每个关节有最小和最大旋转限制
    • 可以配置多个独立的IK链
IK在游戏和动画中的应用

IK技术在游戏和动画中有广泛应用:

  1. 角色交互:角色抓取物体、攀爬、游泳等动作
  2. 脚部放置:角色在不平整地面行走时自动调整脚部位置和姿态
  3. 面部表情:控制面部骨骼实现表情动画
  4. 物理模拟:与物理引擎结合实现更真实的动作

IK技术可以大大减少动画师的工作量,尤其是对于复杂的肢体运动。同时,它也能使角色行为更加自然,增强游戏和虚拟环境的沉浸感。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第29节:现代CNN架构-Inception系列模型

引言 Inception系列模型是卷积神经网络(CNN)发展历程中的重要里程碑,由Google研究人员提出并不断演进。这一系列模型通过创新的架构设计,在保持计算效率的同时显著提升了图像识别任务的性能。从最初的Inception v1到最新的Inception-ResNet,每一代Inception模型都引入了突破…

【深度学习】将本地工程上传到Colab运行的方法

1、将本地工程&#xff08;压缩包&#xff09;上传到一个新的colab窗口&#xff1a;如下图中的 2.zip&#xff0c;如果工程中有数据集&#xff0c;可以删除掉。 2、解压压缩包。 !unzip /content/2.zip -d /content/2 如果解压出了不必要的文件夹可以递归删除&#xff1a; #…

RabbitMQ 中的六大工作模式介绍与使用

文章目录 简单队列&#xff08;Simple Queue&#xff09;模式配置类定义消费者定义发送消息测试消费 工作队列&#xff08;Work Queues&#xff09;模式配置类定义消费者定义发送消息测试消费负载均衡调优 发布/订阅&#xff08;Publish/Subscribe&#xff09;模式配置类定义消…

Android HttpAPI通信问题(已解决)

使用ClearTextTraffic是Android中一项重要的网络设置,它控制了应用程序是否允许在不使用HTTPS加密的情况下访问网络。在默认情况下,usescleartexttraffic的值为true,这意味着应用程序可以通过普通的HTTP协议进行网络通信。然而,这样的设置可能会引发一些安全问题,本文将对…

【SSM-SpringMVC(二)】Spring接入Web环境!本篇开始研究SpringMVC的使用!SpringMVC数据响应和获取请求数据

SpringMVC的数据响应方式 页面跳转 直接返回字符串通过ModelAndView对象返回 回写数据 直接返回字符串返回对象或集合 页面跳转&#xff1a; 返回字符串方式 直接返回字符串&#xff1a;此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转 RequestMapping("/con&…

docker安装mysql8, 字符集,SQL大小写规范,sql_mode

一、Docker安装MySQL 使用Docker安装MySQL,命令如下 docker run -d \-p 3306:3306 \-v mysql_conf:/etc/mysql/conf.d \-v mysql_data:/var/lib/mysql \--name mysql \--restartalways \--privileged \-e MYSQL_ROOT_PASSWORD1234 \mysql:8.0.30参数解释 &#x1f433; dock…

【SSM-SSM整合】将Spring、SpringMVC、Mybatis三者进行整合;本文阐述了几个核心原理知识点,附带对应的源码以及描述解析

SSM整合的基础jar包 需要创建的层级&#xff1a; controller层 该层下需要创建对应的控制器Servlet POJO文件夹 该层下需要创建与数据库对应的POJO类 mapper层 该层下需要创建Mapper的接口实现 service层 该层下需要创建业务层的接口及其接口实现 需要创建的配置文件&#x…

MYSQL数据库集群高可用和数据监控平台

项目环境 项目拓扑结构 软硬件环境清单 软硬件环境清单 软硬件环境清单 主机名IP硬件软件 master1 192.168.12.130 VIP&#xff1a;192.168.12.200 cpu:1颗2核 内 存&#xff1a;2GB HDD&#xff1a;20GB 网 络&#xff1a;NAT VmWare17 OpenEuler22.03 SP4 MySql8.0.3…

uni-app vue3版本打包h5后 页面跳转报错(uni[e] is not a function)

先看问题 解决方案 在HBuilderX项目中&#xff0c;若需在Web配置中显式关闭摇树优化&#xff08;Tree Shaking&#xff09;&#xff0c;可以通过以下步骤实现&#xff1a;首先&#xff0c;在配置中打开摇树优化&#xff0c;然后再将其关闭。这样操作后&#xff0c;配置文件中会…

【Redis】缓存穿透、缓存雪崩、缓存击穿

1.缓存穿透 是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;导致请求直接穿透缓存到达数据库&#xff0c;给数据库带来压力的情况。 常见的解决方案有两种&#xff1a; 缓存空对象&#xff1a;实现简单&#xff0c;维护方便&am…

告别数据僵尸!Redis实现自动清理过期键值对

在这个数据爆炸的时代&#xff0c;内存就像珍贵的土地资源&#xff0c;而Redis则是这片土地上的智能管家。它不仅能高效存储数据&#xff0c;还能像秋叶定时凋零般&#xff0c;让键值对在指定时间自动消失。今天&#xff0c;就让我们揭开这项"数据保鲜"技术的奥秘。 …

web第三次课后作业--基于JDBC对mysql数据库的增删查改操作

一、工程搭建步骤 1.新建java项目&#xff0c;添加jdbc依赖 2.写java程序 3.添加mysql数据源&#xff0c;连接本地数据库 4.运行程序二、运行结果 三、代码 代码解析 加载数据驱动 try {Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundExceptio…

[数据结构]5. 栈-Stack

栈-Stack 1. 介绍2. 栈的实现2.1 基于链表的实现2.2 基于数组的实现 3. 栈操作CreateInitilizateDestoryPushPopTopEmptySize 1. 介绍 栈&#xff08;stack&#xff09; 是一种遵循先入后出逻辑的线性数据结构。顶部称为“栈顶”&#xff0c;底部称为“栈底”。把元素添加到栈…

基于Spring Boot + Vue的高校心理教育辅导系统

一、项目背景介绍 随着高校对学生心理健康教育的重视&#xff0c;传统的人工心理辅导与测评模式已经难以满足广大师生的个性化需求。为了提高心理服务的效率、便捷度和覆盖范围&#xff0c;本项目开发了一个高校心理教育辅导系统&#xff0c;集成心理评测、辅导预约、留言交流…

JavaSwing之-JDialog

JavaSwing之-JDialog JDialog 是 Java Swing 中用于创建对话框窗口的容器类&#xff0c;继承自 Dialog 类&#xff08;AWT&#xff09;&#xff0c;常用于显示临时信息、获取用户输入或执行模态操作。它是 javax.swing.JDialog 包中的类。 与 JFrame 不同的是&#xff0c;JDia…

【学习路线】 游戏客户端开发入门到进阶

目录 游戏客户端开发入门到进阶&#xff1a;系统学习路线与推荐书单一、学习总原则&#xff1a;从底层出发&#xff0c;项目驱动&#xff0c;持续迭代二、推荐学习路线图&#xff08;初学者→进阶&#xff09;第一阶段&#xff1a;语言基础与编程思维第二阶段&#xff1a;游戏开…

部署安装gitlab-ce-17.9.7-ce.0.el8.x86_64.rpm

目录 ​编辑 实验环境 所需软件 实验开始 安装部署gitlab171.配置清华源仓库&#xff08;版本高的系统无需做&#xff09;vim /etc/yum.repos.d/gitlab-ce.repo 2.提前下载包dnf localinstall gitlab-ce-17.9.7-ce.0.el8.x86_64.rpm --rocklinux 3.修改配…

备战菊厂笔试2-BFS记忆化MLE?用Set去重-Set会TLE?用SortedSet剪枝

目录 200.岛屿数量 不用getnei&#xff0c;直接在dfs判断&#xff0c;去掉解包 如果害怕栈溢出那么可以用bfs 2617.网格图中最少访问的格子数 注意特判&#xff01; MLE主要是因为vis占用的内存过大 用SortedSet有序剪枝 什么是SortedSet&#xff1f; 基本性质 导入 …

【RabbitMQ】发布确认机制的具体实现

文章目录 模式介绍建立连接单独确认代码实现逻辑运行结果 批量确认代码实现逻辑运行结果 异步确认实现逻辑介绍代码实现逻辑运行结果 三种策略对比以及完整代码 模式介绍 作为消息中间件&#xff0c;都会面临消息丢失的问题&#xff0c;消息丢失大概分为三种情况&#xff1a; …

React状态管理-对state进行保留和重置

相同位置的相同组件会使得 state 被保留下来 当你勾选或清空复选框的时候&#xff0c;计数器 state 并没有被重置。不管 isFancy 是 true 还是 false&#xff0c;根组件 App 返回的 div 的第一个子组件都是 <Counter />&#xff1a; 你可能以为当你勾选复选框的时候 st…