Three.js - 快速指南

Three.js - 简介

所有现代浏览器都变得更加强大,并且更易于直接使用 JavaScript。它们采用了 WebGL(Web 图形库),这是一种 JavaScript API,它允许您使用 GPU(图形处理单元)的功能在任何兼容的 Web 浏览器中渲染高性能交互式 3D 和 2D 图形。

但是 WebGL 是一个非常低级的系统,只能绘制点、正方形和线等基本对象。但是,直接从 JavaScript 对 WebGL 进行编程是一个非常复杂和冗长的过程。您需要了解 WebGL 的内部细节并学习复杂的着色器语言才能充分利用 WebGL。Three.js 可以让您的生活变得轻松。

什么是 Three.js?

Three.js 是一个开源、轻量级、跨浏览器、通用的 JavaScript 库。 Three.js 在后台使用 WebGL,因此您可以使用它在浏览器中的 HTML <canvas> 元素上呈现图形。由于 Three.js 使用 JavaScript,您可以与其他网页元素交互、添加动画和交互,甚至可以使用某些逻辑创建游戏。

为什么使用 Three.js?

以下功能使 Three.js 成为一个极好的库。

  • 您只需使用 JavaScript 即可创建复杂的 3D 图形。

  • 您可以在浏览器内创建虚拟现实 (VR) 和增强现实 (AR) 场景。

  • 由于它使用 WebGL,因此它具有跨浏览器支持。许多浏览器都支持它。

  • 您可以添加各种材质、纹理并为 3D 对象制作动画。

  • 您还可以加载和处理来自其他 3D 建模软件的对象。

只需几行 JavaScript 和简单的逻辑,您就可以创建任何东西,从高性能交互式 3D 模型到逼真的实时场景。

这些是使用 Three.js 创建的一些出色的网站−

您可以在 three.js 的官方网站上找到许多其他示例

浏览器支持

目前,桌面和移动设备上的所有现代浏览器都支持 WebGL。唯一需要您关注的浏览器是移动版 Opera Mini 浏览器。对于 IE 10 及更早版本,有一个 IEWebGL 插件,您可以从https://github.com/iewebgl/iewebgl./获取。您可以在此处找到有关 WebGL 浏览器支持的详细信息。

一旦您了解了 Three.js 是什么,您就可以继续阅读下一章,了解如何设置项目以开始使用 Three.js。

Three.js - 安装

有很多方法可以将 Three.js 包含在您的项目中。您可以使用以下任何一种方法开始使用 Three.js。然后打开您最喜欢的代码编辑器并开始使用。

下载完整的 Three.js 项目

将完整的 Three.js 项目下载到您的系统中。您可以从此处或从GitHub下载。提取 three.js-master.zip 文件并查看 build 文件夹。您可以找到两个 three.js,three.min.js,这只是一个最小化版本。将这两个文件中的任何一个添加到您的项目文件夹中,并将它们链接到您的 HTML 文件。现在,您可以在项目中使用 Three.js 了。

注意 − 我们建议使用最小化版本,因为它的加载速度更快。

将以下 <script> 标记插入 HTML 的 <head> 元素中,并添加 threejs.min.js 文件的路径。

<script src='/path/to/threejs.min.js'></script>

使用 CDN 链接

您可以从 CDN(内容分发网络)链接文件,CDN 是专用于托管文件的远程站点,因此您可以在线使用它们。您可以使用以下任何网站 −

将以下任何 <script> 标签插入 HTML 的 <head> 元素中。

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r127/three.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/three@0.127.0/build/three.min.js"></script>

安装 Three.js 包

Three.js 也可作为 NPM 上的包使用。如果您的计算机上已设置 Node.js,则可以使用 npm 或 yarn 安装它。

npm install three

yarn add three

然后,您可以将 Three.module.js 文件中的 Three.js 导入到您的 JavaScript 文件中

import * as THREE from 'three'

您可以将 Three.js 与任何 JavaScript 框架(如 React、Angular、Vue)一起使用。

完成项目设置后,让我们开始创作吧。

Three.js - Hello Cube 应用程序

与任何其他编程语言一样,让我们​​通过创建"Hello cube!"应用程序开始学习 Three.js。

HTML

<!DOCTYPE html>
<html>
   <head>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta charset="UTF-8" />
      <title>Three.js - Hello cube</title>
      <style>
         /* Our CSS goes here */
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r127/three.min.js"></script>
   </head>
   <body>
      <div id="threejs-container">
         <!-- Our output to be rendered here →
      </div>
      <script type="module">
         // our JavaScript code goes here
      </script>
   </body>
</html>

如您所见,它只是一个带有 Three.js CDN 的简单 HTML 文件。

CSS

<style>
* {
   margin: 0;
   padding: 0;
   box-sizing: border-box;
   font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
   Oxygen,
   Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
html,
body {
   height: 100vh;
   width: 100vw;
}
#threejs-container{
   position: block;
   width: 100%;
   height: 100%;
}
</style>

上述 CSS 只是 HTML 页面的基本样式。threejs 容器占据了整个屏幕。

JavaScript

这就是我们的 three.js 应用发挥作用的地方。下面的代码在屏幕中间渲染一个立方体。所有这些代码都将放入空的 <script>标签在 HTML 中。

const width = window.innerWidth
const height = window.innerHeight
// 场景
const scene = new THREE.Scene()
scene.background = new THREE.Color('#00b140')
// 相机
const fov = 45 // 又称​​视场
const aspects = window.innerWidth / window.innerHeight
const near = 0.1 // 近裁剪面
const far = 100 // 远裁剪面
const camera = new PerspectiveCamera(fov, aspect, near, far)
camera.position.set(0, 0, 10)
// 渲染器
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
// Creating a cube
const geometry = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshBasicMaterial({ wireframe: true })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)
// 渲染场景
const container = document.querySelector('#threejs-container')
container.append(renderer.domElement)
renderer.render(scene, camera)

让我们一步一步地讨论代码,然后您可以在后续章节中获得有关每个元素的更多信息。我们需要做的第一件事是创建一个场景、一个相机和一个渲染器。这些是构成每个 Three.js 应用程序的基本组件。

场景

const scene = new THREE.Scene()
scene.background = new THREE.Color('#262626')

场景是屏幕上可见所有内容的容器,没有 THREE.Scene 对象,Three.js 就无法渲染任何东西。背景颜色为深灰色,以便我们可以看到立方体。

相机

const camera = new PerspectiveCamera(fov, aspects, near, far)
camera.position.set(0, 0, 10)

相机对象定义我们在渲染场景时将看到的内容。相机的类型不多,但各不相同,但在本例中,您将使用 PerspectiveCamera,它与我们的眼睛看世界的方式相匹配。

渲染器

const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)

渲染器对象负责根据相机计算场景在浏览器中的样子。渲染器有多种类型,但我们主要使用 WebGLRenderer,因为大多数浏览器都支持 WebGL。

除了创建渲染器实例之外,我们还需要设置我们希望它渲染应用程序的大小。最好使用我们想要用应用程序 The Cube 填充的区域的宽度和高度 - 在本例中,是浏览器窗口的宽度和高度。

立方体

const geometry = new THREE.BoxGeometry(2, 2, 2)
constmaterial = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    wireframe: true,
})
const cube = new THREE.Mesh(geometry,material)
scene.add(cube)

上面的代码在屏幕中心创建了一个简单的立方体。我们可以使用 THREE.Mesh 制作任何对象。Mesh 包含两个对象,即几何图形和材质。网格的几何图形定义其形状,材质决定对象的表面属性。

要创建立方体,我们需要 BoxGeometry 和颜色为 0xffffff 的主要材质 (MeshBasicMaterial)。如果将 wireframe 属性设置为 true,它会告诉 Three.js 向我们显示线框而不是实体对象。

渲染场景

const container = document.querySelector('#threejs-container')
container.append(renderer.domElement)
renderer.render(scene, camera)

示例

最后但并非最不重要的一点是,我们将渲染器元素添加到 HTML 文档中。渲染器使用 <canvas> 元素向我们显示场景。在本例中,渲染器将 <canvas> 元素附加到 HTML 中的引用容器。

hello-cube-app.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js – Hello cube</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            overflow: hidden;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Hello Cube App
         // Your first Three.js application
         // 尺寸
         const width = window.innerWidth
         const height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         // cube
         const geometry = new THREE.BoxGeometry(2, 2, 2)
         const material = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            wireframe: true
         })
         const cube = new THREE.Mesh(geometry, material)
         scene.add(cube)
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
      </script>
   </body>
</html>

输出

如果一切正常,输出结果如下所示。尝试使用代码以更好地了解其工作原理。

您现在已经完成了第一个 three.js 应用程序的创建。让我们继续为应用程序添加更多美感。

Three.js - 渲染器和响应能力

场景的基本功能

您知道场景是摄像机、灯光和我们想要在屏幕上渲染的对象的容器。让我们看一下 Scene 对象的一些基本功能 −

添加对象

函数 add(object) 用于将对象添加到场景中。

const scene = THREE.Scene()
scene.add(cube) // 添加立方体
scene.add(sphere) // 添加球体

移除对象

函数 remove(object) 从场景中移除对象。

scene.remove(cube) // 移除最后添加的立方体
scene.remove(sphere) // 移除球体

子对象

在 scene.children 中返回场景中所有对象的数组,包括相机和灯光。

console.log(scene.children) // 输出场景中的所有对象
console.log(scene.children.length) // 输出场景中的元素数量

注意 − 我们可以使用其 name 属性为任何对象命名。名称对于调试目的很方便,但也可以直接从场景中访问对象。

查看以下示例。

scene.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js – The scene
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
            background-color: #262626;
            overflow: hidden;
         }
         #btn-conatiner {
            position: absolute;
            top: 0;
            left: 0;
            height: 10vh;
            width: 100%;
         }
         @media screen and (max-width:600px){
            #btn-container{
               display: flex;
               flex-direction: column;
            }
         }
         .btn {
            padding: 5px 15px;
            margin: 5px 15px;
            font-weight: bold;
            text-transform: uppercase;
         }
         .add {
            color: green;
         }
         .rem {
            color: red;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="btn-conatiner">
         <button class="btn add">Add Cube</button>
         <button class="btn rem">Remove Cube</button>
      </div>
      <div id="threejs-container"></div>
      <script type="module">
         // Experimenting with different methods of scene
         // add, remove, children, getElementById
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         const gui = new dat.GUI()
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
         scene.add(ambientLight)
         const light = new THREE.PointLight(0xffffff, 0.5)
         light.position.set(-10, 10, -10)
         // 对于阴影
         light.castShadow = true
         light.shadow.mapSize.width = 1024
         light.shadow.mapSize.height = 1024
         light.shadow.camera.near = 0.1
         light.shadow.camera.far = 1000
         scene.add(light)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
         camera.position.set(0, 10, 40)
         camera.lookAt(0, 0, 0)
         gui.add(camera.position, 'z', 10, 200, 1).name('camera-z')
         // plane
         const planeGeometry = new THREE.PlaneGeometry(100, 100)
         const plane = new THREE.Mesh(
            planeGeometry,
            new THREE.MeshPhongMaterial({ color: 0xffffff, side: THREE.DoubleSide })
         )
         plane.rotateX(Math.PI / 2)
         plane.position.y = -1.75
         plane.receiveShadow = true
         scene.add(plane)
         // scene.add
         function addCube() {
            const cubeSize = Math.ceil(Math.random() * 3)
            const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)const cubeMaterial = new THREE.MeshLambertMaterial({
               color: Math.random() * 0xffffff
            })
            const cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
            cube.castShadow = true
            cube.name = 'cube-' + scene.children.length
            cube.position.x = -30 + Math.round(Math.random() * 50)
            cube.position.y = Math.round(Math.random() * 5)
            cube.position.z = -20 + Math.round(Math.random() * 50)
            scene.add(cube)
         }
         const add = document.querySelector('.add')
         add.addEventListener('click', () => {
            addCube()
            console.log('cube added')
         })
         // scene.remove
         function removeCube() {
            const allChildren = scene.children
            const lastObject = allChildren[allChildren.length - 1]
            if (lastObject.name) {
               scene.remove(lastObject)
            }
         }
         const remove = document.querySelector('.rem')
         remove.addEventListener('click', () => {
            removeCube()
            console.log('cube removed')
         })
         // scene.children
         console.log(scene.children)
         // responsivenesswindow.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

打开控制台查看场景中的元素。

Add_Cube

使用 name 属性

函数 scene.getObjectByName(name) 直接从场景中按特定名称返回对象。

您还可以添加另一个参数 - recursive。

scene.getObjectByName(name, recursive)

如果将 recursive 参数设置为 true,Three.js 将搜索完整的对象树以找到具有指定名称的事物。

向场景添加雾

此属性允许您为场景设置雾。雾会渲染出一种薄雾,遮盖住远处的物体。

scene.fog = new THREE.Fog(0xffffff, 0.015, 100)

这行代码定义了白色雾 (0xffffff)。您可以使用前面的两个属性来调整雾的显示方式。0.015 值设置 near 属性,100 值设置 far 属性。使用这些属性,您可以确定雾从哪里开始以及雾变浓的速度。

使用 THREE.Fog 对象,雾会线性增加。还有一种不同的方法来设置场景的雾;为此,请使用以下定义 −

scene.fog = new THREE.FogExp2(0xffffff, 0.01)

这次,我们不指定近和远,而只指定颜色 (0xffffff) 和雾的密度 (0.01)。最好对这些属性进行一些实验,以获得所需的效果。

使用 override 材质属性

overrideMaterial 属性强制场景中的所有对象使用相同的材​​质。

scene.overrideMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff })

这里,场景中的所有对象都使用相同的材​​质,即 MeshLambertMaterial。

注意 − THREE.Scene 是一种有时也称为 Scenegraph 的结构。场景图是一种可以容纳图形场景所有必要信息的结构。在 Three.js 中,这意味着 THREE.Scene 包含渲染所需的所有对象、灯光和其他对象。

Scene_Graph

渲染器

渲染器使用摄像头和场景中的信息在屏幕上绘制输出,即 <canvas> 元素。

在 Hello cube 应用中,我们使用了 WebGLRenderer。还有一些其他渲染器可用,但 WebGLRenderer 是迄今为止最强大的渲染器,通常也是您唯一需要的渲染器。

注意 −有基于画布的渲染器基于 CSS 的渲染器基于 SVG的渲染器。尽管它们可以工作并且可以渲染简单的场景,但我不建议使用它们。它们没有得到积极的开发,非常占用 CPU,并且缺乏良好的材质支持和阴影等功能。

Three.js - 响应式设计

调整屏幕大小时,您可以观察到场景没有响应。使网页响应通常是指页面在不同尺寸的显示器(从台式机到平板电脑再到手机)上都能很好地显示。在本章中,您可以了解如何解决 Three.js 应用程序的一些基本问题。

当浏览器大小发生变化时自动调整输出大小

当您调整浏览器大小时,我们必须通知 Three.js 以了解 <canvas> 元素的宽度。对于相机,我们需要更新纵横比属性,该属性保存屏幕的纵横比,对于渲染器,我们需要更改其大小。

window.addEventListener('resize', () => {
    // 更新显示宽度和高度
    width = window.innerWidth
    height = window.innerHeight
    // 更新相机纵横比
    camera.aspect = width / height
    camera.updateProjectionMatrix()
    // 更新渲染器
    renderer.setSize(width, height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    renderer.render(scene, camera)
})

示例

上述代码为您的 Three.js 项目提供了响应能力。

resize-browser.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js – Resizing browser</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Adding responsiveness for Three.js
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         const gui = new dat.GUI()
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         // cube
         const geometry = new THREE.BoxGeometry(2, 2, 2)
         const material = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            wireframe: true
         })
         const cube = new THREE.Mesh(geometry, material)
         scene.add(cube)
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            cube.rotation.x += 0.005
            cube.rotation.y += 0.01
            renderer.render(scene, camera)
          }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

执行代码时,将产生以下输出 −

现在,调整浏览器大小。由于响应式设计,对象将始终重新定位在浏览器的中心。

抗锯齿

锯齿效果是指边缘和对象(使用像素渲染)上出现锯齿状边缘或"锯齿"(也称为阶梯状线条)。

抗锯齿

示例

antialiasing.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Anti-aliasing</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Adding anti-aliasing to Three.js app for removing jaggies
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         const gui = new dat.GUI()
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         // cube
         const geometry = new THREE.BoxGeometry(2, 2, 2)
         const material = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            wireframe: true
         })
         const cube = new THREE.Mesh(geometry, material)
         scene.add(cube)
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // renderer - anti-aliasing
         const renderer = new THREE.WebGLRenderer({ antialias: true })
         renderer.physicallyCorrectLights = true
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            cube.rotation.x += 0.005
            cube.rotation.y += 0.01
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

我们的 Hello cube 应用中的锯齿如下所示。

Web Renderer

我们可以通过将 WebGLRenderer 的 antialias 属性设置为 true 来启用抗锯齿。默认情况下,它是 false。在这里,我们将 antialias 参数设置为 true −

const renderer = new WebGLRenderer({ antialias: true })
renderer.physicallyCorrectLights = true

抗锯齿后,它看起来很平滑,没有像下面这样的锯齿。

Without Jaggies

属性 physicalCorrectLights 告诉 Three.js 是否使用物理上正确的照明模式。默认为 false。将其设置为 true 有助于增加对象的细节。

Three.js - 调试和统计

使用 Dat.GUI

很难不断尝试变量的值,例如立方体的位置。在这种情况下,假设直到你得到你喜欢的东西。这是一个缓慢而繁琐的过程。幸运的是,已经有一个很好的解决方案可以与 Three.js 完美集成,那就是 dat.GUI。它允许您创建一个可以在代码中更改变量的基本用户界面组件。

安装

要在项目中使用 dat.GUI,请从此处下载并添加 <script>标记到 HTML 文件中。

<script type='text/javascript' src='path/to/dat.gui.min.js'></script>

或者您可以使用 CDN,在 HTML 中添加以下 <script> 标记。

<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>

如果您在节点应用程序中使用 Three.js,请安装 npm 包 - dat.GUI 并将其导入到您的 JavaScript 文件中。

npm install dat.gui

yarn add dat.gui
import * as dat from 'dat.gui'

用法

首先,您应该初始化对象本身。它会创建一个小部件并将其显示在屏幕右上角。

const gui = new dat.GUI()

然后,您可以添加要控制的参数和变量。例如,以下代码用于控制立方体的 y 位置。

gui.add(cube.position, 'y')

示例

尝试添加其他位置变量。请参阅此工作代码示例。

cube.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Position GUI</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Adding UI to debug and experimenting different values
         // UI
         const gui = new dat.GUI()
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         // cube
         const geometry = new THREE.BoxGeometry(2, 2, 2)
         const material = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            wireframe: true
         })
         gui.add(material, 'wireframe')
         const cube = new THREE.Mesh(geometry, material)
         scene.add(cube)
         gui.add(cube.position, 'x')
         gui.add(cube.position, 'y')
         gui.add(cube.position, 'z')
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            cube.rotation.x += 0.005
            cube.rotation.y += 0.01
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

您可以使用 name 属性自定义显示的标签。要更改变量行上的标签,请使用 .name("your label")。

gui.add(cube.position, 'y').name('cube-y')

您可以设置获取滑块的最小/最大限制和步骤。以下行允许从 1 到 10 的值,每次将值增加 1。

gui.add(cube.position, 'y').min(1).max(10).step(1)
// 或
gui.add(cube.position, 'y', 1, 10, 1)

如果有许多同名的变量,您可能会发现很难区分它们。在这种情况下,您可以为每个对象添加文件夹。所有与对象相关的变量都放在一个文件夹中。

// 创建一个文件夹
const cube1 = gui.addFolder('Cube 1')
cube1.add(redCube.position, 'y').min(1).max(10).step(1)
cube1.add(redCube.position, 'x').min(1).max(10).step(1)
cube1.add(redCube.position, 'z').min(1).max(10).step(1)
// 另一个文件夹
const cube2 = gui.addFolder('Cube 2')
cube2.add(greenCube.position, 'y').min(1).max(10).step(1)
cube2.add(greenCube.position, 'x').min(1).max(10).step(1)
cube2.add(greenCube.position, 'z').min(1).max(10).step(1)

示例

现在,检查以下示例。

gui-folders.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - More variables</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // 添加文件夹来区分变量

         // 控件
         const gui = new dat.GUI()
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z').min(10).max(60).step(10)
         // cube
         const geometry = new THREE.BoxGeometry(2, 2, 2)
         const material = new THREE.MeshBasicMaterial({
            color: 0xffffff,
            wireframe: true
         })
         const cubeColor = {
            color: 0xffffff
         }
         const materialFolder = gui.addFolder('Material')
         materialFolder.add(material, 'wireframe')
         materialFolder.addColor(cubeColor, 'color').onChange(() => {
            // callback
            material.color.set(cubeColor.color)
         })
         materialFolder.open()
         const cube = new THREE.Mesh(geometry, material)
         scene.add(cube)
         const cubeFolder = gui.addFolder('Cube')
         // for position
         const posFolder = cubeFolder.addFolder('position')
         posFolder.add(cube.position, 'x', 0, 5, 0.1)
         posFolder.add(cube.position, 'y', 0, 5, 0.1)
         posFolder.add(cube.position, 'z', 0, 5, 0.1)
         posFolder.open()
         // for scale
         const scaleFolder = cubeFolder.addFolder('Scale')
         scaleFolder.add(cube.scale, 'x', 0, 5, 0.1).name('Width')
         scaleFolder.add(cube.scale, 'y', 0, 5, 0.1).name('Height')
         scaleFolder.add(cube.scale, 'z', 0, 5, 0.1).name('Depth')
         scaleFolder.open()
         cubeFolder.open()
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
         requestAnimationFrame(animate)
            cube.rotation.x += 0.005
            cube.rotation.y += 0.01
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

您还可以添加一些回调函数。一旦值发生变化,就会触发 onChange。

gui.add(cube.position, 'y').onChange(function () {
    // 根据 y 的新值刷新
    console.log(cube.position.y)
})

让我们看另一个使用 dat.gui 和回调改变颜色的例子。

// 参数
const cubeColor = {
color: 0xff0000,
}
gui.addColor(cubeColor, 'color').onChange(() => {
    // 回调
    cube.color.set(cubeColor.color)
})

当 cubeColor 的颜色发生变化时,上述回调 onChange 会通知 Three.js 更改立方体颜色。

从现在开始,我们将大量使用这个 dat.gui。通过尝试"Hello Cube!"应用程序,确保您已经习惯了它。

  • 统计数据 − 统计数据在大型应用程序中起着重要作用。

Three.js - 相机

相机类型

Three.js 中有两种类型的相机。

Sr.No 相机和说明
1

PerspectiveCamera

Three.js 中有不同的相机。最常见的相机,也是我们一直在使用的相机是 PerspectiveCamera。

2

OrthographicCamera

第二常见的相机是 OrthographicCamera。它指定一个框,其中设置左、右、上、下、近和远。它以二维表示三维物体。

使相机跟随物体

在动画函数中,我们使用 camera.lookAt 函数将相机指向物体的位置函数。我们在渲染的每一帧中都执行此操作。看起来相机正精确地跟随物体的位置。

function animate() {
   const object = scene.getObjectByName('sphere')
   renderer.render(scene, camera)
   camera.lookAt(object.position)
   requestAnimationFrame(render)
}

Three.js - 控件

您可以使用相机控件在场景中移动相机。Three.js 有许多相机控件,您可以使用它们在整个场景中控制相机。您必须从 GitHub 单独获取控件。Three.js 库不包含这些控件。

Sr.No 控件和描述
1

轨道控制

轨道控制允许相机围绕场景中心旋转。

2

轨迹球控制

轨迹球控制类似于轨道控制。但是,它不会保持恒定的相机向上向量。

3

飞行控件

这些是类似飞行模拟器的控件。使用键盘和鼠标移动和操纵。

4

PointerLock 控件

PointerLockControls 实现了内置浏览器指针锁定 API。

在本章中,我们已经了解了最有用的控件。一些开发人员正在为 Three.js 创建更多有用的控件。您可以在此处看到一些其他控件,这些控件有据可查且易于使用。

Three.js - 灯光和阴影

灯光使物体可见,同样,在 Three.js 中,THREE.Light 照亮场景并使某些东西可见。并非所有材料都会受到照明的影响。MeshBasicMaterial 和 MeshNormalMaterial 是自发光的,因此它们不需要照明即可在场景中可见。但是,大多数其他材料都需要照明,例如 MeshLambertMaterial、MeshPhongMaterial、MeshStandardMaterial、MeshPhysicalMaterial 和 MeshToonMaterial。我们将在后续章节中讨论更多材料。在本章中,我们将重点介绍 Three.js 中的不同类型的灯光。

每个灯光都有颜色和强度属性。

  • color −(可选)灯光的十六进制颜色。默认值为 0xffffff(白色)。

  • intensity(强度) −(可选)光的强度/强度的数值。默认值为 1。

投射阴影

来自特定方向的光线可以投射阴影。首先,我们应该让场景准备好投射阴影。

步骤 − 1

我们应该首先告诉渲染器我们想要启用阴影。投射阴影是一项昂贵的操作。WebGLRenderer 仅支持此功能。它使用阴影映射,这是一项特定于 WebGL 的技术,直接在 GPU 上执行。

renderer.shadowMapEnabled = true

上面的代码行告诉渲染器在场景中投射阴影。

注意 − Three.js 默认使用阴影贴图。阴影贴图适用于投射阴影的光源。

场景渲染所有标记为从光源角度投射阴影的物体。

如果您的阴影在边缘看起来有点块状,则意味着阴影贴图太小。要增加阴影贴图大小,您可以为光源定义 shadowMapHeight 和 shadowMapWidht 属性。或者,您也可以尝试更改 WebGLRenderer 的 shadowMapType 属性。您可以将其设置为 THREE.BasicShadowMap、THREE.PCFShadowMap 或 THREE.PCFSoftShadowMap。

// 为阴影添加抗锯齿效果
renderer.shadowMapType = THREE.PCFSoftShadowMap
// 或
directionalLight.shadowMapWidth = 2048
directionalLight.shadowMapHeight = 2048

步骤 − 2

您应该配置对象以投射阴影。您可以告知 Three.js 哪些对象可以投射阴影,哪些对象可以接收阴影。

object.castShadow = true
object.recieveShadow = true

步骤 − 3

以上所有步骤对于每个光源都相同。下一步是设置与阴影相关的属性。

light.castShadow = true
light.shadow.camera.near = 10
light.shadow.camera.far = 100
light.shadow.camera.left = -50
light.shadow.camera.right = 50
light.shadow.camera.top = 50
light.shadow.camera.bottom = -50

第一个属性 castShadow 告诉 Three.js,此光源会投射阴影。由于投射阴影是一项昂贵的操作,因此我们需要定义阴影可以出现的区域。您可以使用 shadow.camera.near、shadow.camera.far 和 shadow.camera.left 等属性来实现。使用上述属性,我们创建了一个类似盒子的区域,Three.js 会在其中渲染阴影。

示例

在此示例中探索更多信息。

directional.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Directional Light</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
   <div id="container"></div>
      <script type="module">
         // Adding directional light to the scene
         // The lights falls from the light only in one direction.
         // You can see the position of light using helpers provided in Three.j
         s for debugging purposes

         // GUI
         const gui = new dat.GUI()
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
         camera.position.set(0, 0, 10)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z', 10, 80, 1)
         camFolder.open()
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
         scene.add(ambientLight)
         const light = new THREE.DirectionalLight()
         light.position.set(2.5, 2, 2)
         light.castShadow = true
         light.shadow.mapSize.width = 512
         light.shadow.mapSize.height = 512
         light.shadow.camera.near = 0.5
         light.shadow.camera.far = 100
         scene.add(light)
         const helper = new THREE.DirectionalLightHelper(light)
         scene.add(helper)
         // 灯光控制
         const lightColor = {
            color: light.color.getHex()
         }
         const lightFolder = gui.addFolder('Directional Light')
         lightFolder.addColor(lightColor, 'color').onChange(() => {
         light.color.set(lightColor.color)
         })
         lightFolder.add(light, 'intensity', 0, 1, 0.01)
         lightFolder.open()
         const directionalLightFolder = gui.addFolder('Position of Light')
         directionalLightFolder.add(light.position, 'x', -10, 10, 0.1)
         directionalLightFolder.add(light.position, 'y', -10, 10, 0.1)
         directionalLightFolder.add(light.position, 'z', -10, 10, 0.1)
         directionalLightFolder.open()
         // plane
         const planeGeometry = new THREE.PlaneGeometry(100, 20)
         const plane = new THREE.Mesh(planeGeometry, new THREE.MeshPhongMateria
         l({ color: 0xffffff }))
         plane.rotateX(-Math.PI / 2)
         plane.position.y = -1.75
         plane.receiveShadow = true
         scene.add(plane)
         // cube
         const geometry = new THREE.BoxGeometry(2, 2, 2)
         const material = new THREE.MeshStandardMaterial({
            color: 0x87ceeb
         })
         const materialFolder = gui.addFolder('Material')
         materialFolder.add(material, 'wireframe')
         materialFolder.open()
         const cube = new THREE.Mesh(geometry, material)
         cube.position.set(0, 0.5, 0)
         cube.castShadow = true
         cube.receiveShadow = true
         scene.add(cube)
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(window.innerWidth, window.innerHeight)
         renderer.shadowMap.enabled = true
         renderer.shadowMap.type = THREE.PCFSoftShadowMap
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            cube.rotation.x += 0.005
            cube.rotation.y += 0.01
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

Spot Light
Sr.No 灯光与描述
1

环境光

它是最基本的光,均匀地照亮整个场景。

2

定向光

定向光来自特定点,从远处直接发射到目标。

3

聚光灯

它是另一种来自特定方向的光,形状为锥体。

4

点光源

点光源是一种从单个点向所有方向发射光线的光源。

5

半球光源

这是一种用于创造自然光的特殊光源。

Three.js - 几何图形

几何图形用于在 Three.js 中创建和定义形状。Three.js 有许多类型的内置几何图形,既有 2D 几何图形,也有 3D 几何图形。

在本章中,我们将讨论基本的内置几何图形。我们首先介绍 2D 几何图形,然后探索所有可用的基本 3D 几何图形。

Sr.No 几何图形 &描述
1

平面几何

THREE.PlaneGeometry 创建一个简单的 2D 矩形。

2

圆形几何

THREE.CircleGeometry 创建一个简单的 2D 圆形。

3

环形几何

THREE.RingGeometry 创建一个简单的 2D 圆形。

3

环形几何

THREE.RingGeometry 创建一个简单的 2D 圆形。

中间有一个孔的 D 盘。

4

盒子几何体

THREE.BoxGeometry 创建一个具有指定尺寸的简单 3D 盒子。

5

球体几何体

THREE.SphereGeometry 创建 3D 球体几何体。

6

圆柱体几何体

要在 Three.js 中创建圆柱体,您可以使用 Three.CylinderGeometry。

7

圆锥体几何体

您可以使用 THREE.ConeGeometry 创建圆锥体。它与 CylinderGeometry 非常相似,只是它只允许您设置半径,而不是 radiusTop 和 radiusBottom。

8

圆环几何体

圆环体是一种管状形状,看起来像甜甜圈。您可以使用 THREE.TorusGeometry 在 Three.js 中创建圆环。

9

TorusKnot 几何

圆环结是一种特殊的结,看起来像一个管子,可以绕自身几圈。

10

多面体几何

多面体是一种只有平面和直边的几何体。

了解有关几何体的更多信息 这里

Three.js - 材质

材质就像物体的皮肤。它定义了几何体的外观。Three.js 提供了许多材质供使用。我们应该根据需要选择材质类型。在本章中,我们将讨论 Three.js 中最常用的材质。

Sr.No 材质和描述
1

MeshBasicMateria

它是 Three.js 中最基本的材质。

2

MeshDepthMaterial

它使用与相机的距离来确定如何以灰度为网格着色。

3

MeshNormalMaterial

此材质使用面法线向量的 x/y/z 值来计算和设置面上显示的颜色的红/绿/蓝值。

4

MeshLambertMaterial

您可以使用此材质创建暗淡、无光泽的表面。

5

MeshPhongMaterial

此材质与 MeshLambertMaterial 类似,但可以创建更有光泽的表面。

6

MeshStandardMaterial

它与 MeshLambertMaterial 或 MeshPhongMaterial 类似,但效果更准确、更逼真。它有两个属性:粗糙度和金属度,而不是光泽度。

7

MeshPhysicalMaterial

它与 MeshStandardMaterial 非常相似。您可以控制材质的反射率。

8

使用多种材质

到目前为止,在创建网格时,您只向其中添加了单一材质。

Three.js - 纹理

纹理是添加到材质中的图像或颜色,用于提供更多细节或美感。纹理是 Three.js 中的一个重要主题。在本节中,我们将了解如何将基本纹理应用于我们的材质。

基本纹理

首先,您应该创建一个加载器。Three.js 有一个内置函数 TextureLoader(),用于将纹理加载到您的 Three.js 项目中。然后,您可以通过在 load() 函数中指定其路径来加载任何纹理或图像。

const loader = new THREE.TextureLoader()
texture.load('/path/to/the/image')

然后,将材质的 map 属性设置为此纹理。就是这样;您已将纹理应用于平面几何体。

纹理具有用于重复、偏移和旋转纹理的设置。默认情况下,three.js 中的纹理不会重复。有两个属性,wrapS(水平环绕)和 wrapT(垂直环绕)用于设置纹理是否重复。并将重复模式设置为 THREE.ReaptWrapping。

texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.magFilter = THREE.NearestFilter

在 Three.js 中,您可以选择当纹理绘制得大于其原始尺寸时发生的情况以及当纹理绘制得小于其原始尺寸时发生的情况。

对于设置滤镜,当纹理大于其原始尺寸时,您可以将 Texture.magFilter 属性设置为 THREE.NearestFilter 或 THREE.LinearFilter。

  • NearestFilter −此滤镜使用它能找到的最近的纹素的颜色。

  • LinearFilter − 此滤镜更高级,使用四个相邻纹素的颜色值来确定正确的颜色。

并且,您可以添加重复纹理的次数。

const timesToRepeatHorizontally = 4
const timesToRepeatVertically = 2
texture.repeat.set(timesToRepeatHorizontally, timesToRepeatVertically)

示例

查看以下示例。

texture.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Checker Board</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Creating a checker-board using Textures
         // applying the texture to 2d plane geometry
         // GUI
         const gui = new dat.GUI()
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z').min(10).max(60).step(10)
         camFolder.open()
         // 灯光
         const ambientLight = new THREE.AmbientLight(0xffffff, 1)
         scene.add(ambientLight)
         // texture
         const planeSize = 10
         const loader = new THREE.TextureLoader()
         const texture = loader.load(' https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/0height.png ')
         texture.wrapS = THREE.RepeatWrapping
         texture.wrapT = THREE.RepeatWrapping
         texture.magFilter = THREE.NearestFilter
         const repeats = planeSize / 2
         texture.repeat.set(repeats, repeats)
         class StringToNumberHelper {
            constructor(obj, prop) {
               this.obj = obj
               this.prop = prop
            }
            get value() {
               return this.obj[this.prop]
            }
            set value(v) {
               this.obj[this.prop] = parseFloat(v)
            }
         }
         const wrapModes = {
            ClampToEdgeWrapping: THREE.ClampToEdgeWrapping,
            RepeatWrapping: THREE.RepeatWrapping,
            MirroredRepeatWrapping: THREE.MirroredRepeatWrapping
         }
         function updateTexture() {
            texture.needsUpdate = true
         }
         gui
            .add(new StringToNumberHelper(texture, 'wrapS'), 'value', wrapModes)
            .name('texture.wrapS')
            .onChange(updateTexture)
         gui
            .add(new StringToNumberHelper(texture, 'wrapT'), 'value', wrapModes)
            .name('texture.wrapT')
            .onChange(updateTexture)
         gui.add(texture.repeat, 'x', 0, 5, 0.01).name('texture.repeat.x')
         gui.add(texture.repeat, 'y', 0, 5, 0.01).name('texture.repeat.y')
         // plane for board
         const geometry = new THREE.PlaneGeometry(planeSize, planeSize)
         const material = new THREE.MeshPhongMaterial({
            map: texture,
            side: THREE.DoubleSide
         })
         const board = new THREE.Mesh(geometry, material)
         board.position.set(0, 0, 0)
         scene.add(board)
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         console.log(scene.children)
         animate()
      </script>
   </body>
</html>

输出

纹理映射

基本颜色映射

这是您添加到对象纹理的基本彩色图像。使用基本颜色映射,我们可以将颜色添加到表面。

const TextureMap = new THREE.TextureLoader().load('/path/to/texture-map')
material.map = TextureMap

您可以使用凹凸贴图、法线贴图或距离贴图添加深度效果。

凹凸贴图

凹凸贴图是灰度图像,其中每个像素的强度决定高度。您只需将材质的 BumpMap 属性设置为纹理即可。它为纹理添加了精细的细节。

const TextureBumpMap = new THREE.TextureLoader().load('/path/to/bump-map')
material.bumpMap = TextureBumpMap

法线贴图

法线贴图描述了每个像素的法线向量,该法线向量应用于计算光线如何影响几何图形中使用的材质。它为平坦表面创造了一种深度感。

const TextureNormalMap = new THREE.TextureLoader().load('/path/to/normal-map')
material.normalMap = TextureNormalMap

位移贴图

虽然法线贴图给人一种深度感,但我们会根据纹理信息使用位移贴图来改变模型的形状。

const textureDisplacementMap = new THREE.TextureLoader().load(
   '/path/to/displacement-map'
)
material.displacemetMap = textureDisplacementMap

粗糙度图

粗糙度图定义哪些区域是粗糙的,并且会影响表面的反射锐度。

const TextureRoughnessMap = new THREE.TextureLoader().load(
    '/path/to/roughness-map'
)
material.roughnessMap = TextureRoughnessMap

环境光遮蔽图

它突出显示了对象的阴影区域。它需要第二组 UV。

const TextureAmbientOcclusionMap = new THREE.TextureLoader().load(
    '/path/to/AmbientOcclusion-map'
)
material.aoMap = TextureAmbientOcclusionMap
// 第二个 UV
mesh.geometry.attributes.uv2 = mesh.geometry.attributes.uv

如果将对象与粗糙度图和环境光遮蔽图进行比较,可以观察到使用 aoMap 后阴影更加突出。

金属度图

它定义了材料像金属的程度。

const TextureMetalnessMap = new THREE.TextureLoader().load(
    '/path/to/metalness-map'
)
material.metalnessMap = TextureMetalnessMap

示例

现在,查看以下示例

texture-maps.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Texture Mapping</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
            Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // 使用不同类型的纹理贴图
         import { OrbitControls } from "https://threejs.org/examples/jsm/controls/OrbitControls.js"

         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0xffffff)
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
         scene.add(ambientLight)
         const light = new THREE.DirectionalLight(0xffffff, 4.0)
         light.position.set(0, 10, 20)
         light.castShadow = true
         light.shadow.mapSize.width = 512
         light.shadow.mapSize.height = 512
         light.shadow.camera.near = 0.5
         light.shadow.camera.far = 100
         scene.add(light)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 10)
         // textures
         const loader = new THREE.TextureLoader()
         const texture = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/5basecolor.jpg')
         const normalmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/2normal.jpg')
         const heightmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/0height.png')
         const roughmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/3roughness.jpg')
         const ambientOcclusionmap = loader.load('https://cloud-nfpbfxp6x-hackclub-bot.vercel.app/4ambientocclusion.jpg')
         const metallicmap = loader.load('https://cloud-nfpbfxp6x-hack-clubbot.vercel.app/1metallic.jpg')
         // plane
         const planeGeometry = new THREE.PlaneGeometry(100, 100)
         const plane = new THREE.Mesh(
            planeGeometry,
            new THREE.MeshPhongMaterial({ color: 0xffffff, side: THREE.DoubleSide })
         )
         plane.rotateX(-Math.PI / 2)
         plane.position.y = -2.75
         plane.receiveShadow = true
         scene.add(plane)
         // object
         const geometry = new THREE.SphereGeometry(1, 64, 64)
         const material1 = new THREE.MeshStandardMaterial({
            map: texture,
            side: THREE.DoubleSide
         })
         const object1 = new THREE.Mesh(geometry, material1)
         object1.position.set(-2.5, 1.5, 0)
         object1.castShadow = true
         scene.add(object1)
         // normal map
         const material2 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap
         })
         const object2 = new THREE.Mesh(geometry, material2)
         object2.position.set(0, 1.5, 0)
         object2.castShadow = true
         scene.add(object2)
         // displacement map
         const material3 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.05
         })
         const object3 = new THREE.Mesh(geometry, material3)
         object3.position.set(2.5, 1.5, 0)
         object3.castShadow = true
         scene.add(object3)
         console.log(object3)
         // roughness map
         const material4 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.05,
            roughnessMap: roughmap,
            roughness: 0.5
         })
         const object4 = new THREE.Mesh(geometry, material4)
         object4.position.set(-2.5, -1.5, 0)
         object4.castShadow = true
         scene.add(object4)
         console.log(object4)
         // ambient occlusion map
         const material5 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.05,
            roughnessMap: roughmap,
            roughness: 0.1,
            aoMap: ambientOcclusionmap
         })
         const object5 = new THREE.Mesh(geometry, material5)
         object5.position.set(0, -1.5, 0)
         object5.geometry.attributes.uv2 = object5.geometry.attributes.uv
         object5.castShadow = true
         scene.add(object5)
         console.log(object5)
         // 用于环境映射
         const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(128, {
            format: THREE.RGBFormat,
            generateMipMaps: true,
            minFilter: THREE.LinearMipmapLinearFilter,
            encoding: THREE.sRGBEncoding
         })
         const cubeCamera = new THREE.CubeCamera(1, 10000, cubeRenderTarget)
         cubeCamera.position.set(0, 100, 0)
         scene.add(cubeCamera)
         // metallic map
         const material6 = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            map: texture,
            side: THREE.DoubleSide,
            normalMap: normalmap,
            displacementMap: heightmap,
            displacementScale: 0.15,
            roughnessMap: roughmap,
            roughness: 0.1,
            aoMap: ambientOcclusionmap,
            metalnessMap: metallicmap,
            metalness: 1,
            envMap: cubeRenderTarget.texture
         })
         const object6 = new THREE.Mesh(geometry, material6)
         object6.position.set(2.5, -1.5, 0)
         object6.geometry.attributes.uv2 = object6.geometry.attributes.uv
         object6.castShadow = true
         scene.add(object6)
         console.log(object6)
         cubeCamera.position.copy(object6.position)
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // renderer - anti-aliasing
         const renderer = new THREE.WebGLRenderer({ antialias: true })
         renderer.physicallyCorrectLights = true
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         const controls = new OrbitControls(camera, renderer.domElement)
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            let objects = [object1, object2, object3, object4, object5, object6]
            objects.forEach((i) => {
               //i.rotation.x += 0.005
               i.rotation.y += 0.01
            })
            controls.update()
            cubeCamera.update(renderer, scene)
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

纹理贴图

还有一些其他贴图可用于在计算机图形中创建真实世界模型。您可以在此处了解更多信息。

Three.js - 绘制线条

您已经了解了 Three.js 中的很多材料。现在让我们看看绘制线条时使用的一些独特材料。我们可以使用线条绘制各种形状和图案。

使用 BufferGeometry

THREE.BufferGeometry 是 Three.js 中所有内置几何图形的基类。您可以通过传递几何图形顶点数组来创建几何图形。

详细了解 BufferGeometry 此处

const points = []
points.push(new THREE.Vector3(-10, 0, 0))
points.push(new THREE.Vector3(0, -10, 0))
points.push(new THREE.Vector3(10, 0, 0))

这些是 Three.js 为我们提供的一些其他元素,用于创建几何图形。THREE.Vector3(x, y, z) - 它在 3D 空间中创建一个点。在上面的代码中,我们向 points 数组中添加了 3 个点。

const geometry = new THREE.BufferGeometry().setFromPoints(points)

如前所述,THREE.BufferGeometry() 创建了我们的几何图形。我们使用 setFromPoints 方法通过点数组设置几何图形。

注意 − 在每个连续的顶点对之间绘制线,但不在第一个和最后一个顶点之间绘制线(线不是闭合的)。

const material = new THREE.LineBasicMaterial({
   // for normal lines
   color: 0xffffff,
   linewidth: 1,
   linecap: 'round', //ignored by WebGLRenderer
   linejoin: 'round', //ignored by WebGLRenderer
})
// or
const material = new THREE.LineDashedMaterial({
   // for dashed lines
   color: 0xffffff,
   linewidth: 1,scale: 1,
   dashSize: 3,
   gapSize: 1,
})

这些是线条的独特材质。您可以使用 THREE.LineBasicMaterial 或 THREE.LineDashedMaterial 中的任何一个。

const line = new THREE.Line(geometry,material)

示例

现在,我们使用 THREE.Line 来绘制线条,而不是使用 THREE.Mesh。现在,您可以在屏幕上看到使用线条绘制的"V"形。

linebasic.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Line basic</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Creating a line using LineBasicMaterial

         // GUI
         const gui = new dat.GUI()
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 50)
         camera.lookAt(0, 0, 0)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z', 10, 100)
         camFolder.open()
         // Line
         const points = []
         points.push(new THREE.Vector3(-10, 0, 0))
         points.push(new THREE.Vector3(0, -20, 0))
         points.push(new THREE.Vector3(10, 0, 0))
         const folders = [gui.addFolder('Poin 1'), gui.addFolder('Poin 2'), gui.addFolder('Poin 3')]
         folders.forEach((folder, i) => {
            folder.add(points[i], 'x', -30, 30, 1).onChange(redraw)
            folder.add(points[i], 'y', -30, 30, 1).onChange(redraw)
            folder.add(points[i], 'z', -30, 30, 1).onChange(redraw)
            folder.open()
         })
         const geometry = new THREE.BufferGeometry().setFromPoints(points)
         const material = new THREE.LineBasicMaterial({
            color: 0xffffff,
            linewidth: 2
         })
         const line = new THREE.Line(geometry, material)
         line.position.set(0, 10, 0)
         scene.add(line)
         function redraw() {
            let newGeometry = new THREE.BufferGeometry().setFromPoints(points)
            line.geometry.dispose()
            line.geometry = newGeometry
         }
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

示例

您可以通过指定顶点来使用线条创建任何类型的几何线框。查看以下我们绘制虚线的示例。

dashedline.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - Dashed line</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Creating dashed line using LineDashedMaterial
         // GUI
         const gui = new dat.GUI()
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // 相机
         const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100)
         camera.position.set(0, 0, 50)
         camera.lookAt(0, 0, 0)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z', 10, 100)
         camFolder.open()
         // Line
         const points = []
         points.push(new THREE.Vector3(-10, 0, 0))
         points.push(new THREE.Vector3(0, -20, 0))
         points.push(new THREE.Vector3(10, 0, 0))
         const folders = [gui.addFolder('Poin 1'), gui.addFolder('Poin 2'), gui.addFolder('Poin 3')]
         folders.forEach((folder, i) => {
            folder.add(points[i], 'x', -30, 30, 1).onChange(redraw)
            folder.add(points[i], 'y', -30, 30, 1).onChange(redraw)
            folder.add(points[i], 'z', -30, 30, 1).onChange(redraw)
            folder.open()
         })
         const geometry = new THREE.BufferGeometry().setFromPoints(points)
            const material = new THREE.LineDashedMaterial({
            color: 0xffffff,
            linewidth: 2,
            scale: 1,
            dashSize: 3,
            gapSize: 2
         })
         const line = new THREE.Line(geometry, material)
         line.computeLineDistances()
         line.position.set(0, 10, 0)
         scene.add(line)
         console.log(line)
         function redraw() {
            let newGeometry = new THREE.BufferGeometry().setFromPoints(points)
            line.geometry.dispose()
            line.geometry = newGeometry
         }
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer()
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

Three.js - 动画

动画为我们的网站注入了活力,正如您所见,大多数示例都使用了动画。让我们看看如何向我们的 Three.js Web 应用程序添加基本动画。

如果您想将动画添加到 Three.js 场景,则需要多次渲染场景。为此,您应该使用标准 HTML5 requestAnimationFrame 功能。

function animate() {
    // 安排多次渲染
    requestAnimationFrame(animate)
    renderer.render(scene, camera)
}

上述代码定期执行传递给 requestAnimationFrame 的参数 animate 函数,并多次渲染场景(每 60 毫秒一次)。

现在您有了动画循环,因此现在可以从 animate 函数内部对模型、相机或场景中的其他对象所做的任何更改。

让我们创建一个简单的旋转动画。

function animate() {
    requestAnimationFrame(animate)
    // 旋转立方体
    cube.rotation.x += 0.005
    cube.rotation.y += 0.01
    renderer.render(scene, camera)
}

上面的代码创建了一个旋转的立方体。每次动画渲染时,立方体都会按指定的值旋转,并以无限循环的方式重复。

您还可以将动画添加到场景中的任何其他元素。查看此示例并围绕场景探索不同的动画。

您还可以使用不同的动画库(如 Tween.js、Greensock)使用 Three.js 创建专业动画。

在以下部分中,让我们使用 tween.js 向我们的 3D 对象添加动画

在 Three.js 项目中使用 Twee.js

首先,您应该将库包含在您的项目中。添加脚本标签或从 npm 安装。

<script src="path/to/tween.js"></script>

要使用这个库,我们首先需要创建一个 TWEEN.Tween 对象的实例。

const initial = { x: 0, y: 1.25, z: 0, rot: 0 }
const final = { x: 5, y: 15, z: -10, rot: 2 * Math.PI }
const tween = new TWEEN.Tween(initial)

它创建一个 TWEEN.Tween 实例。我们可以使用此实例将提供的属性从初始值移动到最终值。

tween.to(final)

使用 to 函数,我们告诉 tween 对象我们想要慢慢将初始值更改为最终值。因此,我们将 x 属性从 0 变为 5。第二个参数为 5000,定义此更改应花费多少毫秒。

您还可以选择值随时间的变化方式。例如,您可以使用线性缓动函数。它以恒定的速率更改值,从小变化开始,然后快速增加。TWEEN 中预定义了更多缓动函数。

tween.easing(TWEEN.Easing.Elastic.InOut)

要使 3D 对象动起来,我们需要在每次更改时都收到通知。这是通过 onUpdate() 完成的。如果您想在补间结束时收到通知,请使用 onComplete()。

tween.onUpdate(function () {
    cube.position.set(this.x, this.y, this.z)
    cube.rotation.set(this.rot, this.rot, this.rot)
})

您还可以在补间对象上使用其他几个设置来控制动画的行为方式。在这种情况下,我们告诉补间对象无限重复其动画,并使用溜溜球效果来反转动画。

tween.repeat(Infinity)
tween.yoyo(true)

最后,我们可以通过调用start函数来启动补间对象。

tween.start()

此时,什么都没有发生。您必须更新补间,以便场景渲染文本时更新补间。您可以在动画函数中调用它。

function animate() {
    requestAminationFrame(animate)
    TWEEN.update()
}

现在,您可以看到效果。同样,您可以将任何动画库与Three.js一起使用。

Three.js - 创建文本

通常您需要向场景中添加文本。在本章中,让我们看看如何向场景中添加 2D 和 3D 文本。

将文本绘制到画布并用作纹理

这是向场景添加 2D 文本的最简单方法。您可以使用 JavaScript 创建画布并将其添加到 dom。

const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')

上面的代码创建了一个画布元素,我们将上下文设置为 2d。 canvas.getContext() 方法返回一个对象,该对象提供在画布上绘图的方法和属性,可用于绘制文本、线条、方框、圆圈等。

context.fillStyle = 'green'
context.font = '60px sans-serif
context.fillText('Hello World!', 0, 60)

fillText() 是一种 2D 绘图上下文方法。fillText() 方法允许您在坐标处绘制文本字符串,并使用您提供的 fillStyle 派生的填充(颜色)。您可以使用 font 属性设置文本的字体。

上述代码将字体设置为 60 像素高的 san-serif,并将填充样式设置为绿色。文本"Hello, World!"从坐标 (0, 60) 开始绘制。

// 画布内容用于纹理
const Texture = new THREE.Texture(canvas)
texture.needsUpdate = true

要从画布元素创建纹理,我们需要创建一个新的 THREE.Texture 实例并传入我们创建的画布元素。上面的代码使用画布(在本例中为我们的文本)创建纹理。纹理的 needsUpdate 参数设置为 true。它通知 Three.js 我们的画布纹理已更改,需要在下次渲染场景时更新。

现在,创建一个平面几何图形并将其作为纹理添加到材质中。

var material = new THREE.MeshBasicMaterial({
   map: texture,
   side: THREE.DoubleSide,
})
material.transparent = true
var mesh = new THREE.Mesh(new THREE.PlaneGeometry(50, 10), material)

使用文本几何图形

THREE.TextGeometry 是另一种将文本生成为单个几何图形的几何图形。它需要两个参数,text - 您要渲染的文本,以及其他参数。

参数

  • font − 这是字体的名称。

  • size − 文本的大小。默认值为 100。

  • height − height 属性定义文本的深度;换句话说,文本突出多远才能使其成为 3D。默认值为 50。

  • curveSegments − 曲线上的点数。默认值为 12。

  • bevelEnabled − 斜面提供从文本正面到侧面的平滑过渡。如果将此值设置为 true,则会为渲染的文本添加斜面。默认情况下,此值为 false。

  • bevelThickness − 如果将 bevelEnabled 设置为 true,则它定义斜面的深度。默认值为 10。

  • bevelSize − 它确定斜面的高度。默认值等于 8。

  • bevelOffset − 斜面从文本轮廓开始的距离。默认值为 0。

  • bevelSegments − 斜面段的数量。默认值为 3。

您需要使用 THREE.FontLoader 从其 typeface.json 文件加载字体。

const loader = new THREE.FontLoader()
loader.load('fonts/helvetiker_regular.typeface.json', function (font) {
   const geometry = new THREE.TextGeometry('Hello Three.js!', {
      font: font,
      size: 3,
      height: 0.2,
      curveSegments: 12,
      bevelEnabled: false,
      bevelThickness: 0.5,
      bevelSize: 0.3,
      bevelOffset: 0,
      bevelSegments: 5,
   })
})

现在,您应该向其中添加一些材质并创建一个网格。

const material = new THREE.MeshFaceMaterial([
   new THREE.MeshPhongMaterial({
      color: 0xff22cc,
      flatShading: true,
   }), // front
   new THREE.MeshPhongMaterial({
      color: 0xffcc22
   }), // side
])
const mesh = new THREE.Mesh(geometry, material)
mesh.name = 'text'
scene.add(mesh)

注意 − 使用 THREE.TextGeometry 和材料时,您需要考虑一件事。它可以将两种材料作为一个数组:一种用于渲染文本的正面,另一种用于文本的侧面。如果您只传入一种材料,它将同时应用于正面和侧面。

示例

现在,您可以看到渲染到场景中的文本。查看以下示例。

2d-text.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - 2d text</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   </head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
        // 将 2D 文本添加到 Three.js 场景
        // 在画布上书写,然后将画布作为纹理添加到材质中
        // GUI
         const gui = new dat.GUI()

         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         const size = 256
         const container = document.querySelector('#threejs-container')
         const canvas = document.createElement('canvas'),
         ctx = canvas.getContext('2d')
         function changeCanvas() {
            ctx.font = '20pt Arial'
            ctx.fillStyle = 'white'
            ctx.fillRect(0, 0, canvas.width, canvas.height)
            ctx.fillStyle = 'black'
            ctx.textAlign = 'center'
            ctx.textBaseline = 'middle'
            ctx.fillText('Tutorialspoint!', canvas.width / 2, canvas.height / 2)
         }
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 1)
         scene.add(ambientLight)
         const pointLight = new THREE.PointLight(0xffffff, 0.5)
         pointLight.position.x = 20
         pointLight.position.y = 30
         pointLight.position.z = 40
         scene.add(pointLight)
         // 相机
         const camera = new THREE.PerspectiveCamera(70, width / height, 1, 1000)
         camera.position.z = 500
         scene.add(camera)
         // 渲染器
         const renderer = new THREE.WebGL1Renderer({ antialias: true })
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         // cube
         const texture = new THREE.Texture(canvas)
         const material = new THREE.MeshStandardMaterial({ map: texture })
         const geometry = new THREE.BoxGeometry(200, 200, 200)
         const mesh = new THREE.Mesh(geometry, material)
         scene.add(mesh)
         canvas.width = canvas.height = size
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            changeCanvas()
            texture.needsUpdate = true
            mesh.rotation.y += 0.01
            renderer.render(scene, camera)
         }
         animate()
      </script>
   </body>
</html>

输出

示例

现在让我们再举一个例子来了解如何在场景中添加 3D 文本。

3d-text.html

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Three.js - 3d text</title>
      <style>
         * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: -applesystem, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
               Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
         }
         html,
         body {
            height: 100vh;
            width: 100vw;
         }
         #threejs-container {
            position: block;
            width: 100%;
            height: 100%;
         }
      </style>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.js"></script>
   <head>
   <body>
      <div id="threejs-container"></div>
      <script type="module">
         // Creating 3d text using Text Geometry in Three.js
         // GUI
         const gui = new dat.GUI()
         // 尺寸
         let width = window.innerWidth
         let height = window.innerHeight
         // 场景
         const scene = new THREE.Scene()
         scene.background = new THREE.Color(0x262626)
         // lights
         const ambientLight = new THREE.AmbientLight(0xffffff, 1)
         scene.add(ambientLight)
         const pointLight = new THREE.PointLight(0xffffff, 0.5)
         pointLight.position.x = 20
         pointLight.position.y = 30
         pointLight.position.z = 40
         scene.add(pointLight)
         // 相机
         const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 1000)
         camera.position.set(0, 0, 50)
         const camFolder = gui.addFolder('Camera')
         camFolder.add(camera.position, 'z').min(10).max(500).step(10)
         camFolder.open()
         function createMaterial() {}
         const loader = new THREE.FontLoader()
         // promisify font loading
         function loadFont(url) {
            return new Promise((resolve, reject) => {
               loader.load(url, resolve, undefined, reject)
            })
         }
         async function doit() {
            const font = await loadFont('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json')
            let text = 'Hello World !'
            const geometry = new THREE.TextGeometry(text, {
               font: font,
               size: 3,
               height: 0.2,
               curveSegments: 12,
               bevelEnabled: true,
               bevelOffset: 0,
               bevelThickness: 0.5,
               bevelSize: 0.3,
               bevelSegments: 5
            })
            const material = [
               new THREE.MeshPhongMaterial({
                  color: 0xff22cc,
               flatShading: true
               }), // front
               new THREE.MeshPhongMaterial({
               color: 0xffcc22
               }) // side
            ]
            const mesh = new THREE.Mesh(geometry, material)
            geometry.computeBoundingBox()
            geometry.computeVertexNormals()
            geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1)
            mesh.position.x = -geometry.boundingBox.max.x / 2
            const parent = new THREE.Object3D()
            parent.add(mesh)
            scene.add(parent)
            const opts = geometry.parameters.options
            console.log(opts)
            const geoProps = {
               font: opts.font,
               size: opts.size,
               height: opts.height,
               curveSegments: opts.curveSegments,
               bevelEnabled: opts.bevelEnabled,
               bevelOffset: opts.bevelOffset,
               bevelThickness: opts.bevelThickness,
               bevelSize: opts.bevelSize,
               bevelSegments: opts.bevelSegments
            }
            console.log(geoProps)
            // GUI for exporimenting cube properties
            const props = gui.addFolder('Properties')
            props
               .add(geoProps, 'size', 1, 30)
               .step(1)
               .onChange(redraw)
               .onFinishChange(() => console.dir(mesh.geometry))
            props.add(geoProps, 'height', 0, 30).step(0.1).onChange(redraw)
            props.add(geoProps, 'curveSegments', 1, 30).step(1).onChange(redraw)
            props.add(geoProps, 'bevelEnabled').onChange(redraw)
            props.add(geoProps, 'bevelOffset', 0, 1).onChange(redraw)
            props.add(geoProps, 'bevelThickness', 0, 3).onChange(redraw)
            props.add(geoProps, 'bevelSize', 0, 3).onChange(redraw)
            props.add(geoProps, 'bevelSegments', 1, 8).step(1).onChange(redraw)
            props.open()
            function redraw() {
               camera.position.set(0, 0, 80)
               let newGeometry = new THREE.TextGeometry(text, {
                  font: geoProps.font,
                  size: geoProps.size,
                  height: geoProps.height,
                  curveSegments: geoProps.curveSegments,
                  bevelEnabled: geoProps.bevelEnabled,
                  bevelOffset: geoProps.bevelOffset,
                  bevelThickness: geoProps.bevelThickness,
                  bevelSize: geoProps.bevelSize,
                  bevelSegments: geoProps.bevelSegments
               })
               mesh.geometry.dispose()
               mesh.geometry = newGeometry
               mesh.geometry.parameters.options.depth = 0.2
               console.log(mesh.geometry.parameters.options)
            }
         }
         doit()
         // 响应性
         window.addEventListener('resize', () => {
            width = window.innerWidth
            height = window.innerHeight
            camera.aspect = width / height
            camera.updateProjectionMatrix()
            renderer.setSize(window.innerWidth, window.innerHeight)
            renderer.render(scene, camera)
         })
         // 渲染器
         const renderer = new THREE.WebGL1Renderer({ antialias: true })
         renderer.setSize(width, height)
         renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
         // 动画
         function animate() {
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
         }
         // 渲染场景
         const container = document.querySelector('#threejs-container')
         container.append(renderer.domElement)
         renderer.render(scene, camera)
         animate()
      </script>
   </body>
</html>

输出

Three.js - 加载 3D 模型

3D 模型有多种格式。您可以将大多数模型导入 Three.js 并快速使用它们。有些格式难以使用,对于实时体验效率低下,或者目前 Three.js 尚未完全支持。让我们讨论一些标准格式以及如何将它们加载到 Three.js 文件中。

注意 − Three.js 中仅内置了少数格式加载器。要加载其他格式的模型,您需要包含它们的 JavaScript 文件。您可以在 Three.js repo 的 three/examples/jsm/loaders 目录中找到所有不同的加载器。

对于加载任何模型,我们使用这三个简单的步骤 −

  • 在您的网页中包含 [NameOfFormat]Loader.js。
  • 使用 [NameOfFormat]Loader.load() 加载 URL。
  • 检查回调函数的响应格式并呈现结果。

OBJ 模型加载器

OBJ 文件以文本形式定义材质的几何形状。许多其他 3D 模型软件可以创建 OBJ 格式的模型。在 Threejs 中,导入 OBJ 时,默认材质是白色的 MeshPhongMaterial。您的场景中至少需要一个光源。您可以使用 OBJLoader 加载 OBJ 格式的模型。

要在 Three.js 项目中使用 OBJLoader,您需要添加 OBJLoader JavaScript 文件。

<script type="text/javascript" src="../scripts/OBJLoader.js"></script>

然后,您可以像使用 .load 方法加载纹理一样加载模型。

const loader = new THREE.OBJLoader()
loader.load('path/to/your/.obj file', (object) => {
    scene.add(object)
})

在此代码中,我们使用 OBJLoader 从 URL 加载模型。模型加载完成后,我们提供的回调函数就会被调用,如果您愿意,我们可以自定义加载的网格。

MTL 模型加载器

OBJ 和 MTL 是配套格式,经常一起使用。MTL 文件定义了 OBJ 文件中几何体所使用的材料。MTL 也是一种基于文本的格式。

<script type="text/javascript" src="../scripts/MTLLoader.js"></script>

我们将在此代码片段中同时使用 MTLLoader 和 OBJLoader。

const mtlLoader = new THREE.MTLLoader()
mtlLoader.load('/path/to/your/.mtl file', (materials) => {
   materials.preload()
   // loading geometry
   const objLoader = new THREE.OBJLoader()
   objLoader.setMaterials(materials)
   objLoader.load('path/to/your/.obj file', (object) => {
      mesh = object
      scene.add(mesh)
   })
})

它首先加载材质。然后我们将要加载的 OBJ 文件的材质设置为已加载材质,然后加载 OBJ 文件。它创建我们需要将对象渲染到场景的网格,自定义网格或材质,就像 Three.js 项目中的网格或材质一样。

GLTF 模型加载器

glTF 文件可能包含一个或多个场景、网格、材质、纹理、皮肤、骨架、变形目标、动画、灯光和相机。它是 Three.js 官方推荐的格式。.GLB 和 .GLTF 版本的格式都得到 Three.js 的良好支持。由于 glTF 专注于运行时资产交付,因此传输紧凑且加载速度快。

<script src="../scripts/GLTFLoader.js"></script>

使用 GLTFLoader 对象,您可以导入 JSON (.gltf) 或二进制 (.glb) 格式。

const loader = new THREE.GLTFLoader()
// 加载模型
loader.load('path/to/model.glb', (gltf) => {
    scene.add(gltf.scene)
})

导入的 glTF 模型的场景已添加到我们的 Three.js 项目中。加载的模型可能包含两个场景;您可以指定要导入的场景。

DRACO Loader

DRACOLoader 用于加载使用 Draco 库压缩的几何图形(.drc 格式文件)。 Draco 是一个用于压缩和解压缩 3D 网格和点云的开源库。

glTF 文件也可以使用 DRACO 库进行压缩,也可以使用 glTFLoader 进行加载。在这种情况下,我们可以配置 glTFLoader 以使用 DRACOLoader 解压缩文件

<script src="../scripts/GLTFLoader.js"></script>
<script src="../scripts/DRACOLoader.js"></script>

与任何其他模型一样,您可以使用 DRACOLoader 轻松加载 .drc 文件。然后,您可以将材质添加到加载的几何体并将网格渲染到场景中。

const loader = new THREE.DRACOLoader()
loader.setDecoderPath('/scripts/draco/')
// 加载 Draco 几何体
loader.load('path/to/your/.drc file', (geometry) => {
   const material = new THREE.MeshStandardMaterial({ color: 0xffffff })
   const mesh = new THREE.Mesh(geometry, material)
   scene.add(mesh)
})

当您想要导入使用 Draco 库压缩几何图形的 glTF 文件格式时,使用此代码片段。

const dracoLoader = new THREE.DRACOLoader()
dracoLoader.setDecoderPath('/scripts/draco/')
dracoLoader.setDecoderConfig({ type: 'js' })
// 加载使用 draco 库的 glTF 模型
const loader = new THREE.GLTFLoader()
loader.setDRACOLoader(dracoLoader)
loader.load('models/monkey_compressed.glb', (gltf) => {
    scene.add(gltf.scene)
})

STL 模型加载器

STL 模型格式广泛用于快速原型制作、3D 打印和计算机辅助制造。

STL 文件仅描述 3D 对象的表面几何形状,不包含任何颜色、纹理或其他常见 3D 建模属性的表示。您可以将它们添加到回调函数中。

<script src="../scripts/STLLoader.js"></script>

我们使用 .stl 文件中的几何形状,并在将其添加到场景之前为其添加材质。

const material = new THREE.MeshPhysicalMaterial({ color: 0xaaaaaa })
const loader = new THREE.STLLoader()
loader.load('path/to/your/.stl file', (geometry) => {
   const mesh = new THREE.Mesh(geometry, material)
   scene.add(mesh)
})

您可以将许多其他格式加载到 Three.js 项目中。上面提到的是标准格式。加载器文件有据可查且易于使用。

故障排除

如果您无法正确加载模型,或者模型扭曲、变色或完全丢失。以下是 Three.js 官方网站中提到的一些故障排除步骤 −

  • 检查 JavaScript 控制台是否有错误,并确保在调用 .load() 时使用了 onError 回调来记录结果。

  • 在另一个应用程序中查看模型。对于 glTF,Three.js 和 Babylon.js 提供拖放查看器。如果模型在一个或多个应用程序中正确显示,请针对 Three.js 提交错误。如果模型无法在任何应用程序中显示,您应该向用于创建模型的应用程序提交错误。

  • 尝试将模型放大或缩小 1000 倍。许多模型的缩放比例不同,如果相机位于模型内部,大型模型可能不会显示。

  • 尝试添加和定位光源。模型可能隐藏在黑暗中。

  • 在网络选项卡中查找失败的纹理请求,例如 C:\Path\To\Model exture.jpg。改用相对于模型的路径,例如 images/texture.jpg - 这可能需要在文本编辑器中编辑模型文件。

寻求帮助

假设您已经完成了上述故障排除过程,但模型仍然无法正常工作。在这种情况下,正确的寻求帮助的方法可以让您更快地找到解决方案。在 Three.js 论坛上发布问题,并尽可能以任何可用的格式包含您的模型(或具有相同问题的更简单模型)。包含足够的信息,以便其他人可以快速重现问题 - 最好是现场演示。

Three.js - 库和插件

官方 three.js 示例作为 t​​hree.js 存储库的一部分进行维护,并始终使用最新版本的 three.js。

此处列出了外部开发的与 three.js 兼容的库和插件

物理

后期处理

除了官方 three.js 后期处理效果之外,还可以通过外部库支持一些其他效果和框架。

交叉和光线投射性能

文件格式

除了官方 three.js 加载器之外,还可以通过外部库支持一些其他格式。

3D文本和布局

粒子系统

逆运动学

游戏 AI

包装器和框架

这是 Three.js 官方社区维护的列表。除此之外,还有许多其他库和插件,可以更轻松地添加精美的效果。