React + three.js 3D模型骨骼绑定

系列文章目录

  1. React 使用 three.js 加载 gltf 3D模型 | three.js 入门
  2. React + three.js 3D模型骨骼绑定

项目代码(github):https://github.com/couchette/simple-react-three-skeleton-demo
项目代码(gitcode):https://gitcode.com/qq_41456316/simple-react-three-skeleton-demo.git

文章目录

  • 系列文章目录
  • 前言
  • 一、3D 模型骨骼绑定是什么?
  • 二、为什么选择 React 和 Three.js?
  • 三、如何在 React 中实现 3D 模型骨骼绑定?
  • 四、具体实现步骤
    • 1、创建项目配置环境
    • 2. 创建组件
    • 3. 使用组件
    • 4. 运行项目
  • 结语


前言

在当今互联网世界中,网页技术的发展已经超越了以往的想象。其中,三维图形技术在网页中的应用日益普遍,而 React 和 Three.js 正是其中的两个热门选择。本文将介绍如何将 Three.js 中的 3D 模型骨骼绑定示例迁移到 React 中,为读者提供一个简单易懂的入门指南。


一、3D 模型骨骼绑定是什么?

在三维计算机图形学中,骨骼绑定是一种常用的技术,用于将一个三维模型与其骨骼系统相连接。这样的连接使得模型能够进行动画表现,仿佛具有生命一般。通过控制骨骼系统的姿势和运动,可以实现模型的各种动作,比如行走、跳跃、转身等。

二、为什么选择 React 和 Three.js?

React 是一个流行的 JavaScript 库,用于构建用户界面。它的组件化和声明式的特性使得构建交互式 UI 变得更加简单。而 Three.js 则是一个用于创建 3D 图形的 JavaScript 库,它提供了丰富的功能和 API,使得在网页中展示三维模型变得轻而易举。

将这两者结合起来,可以让开发者在 React 的基础上轻松地添加复杂的三维图形功能,为用户提供更加丰富的交互体验。

三、如何在 React 中实现 3D 模型骨骼绑定?

要在 React 中实现 3D 模型骨骼绑定,我们可以借助 Three.js 提供的示例来进行学习和实践。具体地,我们可以参考 Three.js 官方示例中的 webgl_animation_skinning_ik 示例,该示例展示了一个带有骨骼动画的人物模型,并且可以通过鼠标交互来控制模型的运动。

通过将这个示例移植到 React 中,我们可以使得代码更具可维护性和可扩展性,同时也能够让 React 开发者轻松地使用 Three.js 的功能。在移植过程中,我们需要注意将 Three.js 的相关代码封装成 React 组件,并正确处理 React 的生命周期以及状态管理等方面的问题。

四、具体实现步骤

1、创建项目配置环境

使用 create-reacte-app 创建项目

npx create-react-app simple-react-three-skeleton-demo
cd simple-react-three-skeleton-demo

安装three.js

npm i three

2. 创建组件

src目录创建components文件夹,在components文件夹下面创建ThreeContainer.js文件。
首先创建组件,并获取return 元素的ref

import * as THREE from "three";
import { useRef, useEffect } from "react";function ThreeContainer() {const containerRef = useRef(null);const isContainerRunning = useRef(false);return <div ref={containerRef} />;
}export default ThreeContainer;

接着将three.js自动创建渲染元素添加到return组件中为子元素(可见containerRef.current.appendChild(renderer.domElement);),相关逻辑代码在useEffect中执行,完整代码内容如下

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";
import { useRef, useEffect } from "react";function ThreeContainer() {const containerRef = useRef(null);const isContainerRunning = useRef(false);useEffect(() => {if (!isContainerRunning.current && containerRef.current) {isContainerRunning.current = true;let scene, camera, renderer, orbitControls, transformControls;let mirrorSphereCamera;const OOI = {};let IKSolver;let stats, gui, conf;const v0 = new THREE.Vector3();init().then(animate);async function init() {conf = {followSphere: false,turnHead: true,ik_solver: true,update: updateIK,};scene = new THREE.Scene();scene.fog = new THREE.FogExp2(0xffffff, 0.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); // soft white lightscene.add(ambientLight);renderer = new THREE.WebGLRenderer({antialias: true,logarithmicDepthBuffer: true,});renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth, window.innerHeight);containerRef.current.appendChild(renderer.domElement);stats = new Stats();containerRef.current.appendChild(stats.dom);orbitControls = new OrbitControls(camera, renderer.domElement);orbitControls.minDistance = 0.2;orbitControls.maxDistance = 1.5;orbitControls.enableDamping = true;const dracoLoader = new DRACOLoader();dracoLoader.setDecoderPath("/draco/");// dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");const gltfLoader = new GLTFLoader();gltfLoader.setDRACOLoader(dracoLoader);const gltf = await gltfLoader.loadAsync("/models/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);orbitControls.target.copy(OOI.sphere.position); // orbit controls lookAt the sphereOOI.hand_l.attach(OOI.sphere);// mirror sphere cube-cameraconst 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;transformControls = new TransformControls(camera, renderer.domElement);transformControls.size = 0.75;transformControls.showX = false;transformControls.space = "world";transformControls.attach(OOI.target_hand_l);scene.add(transformControls);// disable orbitControls while using transformControlstransformControls.addEventListener("mouseDown",() => (orbitControls.enabled = false));transformControls.addEventListener("mouseUp",() => (orbitControls.enabled = true));OOI.kira.add(OOI.kira.skeleton.bones[0]);const iks = [{target: 22, // "target_hand_l"effector: 6, // "hand_l"links: [{index: 5, // "lowerarm_l"rotationMin: new THREE.Vector3(1.2, -1.8, -0.4),rotationMax: new THREE.Vector3(1.7, -1.1, 0.3),},{index: 4, // "Upperarm_l"rotationMin: new THREE.Vector3(0.1, -0.7, -1.8),rotationMax: new THREE.Vector3(1.1, 0, -1.4),},],},];IKSolver = new CCDIKSolver(OOI.kira, iks);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");gui.add(conf, "update").name("IK manual update()");gui.open();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) {// orbitControls follows the sphereOOI.sphere.getWorldPosition(v0);orbitControls.target.lerp(v0, 0.1);}if (OOI.head && OOI.sphere && conf.turnHead) {// turn headOOI.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) {updateIK();}orbitControls.update();renderer.render(scene, camera);stats.update(); // fps statsrequestAnimationFrame(animate);}function updateIK() {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);}}}, []);return <div ref={containerRef} />;
}export default ThreeContainer;

3. 使用组件

修改App.js内容如下

import "./App.css";
import ThreeContainer from "./components/ThreeContainer";function App() {return (<div><ThreeContainer /></div>);
}export default App;

4. 运行项目

运行项目 npm start最终效果如下
请添加图片描述

结语

通过本文的介绍,相信读者对于在 React 中实现 3D 模型骨骼绑定有了初步的了解。如果你对此感兴趣,不妨动手尝试一下,可能会有意想不到的收获。同时,也欢迎大家多多探索,将 React 和 Three.js 的强大功能发挥到极致,为网页应用增添更多的乐趣和惊喜。

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

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

相关文章

spring(7)-事务

事务 1、 事务概述2、spring对事务的支持2.1 spring事务管理API2.2 事务属性2.2.1 事务传播行为2.2.2 案例2.2.2.1 REQUIRED2.2.2.2 REQUIRES_NEW2.2.2.3 NESTED 2.2.3 事务隔离行为2.2.3.1 测试2.2.3.2 读未提交2.2.3.3 读提交2.2.3.4 可重复读 2.2.4 事务超时2.2.5 只读事务2…

OpenHarmony南向开发案例:【智能垃圾桶】

样例简介 智能垃圾桶可以通过数字管家应用来监测垃圾桶当前可用容量&#xff0c;提醒主人及时处理垃圾&#xff1b;通过日程管家可以实现和其他智能设备联动。 核心组件位置功能距离传感器置于垃圾桶盖内侧感应垃圾量红外传感器置于垃圾桶前端感应是否有人靠近光敏电阻开发板…

2006-2021年各省能源消费总量数据(无缺失)

2006-2021年各省能源消费总量数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;2006-2021年 2、来源&#xff1a;能源年鉴、各省年鉴 3、范围&#xff1a;30个省 4、指标&#xff1a;能源消费总量&#xff08;万吨标煤&#xff09; 5、缺失情况&#xff1a;无缺失 …

AI人工智能讲师简历大模型讲师叶梓大模型技术与应用培训提纲

叶梓&#xff0c;工学博士&#xff0c;高级工程师。现某大型上市企业资深技术专家。 2005年上海交通大学计算机专业博士毕业&#xff0c;在校期间的主研方向为数据挖掘、机器学习、人工智能。毕业后即进入软件行业从事信息化技术相关工作&#xff1b;负责或参与了多项国家级、省…

Docker Nginx 部署Vue项目

先弄个ngix镜像&#xff0c;还原到linux里面 发布包放的位置 nginx配置文件 server {listen 8049;server_name localhost;#charset koi8-r;access_log /var/log/nginx/host.access.log main;error_log /var/log/nginx/error.log error;location / {# root 根目录&a…

Vue3报错:‘defineProps‘ is not defined no-undef

解决方法 在package.json中添加 "vue/setup-compiler-macros": true 记得在上面的 "node": true 后面加一个逗号 "eslintConfig": {"root": true,"env": {"node": true,"vue/setup-compiler-macros": t…

Compose UI 之 Card 卡片组件

Card Card 是用于显示带有圆角和可选阴影的矩形内容容器。它通常用于构建用户界面,并可以包含标题、文本、图像、按钮等元素,表示界面上的可交互元素,我们称它是 “卡片”。 Card 使用的一些经典的场景: 列表数据,例如 新闻列表,产品列表等。信息提示框,使用 Card 组件…

一起学习python——基础篇(13)

前言&#xff0c;python编程语言对于我个人来说学习的目的是为了测试。我主要做的是移动端的开发工作&#xff0c;常见的测试主要分为两块&#xff0c;一块为移动端独立的页面功能&#xff0c;另外一块就是和其他人对接工作。 对接内容主要有硬件通信协议、软件接口文档。而涉…

windows软件在更新的时候,会自动找到旧版本软件的位置,这个功能如何实现 ?

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

root@localhost‘s password: Permission denied, please try again.

编辑、etc/ssh/sshd_config文件 ,将PermitRootLogin这行改为yes rootubuntu:/home/ubuntu# vim /etc/ssh/sshd_config 重新加载改文件 /etc/init.d/ssh restart

AI防关联引流系统:解锁TikTok全球流量密码 轻松驾驭全球流量

TikTok的风潮席卷全球&#xff0c;吸引了无数运营者的目光。在这个崭新的出海风口&#xff0c;每一位创作者都渴望脱颖而出。而在这其中&#xff0c;掌握并运用各种TikTok运营工具无疑是一把利剑&#xff0c;能够帮助我们披荆斩棘&#xff0c;事半功倍。 在众多运营工具中&…

element的el-table表格自定义表头解决数据不更新问题

场景&#xff1a; 需要生成一个表格&#xff0c;表头由后端返回&#xff0c;自定义生成。 问题&#xff1a; 通过插槽传入表头&#xff0c;但是&#xff0c;如果我把表头初始值赋值为null&#xff0c;虽然可以正常显示表头&#xff0c;但是一开始会报错&#xff1b;如果我把表…

策略模式类图与代码

某大型购物中心欲开发一套收银软件&#xff0c;要求其能够支持购物中心在不同时期推出的各种促销活动&#xff0c;如打折、返利(例如&#xff0c;满300返100),等等。现采用策略(Strategy)模式实现该要求&#xff0c;得到如图7.13 所示的类图。 【Java 代码】 import java.util…

IIS中部署netcore程序出现500错误如何处理?

500错误在IIS部署中经常出现&#xff0c;但是解决非常耗时 官方也有给出一些指引&#xff0c;但是无法解决根本问题 建议检查netcore相关组件是否正确安装&#xff0c;如下&#xff1a; aspnetcore-runtime-3.1.32-win-x64 dotnet-hosting-3.1.32-win dotnet-runtime-3.1.…

postgresql或者opengauss中查询表的描述

在使用postgresql数据库&#xff0c;或者opengauss数据库时&#xff0c;如果要通过模式名&#xff08;schema&#xff09;和表名&#xff08;table&#xff09;&#xff0c;查询表的描述信息&#xff0c;可以使用下面的SQL&#xff1a; select pt.schemaname, pt.tablename ,p…

【域适应】基于迁移成分分析(TCA) 的典型二分类问题(python)

关于 传统迁移成分分析&#xff0c;区别于深度迁移学习&#xff0c;是一种常规的对特征数据进行对齐的一种技术。 广泛应用于&#xff0c;两个域&#xff08;源域&#xff0c;目标域&#xff09;数据特征分布不一致&#xff0c;需要实现目标域数据分析任务中。 文章参考&…

大话设计模式——21.中介者模式(Mediator Pattern)

简介 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互 UML图 应用场景 大量的连接使得一个对象不可能在没有其他对象的支持下工作&#xff0c;系统表现为一个不可分割的…

c++之旅第九弹——模版

大家好啊&#xff0c;这里是c之旅第九弹&#xff0c;跟随我的步伐来开始这一篇的学习吧&#xff01; 如果有知识性错误&#xff0c;欢迎各位指正&#xff01;&#xff01;一起加油&#xff01;&#xff01; 创作不易&#xff0c;希望大家多多支持哦&#xff01; 一.模版的概念…

3D开发工具HOOPS:推动汽车行业CAD可视化发展

在最近的行业对话中&#xff0c;Tech Soft 3D&#xff08;HOOPS厂商&#xff09;的Jonathan Girroir和Actify的Peter West探讨了CAD可视化在当代企业中的重要性和挑战。作为CAD可视化领域的佼佼者&#xff0c;Actify通过其广受欢迎的Spinfire应用&#xff0c;赋能了全球40多个国…

【上海大学计算机组成原理实验报告】三、微指令系统实验

一、实验目的 了解译码器、微指令结构的基本工作原理。学习设计微指令的方法。 二、实验原理 根据实验指导书的相关内容&#xff0c;本实验所用的实验箱的微指令系统控制总线宽度为24位&#xff0c;每个地址单元宽度也为24位&#xff0c;其中微指令存储器由3片8位存储器按照…