哈Ha! 我向您介绍Bret Cameron的文章 “ JavaScript in 3D:Three.js简介”的翻译。引言
Three.js是一个功能强大的工具。 它有助于在浏览器中以可接受的性能使用3D设计。 入门Three.js可能很棘手,尤其是如果您以前从未涉足3D编程世界。
我对Unity和C#游戏引擎有一些基本的经验,但是,许多概念对我来说还是新的。 我得出的结论是,对于初学者来说,现在几乎没有资源,因此我决定写这篇文章。 在其中,我们将考虑Three.js场景的基本元素,从多边形网格和材料到几何图形,加载器等等。
在本文的最后,您将对为将来的Web项目添加额外的维度所需要的基本方面有扎实的理解。
来自
Ben Houston ,
Thomas Diewald和
StrykerDoesAnimation的Three.js示例 。
载体和容器-基本构建块
通常在Three.js中有两个主要的类-Vector3和Box3。 如果您是3D的新手,这听起来可能有点抽象,但是您会遇到更多次。
矢量3
最基本的3D类,包含三个数字:
x,y和z 。 数字是3D空间中点的坐标或方向和长度。 例如:
const vect = new THREE.Vector3(1, 1, 1);
Three.js中的大多数构造函数都接受
Vector3类型的对象作为输入参数,例如
Box3Box3
此类表示长方体(3D容器)。 它的主要任务是围绕其他对象创建一个容器-这就是3D对象可容纳的最小的长方体。 每个
Box3都围绕
x,y和z轴对齐
。 如何使用
Vector3创建容器的示例:
const vect = new THREE.Vector3(1, 1, 1); const box = new THREE.Box3(vect);
如何在现有3D对象周围创建容器的示例:
const box = new THREE.Box3(); box.setFromObject(object);
您可以在没有这些深入知识的情况下创建网格,但是一旦您开始提出或更改模型,这些类肯定会派上用场。 现在,我们将从抽象转向更可见的事物。
多边形网格
在Three.js中,舞台上的主要视觉元素是
Mesh 。 这是一个由三角形矩形(多边形网格)组成的3D对象。 它使用两个对象构建:
几何形状 -确定其形状,
材质 -确定外观。
它们的定义似乎有点令人困惑(例如,
Geometry类可能包含有关颜色的信息),但是主要的不同之处在于。
几何形状
根据您要完成的任务,您可能想要在Three.js中定义几何或从文件导入另一个。
使用
THREE.TorusKnotGeometry之类的
函数 ,我们可以用单行代码创建复杂的对象。 我们将很快解决这个问题,但是首先考虑简单的形式。
最简单的3D图形(长方体或容器)可以由
width ,
height和
depth参数指定。
const geometry = new THREE.BoxGeometry( 20, 20, 20 );
对于球体,
radius ,
widthSegments和
heightSegments参数的值
最小 。 最后两个变量指示模型应使用多少个三角形表示球体:数字越大,看起来越平滑。
const geometry = new THREE.SphereGeometry( 20, 64, 64 );
如果要制作尖锐或三角形的形状,则可以使用圆锥形。 他的论点是前两个数字的论点的结合。 下面,我们规定了
radius ,
widthSegments和
radiusSegments 。
const geometry = new THREE.ConeBufferGeometry( 5, 20, 32 );
这只是最常见数字的一部分。 Three.js的框内有很多形状,可以在文档中找到它们。 在本文中,我们将研究基于
TorusKnotGeometry方法构建的更有趣的表单。
为什么这些形状看起来完全一样? 这个问题不在本文讨论范围之内,但是我敦促您尝试使用参数值,因为用一行代码就可以得到非常有趣的形状!
const geometry = new THREE.TorusKnotGeometry(10, 1.3, 500, 6, 6, 20);
https://codepen.io/BretCameron/pen/gOYqORg用料
几何定义了3D对象的形状,但没有定义它们的外观。 要解决此问题,我们需要材料。
Three.js提供了10种开箱即用的材料,每种材料都有自己的优势和可自定义的参数。 我们将只考虑最有用的部分。

网格普通材料
对于快速启动和启动很有用。我们将从上面示例中使用的多色材质
MeshNormalMaterial开始。 它对应于RGB面板中的法向矢量,换句话说,颜色用于确定矢量在3D空间中的位置。
const material = new THREE.MeshNormalMaterial();
请注意,如果要更改材质的颜色,可以使用CSS滤镜并更改饱和度:
filter: hue-rotate(90deg) .
以我的经验,这种材料对于快速分层和启动更为有用。 为了更好地控制对象,最好使用其他东西。
网状材料
仅显示骨骼时有用。如果要给图形赋予单一颜色,则仅在不应用照明的情况下才可以使用
MeshBasicMaterial 。 我发现使用该材质来渲染模型的骨架很有用。 要仅绘制骨架,您需要传递
{wireframe:true}作为参数。
const material = new THREE.MeshBasicMaterial({ wireframe: true, color: 0xdaa520 });
这种材料的主要缺点是有关材料深度的信息会完全丢失。 每种材质都可以选择仅显示骨骼,但是只有一种材质可以解决深度不足的问题
-MeshDepthMaterial网状材料
对于高性能但精度较低有用。这是考虑光的第一种材料,因此您需要向场景添加一些光。 在下面的代码中,我们添加带有黄色调的聚光灯以创建温暖的效果。
const scene = new THREE.Scene(); const frontSpot = new THREE.SpotLight(0xeeeece); frontSpot.position.set(1000, 1000, 1000); scene.add(frontSpot); const frontSpot2 = new THREE.SpotLight(0xddddce); frontSpot2.position.set(-500, -500, -500); scene.add(frontSpot2);
现在为我们的图添加材料。 由于我们的身材就像装饰,我建议添加更多的金色。 另一个参数
emissive是来自对象本身(没有光源)的对象颜色。 通常,这在深色情况下效果更好-例如,在灰色的深色阴影中,如下例所示
const material = new THREE.MeshLambertMaterial({ color: 0xdaa520, emissive: 0x111111, });
在下面的示例中可以看到,颜色或多或少是正确的,但是颜色与灯光的交互方式并没有增加真实感。 要解决此问题,我们需要使用
MeshPhongMaterial或
MeshStandardMaterial。网状材料
对于中等性能和中等精度很有用。这种材料可以在性能和渲染精度之间进行权衡,因此对于需要高效且渲染比
MeshLambertMaterial更高的应用程序,该材料是不错的选择
。现在我们可以更改
镜面反射属性,该属性会影响表面反射的亮度和颜色。 如果发射属性通常是深色的,则
镜面反射最适合浅色。 下面我们用浅灰色。
const material = new THREE.MeshPhongMaterial({ color: 0xdaa520, emissive: 0x000000, specular: 0xbcbcbc, });
从视觉上看,上方的图像更令人信服地反射了光线,但仍不完美。 白光太亮,并且材料看起来比金属更有棱纹(我们为此努力)。 使用
MeshStandardMaterial可以获得更好的结果
。网格标准材料
对于高精度但低生产率很有用。这是所有设备中最精确的材料,尽管使用它会带来使用更多功率的成本。
MeshStandartMaterial与其他
金属 度和
粗糙度参数一起使用,每个参数的取值范围为0到1。
金属性参数会影响对象的反射方式,使其更接近金属的性质。 这是因为,与金属等电介质不同,金属等导电材料具有不同的反射特性。
粗糙度为定制增加了一层。 您可以想象它与光泽相反:0-非常光泽,1-非常哑光。
const material = new THREE.MeshStandardMaterial({ color: 0xfcc742, emissive: 0x111111, specular: 0xffffff, metalness: 1, roughness: 0.55, });
这是Three.js中介绍的最现实的材料,也是最耗资源的
我们下面讨论的材料是我最常遇到的材料,您可以在扩展坞中看到所有选项。
装载机
如上所述,您可以手动定义几何和多边形网格。 实际上,人们经常从文件中加载其几何形状。 幸运的是,Three.js几乎没有支持许多3D格式的下载器。
主
ObjectLoader使用
JSON Object / Scene格式加载JSON文件。 大多数引导加载程序需要手动导入。 您可以在此处找到受支持的引导程序的完整列表并导入它们。 以下是您可以导入的内容的小清单。
推荐的在线查看格式是GLTF,因为该格式“旨在于在运行时交付资产,便于传输和快速下载”。
当然,可能有许多原因偏爱某种类型的文件(例如,如果质量优先或3D打印需要准确性)。 导入GLTF时,最佳的在线性能。
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import model from '../models/sample.gltf'; let loader = new GLTFLoader(); loader.load(model, function (geometry) {
全部放在一起
Three.js看上去令人生畏的原因之一是,您可以只用几行代码就可以创建一些内容。 在上面的每个示例中,我们需要创建一个场景和一个摄像机。 为简化起见,我将此代码保留在审查范围之外,但现在我们将看到它们的外观。
组织代码的方式取决于您。 在较简单的示例(如本文)中,将所有代码都写在一个地方是有意义的。 但是在实践中,将各个元素分开是有用的,以便扩展代码库及其管理。
为简单起见,我们将考虑绘制为单个对象的元素,因此将所有代码放在一个文件中。
我需要使用框架吗?
最后,是时候讨论是否要在您喜欢的框架中使用Three.js? 目前,React有一个很好的
react-three-fiber软件包。 对于React用户,使用这样的包有明显的优势-您维护了一种用于处理组件的结构,该结构允许您重用代码。
对于初学者,我建议您从通常的
Vanila JS开始 ,因为有关Three.js的大多数在线材料都与Vanila JS上的Three.js有关。 根据我的学习经验,这可能会令人困惑并且很难通过一个包来学习-例如,您将不得不将Three.js对象和方法转换为组件和道具。 (一旦您学习Three.js,就可以使用任何包)。
如何将Three.js添加到框架
Three.js提供了一个HTML对象(最常称为
renderer.domElement ),可以将其添加到应用程序中的任何HTML对象中。 例如,如果您有一个
id =“ threejs”的
div ,则可以在Three.js代码中简单地包含以下代码:
document.getElementById('threejs').appendChild(renderer.domElement);
一些框架具有访问树的DOM节点的首选路径。 例如,React中的
ref ,Vue中的
$ ref或
Angular中的
ngRef,在直接访问DOM元素的背景下,它看起来像一个巨大的加号。 作为示例,让我们看一下React的快速实现。
反应策略
如果使用React,那么有一种方法可以将Three.js文件嵌入到您的组件之一中。 在文件
ThreeEntryPoint.js中,我们将编写以下代码:
export default function ThreeEntryPoint(sceneRef) { let renderer = new THREE.WebGLRenderer();
我们将此导出为带有一个参数的函数:对组件中元素的引用。 现在我们可以创建我们的组件
import React, { Component } from 'react'; import ThreeEntryPoint from './threejs/ThreeEntryPoint'; export default class ThreeContainer extends Component { componentDidMount() { ThreeEntryPoint(this.scene); } render() { return ( <> <div ref={element => this.scene = element} /> </> ); } }
导入的
ThreeEntryPoint函数
必须在
componentDidMount方法中调用,并使用引用将新
div作为参数传递
作为使用这种方法的示例,您可以克隆存储库并自己尝试:
https :
//github.com/BretCameron/three-js-sample 。
结论
我仍然可以谈论Three.js,但我希望本文能给您足够的信息,以开始使用这项强大的技术。 当我开始学习Three.js时,找不到像本文这样的单一资源,因此我希望我可以帮助初学者更容易地使用该技术。