使用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 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 。
结果应如下图所示。

"第一次运行"--作者的屏幕截图
你可以点击箱子,你会看到它们每次都被扔掉了。你可以通过修改'app.js'文件中api.applyImpulse 部分的值来增加抛出的强度。
例如,你可以试试。
api.applyImpulse([0, 5, -10], [1, 1, 1])

"冲动效果" - Gif by Author
通过改变onClick 触发动作,改变盒子上的效果,使盒子像无人机一样盘旋或向上飞。
onClick={() =>
// This makes the object fly upwards when clicked on
api.velocity.set(0, 2, 0)
}

"速度效应" - 作者: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)
}
添加球形物体
现在让我们在场景中添加其他的球状物体。由于我们已经将函数导入到我们的项目中,所以我们将首先开始添加函数。
这些函数将如下所示。

"冲力和速度效果" - 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>
注意:你可以使用任何你想要的球形对象。只要记住,你添加的对象越多,就会使用更多的内存,因此网页可能需要更长的时间来加载或渲染,甚至会降低性能。

"球体" - 作者: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.
- 学习了所安装包的功能。
- 运行应用程序。
- 学习了如何添加更多的对象,如球体。
- 找到了在哪里可以找到更多的对象和模型。
三维对象是非常强大的人工制品。它们详细描述了我们用眼睛看到的世界上的物体,以及它们与其他物体的互动方式。它们在计算机环境中代表这些信息,用于有趣的互动,也用于详细分析或商业服务。
三维建模的报酬很高,因为与静态应用相比,有经验的创造者和开发者的数量并不高。
对于那些知道如何创造、与之互动和探索的程序员来说,这已经变成了一个重要的 "金矿"。