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

devtools/2024/10/24 11:15:30/

系列文章目录

  1. React 使用 three.js 加载 gltf 3D模型 | three.js 入门
  2. React + three.js 3D模型骨骼绑定
  3. React + three.js 3D模型面部表情控制
  4. React + three.js 实现人脸动捕与3D模型表情同步
  5. 结合 react-webcam、three.js 与 electron 实现桌面人脸动捕应用

项目代码(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.ppmy.cn/devtools/23474.html

相关文章

远程控制安卓手机:便捷、高效与安全的方法

在移动设备的领域里&#xff0c;远程控制安卓手机的能力也变得越来越重要。这种技术可以让我们在远程地点方便地操作手机&#xff0c;无论是处理紧急事务、帮助他人解决问题&#xff0c;还是仅仅为了享受科技带来的便利。本文将为你介绍2种便捷、高效且安全的方法&#xff0c;让…

自动驾驶的关键在于安全、智能与舒适

自动驾驶的关键在于安全、智能与舒适。这三个方面是实现自动驾驶技术成功的关键要素。 安全是自动驾驶技术的首要考虑因素。自动驾驶系统需要能够准确地感知和理解周围的交通环境&#xff0c;并根据这些信息做出适当的驾驶决策。安全性还包括对意外情况的应对能力&#xff0c;例…

2024.4.28力扣每日一题——负二进制转换

2024.4.28 题目来源我的题解方法一 进制转换方法二 模拟进位 题目来源 力扣每日一题&#xff1b;题序&#xff1a;1017 我的题解 方法一 进制转换 对于以-2为基数的系统&#xff0c;可以这样理解&#xff1a;在-2进制中&#xff0c;每一位的权重是-2的幂。这与传统的二进制表…

【北京迅为】《iTOP龙芯2K1000开发指南》-第四部分 ubuntu开发环境搭建

龙芯2K1000处理器集成2个64位GS264处理器核&#xff0c;主频1GHz&#xff0c;以及各种系统IO接口&#xff0c;集高性能与高配置于一身。支持4G模块、GPS模块、千兆以太网、16GB固态硬盘、双路UART、四路USB、WIFI蓝牙二合一模块、MiniPCIE等接口、双路CAN总线、RS485总线&#…

【UE5】数字人基础

这里主要记录一下自己在实现数字人得过程中涉及导XSens惯性动捕&#xff0c;视频动捕&#xff0c;LiveLinkFace表捕&#xff0c;GRoom物理头发等。 一、导入骨骼网格体 骨骼网格体即模型要在模型雕刻阶段就要雕刻好表捕所需的表情体(blendshape)&#xff0c;后面表捕的效果直…

LLM学习之自然语言处理简单叙述

自然语言处理基础 自然语言处理&#xff1a;让计算机读懂人所写好的这些文本&#xff0c;能够像人一样进行交互。 自然语言处理的任务和应用 任务&#xff1a; 词性标注 part of speech tagging 动词&#xff0c;名词&#xff0c;形容词&#xff1f; 命名实体的识别 name…

IDEA 中的奇技淫巧

IDEA 中的奇技淫巧 书签 在使用ctrlalt方向键跳转时&#xff0c;或者追踪代码时&#xff0c;经常遇到的情况是层级太多&#xff0c;找不到代码的初始位置&#xff0c;入口。可以通过书签的形式去打上一个标记&#xff0c;后续可以直接跳转到书签位置。 标记书签&#xff1a;c…

keytool,openssl的使用

写在前面 在生成公钥私钥&#xff0c;配置https时经常需要用到keytool&#xff0c;openssl工具&#xff0c;本文就一起看下其是如何使用的。 keytool是jdk自带的工具&#xff0c;不需要额外下载&#xff0c;但openssl需要额外下载 。 1&#xff1a;使用keytool生成jks私钥文件…