Three.js 介绍(draft)

学习资料:
 
Example:
 
 

Three.js概述

Three.js 是基于 WebGL 技术,用于浏览器中开发 3D 交互场景的 JS 引擎。它对WebGL提供的接口进行了封装,降低了学习成本并提升了开发效率。web 3D主要应用于几个方面:
例子:
默认 WebGL 只支持简单的 点、线、三角,Three.js 就是在此 WebGL 基础之上,封装出强大且使用起来简单的 JS 3D 类库。
目前主流现代浏览器都已支持 WebGL,也意味着支持 Three.js。
 

3大核心关键模块

场景(scene)

场景是所有物体的容器。

场景的几个概念

概念1:一个局部的相对空间,即为一个场景

例如太阳系就是一个空间(场景)

概念2:一个空间(场景) 又可能是由 几个子空间(场景) 组合而成

太阳系由 8 大行星构成
行星除了本身之外还包卫星,例如地球和月球
地球上又包含陆地和海洋
陆地上又包含中国,中国包含你此刻所处的空间

概念3:表面上添加某场景,但实际上执行的是合并场景

相机(camera)

决定场景中哪些角度的内容会显示出来。
Three.js中我们常用的有两种类型的相机:正交(orthographic)相机、透视(perspective)相机。一般情况下为了模拟人眼我们都是使用透视相机; 正交镜头的特点是,物品的渲染尺寸与它距离镜头的远近无关。也就是说在场景中移动一个物体,其大小不会变化。正交镜头适合2D游戏。 透视镜头则是模拟人眼的视觉特点,距离远的物体显得更小。透视镜头通常更适合3D渲染。
const fov = 75; const aspect = 2; // the canvas default const near = 0.1; const far = 5; const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
fovfield of view的缩写。 当前的情况是垂直方向为75度。 注意three.js中大多数的角用弧度表示,但因透视摄像机使用角度表示。
aspect指canvas的显示比例
nearfar代表摄像机前方将要被渲染的空间。 任何在这个范围前面或者后面的物体都将被裁剪(不绘制)。
notion image

渲染器(renderer)

将 相机 中的内容渲染到浏览器页面中。
const renderer = new WebGLRenderer({ canvas: canvasRef.current })
关键词:

光源(Light)

指不同种类的光。
在Three.js中光源是必须的,如果一个场景你不设置灯光那么世界将会是一片漆黑。Three.js内置了多种光源以满足特定场景的需要。
notion image

几何体(Geometry)

顾名思义,就是几何体,例如 球体、立方体、平面、以及自定义的几何体(汽车、动物、房子、数目等)。
在 Three.js 中,一个几何体的来源有 3 个:
  1. Three.js 中内置的一些基本几何体
  1. 自己创建自定义的几何体
  1. 通过文件加载进来的几何体
notion image
notion image

材质(Material)

几何体的表面属性,包括颜色、光亮程度。
光亮程度是指物体表面反射光的能力值。Three.js 内置了不同的材质,不同材质对应不同的光亮程度。
内置材质 MeshBasicMaterial 是一种不可以反射光的材质,光源以何种角度照射到该物体上,该物体都不显示 “光亮”,而仅仅以材质本身的颜色或纹理来显示。
材质(material) 即 线段属性或物体表面的一些颜色、贴图、光亮程度、反光特性、粗糙度等属性。
按照用途,所以材质大体上可以划分为:
  1. 点材质(应用来点、粒子上)
  1. 线性材质(应用在线段或虚线上)
  1. 基础材质(应用在面上的各种材质)
  1. 特殊用途的材质(例如阴影)
  1. 自定义材质
一个材质可以引用一个或多个纹理。
 
材质基础
材质名称
解释说明
所有材质的父类
点材质
材质名称
解释说明
点材质(粒子材质)
线性材质
材质名称
解释说明
线段材质(颜色、宽度、断点、连接点等属性)
虚线材质
基础材质(针对”面“)
材质名称
解释说明
最基础的材质,不反射光,仅显示材质本身颜色
仅顶点处反射光
自带光效(明暗)的材质
任何点都反射光,拥有光泽度
卡通着色
除光泽度外,还有粗糙度和金属度
除光泽度、粗糙度、金属度外,还有清漆度和清漆粗糙度
特殊用途材质
材质名称
解释说明
阴影材质
另外一种阴影投射材质
远近距离深度着色材质
网格法向量材质
精灵材质/雪碧材质
自定义材质
材质名称
解释说明
着色器材质
原始着色器材质

纹理(Texture)

纹理可以简单理解为一种图像或一张图片,用来包裹到几何体表面上。
材质(material) 更多是表达一个物体表面的物理特性和一些简单的外观颜色。而 纹理(Texture) 则专门来设置物体表面贴合的彩色图片的,具体做法就是:
  1. 使用纹理加载器 TextureLoader 加载外部图片(.jpg或.png)
  1. 通过设置 物体的 .map 属性,将加载得到的外部图片贴合在物体表面
纹理图片尺寸:是指图片本身的宽高尺寸
物体渲染面尺寸:是指最终在镜头中物体某一个面所渲染出的尺寸
物体渲染尺寸是由 物体本身大小和物体距离镜头的远近来共同决定的。
纹理图片尺寸和物体渲染面尺寸几乎是不可能刚好完全相同的。
这个几率几乎不存在
那么我们就需要考虑,假设 2 者尺寸不相同时,Three.js 是如何处理的。
首先我们先了解一下 mipmap 算法模式。

什么是 mipmap ?

mipmap 是目前 3D 应用最为广泛的纹理映射技术之一。
mipmap 的原则是将图片的每个边(宽和高)对应的分辨率只取上一级的 1/2。
这样便可以通过计算,不断得到 面积为上一级 1/4 的图片数据,直至最终图片为 1像素 * 1 像素。
而 Three.js 会选择最接近于物体渲染面尺寸 的那一级渲染图片,并渲染出效果。
假设 纹理图片尺寸大于渲染面尺寸,此时需要对纹理进行缩小。
这种策略其实相当于牺牲掉了纹理贴图的精准性,换来了计算所需性能上的提升。
这也解释了为什么有时候即使纹理图片尺寸非常大,但某些时候渲染出的物体实际上还是略有模糊。
notion image
notion image

网格(Mesh)

一种特定的 几何体和材质 绘制出的一个特定的几何体系。
网格包含的内容为:几何体、几何体的材质、几何体的自身网格坐标体系
同一个材质和几何体可以被多个mesh使用。
一个场景可以同时添加多个mesh。
在计算机的世界里,一条弧线是由有限个点构成的有限条线段连接得到的。当线段数量越多,长度就越短,当达到你无法察觉这是线段时,一条平滑的弧线就出现了。 计算机的三维模型也是类似的。只不过线段变成了平面,普遍用三角形组成的网格来描述。我们把这种模型称之为 Mesh 模型。 在 threeJs 的世界中,材质(Material)+几何体(Geometry)就是一个 mesh。Geometry就好像是骨架,材质则类似于皮肤,对于材质和几何体的分类见下表格
notion image
 
 

Example:

import * as THREE from 'three'; import { TWEEN } from './jsm/libs/tween.module.min.js'; import { OrbitControls } from './jsm/controls/OrbitControls.js'; // 构建渲染器 WebGLRenderer var renderer = new THREE.WebGLRenderer(); // 设置显示比例 renderer.setPixelRatio( window.devicePixelRatio ); // 构建一个透视投影的相机 var camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 2000 ); // 构建一个轨道控制器,主要就是通过鼠标来控制相机沿目标物体旋转,从而达到像在旋转场景一样,可以从各个不同角度观察物体 var controls = new THREE.OrbitControls( camera, renderer.domElement ); // 构建场景 var scene = new THREE.Scene(); // 构建Phong网格材质MeshPhongMaterial,该材质可以模拟具有镜面高光的光泽表面,一个用于接收阴影的平面,一个用于场景中的物体 Box var matFloor = new THREE.MeshPhongMaterial(); var matBox = new THREE.MeshPhongMaterial( { color: 0xaaaaaa } ); // 构建几何体,同样分别用于 平面 和 Box var geoFloor = new THREE.PlaneBufferGeometry( 2000, 2000 ); var geoBox = new THREE.BoxBufferGeometry( 3, 1, 2 ); // 构建平面网格 mesh var mshFloor = new THREE.Mesh( geoFloor, matFloor ); mshFloor.rotation.x = - Math.PI * 0.5; // 构建 box 网格 mesh var mshBox = new THREE.Mesh( geoBox, matBox ); // 构建环境光 var ambient = new THREE.AmbientLight( 0x111111 ); // 构建 3 个不同颜色的 聚光灯(SpotLight) var spotLight1 = createSpotlight( 0xFF7F00 ); var spotLight2 = createSpotlight( 0x00FF7F ); var spotLight3 = createSpotlight( 0x7F00FF ); // 声明用于描述聚光灯的 3 个不同光束帮助器 var lightHelper1, lightHelper2, lightHelper3; function createSpotlight( color ) { var newObj = new THREE.SpotLight( color, 2 ); newObj.castShadow = true; newObj.angle = 0.3; newObj.penumbra = 0.2; newObj.decay = 2; newObj.distance = 50; newObj.shadow.mapSize.width = 1024; newObj.shadow.mapSize.height = 1024; return newObj; }
 
添加xyz图

2.初始化

function init() { // 将平面,box,环境光以及光源辅助器等全部添加到 scene 中 renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.outputEncoding = THREE.sRGBEncoding; camera.position.set( 46, 22, - 21 ); spotLight1.position.set( 15, 40, 45 ); spotLight2.position.set( 0, 40, 35 ); spotLight3.position.set( - 15, 40, 45 ); lightHelper1 = new THREE.SpotLightHelper( spotLight1 ); lightHelper2 = new THREE.SpotLightHelper( spotLight2 ); lightHelper3 = new THREE.SpotLightHelper( spotLight3 ); matFloor.color.set( 0x808080 ); mshFloor.receiveShadow = true; mshFloor.position.set( 0, - 0.05, 0 ); mshBox.castShadow = true; mshBox.receiveShadow = true; mshBox.position.set( 0, 5, 0 ); scene.add( mshFloor ); scene.add( mshBox ); scene.add( ambient ); scene.add( spotLight1, spotLight2, spotLight3 ); scene.add( lightHelper1, lightHelper2, lightHelper3 ); document.body.appendChild( renderer.domElement ); onWindowResize(); window.addEventListener( 'resize', onWindowResize ); controls.target.set( 0, 7, 0 ); controls.maxPolarAngle = Math.PI / 2; controls.update(); }
 

3.渲染

//补间动画 function tween( light ) { new TWEEN.Tween( light ).to( { angle: ( Math.random() * 0.7 ) + 0.1, penumbra: Math.random() + 1 }, Math.random() * 3000 + 2000 ) .easing( TWEEN.Easing.Quadratic.Out ).start(); new TWEEN.Tween( light.position ).to( { x: ( Math.random() * 30 ) - 15, y: ( Math.random() * 10 ) + 15, z: ( Math.random() * 30 ) - 15 }, Math.random() * 3000 + 2000 ) .easing( TWEEN.Easing.Quadratic.Out ).start(); } function animate() { tween( spotLight1 ); tween( spotLight2 ); tween( spotLight3 ); setTimeout( animate, 5000 ); } function render() { TWEEN.update(); if ( lightHelper1 ) lightHelper1.update(); if ( lightHelper2 ) lightHelper2.update(); if ( lightHelper3 ) lightHelper3.update(); renderer.render( scene, camera ); requestAnimationFrame( render ); } init(); render(); animate();