概念理解
在3D世界,或者说三维世界,每个物体(three中对应的“名词”是Mesh)都有自己的形状(Geometry),以及材质(Material)。 偏一下题,我突然想到了这可以关联现实世界的摄影:
- 同样都是“将物体/人展现出来”,所以需要有“物体Mesh”的概念(3D中只有“物体”,一切都是“物体”)、需要有“相机 Camera”的概念
- 物体/人的形状对你如何拍照、拍摄角度同样有不可忽视的影响,所以需要有“场景 Scene”的概念
- 物体的材质同样需要不同的“光”去定格,所以需要有“光”的概念
是的,光! 如同现实世界有光和阴影,可以展示不同的明暗效果一样,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”,欢迎关注!希望能够帮到大家,也希望能互相交流!一起学习共同进步