开启Three.js之旅(一)基础详解

436 阅读13分钟

Three.js必备

官方文档

推荐文档

官方在线编辑平台

推荐免费的素材网站

官网一些说法用词对于刚接触的我有些晦涩,下面的我会追加自己的理解,欢迎来指正讨论。

构建项目

可以结合多种框架构建,也可以结合TS

  1. 非框架,JavaScript 例如我们可以使用vite进行帮助我们构建 npm init vite 进行一系列的设置,构建好项目之后,我们可以进行一些初始化的设置 下面我们来举一个例子 在main.js中写一个demo
    import './style.css'
     // 导入three.js
     import * as THREE from 'three'
    
     // 创建场景
     const scene = new THREE.Scene()
    
     // 创建相机--------------------------------fov,  宽高比,  近距离,  远距离
     const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
    
     // 创建渲染器
     const renderer = new THREE.WebGLRenderer()
     renderer.setSize(window.innerWidth, window.innerHeight)
     document.body.appendChild(renderer.domElement)
    
     // 创建立方体
     const geometry = new THREE.BoxGeometry(1, 1, 1)
     // 创建立方体的材质
     const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
     // 把立方体和材质添加到场景中
     const cube = new THREE.Mesh(geometry, material)
     // 设置立方体的位置
     cube.position.x = 0.5
     cube.position.y = 0.5
     cube.position.z = 0.5
     // 把立方体添加到场景中
     scene.add(cube)
    
     // 设置相机的位置
     camera.position.z = 5
     camera.lookAt(0, 0, 0)
    
     // 渲染函数
     function render() {
       requestAnimationFrame(render)
       cube.rotation.x += 0.01
       cube.rotation.y += 0.01
       renderer.render(scene, camera)
     }
    
     render()
    
    结果,一个正在旋转的正方体

image.png

  1. 结合Vue 基本逻辑与上面相同,找到合适的页面,然后进行初始化,在这里就不再多赘述
  2. 结合React 除了基本的初始化,上面的代码写在useEffect中的回调即可

核心流程

stateDiagram-v2
Camera --> renderer.render(scene,camera)
Scene --> renderer.render(scene,camera)
renderer --> renderer.render(scene,camera)
renderer.render(scene,camera) --> appendChild(renderer.domElement)

场景(scene)

我们可以把整个三维场景认为是scene,也就是放置物体,灯光和摄像机的地方

const scene = new THREE.Scene();

相关属性

  • background 默认值是null,支持Texture,或者等边矩形
  • environment 默认值是null, 该纹理贴图会被设定为所有物理材质的环境贴图

环境贴图

关键阐释: hdr: 高动态范围成像(英语:High Dynamic Range Imaging,简称HDRI或HDR),在计算机图形学电影摄影术中,是用来实现比普通数位图像技术更大曝光动态范围(即更大的明暗差别)的一组技术。高动态范围成像的目的就是要正确地表示真实世界中从太阳光直射到最暗的阴影这样大的范围亮度

import * as THREE from 'three';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
const rgbeLoader = new RGBELoader();
export default function setEnvironment(scene) {
    rgbeLoader.load('./envMap.hdr', function (envMap) {
        scene.environment = envMap;
        // hdr作为环境贴图生效,设置.mapping为EquirectangularReflectionMapping
        envMap.mapping = THREE.EquirectangularReflectionMapping;
    })
}

AxesHelper-辅助类

我们可以建立辅助线,方便我们开发

// AxesHelper:辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(150); // 可以自由设置大小
scene.add(axesHelper);

如图

image.png

three.js坐标轴颜色红R、绿G、蓝B分别对应坐标系的xyz轴,对于>three.js的3D坐标系默认y轴朝上

场景Scene

场景我们可以就是理解为场景,我们可以往里面加入很多东西 就像一间屋子的场景,我们可以往里面加桌子椅子一样

THREE.Scene()是一个构造函数,返回一个实例scene

属性

  • background

    若不为空,在渲染场景的时候将其设置为背景,且背景总是首先被渲染的。

  • environment

    若该值不为null,则该纹理贴图将会被设为场景中所有物理材质的环境贴图(然而,该属性不能够覆盖已存在的、已分配给MeshStandardMaterial.envMap的贴图)

    关于这个是整个场景的环境,我可以把整个场景置地于室内,也可以置地于蓝蓝的天空之下

相机Camera

PerspectiveCamera

用来模拟人眼看到景象,也是用的最多的一种相机

const camera = new THREE.PerspectiveCameraPerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number );

参数说明

  1. fov 相机锥体垂直视野角度
  2. aspect 视锥体长宽比
  3. near 视锥体近端面
  4. far 远端面(后面我增加一个图来说明情况)

属性

  1. zoom 缩放倍数 默认值为1

OrbitControls -- 轨道控制器

通过轨道控制器,我们可以自由的使用鼠标去拖动场景,调整我们看到的场景角度

原理就是:通过监听canvas的事件,来改变相机的参数,就可以改变我们看到的视角

Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动。

OrbitControls本质就是改变相机的参数,比如相机的位置属性

OrbitControls 是一个附加组件,必须显式导入

const renderer = new THREE.WebGLRenderer(); 
renderer.setSize( window.innerWidth, window.innerHeight ); 
document.body.appendChild( renderer.domElement ); 
const scene = new THREE.Scene(); 
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 ); 
const controls = new OrbitControls( camera, renderer.domElement ); //controls.update() must be called after any manual changes to the camera's transform camera.position.set( 0, 20, 100 );
controls.update(); 
function animate() { 
    requestAnimationFrame( animate ); // required if controls.enableDamping or controls.autoRotate are set to true 
    controls.update();   // 这里不更新的话,很多效果是没有的,比如阻尼效果
    renderer.render( scene, camera ); 
}

注意

camera和OrbitControls的关系

渲染器

WebGLRenderer

使用WebGL渲染你的场景

WebGLRenderer是构造器

构造器生成的实例, 需要挂在到对应的元素上

最终步骤都是加入到对应的元素

document.getElementById("container").appendChild('renderer');

方法

  • setSize(width:Number, height:Number, updateStyle:Boolean)
  • updateProjectionMatrix ,更新投影矩阵

注意

canvas画布宽高度动态变化,需要重置渲染器输出画布canvas尺寸和更新相机和渲染的参数,否则无法正常渲染。

// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
    // 重置渲染器输出画布canvas尺寸
    renderer.setSize(window.innerWidth, window.innerHeight);
    // 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
    camera.aspect = window.innerWidth / window.innerHeight;
    // 如果相机视锥体相关参数发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
    camera.updateProjectionMatrix();
};

CSS3DRenderer

也叫做css 3D渲染器

通过css3的transform属性,将层级的3D变换应用到DOM元素上。

限制:

  • 不能使用three.js的材质系统
  • 不能使用几何体

CSS3DRenderer仅仅关注普通的DOM元素(因为毕竟是css)

CSS3Renderer()是一个构造函数,返回一个实例

方法:

  • getSize()
  • setSize() 调整尺寸

最终步骤都是加入到对应的元素

document.getElementById("container").appendChild('cssrenderer.domElement');

灯光/阴影

现实中物体表面的明暗是受光的影响的。

在three.js中,用Mesh网格模型来模拟现实中的物体

stateDiagram-v2
AmbientLight环境光 --> Light
PointLight点光源 --> Light
SpotLight聚光灯 --> Light
Direction平行光 --> Light
Light --> Mesh
Mesh --> 漫反射MeshLamberMaterial
Mesh --> 高光MeshPhongMaterial
Mesh --> 物理MeshStandardMaterial、MeshPhysicalMaterial
Mesh --> 不受影响MeshBasicMaterial

Light -- 基类

光源的基类

Properties

  • color
  • intensity -- 光照强度

Methods

  • dispose -- 释放由该实例分配的GPU资源
  • copy
  • toJSON

AmbientLight -- 环境光

环境光会均匀的照亮场景中的所有物体。不能用来做投射阴影,因为没有方向。

构造函数

  • color
  • intensity -- 光照强度

Properties

  • isAmbientLight

demo

const light = new THREE.AmbientLight( 0x404040 , 1);
scene.add(light);

DirectionalLight -- 平行光

平行光是沿着特定方向发射的光,常常用来模拟太阳光的效果

构造函数

  • color 可选
  • intensity 强度

demo

const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5);
scene.add( directionalLight );

PointLight -- 点光源

点光源

demo

const pointLight = new THREE.PointLight(0xffffff, 1.0);  // 光源颜色, 光照强度

Properties

  • decay -- 光源衰减 0.0不衰减,默认2.0
  • position
    • set(x, y, z) -- 设定位置

阴影

加载器

关于加载器

值得我们注意的是加载文件路径的问题

以Vue为例,应该是静态目录下public存放我们的模型, 无论你在哪一级,three都会从这个目录下拿资源, ./对应的文件就可以了(不要用相对位置去拿资源或者其它)

Cache

也是很少直接使用,一般直接配置就可以

THREE.Cache.enabled = true   // 全局生效,要在所有FileLoader的加载器上启用缓存

其大致意思就是,当 THREE.Cache.enabled 设置为 true 时,Three.js 会启用缓存机制,它会自动缓存加载的纹理、模型等资源数据。这可以提高性能和加载速度,因为在后续使用相同资源时,可以直接从缓存中获取,而不需要重新加载。

THREE.Cache.enabled 设置为 false 时,缓存机制将被禁用,每次加载资源时都会重新加载。

默认为false

Methods

  • add(key, file)
  • get(key)
  • remove(key)
  • clear() -- 清除缓存中所有的值

一个简单的缓存系统,内部使用FileLoader

FileLoader

一般很少直接使用,直接使用更加高级的加载器,其会被大多数加载器内部使用

使用XMLHttpRequest来加载资源的低级类,并由大多数加载器内部使用。 它也可以直接用于加载任何没有对应加载器的文件类型。

demo

Methods

Loader

用于实现加载器的基类

Properties

  • path -- 加载资源的基本路径
  • crossOrigin -- 允许CROS的其他域加载url。默认为anonymous

Methods

  • load() -- 要看具体的加载器的用法
  • loadAsync

GLTFLoader

GLTF,用于更高效地传输、加载3D内容。该文件以JSON(.gltf)格式或者二进制(.glb)格式提供。

一个glTF组件可传输一个或多个场景, 包括网格、材质、贴图、蒙皮、骨架、变形目标、动画、灯光以及摄像机

RGBELoader

用来加载高动态范围(HDR)环境贴图。

HDR环境贴图以.hdr格式存储,表示更广泛的亮度范围

TextureLoader

加载texture的一个类。 内部使用ImageLoader来加载文件。

demo

// 立即使用纹理进行材质创建
const texture = new THREE.TextureLoader().load('textures/land_ocean_ice_cloud_2048.jpg' );
const material = new THREE.MeshBasicMaterial( { map: texture } );

TextureLoader

加载texture的一个类。 内部使用ImageLoader来加载文件

我们可以用它来加载图片,去做纹理

const texture = new THREE.TextureLoader().load( 'textures/land_ocean_ice_cloud_2048.jpg' );

创建材料时可以用到这个纹理

const material = new THREE.MeshBasicMaterial( { map: texture } );

就是一个可以把图片加工成纹理,可以生成material使用

Methods

  • load( url, onLoad, onProgress, onError )

    url -- 文件路径

    onLoadonProgressonError -- 字如其意,Function

纹理、贴图

纹理

可以直接作用于材质Metarial上,对应map,我们直接可以看下面这个例子

const geometry = new THREE.PlaneGeometry(2000, 2000);
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./瓷砖.jpg');
const material = new THREE.MeshLambertMaterial({
    // 设置纹理贴图:Texture对象作为材质map属性的属性值
    map: texture,//map表示材质的颜色贴图属性
});
const mesh = new THREE.Mesh(geometry, material);

贴图

贴图分为很多种,普通贴图、高光贴图、透明贴图、环境贴图、光照贴图、环境光遮蔽贴图等 贴图有专门的加载器TextLoader, 值得注意的是,加载资源路径默认是public路径之下的。

雾有两种方式

  1. 线性型 由近及远,雾线性增加
  2. 指数型 由近及远,指数增加(近处很清晰,慢慢向远处会急剧增加)

材质

顾名思义就是材质的意思

光照对材质的影响

image.png

Metarial

材质的抽象基类

就只是材质,与渲染器无关

Constructor

Properities

  • alphaHash -- 启用alphaHash透明度
    应用透明度贴图等,要开启的选项,用来控制整个表面的透明度(白色完全透明,黑色完全不透明),默认为null

Methods

  • clone -- 返回相同的材质
  • copy -- copy材质

MeshBasicMaterial

基础网格材质,不会受到光照(light)的影响

MeshLambertMaterial

对于光照(light)会有漫反射的效果

漫反射是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,由于各点的法线方向不一致,反射光线会向不同的方向无规则地反射,这种反射称为“漫反射”或“漫射”。

MeshPhysicalMaterial

MeshStandardMaterial

基于物理的渲染(PBR), 在实践中,该材质提供了比MeshLambertMaterialMeshPhongMaterial 更精确和逼真的结果,代价是计算成本更高。

这个应该是材质中最逼真的,最好看的(个人认为),能够写出金属光泽

Properties

  • alphaMap -- 贴图是一张灰度纹理,用于控制整个表面的不透明度
  • aoMap
  • aoMapIntensity
  • color -- 材质的颜色
  • envMap -- 环境贴图
  • fog -- 材质是否受雾的影响
  • map -- 贴图,可以结合纹理(texture)
  • metalness -- 材质与金属的相似度。金属1.0 ~ 非金属0.0
  • roughness -- 材质的粗糙程度。 平滑的镜面反射0.0 ~ 完全漫反射1.0

metalness 金属度越高,材料表面会越暗(因为更像金属)

Methods(见Material)

Evenets

  • change -- 当摄像机被组件改变时触发
  • start -- 初始化交互时触发
  • end -- 当交互结束时触发

Attributes

  • autoRotate -- 自动旋转
  • maxDistance -- 相机最多向外移动多少
  • minDistance -- 相机最多向内移动多少
  • target -- 控制器的焦点,object的轨道围绕它运行,可以更改其焦点
  • enableDamping -- 设置带阻尼的惯性(设置之后操作体验感更好)
  • dampingFactor -- 设置阻尼系数 范围0~1之间

更多属性参考官方文档

Methods

  • update 更新控制器。必须在摄像机的变换发生任何手动改变后调用

物体

Mesh 网格

几何体

几何体是由面构成的,在three.js中面是由一个或多个三角形构成(其实计算机很多构型上都是三角形)

BoxGeometry
BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)

Params

  1. width
  2. height
  3. depth
  4. widthSegments 可选宽度的分段数
  5. heightSegments 可选深度的分段数
  6. depthSegments 可选的深度的分段数

Properties

关于位置和相对位置

我们知道直接添加一个物体会默认相对于原点(0,0,0)

但是我们可以在一个物体中添加一个物体,子物体就会相对于父物体,看下面的例子 例如:

// 创建立方体
const geometry = new THREE.BoxGeometry(1, 1, 1)
// 创建立方体的材质
const material_1 = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const material_2 = new THREE.MeshBasicMaterial({ color: 0xde3c53 })
// 创建父物体
const parent = new THREE.Mesh(geometry, material_1)
// 创建立方体
const cube = new THREE.Mesh(geometry, material_2)
// 父立方体中加入子立方体
parent.add(cube)
// 设置立方体的位置
parent.position.x = -3
cube.position.x = 3
// 把立方体添加到场景中
scene.add(parent)

结果

image.png

动画系统

requestAnimationFrame 利用window.requestAnimationFrame实现渲染

旋转动画

推荐参考文章

矩阵变换

Three.js使用matrix编码3D变换

解决方案

给模型绑定click函数

其实什么函数都可以,判断点击

鼠标放模型上变小手

优化方案

优化模型体积之神 -- DRACOLoader

这个优化真的很厉害,模型一般比较大,用这个基本可以优化自身的一半,甚至更多

尽量复用,使用一个DRACOLoader的实例即可

我们来封装一个函数(结合Vue,模型都是glb, 其实这个功能很强大,能优化很多种格式的)

initDracoLoader(){
  this.dracollLoader = new DRACOLoader();  // 属于拓展,需要显式引入
  this.dracollLoader.setDecoderPath('./draco/');
  this.dracollLoader.preload();
}

使用

const loader = new GLTFLoader();
loader.setDRACOLoader(this.dracollLoader)
loader.load(element, (gltf) =>{
    ...
})

有几个需要注意的点:

  1. setDecoderPath('/draco/')的路径,是自定义的。这里需要一个操作就是把对应的文件夹copy到public目录下(其它也可以)

    关键是在threejs包中找到,版本不同,可能文件的位置不同,

image.png

  1. 别忘记了对你原本的glb文件,分别进行处理
gltf-pipeline -i old.glb -o new.glb -d

这个工具需要全局下载

npm install -g gltf-pipeline

推荐文章

Three——glb模型压缩

github-draco