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

系列文章目录

  1. React 使用 three.js 加载 gltf 3D模型 | three.js 入门
  2. React + three.js 3D模型骨骼绑定
  3. 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.rhkb.cn/news/305419.html

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

相关文章

保姆级Xshell安装教程

简介 Xshell 是一个强大的安全终端模拟软件&#xff0c;它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议。Xshell 通过互联网到远程主机的安全连接以及它创新性的设计和特色帮助用户在复杂的网络环境中享受他们的工作。 Xshell可以在Windows界面下用来访问远端不…

Windows:Redis数据库图形化中文工具软件——RESP(3)

这个是用于连接redis数据库的软件工具&#xff0c;安装在windows上的图形化界面&#xff0c;并且支持中文&#xff0c;是在github上的一个项目 1.获取安装包 发布 lework/RedisDesktopManager-Windows (github.com)https://github.com/lework/RedisDesktopManager-Windows/rel…

vulhub之fastjson篇-1.2.27-rce

一、启动环境 虚拟机:kali靶机:192.168.125.130/172.19.0.1(docker地址:172.19.0.2) 虚拟机:kali攻击机:192.168.125.130/172.19.0.1 本地MAC:172.XX.XX.XX 启动 fastjson 反序列化导致任意命令执行漏洞 环境 1.进入 vulhub 的 Fastjson 1.2.47 路径 cd /../../vulhub/fa…

Vue中如何使用Tailwind CSS样式?多次引用不成功?具体步骤怎么做?

一、安装Tailwind CSS和依赖 在你的Vue项目中安装Tailwind CSS及其依赖。你可以使用npm或yarn来安装。 npm install tailwindcsslatest postcsslatest autoprefixerlatest # 或者yarn add tailwindcsslatest postcsslatest autoprefixerlatest 二、初始化Tailwind CSS np…

Qt中播放GIF动画

在Qt应用程序中&#xff0c;如果你想在QLabel控件上播放GIF动画&#xff0c;可以使用QMovie类与QLabel配合来实现。以下是详细步骤和代码示例&#xff1a; 步骤1&#xff1a;引入必要的头文件 首先&#xff0c;在你的源代码文件中包含QMovie和QLabel相关的头文件&#xff1a;…

rust使用print控制台打印输出五颜六色的彩色红色字体

想要在控制台打印输出彩色的字体&#xff0c;可以使用一些已经封装好的依赖库&#xff0c;比如ansi_term这个依赖库&#xff0c;官方依赖库地址&#xff1a;https://crates.io/crates/ansi_term 安装依赖&#xff1a; cargo add ansi_term 或者在Cargo.toml文件中加入&#…

如何在群晖本地搭建在线PS工具Potopea并实现无公网IP远程编辑图片

文章目录 1. 部署Photopea2. 运行Photopea3. 群晖安装Cpolar4. 配置公网地址5. 公网访问测试6. 固定公网地址 本文主要介绍如何在群晖NAS使用Docker部署Potopea在线图片编辑工具&#xff0c;并结合cpolar内网穿透实现公网环境可以远程访问本地部署的Potopea. Photopea是一款强大…

2024年4月12日 十二生肖 今日运势

小运播报&#xff1a;2024年4月12日&#xff0c;星期五&#xff0c;农历三月初四 &#xff08;甲辰年戊辰月丙午日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;羊、狗、虎 需要注意&#xff1a;牛、马、鼠 喜神方位&#xff1a;西南方 财神方位&#xff1a;…

【C++算法】线性DP详解:数字三角形、最长上升子序列、最长公共子序列、最长公共子串、字符串编辑距离

文章目录 1&#xff09;数字三角形1&#xff1a;顺推2&#xff1a;逆推 2&#xff09;最长上升子序列1&#xff1a;线性DP做法2&#xff1a;二分优化 3&#xff09;最长公共子序列4&#xff09;最长公共子串5&#xff09;字符串编辑距离 1&#xff09;数字三角形 1&#xff1a…

4/7 QT_day1

#include "mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {//窗口设置this->setWindowTitle("小黑子(little black son)");this->setWindowIcon(QIcon("D:\\qq文件\\Pitrue\\pictrue\\black.jpg"));this-&g…

【理解-IO多路复用】

文章目录 多路复用的介绍select ()poll()epoll() 多路复用的介绍 IO多路复用是一种技术&#xff0c;允许单个线程同时管理多个输入/输出通道&#xff0c;如网络套接字或文件描述符。 在IO多路复用中&#xff0c;这些通道被注册到一个事件管理器&#xff0c;然后通过阻塞方式等…

Vue列表渲染

一、Vue列表渲染 1.用 v-for 把一个数组对应为一组元素 我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法&#xff0c;其中 items 是源数据数组&#xff0c;而 item 则是被迭代的数组元素的别名。 <ul id"exampl…

MATLAB有限元结构动力学分析与工程应用-徐斌|【PDF电子书+配套Matlab源码】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

Spring Cloud学习笔记:Eureka简介,Eureka简单样例

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 [TOC](目录)1、Eureka 1.1、架构 Eureka是SpringCloud Nexflix的核心子模块&#xff0c;其中包含Server和Client。 Server提供服务注册&#xff0c;存储所有可用服务节点。 Client用于简化和Server的通讯复杂…

股票高胜率的交易法则是什么?

股票交易中的高胜率交易法则并非一成不变&#xff0c;而是根据市场状况、个人投资风格和经验等多种因素综合而定的。以下是一些有助于提升交易胜率的法则和策略&#xff1a; 1.趋势跟踪法则&#xff1a;在股票交易中&#xff0c;趋势跟踪是一种有效的策略。通过观察大盘和个股…

算法训练营第二十三天(二叉树完结)

算法训练营第二十三天&#xff08;二叉树完结&#xff09; 669. 修剪二叉搜索树 力扣题目链接(opens new window) 题目 给定一个二叉搜索树&#xff0c;同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[L, R]中 (R>L) 。你可能需要改…

AI技术创业:挖掘未来的黄金机会

前言 在科技飞速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正逐渐成为引领创新的重要力量。AI技术不仅为各行各业带来了巨大的变革&#xff0c;也为创业者们提供了前所未有的机会。那么&#xff0c;站在AI的风口上&#xff0c;创业者们该如何把握这些机会…

基于 MATLAB 和 App Designer 的 UI 交互框架开发的一款电力系统潮流计算工具

基于 MATLAB 和 App Designer 的 UI 交互框架开发的一款电力系统潮流计算工具 文章目录 基于 MATLAB 和 App Designer 的 UI 交互框架开发的一款电力系统潮流计算工具一、软件介绍二、软件功能1、数据输入 2、潮流作业设置3、 潮流结果报表及可视化三、 软件设计思路1 、牛顿拉…

【Linux的进程篇章 - 进程终止和进程等待的理解】

Linux学习笔记---008 Linux之fork函数、进程终止和等待的理解1、fork函数1.1、什么是fork?1.2、fork的功能介绍1.3、fork函数返回值的理解1.4、fork函数的总结 2、进程的终止2.1、终止是在做什么&#xff1f;2.2、进程终止的3种情况 3、进程的终止3.1、进程终止的三种情况3.2、…

如何用Java后端处理JS.XHR请求

Touching searching engine destroies dream to utilize php in tomcat vector.The brave isn’t knocked down&#xff0c;turn its path to java back-end. Java Servlet Bible schematic of interaction between JS front-end and Java back-end Question 如何利用Java…