NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割

越来越多的站点使用交互式 3d 动画。 这些站点的交互体验让您感觉自己正在与真实世界的对象进行交互。 这种趋势只会增长,因为公司承诺进行巨额投资,以便他们能够在 Metaverse 世界中站稳脚跟。

以下是两个业界公认的交互式 3d 动画使用示例:

Bruno Simons 获奖网站 - 这个网站的设计就像一个 3D 游戏,你可以通过汽车导航来浏览网站。

Github 主页 - Github 整合了这个交互式 3d 地球仪,你可以与之交互以查看实时 Github 提交。

这两个站点都是使用 three.js 构建的。 three.js 的问题在于它的学习曲线非常陡峭。 你必须写很多代码来做简单的事情。 幸运的是,在 react 中,我们有一个 react-three-fiber 形式的解决方案。 React-three-fiber 通过利用简化了 api 的基于组件的模式,简化了很多复杂的编码。 在底层仍然是 three.js,没有任何妥协。

如果你打算在未来的 React.js 项目中使用交互式 3D 动画,并且想了解更多关于 react-three-fiber 的信息,那么这篇文章将是开始这一旅程的完美选择。

这篇文章将涵盖的内容包括:

  • three.js、React-three-fiber 和交互性的快速介绍
  • 3D 坐标系等 3D 概念的基本介绍
  • 关于 3d 项目如何在 three.js 中构建的一些基本概念
  • 如何使用 react-three-fiber 和 react-spring 构建第一个 3d 交互式应用程序

1、React-three-fiber快速介绍

Three.js 是事实上的 3d 动画库,已在 Javascript 开发人员中流行起来。 React-three-fiber 是 React.js 的 three.js 渲染器。 你可以用 three.js 做的一切都可以用 react-three-fiber 来完成。

此外,3d 对象也可以相对容易地进行交互。 例如,你可以附加事件处理程序来处理悬停和单击事件。 在 React.js 中,可以通过状态管理来管理 3d 对象的状态,进而更改其属性。 在我们要构建的演示中,我们将分别在悬停和单击时更改 3d 立方体的颜色和大小。

React-three-fiber 也有一个很好的生态系统。 它甚至包括流行的动画库 react-spring 的集成。 在本文中,您将学习如何将 react-spring 与 react-three-fiber 集成。 您可以在此处查看更多适用于 react-three-fiber 的库,可以看到它有一个也有详细记录的辅助功能模块。

2、关于 3d 坐标系

当使用 CSS 和 Html 时,我们使用相对或绝对位置定位事物,可以将 div 的左、右、上和下属性的值设置为该 div 的绝对位置。 当使用 3D 时,则需要了解如何在 3d 空间中定位事物,这是一个需要掌握的全新概念。

想象你在一个房间里,有一把椅子漂浮在地板和天花板之间的稀薄空气中,在 3d 中你可以使用三个值来定位它的位置,这是 x、y 和 Z 值。 这些值将相对于某个原点,假设原点将是房间的一个选定角落。 让我们看另一个位于房间角落的透明立方体示例。

立方体的高度、宽度和深度均为 2 个单位。

在图中,我用圆圈和相关坐标标记了立方体的 8 个角,房间的角是原点 (0,0,0)。 我还用红色箭头标记了 X、Y 和 Z 轴。 我们习惯于处理 X 和 Y 轴,这被视为 2D。 但是这里我们有一个额外的 Z 轴,可以看作是深度。

正如你在示例中看到的那样。 如果查看值为 (2,2,2) 的立方体角,你会发现这是立方体中唯一不接触左墙、右墙或地板但从地面升高的点。 这也是唯一没有零值的点。 所以这里重要的是前两个数字是 2D 中的 x、y 轴位置,第三个数字处理深度。

这是一个基本的解释,但请注意所有的轴也可以是负数,这意味着这些点将落在我们可见的空间之外。 你可以将其视为绝对定位的 div,可以在其中赋予负左值或负顶值以将其移出其父 div。

作为练习,你可以去 three.js 官方游乐场 进行一些尝试。

在上面的屏幕截图中,我所做的只是添加一个立方盒子,拖动手柄并观察位置值。 你可以拖动所有手柄并对此进行试验。

3、基本的 3D 项目结构

在我们深入研究 React-three-fiber 之前,需要对项目在 three.js 中的结构有一个基本的了解。 下面是概述这一点的图表。

上图中显示的内容列举如下:

  • 场景 - 一个场景将包含所有 3D 对象。 每个对象也称为网格
  • 网格 - 这是一个基本的场景对象,它用于保存在 3D 空间中表示形状所需的几何体和材质。
  • 几何 - 几何定义形状,你可以将其视为没有图形细节的骨架结构
  • 材质 - 定义形状表面的外观,这将是图形细节。
  • Camera - 这将捕获场景中的所有内容,它还有一个位置值。 光 - 你需要一个光源才能看到物体。 如果没有光源,那么将看不到现实生活中的颜色和阴影。

渲染没有光或颜色的立方体的基本代码如下所示:

//The renderer will have access to the Canvas DOM element to
//update the display to show our 3d cube
const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)

// Create Scene and camera
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)

//Mesh  - Holds the 3d object that will be added to scene
const mesh = new THREE.Mesh()

//Geometry  is a property of Mesh
// Our mesh will have a geometry for a box or cube
mesh.geometry = new THREE.BoxGeometry()
//Material  is also a property of Mesh
mesh.material = new THREE.MeshStandardMaterial()

//Add our Mesh to the scene
scene.add(mesh)

// After you have composed your scene with your mesh 
// and added the camera, you can render the whole thing
// with animation loop
function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}

animate()

如果你看了代码中的注释,你就会对结构有一个大概的了解。 我不会深入探讨它,因为它已经很安静了。 这就是 React 和 React-three-fiber 派上用场的地方。 React-three-fiber 抽象出上述复杂性,允许我们以声明方式创建 3D 动画。 在这篇文章的其余部分,我将向你展示如何使用 react-three-fiber 和 spring 动画构建具有流畅动画的交互式立方体。

4、React-three-fiber项目开发

在本节中,我们将构建一个交互式立方体,它会旋转,当你将鼠标悬停在它上面时会改变颜色,当你单击鼠标时它会变大。 我们也将使用 react-spring 来实现流畅的动画。

4.1项目设置

假设你已经使用 create-react-app 设置了一个基本的 React 应用程序。 然后你可以运行:

npm install three @react-three/fiber

这将安装 three.js 和 react-three-fiber。 然后安装 Drei 依赖项:

npm install @react-three/drei

.请注意 drei 组件为 react-three.fiber 提供了一些强大的功能,但我们只会将其用于光源。

4.2 添加一些基本样式

然后在你的 app.css 中复制以下基本样式:

//app.css
html,
body,
#root {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

4.3 基本 app.js 结构

这是我们 app.js 的骨架结构。 随着我们的进行,我们将填补空白。

import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import  "./app.css"

function Cube(props) {

      // Code for our 3d cube  goes here. In other words Our mesh

}


function App() {
  return (
   <Canvas>
     <ambientLight />
     <Cube />
   </Canvas>
  );
}

export default App;

在顶部,我们有依赖项。 我们将使用 useState 和 useRef。

import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import  "./app.css"

我们将让 react-three-fiber 处理渲染,这就是我们使用 useRef 的原因,这允许我们直接访问 DOM。

我们从 react-three-fiber 导入 Canvas,这允许我们创建一个 WebGl 容器来渲染我们的 3D。 useFrame 是 react-three-fiber 的标准动画钩子。

我们还附加了我们在最后一步中编写的 app.css。

让我们看看我们的 App 函数:

function App() {
  return (
   <Canvas>
     <ambientLight />
     <Cube />
   </Canvas>
  );
}

在 App 函数的 JSX 部分,我们使用 react-three-fiber Canvas 组件作为包装器。 这有两个子元素,一个是光源。 第二个元素是  ,该组件将渲染定义 3d 立方体的网格。 正如你之前在我们的框架代码中看到的那样,我们尚未为此编写代码。

请注意,通常还会添加一个摄像头,但对于我们的示例,我们可以保留它,因为 React-three-fiber 会自动添加一个具有默认位置的摄像头。 所以我们将使用默认值。

4.4 定义3d 立方体

我们的 cube 函数将如下所示:

function Cube(props) {
  // Use useRef hook to access the mesh element
  const mesh=useRef()

  // Jsx to rnder our cube
  return (
          <mesh ref={mesh}>
             <boxGeometry args={[2,2,2]}/>
             <meshStandardMaterial /> 
          </mesh>

  )
}

我们在这里所做的就是创建一个具有 ref={mesh} 属性的  元素,我们正在使用它以便 react-three-fiber 可以直接访问 mesh 元素。 因此,我们有一行 const mesh=useRef() 来声明这个 ref 值。  元素有一个子元素和 。 请记住,在 three.js 中,网格元素具有几何形状和材质,这就是这些元素的用途。

元素中的参数用于维度。 我们使用具有三个值的 na 数组来创建一个高度、宽度和深度都等于单位的立方体。

我们的 app.js 代码现在看起来像这样:

import { useState, useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import "./app.css";

function Cube(props) {
  // Use useRef hook to access the mesh element
  const mesh=useRef()

  // Jsx to render our 3d cube. Our cube will have height
  // width and depth equal 2 units. 
  // You also need a material so that you can add color
  // and show shadows. We are using the standard
  // material <<meshStandardMaterial /> 
  return (
          <mesh ref={mesh}>
             <boxGeometry args={[2,2,2]}/>
             <meshStandardMaterial /> 
          </mesh>

  )
}

// Basic app structure to render a 3d cube
//<ambientLight /> is the standard light to use, otherwise
// everything comes out as black
function App() {
  return (
    <Canvas>
      <ambientLight />
      <Cube />
    </Canvas>
  );
}

export default App;

在浏览器中,你会看到一个灰色框,如下所示。 但它实际上是一个立方体。 目前我们只看到正面。 在下一节中,我们将添加一些颜色和旋转。

4.5 添加灯光、颜色和动画

为了给我们的立方体真实的阴影,我们需要添加一个带有位置的特定点光源元素,看起来像 pointLight position={[10,10,10]} />这样 。 这是要在 <ambientLight/>  之后添加我们的 App 功能。

我们的 App 功能现在看起来像这样:

function App() {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10,10,10]} />
      <Cube />
    </Canvas>
  );
}

现在让我们将注意力转移到我们的 Cube 函数上来添加颜色和基本动画。 为了给我们的立方体添加颜色,我们向 meshStandardMaterial 元素添加了一个属性,所以它变成了 <meshStandardMaterial color={"orange"}/> 。 这里我们将颜色设置为橙色。

要添加一个基本的旋转动画,我们使用动画帧钩子,它看起来像这样 useFrame ( ()=> (mesh.current.rotation.x += 0.01)) 。 在这里,我们正在访问 mesh.current.rotation.x 值以将其增加 0.01 个单位。 它基本上在 x 轴上旋转。

我们的代码如下所示:

import { useState, useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import "./app.css";

function Cube(props) {
  // Use useRef hook to access the mesh element
  const mesh = useRef();

  //Basic animation to rotate our cube using animation frame
  useFrame ( ()=> (mesh.current.rotation.x += 0.01))

  // Jsx to render our 3d cube. Our cube will have height
  // width and depth equal 2 units.
  // You also need a material so that you can add color
  // and show shadows. We are using the standard
  // material <<meshStandardMaterial />   

  return (
    <mesh ref={mesh}>
      <boxGeometry args={[2, 2, 2]} />
      <meshStandardMaterial />
      <meshStandardMaterial color={"orange"}/> 
    </mesh>
  );
}

// Basic app structure to render a 3d cube
//<ambientLight /> is the standard light to use, otherwise
// everything comes out as black
function App() {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10,10,10]} />
      <Cube />
    </Canvas>
  );
}

export default App;

很好,我们的 3D 立方体有了色彩和阴影,它在 3D 空间中移动。

4.6 动态改变立方体的颜色

我们的目标是在将鼠标悬停在立方体上时让立方体改变颜色。 如你所知,如果要更改显示中的某些属性,那么需要使用状态变量和事件处理程序。

在 Cube 函数中,让我们引入一个状态变量来存储悬停状态:

const [hovered,setHover] = useState(false)

现在我们所要做的就是将事件处理程序绑定到  元素。 幸运的是网格组件有它的悬停事件处理程序,它们是:onPointerOver 和 onPointerOut。 这允许我们切换悬停进出的值。 所以我们的网格元素开始标签看起来像这样:

<mesh ref={mesh} 
               onPointerOver={ (event)=> setHover(true)} 
               onPointerOut={(event)=> setHover(false)} >

最后一部分是在状态变为悬停时将颜色更改为亮粉色。 这可以通过 meshStandardMaterial 元素的颜色属性的三元表达式来完成。 这样就变成了:

<meshStandardMaterial color={hovered ? "hotpink" : "orange"}/>

我们的立方体函数现在看起来像这样:

function Cube(props) {
  // Use useRef hook to access the mesh element
  const mesh = useRef();

  // State values for hover
  const [hovered, setHover] = useState(false);

  //Basic animation to rotate our cube using animation frame
  useFrame(() => (mesh.current.rotation.x += 0.01));

  // Jsx to render our 3d cube. Our cube will have height
  // width and depth equal 2 units.
  // You also need a material so that you can add color
  // and show shadows. We are using the standard
  // material <<meshStandardMaterial />

  return (
    <mesh
      ref={mesh}
      onPointerOver={(event) => setHover(true)}
      onPointerOut={(event) => setHover(false)}
    >
      <boxGeometry args={[2, 2, 2]} />
      <meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
    </mesh>
  );
}

这就是它在悬停时改变颜色的全部。

4.7 添加事件

Three.js 有自己的动画钩子,但是three.js 做不到的我们可以用react-spring 动画来实现。 为了顺利调整立方体的大小,这次我们可以使用 react-spring 钩子。

提醒一下,要安装 react-spring 以便你可以将它与 react-three-fiber 一起使用,你需要运行以下命令:npm install three @react-spring/three。 然后你需要将它导入到 app.js 中,比如:

import { useSpring, animated } from '@react-spring/three'

app.js 顶部的导入部分现在看起来像这样:

import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import { useSpring, animated } from "@react-spring/three";

回到手头的任务,我们需要在点击事件中调整立方体的大小。 再次像之前改变颜色的任务一样,我们需要一个状态变量来存储单击时网格的活动状态。 因此,我们将以下行添加到我们的立方体函数中:

const [active,setActive] = useState(false)

现在我们向网格添加一个点击处理程序,就像之前我们有一个箭头函数来改变点击状态:onClick = {(event)=> setActive(!active)},所以我们的网格元素开始标签看起来像:

<mesh ref={mesh}
            onPointerOver={ (event)=> setHover(true)}
            onPointerOut={(event)=> setHover(false)}
            onClick = {(event)=> setActive(!active)}
          >

接下来,如果活动状态为真,我们需要将立方体的比例增加 1.5。 现在棘手的部分是 react-spring 将处理尺寸变化。

我们将把它应用到网格元素,所以我们需要做的是首先将网格元素重命名为 animated.mesh。 所以 .... 将变成 <animated.mesh>....</animated.mesh>。 我们还将设置一个比例属性,这个比例属性将由 react-spring 钩子处理,所以我们只需说 <animated.mesh scale={scale}>....</animated.mesh> 这样我们的 网格打开和关闭标签现在看起来像这样:

		 <animated.mesh ref={mesh}
            onPointerOver={ (event)=> setHover(true)}
            onPointerOut={(event)=> setHover(false)}
            onClick = {(event)=> setActive(!active)}
            scale = { scale}
          >  .......

            ....
          </animated.mesh>

现在我们简单地使用 react-spring 钩子来设置大小和处理动画。 以下代码行可以解决问题:

 const { scale } = useSpring({ scale: active ? 1.5 : 1 })

这里发生的是,我们正在传递一个三元表达式来检查活动状态是真还是假。 react-spring 将处理动画。

大功告成! app.js 的最终代码如下所示:

import {useState,useRef} from "react"
import {Canvas, useFrame} from "@react-three/fiber"
import { useSpring, animated } from "@react-spring/three"
import "./app.css"



function Cube(props) {
  // Use useRef hook to access the mesh element
  const mesh = useRef();

  // State values for hover and active state
  const [hovered, setHover] = useState(false);
  const [active, setActive] = useState(false);

  //Basic animation to rotate our cube using animation frame
  useFrame(() => (mesh.current.rotation.x += 0.01));

  //Spring animation hook that scales size based on active state
  const { scale } = useSpring({ scale: active ? 1.5 : 1 });

  // Jsx to render our 3d cube. Our cube will have height
  // width and depth equal 2 units.
  // You also need a material so that you can add color
  // and show shadows. We are using the standard
  // material <<meshStandardMaterial />

  return (
    <animated.mesh
      ref={mesh}
      onPointerOver={(event) => setHover(true)}
      onPointerOut={(event) => setHover(false)}
      onClick={(event) => setActive(!active)}
      scale={scale}
    >
      <boxGeometry args={[2, 2, 2]} />
      <meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
    </animated.mesh>
  );
}

function App() {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10, 10, 10]} />
      <Cube />
    </Canvas>
  );
}

export default App;

原文链接:Crash course in interactive 3d animation with React-three-fiber and React-spring

BimAnt翻译整理,转载请标明出处