如何使用Threejs和React Three Fiber在React中创建一个3D世界

1,025 阅读9分钟

使用Threejs和React Three Fiber在React中创建一个3D世界

自20世纪60年代首次出现在计算机中以来,3D环境已经走过了漫长的道路。它的后续产品是3D建模,见于1970年代。三维可视化和工作的能力得到了用户和开发者的喜爱,因此其受欢迎程度也在不断提高。这复制了现实生活中物体的长度、宽度和高度的特征。

三维物体是令人着迷的。在与计算机互动时,无论是在游戏、视频、网站,甚至是模拟中都能体验到。与二维物体不同,更多的角度、光线、阴影和对比度是可见的,更吸引人。你可以在篇文章中阅读更多关于3D物体和如何获得它们以及它们的重要性。

今天,3D环境和物体的特点、功能、格式和方面都比以前多。我们都期望随着时间的推移,这种趋势在发现、发明和普及方面都会增加。

在本教程中,你将学习如何在React中使用Three.js和React Three Fiber创建一个3D世界。

先决条件

要跟上本指南,你需要具备以下条件。

  • 安装了网络IDE。
  • React基础知识。
  • 稳定的互联网连接。

初始化一个新的反应项目

创建一个名为 "React-3d-environment "的项目文件夹,并导航到其中。这可以通过终端上的以下命令来实现。

mkdir "React-3d-environment"
cd React-3d-environment

现在让我们在终端上运行以下命令。

npx create-react-app@latest react-threejs-3d-environment-app --use-npm
cd react-threejs-3d-environment-app

这将使用最新版本的React创建一个新的React应用,名为 "react-threejs-3d-environment-app"。这可能需要几分钟时间,取决于你的网络连接速度和稳定性。一旦完成,它将在终端打开。

现在,在Linux/Ubuntu和MacOS中使用code ,或在Windows中使用code . ,在安装在机器上的VSCode IDE中打开该应用程序。

如果它没有启动,你可以手动打开它并打开其中的文件夹。

文件夹结构

在我们的应用程序目录内有许多文件,我们的项目可能不需要它们。生成的应用程序文件夹结构如下所示。

.React-threejs-environment-example (root directory)
├── build (Folder)
├── node_modules (Folder)
├── public (Folder)
│   ├── favicon.ico (File)
│   ├── index.html (File)
│   ├── logo192.png (File)
│   ├── logo512.png (File)
│   ├── manifest.json (File)
│   └── robots.txt (File)
├── src
│   ├── App.css (File)
│   ├── App.js (File)
│   ├── App.test.js (File)
│   ├── index.css (File)
│   ├── index.js (File)
│   └── logo.svg (File)
│   └── reportWebVitals.js (File)
│   └── setupTests.js (File)
├── .gitignore (File)
├── package.json (File)
├── package-lock.json (File)
└── README.md (File)

我们将只关注 "index.js "文件和 "public "文件夹中的内容。因此,在这个项目中,我们将忽略其他内容。你可以留下它们,以备你想把你的代码带到更远的地方。

至于'build'和'node_modules'文件夹,我们将不研究它们,因为它们是自动生成的。

安装依赖项

我们将需要并安装以下依赖项。

  • @react-three/cannon
  • @react-three/drei
  • 俏皮的颜色调色板
  • Reaction-three-fiber

[threejs]用于在网络应用中创建3D模型,可以在网页中,也可以在Node.js或React环境中。

'@react-three/cannon'有助于在创建的场景中提供现实生活中的物理和3D模型。react-three-fiber"用于用可重用的、独立的组件声明性地构建场景,这些组件对状态有反应,易于互动,并能进入任何React的生态系统,而"@react-three/drei"则用于创建对"react-three-fiber"有用的帮助器抽象我们将使用'ice-color-palettes'**库来提供一个漂亮的颜色模板,以快速用于我们的模型。

通过运行下面的命令进行安装。

 npm i @react-three/cannon @react-three/drei nice-color-palettes react-three-fiber three

package.json(文件)

我们的'package.json'文件内容将看起来如下。

{
  "name": "react-threejs-3d-environment-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@react-three/cannon": "^2.5.0",
    "@react-three/drei": "^7.1.1",
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^11.2.7",
    "@testing-library/user-event": "^12.8.3",
    "nice-color-palettes": "^3.0.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3",
    "react-three-fiber": "^6.0.13",
    "three": "^0.130.1",
    "web-vitals": "^1.1.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

public文件夹

现在,在公共文件夹中,我们将从以下链接中获取 "carbon_normal.jpg"。只要下载它并复制粘贴到那里。

它看起来应该如下所示。

carbon_normal.jpg

"carbon_normal Textuture" - 作者提供的纹理图像

或者,如果你使用的是Chrome浏览器或其他浏览器,只需在上面的图片上点击右键,选择 "保存图片 "选项。将其保存为'carbon_normal.jpg',放在'public'文件夹中。

src文件夹

在src文件夹中,有三个主要文件。'App.js'、'index.js'和'style.css'。我们将重点讨论'App.js'文件。

App.js(文件)

在'App.js'中,我们将做以下工作。

  • 导入我们已安装的依赖项
// Box and planes are individual 3D components
// Suspense is used to “wait” for something before they can render
// useMemo shall be used to avoid expensive calculations each time it is rendered hence it is optimized. Check it out at https://reactjs.org/docs/hooks-reference.html#usememo
// useLoader is a hook which loads assets and suspends for easier fallback- and error-handling
// Physics brings in the Physics effect in the environment
// useBox, usePlane, useSphere and many more from cannon are used to import the models used in this project
// niceColors are used for beautification of objects
import * as THREE from 'three'
import { Box, Plane } from "@react-three/drei";
import React, { Suspense, useMemo }  from "react";
import { Canvas, useLoader } from "react-three-fiber";
import { Physics, useBox, usePlane, useSphere } from "@react-three/cannon";
import niceColors from 'nice-color-palettes';
import "./index.css";

使用的模块的目的正如上面的代码注释中所述。

  • 我们将添加一个平面函数。这有助于我们在代码中根据传入的参数生成多个对象。

该函数如下所示。

// It allows you to input a color as an argument
// The material used will have been built by a mesh material
// Remember, you can still add other attributes in the object created
// We won't add it's mass since itwill begin to fall
function PhyPlane({ color, ...props }) {
  const [ref] = usePlane(() => ({ ...props }));

  return (
      <Plane args={[1000, 1000]} ref={ref}>
        <meshStandardMaterial color={color} />
      </Plane>
  );
}

注意:每当代码中使用*'......props*'时,它们都是程序接受的任意输入,它们返回React元素,描述屏幕上应该出现的内容。

  • 我们也来添加我们的盒子函数

它对物理学有一定的服从性。

/ It has a mass so that it will always be under a gravitational effect to fall
// We have put in some trigger events which will cause some action to occur; that is on-click
// They shall be built out of a mesh material
function PhyBox(props) {
  const [ref, api] = useBox(() => ({ args: [1, 1, 1], mass: 1, ...props }));

  return (
      <Box
          args={[1, 1, 1]}
          ref={ref}
          onClick={() =>

              // This shoots the object when clicked on
              api.applyImpulse([0, 5, -10], [0, 0, 0])
              // &&

              // This makes the object fly upwards when clicked on

              // api.velocity.set(0, 2, 0)

          }
      >
        <meshNormalMaterial />
      </Box>
  );
}
  • 添加App函数来创建我们要渲染的应用程序和我们的导出应用程序函数
function App() {
    return (

        // Canvas holds all our items and scene
        // Set camera position and focus
        <Canvas camera={{ position: [0, 0, 0], near: 0.1, far: 1000 }}>

            // Set gravity
            // All items we want to see the effect of gravity on them shall be inside the Physics tags
            // These include the planes and boxes and any other models
            // Four planes are created which shall hold the contents as a platform
            <Physics gravity={[0, -10, 0]}>
                <PhyPlane
                    color={niceColors[17][5]}
                    position={[0, -2, 0]}
                    rotation={[-Math.PI / 2, 0, 0]}
                />
                <PhyPlane color={niceColors[17][2]} position={[0, 0, -10]} />
                <PhyPlane color={niceColors[17][3]} position={[-6, 0, -10]} rotation={[0, 2, 0]} />
                <PhyPlane color={niceColors[17][1]} position={[6, 0, -10]} rotation={[0, -2, 0]} />

                // Three objects are placed in different positions in the x, y, and z axis
                <PhyBox position={[2, 0, -5]} />
                <PhyBox position={[0, 0, -5]} />
                <PhyBox position={[-2, 0, -5]} />
            </Physics>

            // This is for provision of ambient lighting in the scene
            <ambientLight intensity={0.3} />

            // We have added some pointLight here at the position showed
            <pointLight intensity={0.8} position={[5, 0, 5]} />

            // Apart from ambient light and point light, you can add others such as fog
        </Canvas>
    );
}

export default App;

运行应用程序

现在,你可以使用下面的终端命令来运行这个应用程序。

npm start

在网络浏览器上访问该应用程序,网址是localhost:3000

结果应如下图所示。

First run

"第一次运行"--作者的屏幕截图

你可以点击箱子,你会看到它们每次都被扔掉了。你可以通过修改'app.js'文件中api.applyImpulse 部分的值来增加抛出的强度。

例如,你可以试试。

api.applyImpulse([0, 5, -10], [1, 1, 1])

Impulse effect

"冲动效果" - Gif by Author

通过改变onClick 触发动作,改变盒子上的效果,使盒子像无人机一样盘旋或向上飞。

    onClick={() =>
              // This makes the object fly upwards when clicked on
              api.velocity.set(0, 2, 0)
          }

Velocity effect

"速度效应" - 作者:Gif

奖励/有趣的活动。你的点击率有多高?

你能快速点击所有三个盒子并将它们同时保持在空中吗?你能保持两个多长时间?

记住,如果有一个碰到了底层平面,你就输了!

试着用'和'的逻辑运算符将上面的两个效果合二为一,增加另一种效果,如下图所示。

    onClick={() =>
              // This shoots the object when clicked on
              api.applyImpulse([0, 5, -10], [1, 1, 1])
                  
              &&
              // This makes the object to fly upwards when clicked on
              api.velocity.set(0, 2, 0)
          }

添加球形物体

现在让我们在场景中添加其他的球状物体。由于我们已经将函数导入到我们的项目中,所以我们将首先开始添加函数。

这些函数将如下所示。

Impulse and velocity effects

"冲力和速度效果" - Gif by Author

// This  is used to create spherical objects in the app
// 'carbon_normal.jpg' is used as a texture loader for each one of them
// The mass is 1, while the start position; that is, when the app is started is obtained randomly in the x and y axis
// We shall use some blocks of code to setup the color of each sphere so that each sphere may look differently
function InstancedSpheres({ number }) {
  const map = useLoader(THREE.TextureLoader, '/carbon_normal.jpg')
  const [ref] = useSphere(index => ({
    mass: 1,
    position: [Math.random() - 0.5, Math.random() - 0.5, index * 4],
    args:1
  }))
  const colors = useMemo(() => {
    const array = new Float32Array(number * 3)
    const color = new THREE.Color()
    for (let i = 0; i < number; i++)
      color
          .set(niceColors[17][Math.floor(Math.random() * 5)])
          .convertSRGBToLinear()
          .toArray(array, i * 3)
    return array
  }, [number])
  return (
      <instancedMesh ref={ref} castShadow receiveShadow args={[null, null, number]}>
        <sphereBufferGeometry attach="geometry" args={[1, 16, 16]}>
          <instancedBufferAttribute attachObject={['attributes', 'color']} args={[colors, 3]} />
        </sphereBufferGeometry>
        <meshPhongMaterial
            attach="material"
            vertexColors={THREE.VertexColors}
            normalMap={map}
            normalScale={[1, 1]}
            normalMap-wrapS={THREE.RepeatWrapping}
            normalMap-wrapT={THREE.RepeatWrapping}
            normalMap-repeat={[10, 10]}
        />
      </instancedMesh>
  )
}

现在,在 "函数App()"中,将对象添加到Canvas的 "悬念 "标签内,就在关闭的物理标签上方(</Physics>)。在渲染项目时,我们将不显示任何东西。

我们将指定对象的数量,在我们的例子中,我们将使用十(10)。

代码如下所示。

<Suspense fallback={null}>
    <InstancedSpheres number={10} />
</Suspense>

注意:你可以使用任何你想要的球形对象。只要记住,你添加的对象越多,就会使用更多的内存,因此网页可能需要更长的时间来加载或渲染,甚至会降低性能。

Spheres

"球体" - 作者:Gif

有趣的活动提示:你可以像前面所说的那样给立方体添加更多的力量,以有效地打击球体。

深入挖掘

你可以使用下面的代码将你的threejs环境设置为可用的最大物理学设置。

<Physics gravity={[0, -10, 0]} size={100} tolerance={0.001} iterations={5} broadphase={"Naive"} step={1/60} shouldInvalidate={true} children allowSleep={false} axisIndex={0} defaultContactMaterial={1e6}>

你可以对任何即将到来的想法进行更多的创意,对3D建模没有任何限制。

注:所提供的图片是在程序运行过程中直接从屏幕上抓取的,或者是来自授权的资料库。照片是由Francis Kaguongo提供的。

结论

在这篇文章中,你已经完成了以下工作。

  • 创建了一个新的React项目
  • 安装了需要的软件包。@react-three/cannon,@react-three/drei,nice-color-palettes,react-three-fiber, andthree.
  • 学习了所安装包的功能。
  • 运行应用程序。
  • 学习了如何添加更多的对象,如球体。
  • 找到了在哪里可以找到更多的对象和模型。

三维对象是非常强大的人工制品。它们详细描述了我们用眼睛看到的世界上的物体,以及它们与其他物体的互动方式。它们在计算机环境中代表这些信息,用于有趣的互动,也用于详细分析或商业服务。

三维建模的报酬很高,因为与静态应用相比,有经验的创造者和开发者的数量并不高。

对于那些知道如何创造、与之互动和探索的程序员来说,这已经变成了一个重要的 "金矿"。