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

news2024/11/27 17:58:28

系列文章目录

  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 light
        scene.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 sphere
        OOI.hand_l.attach(OOI.sphere);

        // mirror sphere cube-camera
        const 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 transformControls
        transformControls.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 sphere
          OOI.sphere.getWorldPosition(v0);
          orbitControls.target.lerp(v0, 0.1);
        }

        if (OOI.head && OOI.sphere && conf.turnHead) {
          // turn head
          OOI.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 stats

        requestAnimationFrame(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.coloradmin.cn/o/1587277.html

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

相关文章

solidworks electrical 2D和3D有什么区别

SolidWorks Electrical 是一款专为电气设计开发的软件工具&#xff0c;它提供了两种主要的工作环境&#xff1a;2D电气设计和3D电气集成设计。两者在功能和应用场景上存在显著的区别&#xff1a; SolidWorks Electrical 2D 设计 特点与用途&#xff1a; SolidWorks Electrica…

贪心算法:柠檬水找零

题目链接&#xff1a;860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; 收的钱只能是5、10、20美元&#xff0c;分类讨论&#xff1a;收5美元无需找零&#xff1b;收10美元找零5元&#xff1b;收20美元找零15美元。其中对于找零15美元的方案有两种&#xff0c;此处涉及…

集群开发学习(一)(安装GO和MySQL,K8S基础概念)

完成gin小任务 参考文档&#xff1a; https://www.kancloud.cn/jiajunxi/ginweb100/1801414 https://github.com/hanjialeOK/going 最终代码地址&#xff1a;https://github.com/qinliangql/gin_mini_test.git 学习 1.安装go wget https://dl.google.com/go/go1.20.2.linu…

华为OD技术面试-有序数组第K最小值

背景 2024-03-15华为od 二面&#xff0c;记录结题过程 有序矩阵中第 K 小的元素 - 力扣&#xff08;LeetCode&#xff09; https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix/submissions/512483717/ 题目 给你一个 n x n 矩阵 matrix &#xff0c;其…

Ubuntu 安装Java、Git、maven、Jenkins等持续集成环境

Ubuntu 持续集成 安装OpenJdk 查看所有可安装的 JDK 版本 apt list OpenJDK\*使用 apt 安装 JDK&#xff08;以 11为例&#xff09;,最好是用11&#xff0c;java8对应的jenkins会有兼容问题。 sudo apt install openjdk-11-jdk openjdk-11-jre安装成功后&#xff0c;可以使用以…

图像处理与视觉感知---期末复习重点(7)

文章目录 一、图像压缩1.1 三种冗余1.2 模型1.3 信息测量 二、无误差压缩2.1 哈夫曼编码2.1.1 步骤2.1.2 例题 2.2 算术编码 三、变换编码 一、图像压缩 1.1 三种冗余 1. 三种基本的是数据冗余为&#xff1a;编码冗余、像素间冗余、心理视觉冗余。 2. 编码冗余&#xff1a;如果…

unity——Button组件单击双击长按功能

1.实现单击、双击、长按功能 using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; public class ButtonControl_Click_Press_Double : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler {publi…

反序列化漏洞笔记

1 PHP 序列化基础概念 1.1 什么是序列化 序列化可以实现将对象压缩并格式化&#xff0c;方便数据的传输和存储。 为什么要序列化&#xff1f; PHP 文件在执行结束时会把对象销毁&#xff0c;如果下次要引用这个对象的话就很麻烦&#xff0c;所以就有了对象序列化&#xff0…

Linux:Redis7.2.4的源码包部署(2)

本章使用的是centos9进行部署 1.获取rpm安装包 Index of /releases/ (redis.io)https://download.redis.io/releases/这个网站有历史的版本&#xff0c;我这里使用的是最新版7.2.4进行安装 点击即可进行下载 方进Linux中&#xff0c;如果你的Linux中可以直接使用wget去下载 2…

如何排查k8s集群中Pod内mysqld进程占用内存消耗过高?

文章目录 1. **查看容器资源使用情况**&#xff1a;2. **进入容器内部**&#xff1a;3. **检查进程内存使用**&#xff1a;4. **MySQL服务器状态检查**&#xff1a;5. **MySQL日志分析**&#xff1a;6. **使用专门的MySQL监控工具**&#xff1a;7. **配置文件检查**&#xff1a…

视频基础学习五——视频编码基础二(编码参数帧、GOP、码率等)

系列文章目录 视频基础学习一——色立体、三原色以及像素 视频基础学习二——图像深度与格式&#xff08;RGB与YUV&#xff09; 视频基础学习三——视频帧率、码率与分辨率 视频基础学习四——视频编码基础一&#xff08;冗余信息&#xff09; 视频基础学习五——视频编码基础…

Redis从入门到精通(十三)Redis分布式缓存(一)RDB和AOF持久化、Redis主从集群的搭建与原理分析

文章目录 第5章 Redis分布式缓存5.1 Redis持久化5.1.1 RDB持久化5.1.1.1 执行时机5.1.1.2 bgsave原理 5.1.2 AOF持久化5.1.2.1 AOF原理5.1.2.2 AOF配置5.1.2.3 AOF文件重写 5.1.3 RDB和AOF的对比 5.2 Redis主从5.2.1 搭建主从结构5.2.2 主从数据同步原理5.2.2.1 全量同步5.2.2.…

基于WEB的水库水情自动测报系统的研究与设计(论文+源码)_kaic

摘要 水情信息是水利管理最重要的基础信息&#xff0c;是水文预报、水资源管理、防汛抗旱决策的主要依据。水情自动测报系统是一个自动采集、传输、处理水情信息的实时测报系统&#xff0c;可对水库流域内的水情、水文和气象数据&#xff0c;如雨量、流量、水位等&#xff0c;实…

opc ua 环境构建(记录一)

1、准备 Siemens Simatic WinCC v7.5 二、配置 SIMATIC NET与S7-200 SMART 集成以太网口OPC 通信(TIA平台) 硬件: ①S7-200 SMART ②PC 机 ( 集成以太网卡) 软件: ① STEP 7-Micro/WIN SMART V2.1 ② STEP 7 Professional(TIA Portal V13 SP1 Upd 9) ③ SIMATIC NET …

Nginx的基本使用

目录 介绍Nginx&#xff1a; 其优点有很多&#xff1a; 如何下载Nginx&#xff1a; 下载Nginx 启动Nginx ​编辑 如何用Nginx创建网站 Nginx自带的网站 分析网页 转变ip地址为自己的网页 换内容 换文件 介绍Nginx&#xff1a; Nginx是一个高性能的HTTP和反向代理w…

Ubuntu (Linux系统) 下载安装 Qt 环境

在官网http://download.qt.io/archive/qt/ 下载安装包&#xff0c;默认linux平台下提供的安装包以run后缀结尾 也可以选择其它地址下载 Qt官网下载地址&#xff1a;https://download.qt.io&#xff1b; 国内镜像下载地址&#xff1a;https://mirrors.cloud.tencent.com/qt/ 。建…

Scrapy框架内存泄漏问题及解决

说明&#xff1a;仅供学习使用&#xff0c;请勿用于非法用途&#xff0c;若有侵权&#xff0c;请联系博主删除 作者&#xff1a;zhu6201976 一、问题背景及原因 官方文档&#xff1a;Debugging memory leaks — Scrapy 2.11.1 documentation Scrapy是一款功能强大的网络爬虫框…

4.11java学习总结

File 创建file类的方法 import java.io.File;public class Main {public static void main(String[] args) {String l "D:\\text.txt";String l1 "D:\\";String l2 "test.txt";File f1 new File(l);System.out.println(f1);File f2 new Fi…

力扣HOT100 - 48. 旋转图像

解题思路&#xff1a; 要求原地旋转 可以先上下翻转&#xff0c;再沿主对角线反转&#xff08;左上到右下的对角线&#xff09; class Solution {public void rotate(int[][] matrix) {int n matrix.length;// 上下翻转for (int i 0; i < n / 2; i) {for (int j 0; j &…

systemctl start docker报错(code=exited, status=1/FAILURE)

运行systemctl start docker报错内容如下: 输入systemctl status docker.service显示以下内容&#xff1a; 本次启动不起来与docker服务无关 具体解决问题是修改 /etc/docker/daemon.json&#xff0c;vim /etc/docker/daemon.json # 添加如下内容 {"registry-mirrors&qu…