Three

Three.js 基础知识与核心概念

在本节实验中,我们将会一起进入到 Three.js 基础知识与核心概念学习的阶段。

知识点

  • 场景、摄相机、渲染器
  • 几何体、材质
  • 光源
  • 创建一个正方体

Three.js 的理论知识

引入:基础知识

先看下面这样一段代码:

const scene = new THREE.Scene(); // 定义场景
const camera = new THREE.PerspectiveCamera(
  55,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
); //定义摄相机
const renderer = new THREE.WebGLRenderer(); // 使用 WebGLRenderer 渲染器,该渲染器会调用电脑显卡进行渲染
renderer.setSize(window.innerWidth, window.innerHeight);

这段代码中,我们首先定义了一个场景 ( scene )、照相机 ( camera ) 和 渲染器 ( renderer )对象。首先,场景是一个容器,主要用于保存、跟踪所要渲染的物体和使用的光源。如果没有场景,Three.js 就无法渲染任何物体。其次,摄相机决定了能够在场景中看到什么,即屏幕上哪些东西需要渲染。最后,渲染器是基于摄相机的角度来计算场景对象在浏览器中会渲染成什么样子,然后调用底层 API 执行真正的场景绘制工作。可能你会觉得简单,但是就是这样,创建一个场景只需要场景、相机和渲染器三个对象。

进一步地,我们对这三个对象进行讲解:

  1. 场景。在 Three.js 中,场景只有一种,就是用 THREE.Scene 来表示。它是所有不同对象的容器,可以用来保存所有图形场景的必要信息。也就是说, THREE.Scene 保存了所有对象、光源和渲染所需的其他对象。例如,你想要显示一个橙子,就需要将橙子这个对象加入到场景中。下面,我们给出一段代码,对场景的功能进行阐述:
const scene = new THREE.Scene(); // 定义场景
const camera = new THREE.PerspectiveCamera(
  55,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
); //定义摄相机
scene.add(camera); // 向场景中添加摄像机

const geometry = new THREE.SphereGeometry(5, 32, 32); // 定义球体
const material = new THREE.MeshBasicMaterial({ color: 0xff9800 }); // 定义材质
const sphere = new THREE.Mesh(geometry, material);
sphere.name = "sphere-" + scene.children.length;
scene.add(sphere); //将球体添加到场景
scene.remove(sphere); //将球体从场景中移除
console.info(scene.children);

上述代码中,我们分别应用了场景相关方法:

  • THREE.Scene.Add: 用于向场景中添加对象,该方法还可以创建对象组;
  • THREE.Scene.Remove: 用于移除场景中的对象;
  • THREE.Scene.children: 用于获取场景中所有的子对象列表,包括摄像机和光源;
  • THREE.Scene.getObjectByName: 利用 name 属性,获取场景中特定的对象;

上述方法适合场景相关的重要方法,通常情况下这些方法可以满足大部分需求。

  1. 摄相机。在 Three.js 中提供了两种主要的摄像机,分别是正交投影摄像机和透视投影摄像机 (目前还支持了 VR 摄像机)。首先,使用正交投影摄像机 (OrthographicCamera) ,你看到的物体所被渲染的尺寸都是一样的,因为对象相对于摄像机的距离对渲染的结果是没有影响的。这种摄像机通常被用在二维游戏中。下面,我们定义一个正交投影摄像机:
const camera = new THREE.OrthographicCamera(
  width / -2,
  width / 2,
  height / 2,
  height / -2,
  1,
  1000
);
scene.add(camera);

正交投影摄像机的参数定义了摄像机"视锥体"的大小。其中,视锥体指定是建模世界中可能出现在屏幕上的空间区域,也就是摄像机的视野。

接着,我们再来看看透视投影摄像机 (PerspectiveCamera) ,这也是我们实验中大部分使用的类型。因为这种摄像机的效果更贴近真实世界。如下图所示,摄像机接收的参数会影响你所看到的视野。

20-1

THREE.PerspectiveCamera 有如下参数:

  • fov: 视场,即在摄像机中能够看到的那部分场景。推荐默认值:50。
  • aspect: 长宽比。这是渲染结果的横向尺寸和纵向尺寸的比值。推荐默认值: window.innerWidth / window.innerHeight
  • near: 近面距离。定义了从距离摄像机多近的距离开始渲染。通常情况下这个值会设置的尽可能小,这样就能从摄像机的位置看到尽可能多的物体。推荐默认值:0.1。
  • far: 远面距离。定义了摄像机从它所处的位置能够看多远。如果这个值设置的较小,那么场景中有一部分不会被渲染;如果设置较大,会影响性能。推荐默认值:1000。
  • zoom: 变焦。用于放大或缩小场景。值小于 1,场景被缩小;大于 1,场景被放大。推荐默认值:1。

目前,我们已经介绍了如何创建摄像机,以及摄像机的各个参数。通常来说,摄像机会指向场景的中心,用坐标来表示就是 position(0,0,0) 。同时,我们可以改变摄像机指向的位置: camera.lookAt(new THREE.Vector3(x,y,z));

  1. 渲染器。Three.js 除了提供基于 WebGL 的渲染器外,还有其他的渲染器,比如基于 HTML canvas 的渲染器、基于 CSS 的渲染器,基于 SVG 的渲染器。但是这些渲染器并不推荐使用,一方面它们十分消耗 CPU 资源,另一方面缺乏对一些功能(如:阴影、材质)的支持。在 Three.js 主要使用 WebGLRenderer() 渲染器,一般会经历如下三个过程:
  • 创建渲染器;
  • 开始渲染: render(scene, camera)
  • 添加到 canvas 对象中;

通过上面对三个概念的讲解,你可以从实际生活中理解。例如实际生活中拍照的例子,渲染器起到的作用就是执行拍照这个动作。

网络模型

几何体、材质、贴图、颜色这些概念属于网络模型( Mesh )的范畴中。本小节中,我们将对这些概念进行讲解。

  • 几何体 ( Geometry )。前面的代码中,我们已经使用过了几何体。我们可以知道,创建一个立方体的方法是: const box = new THREE.BoxGeometry(50,50,50); ,即我们创建了一个长宽高都是 50 的立方体。像大多数三维库一样, Three.js 中几何体基本上是三维空间中的点(顶点)集和将这些点连接起来的面。例如,一个立方体有 8 个角,每个角都可以用 x,y 和 z 坐标点来定义,所以每个立方体在三维空间中都有 8 个顶点。其次,一个立方体有 6 个面,每个角有一个顶点。在 Three.js 中,每个面都是包含 3 个顶点的三角形。所以,立方体的每个面都是由两个三角形面组成的。当你使用几何体时,不需要自己定义几何体的所有顶点和面。例如,定义立方体只需要定义长、宽、高。 Three.js 会基于这些信息在正确的位置创建一个拥有 8 个顶点和 12 个三角形面的立方体。当然,我们还可以自定义顶点和面来创建几何体:
const vertices= [new THREE.Vector3(1,2,1),...,new THREE.Vector3(-1,-1,1)];
const faces = [new THREE.Face3(0,2,1),...,new THREE.Face3(3,6,4)];
const geometry = new THREE.Geometry();
geometry.vertices = vertices;
geometry.faces = faces;
geometry.computeFaceNormals();// computeFaceNormals 方法让Three.js 决定每个面的法向量,而法向量用于决定不同光源下的颜色

需要注意的是,创建面的顶点时的创建顺序,因为顶点顺序决定了某个面是面向还是背向摄像机。如果你想创建面向摄像机的面,那么顶点的顺序是顺时针的,反之顶点的顺序是逆时针的。

  • 材质 ( Material )。材质就像物体的皮肤,决定了几何体的外表。例如,材质可以定义一个几何体看起来是否像金属,是否是透明的等等。在 Three.js 中,材质对象有一些共同的属性,例如:常用的基础属性(控制物体的透明度、可见性、引用),融合属性(决定物体如何与背景融合)以及高级属性(控制底层 WebGL 上下文对象渲染物体的方式)。如下图所示,我们为大家整合了三类属性:

20-2

这些材质从简单的 THREE.MeshBasicMaterial 到复杂的 THREE.ShaderMaterial。如果你知道如何使用一种材质,可能也知道如何使用其他材质。例如,我们通过下列方式定义 最简单的材质对象:

const material = new THREE.MeshBasicMaterial({ color: Ox0000ff }); // 设置材质的颜色属性

另外,需要记住的是,材质的大部分属性都可以在运行时修改。但是有一些(如: side )是不能在运行时修改的。如果你要修改,需要将 needsUpdate 属性设置为 true

从对上面概念的阐述我们知道了创建一个网格模型,需要几何体、材质。当网格创建好之后,我们就可以将它添加到场景中并进行渲染。同样,我们可以对网格对象进行操作,网格对象有下列几种方法和属性:

  1. position: 决定网格对象相对于父对象的位置(通常父对象就是添加该对象的场景);
  2. rotation: 设置绕每个轴的旋转弧度;
  3. scale: 该属性可以沿着 x, y, z 轴缩放对象;
  4. translateX(amount): 沿着 x 轴将对象平移 amount 距离;
  5. translateY(amount): 沿着 y 轴将对象平移 amount 距离;
  6. translateZ(amount): 沿着 z 轴将对象平移 amount 距离;
  7. visible(amount): 决定网格对象的可见性。该属性值为 false 时, THREE.Mesh 将不会被渲染到场景中;

光源

Three.js 中的光源可以分为两种类型。我们用下图进行分解:

20-3

其中,基础光源是最常见的,所有这些光源都是基于 THREE.Light 对象扩展的。对于这些光源,我们需要知道的是:

  • THREE.AmbientLight 光源的颜色可以附加到场景中的每一种颜色上,通常用来柔化生硬的颜色和阴影。
  • THREE.PointLight 光源会朝所有方向发射光线,不能被用来阴影。
  • THREE.SpotLight 光源类似手电筒,它有一个锥形的光束,可以配置它随着距离的增大而逐渐变弱,并且可以生成阴影。
  • THREE.Directional 光源相当于远光的效果,比如太阳光。它的光线呈平行,其光强并不会随着与目标对象距离的增大而减弱。
  • THREE.HemisphereLight 光源,该光源考虑了天空和地面的反射,可以具备更加自然的户外效果。
  • THREE.AreaLight 不从单个点发射光线,而是从一个很大的区域发射光线。

对于这些光源,具体的使用我们在后面的实验中将穿插进行介绍。

至此,我们将 Three.js 中的基础和核心的理论知识进行了阐述。你不一定需要知道他们怎么使用,但一定要知道他们的含义。在具体的使用时,可以直接到 官网 查看相关的 API ,查看使用方法。

实验:使用 Three.js 绘制正方体

在开始实验之前,我们需要怎么加载 Three.js 文件。便捷的方式就是使用 CDN 的方式引入该文件。

  1. 首先,我们创建一个场景:我们需要添加三个必要的对象 —— 场景、摄像机和渲染器。我们在 WebIDE 中新建文件 index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>20-使用 Three.js 绘制正方体</title>
    <style>
      body {
        margin: 0;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.js"></script>
    <script>
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        65,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      ); //使用 透视摄像机,第一个参数:视野角度,第二个参数是长宽比值,第三个参数是近面,第四个参数是远面
      const renderer = new THREE.WebGLRenderer(); // 定义 渲染器
      renderer.setSize(window.innerWidth / 2, window.innerHeight / 2); // 设置一个渲染器的大小尺寸,如果你希望保持你的应用程序的尺寸,但是以较低的分辨率来渲染,你可以在调用setSize时,给updateStyle(第三个参数)传入false
      document.body.appendChild(renderer.domElement); //将renderer(渲染器)这个元素添加到我们的HTML文档中
    </script>
  </body>
</html>

接着,先安装 http-server 服务,安装完成后运行该服务,并点击右侧的 Web 服务 打开网页。安装 http-server 的操作如下图所示(该操作在后续的实验中会用到,请谨记该命令):

npm install http-server -g
http-server

20-4

注意在进入下一章节的内容前,需要先按下 Ctrl + C 停止运行 http-server,否则可能导致 8080 端口持续被占用。如果被占用可以输入 pkill http-server 杀死进程。

打开浏览器后,我们会看到如下视图:

20-5

OK,目前场景中正如你看到的还没有任何物体。接下来我们要向场景中添加一个正方体。

  1. 添加正方体:
const geometry = new THREE.BoxGeometry(2, 2, 2); // 定义一个 立方体 对象,这个对象包含了一个立方体中所有的顶点(vertices)和面(faces)
const material = new THREE.MeshLambertMaterial({ color: 0xffffff }); // 定义材质,设置材质的颜色属性,值是一个十六进制
const cube = new THREE.Mesh(geometry, material); // 定义一个网格,它是包含有一个几何体以及应用在此几何体上的材质的对象
cube.castShadow = true;
scene.add(cube); // 添加到场景中
camera.position.z = 5; // 移动摄像机的 z 轴位置
  1. 添加光源:
const spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(-10, 10, 0); //从位置(-10,10,0)开始照射场景
spotLight.castShadow = true; // 启用阴影功能
spotLight.shadow.mapSize = new THREE.Vector2(1024, 1024); //控制阴影的精细程度
spotLight.shadow.camera.far = 100;
spotLight.shadow.camera.near = 0.1;
scene.add(spotLight);
  1. 渲染场景,并使正方体动起来:
function renderScene() {
  // 渲染
  requestAnimationFrame(renderScene); //在每次屏幕刷新时对场景进行绘制
  renderer.render(scene, camera); // 渲染
  cube.rotation.x += 0.01; //改变 rotation 属性值,让正方体转动起来
  cube.rotation.y += 0.01;
}
renderScene();
  1. 完整代码:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>20-使用 Three.js 绘制正方体</title>
    <style>
      body {
        margin: 0;
      }
      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.js"></script>
    <script>
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        65,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      ); //使用 透视摄像机,第一个参数:视野角度,第二个参数是长宽比值,第三个参数是近面,第四个参数是远面
      const renderer = new THREE.WebGLRenderer(); // 定义 渲染器
      renderer.setSize(window.innerWidth / 2, window.innerHeight / 2); // 设置一个渲染器的大小尺寸,如果你希望保持你的应用程序的尺寸,但是以较低的分辨率来渲染,你可以在调用setSize时,给updateStyle(第三个参数)传入false
      document.body.appendChild(renderer.domElement); //将renderer(渲染器)这个元素添加到我们的HTML文档中

      const spotLight = new THREE.SpotLight(0xffffff);
      spotLight.position.set(-10, 10, 0); //从位置(-10,10,0)开始照射场景
      spotLight.castShadow = true; // 启用阴影功能
      spotLight.shadow.mapSize = new THREE.Vector2(1024, 1024); //控制阴影的精细程度
      spotLight.shadow.camera.far = 100;
      spotLight.shadow.camera.near = 0.1;
      scene.add(spotLight);

      const geometry = new THREE.BoxGeometry(2, 2, 2); // 定义一个 立方体 对象,这个对象包含了一个立方体中所有的顶点(vertices)和面(faces)
      const material = new THREE.MeshLambertMaterial({ color: 0xffffff }); // 定义材质,设置材质的颜色属性,值是一个十六进制
      const cube = new THREE.Mesh(geometry, material); // 定义一个网格,它是包含有一个几何体以及应用在此几何体上的材质的对象
      cube.castShadow = true;
      scene.add(cube); // 添加到场景中
      camera.position.z = 5; // 移动摄像机的 z 轴位置

      function renderScene() {
        // 渲染
        requestAnimationFrame(renderScene); //在每次屏幕刷新时对场景进行绘制
        renderer.render(scene, camera); // 渲染
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
      }
      renderScene();
    </script>
  </body>
</html>

重新刷新浏览器,将看到如下效果(如果不生效请多次刷新,也可直接使用新浏览器或者隐私窗口打开):

20-6

需要的注意的是,如果要添加光源,在物体上添加基本材质 ( MeshBasicMaterial )不会对光源有任何反应,基本材质只会使用指定的颜色来渲染物体。因此,在实验中我们改变了材质。

实验总结

以上就是我们本次实验需要学习的内容。其中,理论部分涉及较多的知识点,你可以通过分类进行处理,根据文中提供的思维导图归纳知识点。其次,我们利用已学的知识进行了实验,希望你可以学以致用,将知识点和实践结合起来。

posted @   雨晨*  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示