Three.js basics summary
Three.js basics summary
Original: https://blog.csdn.net/weixin_42776111/article/details/137536937
Introduction
Three.js is a JavaScript library for creating and displaying 3D graphics in the browser. It is built on WebGL and lets you build 3D scenes and interactions. It supports creating objects, lights, animation, and user input, and importing formats like OBJ and GLTF, plus custom materials and shaders.
Site: https://threejs.org/
Docs: https://threejs.org/docs/
Install
With Node.js and npm, run:
npm install three --save
To install a specific version, for example:
npm install three@0.162.0
Create a DOM container
To show Three.js output, you need a DOM element to mount the 3D scene.
<div ref="edThreeBox" id="ed-three-box"></div>
let dom = this.$refs.edThreeBox
// or
let dom = document.getElementById('ed-three-box')
Scene
Scene is the virtual 3D world. Add all visible content (models, lights, helpers) to the scene.
import { Scene } from "three";
const scene = new Scene();
PerspectiveCamera
Three.js has OrthographicCamera and PerspectiveCamera. PerspectiveCamera simulates how the eye sees the world.
import { PerspectiveCamera } from "three";
let camera = new PerspectiveCamera(50, dom.offsetWidth / dom.offsetHeight, 1, 2000);
camera.position.set(5, 5, 5);
camera.lookAt(new Vector3(0, 0, 0));
WebGLRenderer
WebGLRenderer draws the scene and camera to a 2D image in an HTML element, using WebGL and GPU acceleration.
// Import renderer
import {WebGLRenderer }from "three";
//初始化渲染器
let renderer = new WebGLRenderer();
//把渲染结果挂载到dom节点
dom.appendchild(renderer.domElement);
//设置渲染器大小
renderer.setSize(dom.offsetWidth, dom.offsetHeight, true);
//执行渲染操作
renderer.render(scene, camera);
Three.js 模型
场景、相机初始化完成之后,可以向场景中添加一个简单的模型进行展示。在此之前需要了解三个概念:几何体(物体形状)、材质(物体外观)、网格模型(物体)。
可以简单理解一下:我们创建的模型,就是一个网格模型(物体),比如一个箱子;这个箱子长啥样、有多大,就是几何体(物体形状)控制;这个箱子是什么颜色、粗糙度这种样式是由材质(物体外观)控制。也可以简单理解成“物体是由几何体和材质构成的”,最后添加到场景的是一个物体。
常用几何体

常用材质

Three.js 几何体 Geometry
Three.js提供了各种各样的几何体APl,用来表示三维物体的几何形状。
//导入几何体
import {BoxGeometry }from "three";
球体或者:
import * as Three from "three";
//创建一个立方体几何体圆锥
const geometry = new BoxGeometry(2, 2, 2)
创建一个立方体几何体,长高宽分别为:2、2、2。
常见几何体
//BoxGeometry:长方体
const geometry = new BoxGeometry(100, 100, 100);
// SphereGeometry:球体
const geometry = new SphereGeometry(50);
// CylinderGeometry:圆柱
const geometry = new CylinderGeometry(50,50,100);
// PlaneGeometry:矩形平面
const geometry = new PlaneGeometry(100,50);
//CircleGeometry:圆形平面
const geometry = new CircleGeometry(50);
设置材质:
// 导入材质,这种材质不受光照的影响。
import { MeshBasicMaterial } from "three";
//创建一个材质对象Material
const material = new MeshBasicMaterial({
color: 0xff0000, //0xff0000设置材质颜色为红色
});
设置单面可见:
// 设置模型材质两面可见
const material = new MeshBasicMaterial({
color: 0xff0000, // 0xff0000设置材质颜色为红色
side: DoubleSide, // 设置模型两面可见
});
单双面设置:

如果是平面,我们根据需要可以设置背面可见,或者两面可见。但是对于立方体,如果不需要进入模型内部查看的话,没必要设置双面可见,设置双面可见,将会影响渲染效率,消耗计算机性能。
Three.js 材质Material
如果你想定义物体的外观效果,比如颜色,就需要通过材质Material相关的API实现。
// 导入材质,这种材质不受光照的影响。
import { MeshBasicMaterial } from "three";
//创建一个材质对象Material
const material = new MeshBasicMaterial({
color: 0xff0000, //0xff0000设置材质颜色为红色
});
创建一个材质,设置材质的颜色为红色。
Three.js 网格模型Mesh
实际生活中有各种各样的物体,在threejs中可以通过网格模型Mesh (opens new window)表示一个虚拟的物体,比如一个箱子、一座房子。
// 导入网格模型
import { Mesh } from "three";
// 两个参数分别为几何体geometry、材质material
const mesh = new Mesh(geometry, material); //网格模型对象Mesh
创建了几何体、材质、网格模型后,需要将创建的网格模型添加到场景就可以在页面展示三维模型。场景存在一个 add() 方法,可通过该方法将模型添加到场景。
// 将网格模型添加到场景
scene.add(mesh);
Three.js 渲染场景
通过上面步骤操作完成之后发现页面是黑色的,渲染不出效果,原因是渲染的问题。我们还没有对它进行真正的渲染。为此,我们需要使用一个被叫做“渲染循环”(render loop)或者“动画循环”(animate loop)的东西。
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒),但是不绝对,理想情况下是60次每秒,电脑性能不好或者是代码写的处理逻辑太多消耗太多性能的话,肯定到不了60帧。

Three.js 渲染场景抗锯齿
通过之前的代码添加的模型可以正常展示了,但是仔细看的话,在立方体边线渲染的时候会产生一种锯齿纹。

我们可以通过代码设置来优化一下实现抗锯齿效果。在初始化渲染器的时候可以设置参数,其中一个参数是 antialias ,该参数的作用是是否执行抗锯齿。默认为 false。我们开启一下。
// 初始化渲染器
let renderer = new WebGLRenderer({
antialias:true, // 开启抗锯齿
});
除此之外,我们还可以设置渲染像素比为设备像素比来优化锯齿效果。
// 设置像素比为设备的像素比,防止渲染模糊
renderer.setPixelRatio(window.devicePixelRatio);
但是这些方式都是优化,不是彻底解决。系统默认是关闭的,需要通过上面代码手动开启,既然系统默认关闭就说明开启是有代价的哈,对的,就是开启会影响性能,一个模型两个模型还好,模型多了,可能会出现卡顿的问题嗷。
Three.js 深度冲突
什么是深度冲突,下面创建两个平面,都默认加载到坐标原点:
// 创建平面
const plantGeometry = new THREE.PlaneGeometry(80, 80);
const plantMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide });
let plane = new THREE.Mesh(plantGeometry, plantMaterial);
// 创建平面1
const plantGeometry1 = new THREE.PlaneGeometry(120, 120);
const plantMaterial1 = new THREE.MeshBasicMaterial({ color: 0x00ffff, side: THREE.DoubleSide });
let plane1 = new THREE.Mesh(plantGeometry1, plantMaterial1);
看一下效果:
两个平面显示的若隐若现,为啥呢?当两个面间隙很小,就可能出现深度冲突。从纯理论的角度,你能分清0和0.0000…0000001的大小,但是实际上,电脑GPU精度是有限的,电脑分不清谁在前谁在后,不知道应该先渲染谁,就会出现这个情况。
Three.js 解决深度冲突
解决方案一:拉开间距
let plane1 = new THREE.Mesh(plantGeometry1, plantMaterial1);
plane1.position.z = 0.1
给两个平面之间添加一点距离:

解决方案二:设置webgl渲染器设置对数深度缓冲区
let renderer = new Three.WebGLRenderer({
antialias: true,
logarithmicDepthBuffer: true // 设置深度缓冲区
});
有一点要注意,当两个面间隙过小,或者重合,你设置webgl渲染器深度缓冲区也是无效的,这种方式也是对深度冲突的优化,不是解决。
Three.js 三维坐标系
在Three.js中,渲染三维模型时,当我们使用 scene.add 将模型添加到场景中后,模型默认添加在坐标系原点,也就是 (0,0,0) 处。没错,在Three.js中是存在坐标系的,坐标系存在x轴、y轴、z轴。怎么定义的呢,我们可以使用辅助坐标系进行辅助查看。
// 导入三维坐标系
import { AxesHelper } from "three";
// 实例化一个三维坐标轴,辅助坐标轴长度为 5
const axesHelper = new AxesHelper(5);
// 添加到三维场景
this.scene.add(axesHelper);

看到出现了三根线,我们添加的模型没有设置位置的话,模型默认加载到坐标原点,沿蓝色线为Z轴正方向,沿红色线为X轴正方向,沿绿色线位Y轴正方向。

注意,Three.js中坐标系没有明确的单位,但是模型设计工具可能有,所以说在设计模型的时候需要与美术提前确定好单位,比如渲染房子的单位可能是米,渲染铅笔可能是厘米,切记单位不要混了。
Three.js 模型位置设置
我们如果不想让立方体添加在坐标原点我们可以通过位置设置,修改模型的初始位置。
// 修改模型位置
mesh.position.set(3, 0, 0); // x轴设置为3
// 或者
mesh.position.x = 3

除去位置可以设置之外,还可以对他的缩放、旋转进行设置。具体操作可以查看官方文档。
Three.js 光源对物体的影响
实际生活中物体表面的明暗效果是会受到光照的影响,比如晚上不开灯,你就看不到物体,灯光比较暗,物体也比较暗。在threejs中,咱们用网格模型Mesh模拟生活中物体,所以threejs中模拟光照Light对物体表面的影响,就是模拟光照Light对网格模型Mesh表面的影响。
在 Three.js 提供的材质里面,有可以受光照影响的材质,有不受光照影响的材质。
之前编写的代码,我们没有在场景中添加光线,模型依旧可以看见,是因为我们使用了MeshBasicMaterial 材质,他是一个不受光照影响的材质,如果我们使用其他材质,则立方体就不会显示,因为没有添加光线进行照射。

使用一个受光照影响的材质:
//创建一个材质对象Material,材质受光照影响
const material = new MeshLambertMaterial({
color: 0xff0000, //0xff0000设置材质颜色为红色
});
看一下效果:

小立方体已经看不见了,因为场景里面没有光线,所以说看不见。
Three.js 光源
当使用MeshLambertMaterial材质时,会受到光线的影响, 我们代码里面如果没有设置光线,则使用MeshLambertMaterial材质修饰的模型不可见,这个时候,我们添加光线后,便可以看见。
Three.js提供了多种模拟生活中光源的API。

光源特点:

Three.js 点光源
点光源 PointLight (opens new window) 可以类比为一个发光点,就像生活中一个灯泡,以灯泡为中心向四周发射光线。
// 导入点光源
import { PointLight } from "three";
// 创建点光源并设置白色光、光照强度200、最远照射距离10
const light = new PointLight(0xffffff, 200, 10);
// 设置点光源的位置,x轴5,y轴5,z轴3
light.position.set(5, 5, 3);
// 将点光源添加到场景
scene.add(light);
把点光源想象为一个电灯泡,在3D空间中,放的位置不同,模型的渲染效果就不一样。
注意光源位置尺寸大小:如果你希望光源照在模型的外表面,那你就需要把光源放在模型的外面。

Three.js 相机控件OrbitControls
平时开发调试代码,或者展示模型的时候,可以通过相机控件 OrbitControls 实现旋转缩放预览效果。就是可以像百度地图一样,通过鼠标来旋转场景、缩放场景、移动场景。
// 导入相机控件(轨道控制器)
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 创建相机控件
const controls = new OrbitControls(camera, renderer.domElement);
【默认使用规则】
- 旋转:拖动鼠标左键 THREE.MOUSE.ROTATE;
- 缩放:滚动鼠标中键 THREE.MOUSE.DOLLY;
- 平移:拖动鼠标右键 THREE.MOUSE.PAN;
可以通过设置修改鼠标键的功能项:
controls.mouseButtons = { // 设置鼠标功能键(轨道控制器)
LEFT: null, // 左键无功能
MIDDLE: MOUSE.DOLLY, // 中键缩放
RIGHT: MOUSE.ROTATE // 右键旋转
}
在相机控件变化的时候,我们可以使用监听事件,来获取当前场景或者是相机数据,这样方便我们调试相机视角。比如,当我们想设置相机拍摄某个视角,但是有无法确定项目应该设置的最佳位置时,我们可以通过相机控件手动移动到目标位置,然后就可以通过变化事件监听,看到当前相机位置。
controls.addEventListener('change', () => {
// 浏览器控制台查看相机位置变化
console.log('camera.position', camera.position);
});
看一下打印的结果:

Three.js 布局自适应
在上面案例中,我们想让挂载的DOM自适应页面的变化,比如我们的DOM使用百分比布局,当浏览器窗体拖宽的时候 three.js 渲染的区域不能很好的自适应。
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
// 重置渲染器输出画布canvas尺寸
renderer.setSize(dom.offsetWidth, dom.offsetHeight);
// 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
camera.aspect = dom.offsetWidth / dom.offsetHeight;
// 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
// 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
// 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
camera.updateProjectionMatrix();
// 如果使用了OrbitControls,则必须在摄像机的变换发生任何手动改变后更新OrbitControls
controls.update()
};
Three.js 克隆.clone() 和 复制.copy()
克隆 .clone()、复制 .copy() 是threejs很多对象都具有的方法,比如三维向量对象Vector3、网格模型Mesh、几何体、材质。 克隆 .clone() 简单说就是复制一个和原对象一样的新对象,但他不是深度拷贝。 复制 .copy() 简单说就是把一个对象属性的属性值赋值给另一个对象。
对材质的影响:
- 当一个场景中模型使用同一套材质时,修改其中任意一个模型的材质,其余材质均被修改。
- 当一个场景中模型使用各自创建的材质,修改其中任意一个模型材质,不会对其他模型材质造成影响。 当使用
clone - 克隆某一个模型时,其材质是共享的原模型材质,修改材质后对原模型材质有影响。
Three.js 建模
对于简单的立方体、球体等模型,你可以通过three.js的几何体相关API快速实现,不过复杂的模型,比如一辆轿车、一栋房子、一个仓库,一般需要通过3D建模软件来实现。
3D美术常用的三维建模软件,比如Blender、3dmax、C4D、maya 等。
一个公司对于三维开发的分工:
- 3D美术:使用三维建模软件绘制3D模型,导出gltf等常见格式。
- 2D美术:根据三维模型设计贴图。
- WebGL开发:加载解析三维软件导出的三维模型。比如使用Blender三维建模软件导出gltf格式模型,然后再通过threejs加载三维模型。
Three.js GLTF模型解释
GLTF格式是新2015发布的三维模型格式,随着物联网、WebGL、5G的进一步发展,会有越来越多的互联网项目Web端引入3D元素,你可以把GLTF格式的三维模型理解为.jpg、.png格式的图片一样,现在的网站,图片基本是标配,对于以后的网站来说如果需要展示一个场景,使用3D来替换图片表达也是很正常的事情。图片有很多格式,对于三维模型自然也是如此,Web开发的时候图片会有常用格式,对于Web3D开发也一样,肯定会根据需要选择一个常见的大家都熟悉的格式,随时时间的发展,GLTF必然称为一个极为重要的标准格式。
不仅three.js,其它的WebGL三维引擎cesium、babylonjs都对gltf格式有良好的的支持。

Three.js 加载Gltf模型
GLTFLoader就是three.js的一个扩展库,专门用来加载gltf格式模型加载器。
// 导入模型加载器
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// 实例化一个gltf的加载器对象
let loader = new GLTFLoader();
// 加载gltf模型
loader.load("/static/models/model/model.gltf", (gltf) => {
scene.add(gltf.scene);
})
展示效果:

Three.js 雾化效果
看上面加载的模型,环境黑色和模型之间的边界,棱角分明,我们可以使用雾化效果修饰一下,让边界不是很明显:
// 场景开启雾化效果
scene.fog = new Three.Fog(0x000000, 650, 900);

场景的雾化效果,是针对于相机。三个参数是:雾化颜色、起始位置、结束位置。
如果渲染器背景为黑色,无特殊情况下,建议使用相同的颜色做为雾化效果。
Three.js 射线控制器Raycaster
光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)。

在二维平面,点击一个按钮很简单,因为屏幕是平面的,页面也是平面的,根据 X、Y 就可以定位元素位置。

在三维,渲染的是一个立体的场景,我们就不能单纯通过电脑屏幕的 X、Y 来获取元素位置,因为三维存在 Z 轴。

在现实场景中,我们如果想让同行的朋友关注远处的一座山,我们只需伸手指向那座山,朋友就会根据当时的场景,结合你看的方向,结合你手指的方向,他就可以知道你说的是那座山。
在这个过程中,并没有直接把手指怼到山上,朋友依旧可以理解我们的意思。同理在三维场景中,我们想要获取某个物体,并不需要让鼠标怼到模型上。
在threejs中,提供了射线控制器,可以帮我们实现类似的效果。首先引入射线控制器:
// 实例化射线控制器
let raycaster = new Three.Raycaster();
射线发射器 Raycaster 会根据鼠标在二维屏幕中点击的位置,结合三维场景和相机数据,从屏幕向鼠标点击的方向发出一条射线,把被射线穿过模型返回成一个列表,列表的顺序就是射线穿过模型的先后顺序。
所以我们照着某个模型点过去,射线一定会穿过小方块,当然可能还有其他的模型一起被穿过了,但是第一个穿过的肯定目标小方块。
我们首先需要知道鼠标是在屏幕哪个地方点击的,获取鼠标在页面点击的坐标,这个很简单:
// 创建鼠标点击事件获取鼠标点击位置
renderer.domElement.addEventListener("click", event => {
x = event.offsetX
y = event.offsetY
})
射线控制器创建完成,它有一个 setFromCamera 方法,用来通过像机和鼠标位置更新射线,需要传入两个参数,分别是 在标准化设备坐标中鼠标的二维坐标 和 场景。
raycaster.setFromCamera(mouse, camera)
问题来了!!
setFromCamera 方法的相机没有疑义,但是在标准化设备坐标中鼠标的二维坐标 有点问题。
在监听鼠标点击事件获取的坐标,是相对于屏幕的。标准化设备坐标中鼠标的二维坐标 是 threejs 视角的鼠标位置,这个位置和我们通过点击事件获取出来的相对于屏幕的鼠标位置是不一样的。

对于 threejs 而言,他的原点就是屏幕宽度的一半和屏幕高度的一半。所以:
横轴: (x - width / 2) / (width / 2)
纵轴: (height / 2 - y) / (height / 2)
化简一下就是:
x / width * 2 - 1
-y * 2 / height + 1
通过上面化简的公式,就可以将获取到的鼠标坐标转化为 threejs 坐标:
let Sx = event.clientX; // 获取鼠标x轴坐标
let Sy = event.clientY; // 获取鼠标y轴坐标
let x = (Sx / dom.offsetWidth) * 2 - 1; // 转换x坐标
let y = -(Sy / dom.offsetHeight) * 2 + 1; // 转换y坐标
let raycaster = new Three.Raycaster(); // 射线控制器
raycaster.setFromCamera(new Three.Vector2(x, y), camera); // 通过像机和鼠标位置更新射线
Raycaster 射线控制器还有一个方法叫 intersectObjects,他的作用就是获取被射线穿过的模型数量。他需要传递一个参数,是检测和射线相交的一组物体。
// 获取射线穿过模型列表
const intersection = raycaster.intersectObjects(scene.children)
他会返回啥呢,他返回的是被射线穿过的模型顺序,是一个数组,按照被射线穿过的模型顺序组装起来的数组,就是先穿过谁,谁就在前面。但是要注意了,场景中添加的东西,都会被检测到,比如辅助线这些。
if (intersects.length > 0) { // 如果存在穿过的模型
if (chooseMesh) { // 如果之前有设置了颜色的模型
chooseMesh.material.color.set(0xffffff); // 恢复之前颜色
}
chooseMesh = intersects[0].object; // 修改被设置的模型为射线穿过的第一个模型
chooseMesh.material.color.set(0xffff00); // 将穿过的第一个模型设置为黄色
}

Three.js CSS 2D渲染器 CSS2DRenderer
通过 CSS2DRenderer 可以把HTML元素作为标签标注三维场景。但是注意一点,就是他只支持100%的浏览器缩放比例正常运行。在此过程中,需要将两个库导入一下:CSS2DRenderer、 CSS2DObject。
导入的方式很简单:
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
如果你希望将三维物体和基于HTML的标签相结合,则这一渲染器将十分有用。在这里,各个DOM元素也被包含到一个 CSS2DObject 实例中,并被添加到场景图中。
它允许开发者将HTML元素作为标签标注到三维场景中,这对于在三维地图或者图形中添加文本标签特别有用。CSS2DRenderer是CSS3DRenderer的简化版本,它主要支持位移变换,这意味着可以使用它来在三维空间中定位HTML元素,但不支持旋转或缩放等其他三维变换。
CSS2DRenderer 创建
// 创建一个CSS2的渲染器
function createCSS2DRendererFun(dom) {
let labelRenderer = new CSS2DRenderer();
// 设置渲染器大小
labelRenderer.setSize(dom.offsetWidth, dom.offsetHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.pointerEvents = 'none';
dom.appendChild(labelRenderer.domElement);
return labelRenderer
}
上面是一个方法,方法中创建了一个CSS2DRenderer渲染器,设置渲染器的大小、属性、挂载位置,最后返回。
这个渲染器和渲染三维场景的渲染器不是一个渲染器,你可以简单的理解成,我们把这个渲染器盖在了三维场景渲染器的上方,用来渲染我们后期需要添加的 HTML 标签。这也就解释了为什么设置他的 postition 等属性。
因为案例的三维场景占据整个屏幕,所以在这里直接挂载到了dom上面了。
CSS2DObject 介绍
CSS2DObject 是 Three.js 中用于在3D场景里渲染HTML元素的类。
- HTML元素包装:它允许开发者将HTML元素包装成可以在3D场景中渲染的对象。
- 场景连接:通过CSS2DObject,HTML元素可以与three.js中的场景连接,这意味着元素可以根据物体的位置和场景的相机位置自动定位和渲染。
- 位置设置:开发者可以通过设置CSS2DObject的position属性来定义HTML元素在3D空间中的位置,也可以获取Mesh(网格)的世界坐标来确定标签的位置。
- 信息展示:CSS2DObject常与CSS2DRenderer一起使用,用于在Three.js中绘制2D效果的标签,这对于展示一些场景相关信息非常有用。
CSS2DObject 创建
// 创建html标签
function tag(name) {
// 创建div标签
let div = document.createElement('div');
// 设置标签显示内容
div.innerHTML = name;
// 添加class
div.classList.add("tag")
// 创建CCS2DObject对象
var label = new CSS2DObject(div);
// 返回CSS2DObject对象
return label
}
上面提供了一个方法用来创建一个HTML标签,设置标签的展示内容,并且最终导出一个 CSS2DObject 对象。
当然HTML的标签我们依旧可以通过 CSS 样式进行修饰。下面是 CSS 对其样式进行修改的代码:
.tag {
background-color: rgba(0, 0, 0, 0.4);
color: #fff;
font-size: 14px;
padding: 4px 10px;
border-radius: 3px;
border: 1px solid #00ffff;
box-sizing: border-box;
}
模型添加2D标签
let group = gltf.scene.getObjectByName('粮仓');
group.traverse((obj) => {
if (obj.type === 'Mesh') {
let label = tag(obj.name)
let v3 = new Three.Vector3();
obj.getWorldPosition(v3);
if (obj.parent.name === '平房仓') {
v3.y += 20
} else if (obj.parent.name === '立筒仓') {
v3.y += 39
} else if (obj.parent.name === '浅圆仓') {
v3.y += 23
}
label.position.copy(v3);
model.add(label);
}
})
在我们拿到粮仓模型之后可以通过递归遍历,拿到所有的模型,之后给相应的粮仓模型添加 label。
getWorldPosition:用于获取某个对象在世界坐标系中的位置。
场景展示HTML标签
在场景中展示 HTML 标签和渲染三维一样。首先创建一个渲染器:
// 添加CSS2DRenderer渲染器
const labelRenderer = createCSS2DRendererFun(dom);
在每一帧切换的时候更新一下:
let animate = () => {
...
labelRenderer.setSize(dom.offsetWidth, dom.offsetHeight);
labelRenderer.render(scene, camera);
....
requestAnimationFrame(animate);
}
animate()
效果展示

标签已经添加到场景进行展示。我们可以发现标签不随相机自动变换大小,并且标签正面始终朝向镜头。
Three.js CSS 3D渲染器 CSS3DRenderer
CSS3DRenderer 是 Three.js 库中的一个组件,用于在 WebGL 场景中渲染 HTML 元素。它允许开发者将DOM元素转换为三维对象,并使用CSS变换来实现三维效果。
导入的方式很简单:
import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
CSS3DRenderer, CSS3DObject 的用法,和 2D 几乎是完全一样的,我们简单写一下,直接之前 2D 的修改就可以了,那我直接贴代码,不做赘述。
Three.js CSS 3D渲染器初始化
// 创建一个CSS3的渲染器
function createCSS3DRendererFun(dom) {
let labelRenderer = new CSS3DRenderer();
labelRenderer.setSize(dom.offsetWidth, dom.offsetHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.pointerEvents = 'none';
dom.appendChild(labelRenderer.domElement);
return labelRenderer
}
CSS3DObject 对象初始化
// 创建html标签
function tag3D(name) {
let div = document.createElement('div');
div.innerHTML = name;
div.classList.add("tag")
var label = new CSS3DObject(div);
div.style.display = 'none';
label.scale.set(0.2, 0.2, 1)
label.rotateY(Math.PI / 2)
return label
}
CSS3DRenderer 使用
let group = Mathia('粮仓');
group.traverse((obj) => {
if (obj.type === 'Mesh') {
let label = tag3D(obj.name)
let v3 = new Three.Vector3();
obj.getWorldPosition(v3);
if (obj.parent.name === '平房仓') {
v3.y += 20
} else if (obj.parent.name === '立筒仓') {
v3.y += 39
} else if (obj.parent.name === '浅圆仓') {
v3.y += 23
}
label.position.copy(v3);
model.add(label);
}
})
// 添加CSS3DRenderer渲染器
const labelRenderer = createCSS3DRendererFun(dom);
展示效果

标签已经添加到场景进行展示。我们可以发现标签随相机自动变换大小,不会一直朝向镜头。
CSS3DSprite 精灵
CSS3DSprite 是 3D 中的一个精灵,怎么理解呢,他和 CSS3DObject 的特性一样,但是他会自动朝向镜头。这玩意儿也是需要导入的,导入很简单。
import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
他和3D标签唯一的区别就是,在创建标签的时候,不生成 CSS3DObject 了,而是使用 CSS3DSprite。
var label = new CSS3DSprite(div);
Gsap 动画
three.js 结合 Gsap(GreenSock Animation Platform)可以创建丰富的3D动画效果。
Gsap是一个功能强大的JavaScript动画库,它支持各种动画需求,包括CSS、SVG、Canvas,以及WebGL等。
官网:https://gsap.com/
首先我们需要通过 npm 安装 gsap。安装很简单,一行命令结束:
npm install gsap
使用案例:
gsap.to(camera.position, {
x: -1.5,
y: 8.0,
z: -15.2,
duration: 1.5,
ease: "power1.inOut"
})
就这些了!
