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-facial-expression-demo
示例项目(gitcode):https://gitcode.com/qq_41456316/simple-react-three-facial-expression-demo

文章目录

  • 系列文章目录
  • 前言
  • 一、实现步骤
    • 1、创建项目配置环境
    • 2. 创建组件
    • 3. 使用组件
    • 4. 运行项目
  • 总结


前言

在本系列的上一篇文章中,我们已经探讨了如何在 React 中利用 three.js 来渲染 3D 模型,现在,我们将深入研究如何运用 three.js 控制这些模型的表情。让我们一同探索如何赋予你的 3D 模型更加生动和丰富的表情吧!


一、实现步骤

1、创建项目配置环境

使用 create-reacte-app 创建项目

npx create-react-app simple-react-three-facial-expression-demo
cd simple-react-three-facial-expression-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组件中为子元素(可见container.appendChild(renderer.domElement);),相关逻辑代码在useEffect中执行,完整代码内容如下

import * as THREE from "three";import WebGPU from "three/addons/capabilities/WebGPU.js";
import WebGL from "three/addons/capabilities/WebGL.js";import WebGPURenderer from "three/addons/renderers/webgpu/WebGPURenderer.js";import Stats from "three/addons/libs/stats.module.js";import { OrbitControls } from "three/addons/controls/OrbitControls.js";import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
import { MeshoptDecoder } from "three/addons/libs/meshopt_decoder.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;init();}async function init() {if (WebGPU.isAvailable() === false &&WebGL.isWebGL2Available() === false) {containerRef.current.appendChild(WebGPU.getErrorMessage());throw new Error("No WebGPU or WebGL2 support");}let mixer;const clock = new THREE.Clock();const container = containerRef.current;const camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight,1,20);camera.position.set(-1.8, 0.8, 3);const scene = new THREE.Scene();scene.add(new THREE.HemisphereLight(0xffffff, 0x443333, 2));const renderer = new WebGPURenderer({ antialias: true });renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth, window.innerHeight);renderer.toneMapping = THREE.ACESFilmicToneMapping;renderer.setAnimationLoop(animate);container.appendChild(renderer.domElement);const ktx2Loader = await new KTX2Loader().setTranscoderPath("/basis/").detectSupportAsync(renderer);new GLTFLoader().setKTX2Loader(ktx2Loader).setMeshoptDecoder(MeshoptDecoder).load("models/facecap.glb", (gltf) => {const mesh = gltf.scene.children[0];scene.add(mesh);mixer = new THREE.AnimationMixer(mesh);mixer.clipAction(gltf.animations[0]).play();// GUIconst head = mesh.getObjectByName("mesh_2");const influences = head.morphTargetInfluences;//head.morphTargetInfluences = null;// WebGPURenderer: Unsupported texture format. 33776head.material.map = null;const gui = new GUI();gui.close();for (const [key, value] of Object.entries(head.morphTargetDictionary)) {gui.add(influences, value, 0, 1, 0.01).name(key.replace("blendShape1.", "")).listen();}});scene.background = new THREE.Color(0x666666);const controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.minDistance = 2.5;controls.maxDistance = 5;controls.minAzimuthAngle = -Math.PI / 2;controls.maxAzimuthAngle = Math.PI / 2;controls.maxPolarAngle = Math.PI / 1.8;controls.target.set(0, 0.15, -0.2);const stats = new Stats();container.appendChild(stats.dom);function animate() {const delta = clock.getDelta();if (mixer) {mixer.update(delta);}renderer.render(scene, camera);controls.update();stats.update();}window.addEventListener("resize", () => {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/610804.html

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

相关文章

嵌入式操作教程_数字信号处理_音频编解码:3-6 AAC音频解码实验

一、实验目的 了解AAC音频格式&#xff0c;掌握AAC音频解码的原理&#xff0c;并实现将AAC格式的音频解码为PCM 二、实验原理 音频编解码的主要对象是音乐和语音&#xff0c;音频的编解码格式可分为无压缩的格式、无损压缩格式、有损音乐压缩格式、有损语音压缩格式和合成算…

Testng测试框架(6)--@Factory动态地创建测试类的实例

工厂允许您动态地创建测试。例如&#xff0c;假设您想创建一个测试方法&#xff0c;该方法将多次访问网站上的某个页面&#xff0c;并且您希望使用不同的值来调用它。 public class TestWebServer {Test(parameters { "number-of-times" })public void accessPage(…

突破界限 千视将在 NAB 2024 展会上展示领先的 AV over IP 技术

突破界限&#xff01;千视将在 NAB 2024 展会上展示领先的 AV over IP技术 作为AV over IP领域的先驱者&#xff0c;Kiloview将于2024年4月14日至17日在NAB展会&#xff08;展台号&#xff1a;SU6029&#xff09;隆重登场&#xff0c;展示我们领先业界的AV over IP产品、解决方…

jenkins 启动linux节点时 控制台中文显示问号乱码

新增一个jenkins节点时&#xff0c;遇到了控制台中文输出问号的问题。 网上各种配置jenkins的全局变量&#xff0c;都不行。 最终是 节点列表 ->对应节点 -> 启动方式 -> 高级 添加JVM选项 -Dfile.encodingUTF-8

ASUS华硕ROG幻16Air笔记本电脑GU605M原装出厂Win11系统工厂包下载,带有ASUSRecovery一键重置还原

适用型号&#xff1a;GU605MI、GU605MY、GU605MZ、GU605MV、GU605MU 链接&#xff1a;https://pan.baidu.com/s/1YBmZZbTKpIu883jYCS9KfA?pwd9jd4 提取码&#xff1a;9jd4 华硕原厂Windows11系统带有ASUS RECOVERY恢复功能、自带所有驱动、出厂主题壁纸、系统属性联机支持…

linux学习:栈(汉诺塔游戏)

第一根上面套着 64 个圆的金片&#xff0c;最大的一个在底下&#xff0c;其余一个比一个小&#xff0c;依次叠上去&#xff0c;庙里的众僧不倦地 把它们一个个地从这根棒搬到另一根棒上&#xff0c;规定可利用中间的一根棒作为帮助&#xff0c;但每次只能 搬一个&#xff0c;而…

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…

自动驾驶硬回灌和软回灌

目录 前言 一、硬回灌 实时性 直接影响行为 高度依赖传感器 二、软回灌 非实时性 系统优化和改进 数据驱动的学习 三、区别与联系 四、自动驾驶中硬回灌和软回灌代表性方法 硬回灌 软回灌 前言 自动驾驶系统中&#xff0c;"硬回灌"和"软回灌"…

【洛谷 P4017】最大食物链计数 题解(深度优先搜索+动态规划+邻接表+记忆化搜索+剪枝)

最大食物链计数 题目背景 你知道食物链吗&#xff1f;Delia 生物考试的时候&#xff0c;数食物链条数的题目全都错了&#xff0c;因为她总是重复数了几条或漏掉了几条。于是她来就来求助你&#xff0c;然而你也不会啊&#xff01;写一个程序来帮帮她吧。 题目描述 给你一个…

uname -a --输出和系统本身相关-内核版本、硬件架构32位/64位

uname命令&#xff1a;主要用于输出一组操作系统的信息 包含内核的版本、硬件架构是32位还是64位 输入--help后看看效果&#xff0c;一目了然 首先输出完整信息&#xff1a; 我们再依次来输出一下&#xff1a; 最后&#xff0c;我们只需要记住uname命令可以输出和系统本身相关…

Python数据结构与算法——算法(贪心算法、动态规划

贪心算法 介绍&#xff1a;贪心算法又称贪婪算法&#xff0c;是指在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;它所做出的是在某种意义上的局部最优解。 贪心算法并不保证会得到最优解&#xff0c;但…

LangChain-18 Caching 将回答内容进行缓存 可在内存中或数据库中持久化缓存

背景描述 可以将问答的内容缓存起来&#xff0c;如果是相同的问题&#xff0c;那么将会直接把答案返回去&#xff0c;可以节约费用和计算。 安装依赖 pip install -qU langchain-core langchain-openai编写代码 我们可以通过 InMemoryCache 进行内存缓存 或者 SQLiteCache …