three.js | 初识3D世界

65 阅读5分钟

概念理解

在3D世界,或者说三维世界,每个物体(three中对应的“名词”是Mesh)都有自己的形状(Geometry),以及材质(Material)。 偏一下题,我突然想到了这可以关联现实世界的摄影:

  1. 同样都是“将物体/人展现出来”,所以需要有“物体Mesh”的概念(3D中只有“物体”,一切都是“物体”)、需要有“相机 Camera”的概念
  2. 物体/人的形状对你如何拍照、拍摄角度同样有不可忽视的影响,所以需要有“场景 Scene”的概念
  3. 物体的材质同样需要不同的“光”去定格,所以需要有“光”的概念

是的,光! 如同现实世界有光和阴影,可以展示不同的明暗效果一样,three中也有一个“灯光Light”的概念。 综上所说,相机拍出来的景色是体现在“照片”上,那3D世界中,当然还应该有一个“渲染器 Renderer”的概念,负责把场景 Scene、相机 Camera、灯光 Light 这些 综合渲染到 canvas 画布上。这就是three的基本概念了。

第一次实践

先来打印下three:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>three-1</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <script type="module">
        import * as THREE from "https://unpkg.com/three@0.174.0/build/three.module.js";
        console.log(THREE)
    </script>
</body>
</html>

如图代表引入成功: 在这里插入图片描述 下面我想在“画布”中放一个正方体!

下面例子只是three其中一个小场景,需要查看其他api请浏览官方文档:three.js

依旧拍照 —— 拍人不可能只拍人,需要在一个“场景”中拍:

const scene = new THREE.Scene();

上面说了,和“形状”有关的名词(其实是three提供的方法)叫Geometry:

// BoxGeometry是Three提供的创建“长方体”的方法,接收参数分别代表长、宽、高
const geometry = new THREE.BoxGeometry(100, 100, 100);

除了长方体,还有“圆柱体CylinderGeometry”、“球体SphereGeometry”、“圆锥ConeGeometry”、“矩形平面PlaneGeometry”、“圆平面CircleGeometry”方法

别急,物体怎么能没有颜色呢?还记得“材质”Material吗,我们让这个物体支持“漫反射”并且颜色是“橘色”:

const material = new THREE.MeshLambertMaterial(({
 color: new THREE.Color('orange')
}));

除了漫反射,还有“无光影响MeshBasicMaterial”、“高光MeshPhongMaterial”,以及两个物理光“MeshStandardMaterial”和“MeshPhysicalMaterial”

我们利用“形状”和“材质”一起创建出一个“物体”,并添加到“场景”中:

const mesh = new THREE.Mesh(geometry, material);
// 就像人拍照时要站在哪里一样,“物体”怎么能没有位置呢?
mesh.position.set(0, 0, 0); // x、y、z坐标
scene.add(mesh);

这样一个物体就创建出来了。 但是还不够。我们说了,“物体”要被看到,除了有这个物体,还需要“有光照射”、“有相机拍”、“有照片承载”:

 const pointLight = new THREE.PointLight(0x00FFFF, 10000);
 pointLight.position.set(80, 80, 80);
 scene.add(pointLight);

光源有了,我们让一束“蓝色”的“点光源”从(80,80,80)的位置以强度10000照向物体(光源默认照向坐标0,0,0)。

three模拟了几种光源效果,除了点光源,还有“环境光AmbientLight”、“聚光灯光源SpotLight”、“平行光DirectionalLight”


 const width = window.innerWidth;
 const height = window.innerHeight;
 const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
 camera.position.set(200, 200, 200);
 camera.lookAt(0, 0, 0);
 const renderer = new THREE.WebGLRenderer();
 renderer.setSize(width, height)
 renderer.render(scene, camera);
 document.body.append(renderer.domElement);

相机和照片也有了,我们的相机位于(200,200,200),看向(0,0,0)。 在这里插入图片描述 注意!依旧拿“拍照”类比,3D世界一切基于“相机Camera”,比如上面这张图,因为相机在(200,200,200),所以主视角是三维坐标都是正数值的某个中心位置,所以图片中物体是“某个点朝向屏幕”!

拿人眼来说,我们看一个物体,其实是在看一个“视锥体”范围内的场景: 在这里插入图片描述 相机拍物体也是一样。 所以Camera的第一个参数是fov,相机视锥体竖直方向视野角度,这里我们设置的是60。改成20试试看: 在这里插入图片描述 “看”不完整了。

Camera的第二个参数是宽高比,也就是这个视椎体的宽和高的比例,一般设置为Canvas画布宽高比width / height。 第三个和第四个参数是展示视椎体的哪一部分,最近是哪(相机视锥体近裁截面相对相机距离),最远是哪(相机视锥体远裁截面相对相机距离)。

然后我们可以观察到很多3D的网站都支持通过鼠标拖动来 360 度观察。这个用 Three 提供的轨道控制器 OrbitControls 即可实现:

// 引入
import { OrbitControls } from 'https://unpkg.com/three@0.174.0/examples/jsm/controls/OrbitControls.js';

然后在“渲染render”后添加代码:


    //...
    const camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
    //...
    const renderer = new THREE.WebGLRenderer();
    //...
    const controls = new OrbitControls(camera, renderer.domElement);
    console.log(controls)

但是只有这样是不行的,renderer创建场景只被调用了一次,没法完成“拖动”。所谓拖动就是不断的把新场景渲染到画布中,所以我们要封装函数并不断调用:

- renderer.render(scene, camera);

+ function render() {
+     renderer.render(scene, camera);
+     requestAnimationFrame(render)
+ }
+ render()

把 render 改成渲染循环,用 requestAnimationFrame 来一帧帧的循环渲染,使调用频率和显示器刷新率一致。

笔者前段时间弄了一个微信公众号:前端Code新谈。里面暂时有webrtc、前端面试和用户体验系列文章,最近暂时搁置了webrtc,新开了一个系列“three.js”,欢迎关注!希望能够帮到大家,也希望能互相交流!一起学习共同进步