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,一经查实,立即删除!

相关文章

多时间尺度源储荷协调调度策略:储能电站特性分布与电网接入

《考虑特性分布的储能电站接入的电网多时间尺度源储荷协调调度策略》&#xff08;作者&#xff1a;金力等&#xff09;这标题听起来有点拗口&#xff0c;但其实它讲的是关于电网里储能电站怎么更高效地工作的策略。 文章的背景和目的是基于当前电网面临的多时间尺度调度的挑战。…

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…

2024认证杯数学建模B题思路模型代码

目录 2024认证杯数学建模B题思路模型代码:4.11开赛后第一时间更新&#xff0c;获取见文末名片 第十三届“认证杯”数学中国数学建模比赛赛后体会 2024认证杯数学建模B题思路模型代码:4.11开赛后第一时间更新&#xff0c;获取见文末名片 第十三届“认证杯”数学中国数学建模比…

SE78图片迁移下载

SAP好像并没有标准的事务码可以方便下载已上传至SAP服务器上的图片&#xff0c;通常的解决方法写一个专门下载这类图片的简单程序来处理。 REPORT Ydownload. DATA : g_bytecount TYPE i,g_content TYPE STANDARD TABLE OFbapiconten INITIAL SIZE …

中国知网:学术资源的宝库与知识共享的平台

中国知网&#xff0c;作为国内领先的学术平台&#xff0c;始终致力于实现全社会知识资源的传播共享与增值利用。自1999年6月创建以来&#xff0c;知网已经成为了中国学术界的重要组成部分&#xff0c;为广大学者、研究人员和学生提供了一个全面、便捷的学术资源库。 一、知网的…

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…

Apache POI

Apache POI的使用和下载 要使用Apache POI&#xff0c;您需要下载POI库&#xff0c;并将它添加到您的项目中。 首先&#xff0c;您需要从Apache POI的官方网站&#xff08;https://poi.apache.org/&#xff09;下载POI库。在网站的“Downloads”页面上&#xff0c;找到“Bina…

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…

128.最长连续序列

给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&#xff1a; 输入&#xff1a;nums [100,4,200,1,3,2] 输出&#xff1a;4 解…

高效学习:从最适合自己的地方学习

1&#xff09;一个人的学习行为&#xff0c;其实就是对新事物的认知由浅入深的过程。在这个过程中&#xff0c;一个人要高效地进行这个过程需要什么呢&#xff1f;全神贯注&#xff0c;反复琢磨。 2&#xff09;有什么东西能够让自己全神贯注&#xff0c;不惜反复琢磨&#xff…

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天精通鸿蒙》 …

从零开始的LeetCode刷题日记:383. 赎金信

一.相关链接 题目链接&#xff1a;383. 赎金信 二.心得体会 这道题是非常简单&#xff0c;和242.有效的字母异位词基本一致&#xff0c;区别在于这里需要比较出现的次数&#xff0c;仅此而已。 三.代码 class Solution { public:bool canConstruct(string ransomNote, str…

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;事半功倍。 在众多运营工具中&…

高新技术企业上市要达到什么条件

企业在申请高新技术企业认定前要开展自我评价&#xff0c;符合条件才向认定机构提出认定申请。高新技术企业生存环境逐渐优化&#xff0c;上市条件逐渐成熟。民营高新技术企业有它独特的特点和财务问题。那么&#xff0c;高新技术企业上市要达到的条件&#xff1f; 一、高新技…