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.hqwc.cn/news/610838.html

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

相关文章

WEB漏洞-文件上传之WAF绕过及安全修复

#上传参数解析&#xff1a; Content-disposition&#xff1a;一般不可更改 Name&#xff1a;表单参数值&#xff0c;不能更改&#xff08;更改需要达到统一&#xff09; Filename&#xff1a;文件名&#xff0c;可以更改 Content-type&#xff1a;文件MIME&#xff0c;视情…

4.11Qt

完善网络聊天室客户端实现 工程文件 QT core gui network 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpSocket> #include<QMessageBox>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEcl…

Linux 函数学习 poll

1、Linux poll 函数 int poll(struct pollfd *fds, nfds_t nfds, int timeout); fds&#xff1a; 需要轮询的fd集合 nfds&#xff1a;需要轮询的fds数量 timeout&#xff1a;超时时间 返回值&#xff1a;0 超时&#xff0c;<0 发生异常&#xff0c;> 0 存在数据变化 …

ETL中如何运用好MQ消息集成

一、ETL的主要作用 ETL&#xff08;Extract, Transform, Load&#xff09;是数据仓库中的关键环节&#xff0c;其主要作用是将数据从源系统中抽取出来&#xff0c;经过转换和清洗后加载到数据仓库中。具体而言&#xff1a; Extract&#xff08;抽取&#xff09;&#xff1a;从…

JS-28-AJAX

一、AJAX的定义 AJAX不是JavaScript的规范&#xff0c;它只是一个哥们“发明”的缩写&#xff1a;Asynchronous JavaScript and XML&#xff0c;意思就是用JavaScript执行异步网络请求。 如果仔细观察一个Form的提交&#xff0c;你就会发现&#xff0c;一旦用户点击“Submit”…

[BT]BUUCTF刷题第14天(4.10)

第14天&#xff08;共5题&#xff09; Web [BJDCTF2020]ZJCTF&#xff0c;不过如此 打开网站直接显示源代码&#xff1a; <?php error_reporting(0); //关闭报错 $text $_GET["text"]; $file $_GET["file"]; if(isset($text)&&(file…

计算机网络 Telnet远程访问交换机和Console终端连接交换机

一、实验要求和内容 1、配置交换机进入特权模式密文密码为“abcd两位班内学号”&#xff0c;远程登陆密码为“123456” 2、验证PC0通过远程登陆到交换机上&#xff0c;看是否可以进去特权模式 二、实验步骤 1、将一台还没配置的新交换机&#xff0c;利用console线连接设备的…

R语言ggplot2绘图学习笔记(基础知识大全)

R语言ggplot2绘图入门笔记 今天分享的内容是在R语言中利用ggplot2进行可视化的入门笔记&#xff0c;适用于初学者了解ggplot2绘图系统。干货满满&#xff0c;建议收藏&#xff01; 首先安装以下R包&#xff1a; install.packages(c("tidyverse", "colorspace&qu…

Python 全栈体系【四阶】(二十三)

第五章 深度学习 三、计算机视觉基本理论 1. 计算机视觉概览 1.1 什么是计算机视觉 计算机视觉在广义上是和图像相关的技术总称。包括图像的采集获取&#xff0c;图像的压缩编码&#xff0c;图像的存储和传输&#xff0c;图像的合成&#xff0c;三维图像重建&#xff0c;图像…

Java快速入门系列-9(Spring框架与Spring Boot —— 深度探索及实践指南)

第九章:Spring框架与Spring Boot —— 深度探索及实践指南 9.1 Spring框架概述9.2 Spring IoC容器9.3 Spring AOP9.4 Spring MVC9.5 Spring Data JPA/Hibernate9.6 Spring Boot快速入门与核心特性9.7 Spring Boot的自动配置与启动流程详解9.8 创建RESTful服务与数据库交互实践…

学习云计算HCIE选择誉天有什么优势?

誉天云计算课程优势实战性强 课程注重实践操作&#xff0c;通过实际案例和实验操作&#xff0c;让学员深入了解云计算的应用场景和实际操作技能。课程内容全面 涵盖所有云计算涉及的IT基础知识、服务器、存储、网络等方面的基础知识&#xff0c;开源操作系统Linux&#xff0c;开…

12-pyspark的RDD算子注意事项总结

目录 相近算子异同总结相近变换算子异同foreach和foreachPartitionfold和reducecoalesce和repatition 相近动作算子异同cache和persist 算子注意事项需要注意的变换算子需要注意的动作算子 PySpark实战笔记系列第三篇 10-用PySpark建立第一个Spark RDD(PySpark实战笔记系列第…