3D中的JavaScript:Three.js简介

哈Ha! 我向您介绍Bret Cameron的文章 “ JavaScript in 3D:Three.js简介”的翻译。

引言


Three.js是一个功能强大的工具。 它有助于在浏览器中以可接受的性能使用3D设计。 入门Three.js可能很棘手,尤其是如果您以前从未涉足3D编程世界。

我对Unity和C#游戏引擎有一些基本的经验,但是,许多概念对我来说还是新的。 我得出的结论是,对于初学者来说,现在几乎没有资源,因此我决定写这篇文章。 在其中,我们将考虑Three.js场景的基本元素,从多边形网格和材料到几何图形,加载器等等。

在本文的最后,您将对为将来的Web项目添加额外的维度所需要的基本方面有扎实的理解。

来自Ben HoustonThomas DiewaldStrykerDoesAnimation的Three.js示例

载体和容器-基本构建块


通常在Three.js中有两个主要的类-Vector3和Box3。 如果您是3D的新手,这听起来可能有点抽象,但是您会遇到更多次。

矢量3


最基本的3D类,包含三个数字: x,y和z 。 数字是3D空间中点的坐标或方向和长度。 例如:

const vect = new THREE.Vector3(1, 1, 1); 

Three.js中的大多数构造函数都接受Vector3类型的对象作为输入参数,例如Box3

Box3


此类表示长方体(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图形(长方体或容器)可以由widthheightdepth参数指定。

 const geometry = new THREE.BoxGeometry( 20, 20, 20 ); 


对于球体, radiuswidthSegmentsheightSegments参数的值最小 。 最后两个变量指示模型应使用多少个三角形表示球体:数字越大,看起来越平滑。

 const geometry = new THREE.SphereGeometry( 20, 64, 64 ); 


如果要制作尖锐或三角形的形状,则可以使用圆锥形。 他的论点是前两个数字的论点的结合。 下面,我们规定了radiuswidthSegmentsradiusSegments

  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, }); 


在下面的示例中可以看到,颜色或多或少是正确的,但是颜色与灯光的交互方式并没有增加真实感。 要解决此问题,我们需要使用MeshPhongMaterialMeshStandardMaterial。

网状材料


对于中等性能和中等精度很有用。

这种材料可以在性能和渲染精度之间进行权衡,因此对于需要高效且渲染比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 import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; // OBJ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; // STL import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'; // FBX import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'; // 3MF import { 3MFLoader } from 'three/examples/jsm/loaders/3MFLoader.js'; 

推荐的在线查看格式是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) { // if the model is loaded successfully, add it to your scene here }, undefined, function (err) { console.error(err); }); 

全部放在一起


Three.js看上去令人生畏的原因之一是,您可以只用几行代码就可以创建一些内容。 在上面的每个示例中,我们需要创建一个场景和一个摄像机。 为简化起见,我将此代码保留在审查范围之外,但现在我们将看到它们的外观。

组织代码的方式取决于您。 在较简单的示例(如本文)中,将所有代码都写在一个地方是有意义的。 但是在实践中,将各个元素分开是有用的,以便扩展代码库及其管理。

为简单起见,我们将考虑绘制为单个对象的元素,因此将所有代码放在一个文件中。

 // Import dependencies import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; //   const scene = new THREE.Scene(); scene.background = new THREE.Color(0x282c34); //  ,       const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); camera.position.z = 5; //  ""      const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); //   DOM   renderer.domElement   document.getElementById('threejs').appendChild(renderer.domElement); //  ,      DOM  let controls = new OrbitControls(camera, document.getElementById('threejs')); controls.target.set(0, 0, 0); controls.rotateSpeed = 0.5; controls.update(); //  ( )   const geometry = new THREE.TorusKnotGeometry(10, 1.3, 500, 6, 6, 20); //    const material = new THREE.MeshStandardMaterial({ color: 0xfcc742, emissive: 0x111111, specular: 0xffffff, metalness: 1, roughness: 0.55, }); //   ,       const mesh = new THREE.Mesh(geometry, material); mesh.scale.x = 0.1; mesh.scale.y = 0.1; mesh.scale.z = 0.1; scene.add(mesh); //  ,       const frontSpot = new THREE.SpotLight(0xeeeece); const frontSpot2 = new THREE.SpotLight(0xddddce); frontSpot.position.set(1000, 1000, 1000); frontSpot2.position.set(-500, -500, -500); scene.add(frontSpot); scene.add(frontSpot2); //   ,           const animate = function () { requestAnimationFrame(animate); mesh.rotation.x += 0.005; mesh.rotation.y += 0.005; mesh.rotation.z += 0.005; renderer.render(scene, camera); }; //    animate(); 

我需要使用框架吗?


最后,是时候讨论是否要在您喜欢的框架中使用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中的$ refAngular中的ngRef,在直接访问DOM元素的背景下,它看起来像一个巨大的加号。 作为示例,让我们看一下React的快速实现。

反应策略


如果使用React,那么有一种方法可以将Three.js文件嵌入到您的组件之一中。 在文件ThreeEntryPoint.js中,我们将编写以下代码:

 export default function ThreeEntryPoint(sceneRef) { let renderer = new THREE.WebGLRenderer(); // ... sceneRef.appendChild(renderer.domElement); } 

我们将此导出为带有一个参数的函数:对组件中元素的引用。 现在我们可以创建我们的组件

 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时,找不到像本文这样的单一资源,因此我希望我可以帮助初学者更容易地使用该技术。

Source: https://habr.com/ru/post/zh-CN477956/


All Articles