快速入门
1.学习环境和引入Threejs
项目的开发环境引入threejs
// 比如安装148版本
npm install three@0.148.0 --save
npm安装后,如何引入three.js
// 引入three.js
import * as THREE from 'three';
npm安装后,如何引入three.js其他扩展库
// 引入扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 引入扩展库GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
2. 第一个3D案例
入门Three.js的第一步,就是认识场景Scene、相机Camera、渲染器Renderer三个基本概念。
(1)创建3D场景
三维场景Scene
三维场景(Scene)对象理解为虚拟的3D场景,用来表示模拟生活中的真实三维场景,或者说三维世界。
// 创建3D场景对象Scene
const scene = new THREE.Scene();
物体形状:几何体Geometry
//创建一个长方体几何对象Geometry
const geometry = new THREE.BoxGeometry(100, 100, 100);
物体外观:材质Material
//创建一个材质对象Material
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,//0xff0000设置材质颜色为红色
});
物体:网格模型Mesh
实际生活中有各种各样的物体,在threejs中可以通过网格模型表示一个虚拟的物体,比如一个地板砖、一个房子。
// 两个参数分别为几何体geometry、材质material
const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
模型位置.position
const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
//设置网格模型在三维空间中的位置坐标,默认是坐标原点
mesh.position.set(0,10,0);
.add()
方法
在threejs中创建了一个表示物体的虚拟对象Mesh,需要通过.add()
方法,把网格模型mesh
添加到三维场景scene
中。
scene.add(mesh);
(2)透视投影相机
Threejs如果想把三维场景Scene
渲染到web网页上,还需要定义一个虚拟相机Camera
,就像你生活中想获得一张照片,需要一台用来拍照的相机。
Threejs提供了正投影相机OrthographicCamera和透视投影相机PerspectiveCamera。根据场景对相机进行选择。
透视投影相机PerspectiveCamera
本质上就是在模拟人眼观察这个世界的规律。
// 实例化一个透视投影相机对象
const camera = new THREE.PerspectiveCamera();
相机位置.position
生活中用相机拍照,你相机位置不同,拍照结果也不同,threejs中虚拟相机同样如此。
比如有一间房子,你拿着相机站在房间里面,看到的是房间内部,站在房子外面看到的是房子外面效果。
//相机在Three.js三维坐标系中的位置
// 根据需要设置相机位置具体值
camera.position.set(200, 200, 200);
相机观察目标.lookAt()
用相机拍照你需要控制相机的拍照目标,具体说相机镜头对准哪个物体或说哪个坐标。对于threejs相机而言,就是设置.lookAt()
方法的参数,指定一个3D坐标。
//相机观察目标指向Threejs 3D空间中某个位置
camera.lookAt(0, 0, 0); //坐标原点
camera.lookAt(mesh.position);//指向mesh对应的位置
定义相机渲染输出的画布尺寸
生活中相机拍照的照片是有大小的,对于threejs而言一样,需要定义相机在网页上输出的Canvas画布(照片)尺寸,大小可以根据需要定义。
threejs虚拟相机渲染三维场景在浏览器网页上呈现的结果称为Canvas画布。
// 定义相机输出画布的尺寸(单位:像素px)
const width = 800; //宽度
const height = 500; //高度
透视投影相机PerspectiveCamera
:视锥体
透视投影相机的四个参数fov, aspect, near, far
构成一个四棱台3D空间,被称为视锥体,只有视锥体之内的物体,才会渲染出来,视锥体范围之外的物体不会显示在Canvas画布上。
PerspectiveCamera( fov, aspect, near, far )
(3)渲染器
生活中如果有了景物和相机,那么如果想获得一张照片,就需要你拿着相机,按一下,咔,完成拍照。对于threejs而言,如果完成“咔”这个拍照动作,就需要一个新的对象,也就是WebGL渲染(WebGLRenderer)。
WebGL渲染器WebGLRenderer
// 创建渲染器对象
const renderer = new THREE.WebGLRenderer();
设置Canvas画布尺寸.setSize()
// 定义threejs输出画布的尺寸(单位:像素px)
const width = 800; //宽度
const height = 500; //高度
renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
渲染器渲染方法.render()
渲染器WebGLRenderer
执行渲染方法.render()
就可以生成一个Canvas画布(照片),并把三维场景Scene呈现在canvas画布上面。
renderer.render(scene, camera); //执行渲染操作
渲染器Canvas画布属性.domElement
渲染器WebGLRenderer
通过属性.domElement
可以获得渲染方法.render()
生成的Canvas画布,.domElement
本质上就是一个HTML元素:Canvas画布。
document.body.appendChild(renderer.domElement);
Canvas画布插入到任意HTML元素中
<div id="webgl" style="margin-top: 200px;margin-left: 100px;"></div>
document.getElementById('webgl').appendChild(renderer.domElement);
3. 三维坐标系-加强三维空间认识
辅助观察坐标系
// AxesHelper:辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
AxesHelper
的xyz轴
three.js坐标轴颜色红R、绿G、蓝B分别对应坐标系的x、y、z轴,对于three.js的3D坐标系默认y轴朝上。
4. 光源对物体表面影响
实际生活中物体表面的明暗效果是会受到光照的影响,threejs中同样也要模拟光照Light
对网格模型Mesh
表面的影响。
受光照影响材质
//MeshBasicMaterial不受光照影响
const material = new THREE.MeshBasicMaterial();
//MeshLambertMaterial受光照影响
const material = new THREE.MeshLambertMaterial();
光源简介
点光源可以类比为一个发光点,就像生活中一个灯泡以灯泡为中心向四周发射光线。
//点光源:两个参数分别表示光源颜色和光照强度
const pointLight = new THREE.PointLight(0xffffff, 1.0);
//环境光:没有特定方向,整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 方向光指向对象网格模型mesh,可以不设置,默认的位置是0,0,0
directionalLight.target = mesh;
光源位置
可以把点光源想象为一个电灯泡,在3D空间中放的位置不同,模型的渲染效果就不一样。
pointLight.position.set(100, 60, 50);
光源辅助器
// DirectionalLightHelper:可视化平行光
const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5,0xff0000);
scene.add(dirLightHelper);
光源添加到场景
光源和网格模型Mesh对应一样是三维场景的一部分,自然需要添加到三维场景中才能起作用。
scene.add(pointLight); //点光源添加到场景中
5. 相机控件OrbitControls
平时开发调试代码,或者展示模型的时候,可以通过相机控件OrbitControls实现旋转缩放预览效果。
- 旋转:拖动鼠标左键
- 缩放:滚动鼠标中键
- 平移:拖动鼠标右键
// 引入
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 使用
const controls = new OrbitControls(camera, renderer.domElement);
// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
controls.addEventListener('change', function () {
renderer.render(scene, camera); //执行渲染操作
});
6. 动画渲染循环
threejs可以借助HTML5的API请求动画帧window.requestAnimationFrame
实现动画渲染。
请求动画帧window.requestAnimationFrame
// requestAnimationFrame实现周期性循环执行
let i = 0;
function render() {
i+=1;
console.log('执行次数'+i);
requestAnimationFrame(render);//请求再次执行函数render
}
render();
7. Canvas画布布局和全屏
threejs渲染输出的结果就是一个Cavnas画布,canvas画布也是HTML的元素之一,这意味着three.js渲染结果的布局和普通web前端习惯是一样的。
非全屏局部布局
<div id="webgl" style="margin-top: 100px;margin-left: 200px;"></div>
// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
const width = 800; //宽度
const height = 500; //高度
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
renderer.render(scene, camera); //执行渲染操作
document.getElementById('wegbl').appendChild(renderer.domElement);
全屏渲染
// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
const width = window.innerWidth; //窗口文档显示区的宽度作为画布宽度
const height = window.innerHeight; //窗口文档显示区的高度作为画布高度
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
canvas画布宽高度动态变化
canvas画布宽高度动态变化,需要更新相机和渲染的参数,否则无法正常渲染。
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
// 重置渲染器输出画布canvas尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
camera.aspect = window.innerWidth / window.innerHeight;
//如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
camera.updateProjectionMatrix();
};
8. stats查看threejs渲染帧率
three.js每执行WebGL渲染器.render()
方法一次,就在canvas画布上得到一帧图像,不停地周期性执行.render()
方法就可以更新canvas画布内容,一般场景越复杂往往渲染性能越低,也就是每秒钟执行.render()
的次数越低。
通过stats.js库可以查看three.js当前的渲染性能,具体说就是计算three.js的渲染帧率(FPS),所谓渲染帧率(FPS),简单说就是three.js每秒钟完成的渲染次数,一般渲染达到每秒钟60次为最佳状态。
//引入
import Stats from 'three/addons/libs/stats.module.js';
//使用
const stats = new Stats();
document.body.appendChild(stats.domElement);
// 渲染函数
function render() {
//requestAnimationFrame循环调用的函数中调用方法update(),来刷新时间
stats.update();
}
render();
9. Threejs常见几何体简介
//BoxGeometry:长方体
const geometry = new THREE.BoxGeometry(100, 100, 100);
// SphereGeometry:球体
const geometry = new THREE.SphereGeometry(50);
// CylinderGeometry:圆柱
const geometry = new THREE.CylinderGeometry(50,50,100);
// PlaneGeometry:矩形平面
const geometry = new THREE.PlaneGeometry(100,50);
// CircleGeometry:圆形平面
const geometry = new THREE.CircleGeometry(50);
双面可见
Three.js的材质默认正面可见,反面不可见,对于矩形平面PlaneGeometry
、圆形平面如果你想看到两面,可以设置side: THREE.DoubleSide
。
side: ThREE.BackSide //背面可见
side: THREE.FrontSide, //正面可见(默认)
side: THREE.DoubleSide, //两面可见
new THREE.MeshBasicMaterial({
side: THREE.FrontSide,
});
10. WebGL渲染器设置(锯齿模糊)
一般实际开发,threejs的WebGL渲染器需要进行一些通用的基础配置,比如渲染模糊或锯齿问题。
渲染器锯齿属性.antialias
const renderer = new THREE.WebGLRenderer({
antialias:true,
});
设置设备像素比.setPixelRatio()
// 不同硬件设备的屏幕的设备像素比window.devicePixelRatio值可能不同
console.log('查看当前屏幕设备像素比',window.devicePixelRatio);
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);
11. gui.js库(可视化改变三维场景)
dat.gui.js是一个前端js库,对HTML、CSS和JavaScript进行了封装,学习开发的时候,借助dat.gui.js可以快速创建控制三维场景的UI交互界面。
// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
// 实例化一个gui对象
const gui = new GUI();
//.add() 功能——改变对象属性值
// 光照强度属性.intensity
gui.add(ambient, 'intensity', 0, 2.0);
// 通过GUI改变mesh.position对象的xyz属性
gui.add(mesh.position, 'x', 0, 180);
gui.add(mesh.position, 'y', 0, 180);
gui.add(mesh.position, 'z', 0, 180);
12. threejs语法总结
Three.js语法总结:类(构造函数)
Three.js提供了各种各样的类(构造函数),通过new
关键字可以实例化类(构造函数),获得一个对象,对象具有属性和方法。
// new实例化类THREE.MeshLambertMaterial,创建一个材质对象
const material = new THREE.MeshLambertMaterial();
// 可以看到材质对象的属性color、side、opacity、transparent...
console.log('查看材质对象',material);
类(构造函数)的参数设置属性
const material = new THREE.MeshLambertMaterial({
color: 0x00ffff,
side:THREE.DoubleSide,
transparent:true,
opacity:0.5,
});
访问对象属性改变属性的值
// 访问对象属性改变属性的值
material.transparent = false;
material.opacity = 1.0;
父类和子类
JavaScript是一个面向对象的语言,有父类和子类的概念,子类是通过父类派生出来的,会继承父类的属性或方法。
- 环境光、平行光源的父类Light
- mesh、light光源的父类Object3D
可以通过文档查询一个类的方法或属性,除了可以查询类本身,还可以查询类的父类。
通过对象的方法改变对象的属性
console.log('模型位置属性',mesh.position);
mesh.position.x = 50;//访问属性改变位置x坐标
mesh.translateX(50);//执行方法改变位置属性
几何体BufferGeometry
1. 几何体顶点位置数据和点模型
缓冲类型几何体BufferGeometry
threejs的长方体BoxGeometry
、球体SphereGeometry
等几何体都是基于BoxGeometry类构建的,BufferGeometry是一个没有任何形状的空几何体,你可以通过BufferGeometry自定义任何几何形状,具体一点说就是定义顶点数据。
//创建一个空的几何体对象
const geometry = new THREE.BufferGeometry();
//类型化数组创建顶点数据
const vertices = new Float32Array([
0, 0, 0, //顶点1坐标
50, 0, 0, //顶点2坐标
0, 100, 0, //顶点3坐标
0, 0, 10, //顶点4坐标
0, 0, 100, //顶点5坐标
50, 0, 10, //顶点6坐标
]);
//创建属性缓冲区对象,3个为一组,表示一个顶点的xyz坐标
const attribue = new THREE.BufferAttribute(vertices, 3);
// 设置几何体attributes属性的位置属性
geometry.attributes.position = attribue;
2. 线模型对象
// 线材质对象
const material = new THREE.LineBasicMaterial({
color: 0xff0000 //线条颜色
});
// 创建线模型对象
const line = new THREE.Line(geometry, material);
// threejs线模型除了Line,还提供了LineLoop,LineSegments,区别在于绘制线条的规则不同。
const line = new THREE.LineLoop(geometry, material); // 闭合线条
const line = new THREE.LineSegments(geometry, material);//非连续的线条
3. 构建一个矩形平面几何体
网格模型Mesh
渲染自定义几何体BufferGeometry
的顶点坐标,通过这样一个例子帮助大家建立**三角形(面)**的概念
定义矩形几何体顶点坐标
一个矩形平面,可以至少通过两个三角形拼接而成。而且两个三角形有两个顶点的坐标是重合的。
注意三角形的正反面问题:保证矩形平面两个三角形的正面是一样的,也就是从一个方向观察,两个三角形都是逆时针或顺时针。
const vertices = new Float32Array([
0, 0, 0, //顶点1坐标
80, 0, 0, //顶点2坐标
80, 80, 0, //顶点3坐标
0, 0, 0, //顶点4坐标 和顶点1位置相同
80, 80, 0, //顶点5坐标 和顶点3位置相同
0, 80, 0, //顶点6坐标
]);
几何体顶点索引数据
网格模型Mesh对应的几何体BufferGeometry,拆分为多个三角后,很多三角形重合的顶点位置坐标是相同的,这时候如果想减少顶点坐标数据量,可以借助几何体顶点索引geometry.index
来实现。
如果几何体有顶点索引geometry.index
,那么你可以吧三角形重复的顶点位置坐标删除。
const vertices = new Float32Array([
0, 0, 0, //顶点1坐标
80, 0, 0, //顶点2坐标
80, 80, 0, //顶点3坐标
0, 80, 0, //顶点4坐标
]);
BufferAttribute
定义顶点索引.index
数据
// Uint16Array类型数组创建顶点索引数据
const indexes = new Uint16Array([
// 下面索引值对应顶点位置数据中的顶点坐标
0, 1, 2, 0, 2, 3,
])
// 索引数据赋值给几何体的index属性
geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组
4. 查看threejs自带几何体顶点
three.js提供的矩形平面PlaneGeometry
、长方体BoxGeometry
、球体SphereGeometry
等各种形状的几何体,他们都有一个共同的父类BufferGeometry
查看几何体顶点位置和索引数据
可以用顶点索引index数据构建几何体,也可以不用,threejs默认的大部分几何体都有三角形的顶点索引数据,具体可以通过浏览器控制台打印几何体数据查看。
const geometry = new THREE.PlaneGeometry(100,50); //矩形平面几何体
console.log('几何体',geometry);
console.log('顶点位置数据',geometry.attributes.position);
console.log('顶点索引数据',geometry.index);
材质属性.wireframe
const material = new THREE.MeshLambertMaterial({
color: 0x00ffff,
wireframe:true,//线条模式渲染mesh对应的三角形数据
});
几何体细分数
Three.js很多几何体都提供了细分数相关的参数,这里以矩形平面几何体PlaneGeometry
为例介绍。
//矩形几何体PlaneGeometry的参数3,4表示细分数,默认是1,1
const geometry = new THREE.PlaneGeometry(100,50,1,1);
const geometry = new THREE.PlaneGeometry(100,50,2,1); //4个三角形
const geometry = new THREE.PlaneGeometry(100,50,2,2); //8个三角形
球体SphereGeometry
细分数
const geometry = new THREE.SphereGeometry( 50, 32, 16 ); //默认分割数32,16
// 如果球体细分数比较低,表面就不会那么光滑。
const geometry = new THREE.SphereGeometry( 15, 8, 8 );
三角形数量与性能
对于一个曲面而言,细分数越大,表面越光滑,但是三角形和顶点数量却越多。
几何体三角形数量或者说顶点数量直接影响Three.js的渲染性能,在不影响渲染效果的情况下,一般尽量越少越好。
5. 旋转、缩放、平移几何体
// 几何体xyz三个方向都放大2倍
geometry.scale(2, 2, 2);
// 几何体沿着x轴平移50
geometry.translate(50, 0, 0);
// 几何体绕着x轴旋转45度
geometry.rotateX(Math.PI / 4);
// 几何体旋转、缩放或平移之后,查看几何体顶点位置坐标的变化
// BufferGeometry的旋转、缩放、平移等方法本质上就是改变顶点的位置坐标
console.log('顶点位置数据', geometry.attributes.position);
模型对象、材质
1. 三维向量Vector3
//向量Vector3对象表示方向
const axis = new THREE.Vector3(2, 3, 4);
axis.normalize(); //向量归一化
//沿着axis轴表示方向平移100
mesh.translateOnAxis(axis, 100);
2. 欧拉Euler与角度属性.rotation
模型的角度属性.rotation
和四元数属性.quaternion
都是表示模型的角度状态,只是表示方法不同,.rotation
属性值是欧拉对象Euler,.quaternion
属性值是是四元数对象Quaternion。
// 创建一个欧拉对象,表示绕着xyz轴分别旋转45度,0度,90度
const Euler = new THREE.Euler( Math.PI/4,0, Math.PI/2);
//绕y轴的角度设置为60度
mesh.rotation.y += Math.PI/3;
mesh.rotateY(Math.PI/3)
3. 模型材质颜色(Color对象)
查看颜色对象Color
文档,可以看到颜色对象有三个属性,分别为.r
、.g
、.b
,表示颜色RGB的三个分量。
// 查看Color对象设置0x00ff00对应的的.r、.g、.b值
const color = new THREE.Color(0x00ff00);
// 通过`.r`、`.g`、`.b`属性改变颜色值
color.r = 0.0;
color.b = 0.0;
//color提供了.setHex()、.setRGB()、.setStyle()、.set()方法
color.setRGB(0,1,0);//RGB方式设置颜色
color.setHex(0x00ff00);//十六进制方式设置颜色
color.setStyle('#00ff00');//前端CSS颜色值设置颜色
color.set(0x00ff00);//十六进制方式设置颜色
color.set('#00ff00');//前端CSS颜色值设置颜色
material.color.set('rgb(0,255,0)'); //rgb形式
4. 模型材质
材质共享
const mesh = new THREE.Mesh(geometry, material);
const mesh2 = new THREE.Mesh(geometry, material);
mesh2.position.x = 100;
// 两个mesh共享一个材质,改变一个mesh的颜色,另一个mesh2的颜色也会跟着改变
// mesh.material和mesh2.material都指向同一个material
// 三者等价:mesh.material、mesh2.material、material
mesh.material.color.set(0xffff00);
// 三者等价:mesh.geometry、mesh2.geometry、geometry
mesh.geometry.translate(0,100,0);
克隆.clone()和复制.copy()
克隆.clone()
简单说就是复制一个和原对象一样的新对象,下面以三维向量对象Vector3给大家举例,其他的threejs对象都可以参照类似的写法。
const v1 = new THREE.Vector3(1, 2, 3);
console.log('v1',v1);
//v2是一个新的Vector3对象,和v1的.x、.y、.z属性值一样
const v2 = v1.clone();
console.log('v2',v2);
复制.copy()
简单说就是把一个对象属性的属性值赋值给另一个对象,下面以三维向量对象Vector3给大家举例,其他的threejs对象都可以参照类似的写法。
const v1 = new THREE.Vector3(1, 2, 3);
const v3 = new THREE.Vector3(4, 5, 6);
//读取v1.x、v1.y、v1.z的赋值给v3.x、v3.y、v3.z
v3.copy(v1);
Mesh克隆.clone()
通过mesh克隆.clone()
一个和mesh一样的新模型对象mesh2。通过克隆.clone()
获得的新模型和原来的模型共享材质和几何体
const mesh2 = mesh.clone();
mesh2.position.x = 100;
// 改变材质颜色,或者说改变mesh2颜色,mesh和mesh2颜色都会改变
// material.color.set(0xffff00);
mesh2.material.color.set(0xffff00);
几何体和材质克隆.clone()
const mesh2 = mesh.clone();
// 克隆几何体和材质,重新设置mesh2的材质和几何体属性
mesh2.geometry = mesh.geometry.clone();
mesh2.material = mesh.material.clone();
// 改变mesh2颜色,不会改变mesh的颜色
mesh2.material.color.set(0xff0000);
mesh.position.copy()
和 mesh.rotation.copy()
改变mesh的位置,使之位于mesh2的正上方(y),距离100。
mesh.position.copy(mesh2.position);//1. 第1步位置重合
mesh.position.y += 100;//1. 第2步mesh在原来y的基础上增加100
两个模型的姿态角度始终保持一样。
// 渲染循环
function render() {
mesh.rotateY(0.01);// mesh旋转动画
// 同步mesh2和mesh的姿态角度一样,不管mesh姿态角度怎么变化,mesh2始终保持同步
mesh2.rotation.copy(mesh.rotation);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
层级模型
1. Group层级模型(树结构)案例
场景对象scene构成的层级模型本身是一个树结构,场景对象层级模型的第一层,也就是树结构的根节点,一般来说网格模型Mesh、点模型Points、线模型Line是树结构的最外层叶子结点。构建层级模型的中间层一般都是通过Threejs的Group
类来完成,Group
类实例化的对象可以称为组对象。
场景对象Scene
、组对象Group
的.add()
方法都是继承自它们共同的基类(父类)Object3D
。.add()
方法可以单独插入一个对象,也可以同时插入多个子对象。
父对象旋转缩放平移变换,子对象跟着变化
网格模型mesh1、mesh2作为设置为父对象group的子对象,如果父对象group进行旋转、缩放、平移变换,子对象同样跟着变换,就像你的头旋转了,眼睛会跟着头旋转。
//沿着Y轴平移mesh1和mesh2的父对象,mesh1和mesh2跟着平移
group.translateY(100);
//父对象缩放,子对象跟着缩放
group.scale.set(4,4,4);
//父对象旋转,子对象跟着旋转
group.rotateY(Math.PI/6)
mesh也能添加mesh子对象
threejs默认mesh也可以添加子对象,其实原因很简单,mesh和Group父类都是Object3D,本质上也可以认为都是Object3D。
//threejs默认mesh也可以添加子对象,mesh基类也是Object3D
mesh1.add(mesh2);
2. 历模型树结构、查询模型节点
模型命名(.name
属性)
在层级模型中可以给一些模型对象通过.name
属性命名进行标记。
const group = new THREE.Group();
group.name='小区房子';
const mesh = new THREE.Mesh(geometry, material);
mesh.name='一号楼';
递归遍历方法.traverse()
Threejs层级模型就是一个树结构,可以通过递归遍历的算法去遍历Threejs一个模型对象包含的所有后代。
// 递归遍历model包含所有的模型节点
model.traverse(function(obj) {
console.log('所有模型节点的名称',obj.name);
// obj.isMesh:if判断模型对象obj是不是网格模型'Mesh'
if (obj.isMesh) {//判断条件也可以是obj.type === 'Mesh'
obj.material.color.set(0xffff00);
}
});
查找某个具体的模型.getObjectByName()
// 返回名.name为"4号楼"对应的对象
const nameNode = scene.getObjectByName ("4号楼");
nameNode.material.color.set(0xff0000);
3. 地坐标和世界坐标
本地(局部)坐标和世界坐标
// mesh的世界坐标就是mesh.position与group.position的累加
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(50, 0, 0);
const group = new THREE.Group();
group.add(mesh);
group.position.set(50, 0, 0);
- 改变子对象的
.position
,子对象在3D空间中的坐标会发生改变。 - 改变父对象的
.position
,子对象在3D空间中的位置也会跟着变化,也就是说父对象.position
和子对象.position
叠加才是才是子对象的.position
。
任何一个模型的本地坐标(局部坐标)就是模型的.position
属性。
一个模型的世界坐标,说的是,模型自身.position
和所有父对象.position
累加的坐标。
.getWorldPosition()
获取世界坐标
// 声明一个三维向量用来表示某个坐标
const worldPosition = new THREE.Vector3();
// 获取mesh的世界坐标,你会发现mesh的世界坐标受到父对象group的.position影响
mesh.getWorldPosition(worldPosition);
console.log('世界坐标',worldPosition);
console.log('本地坐标',mesh.position);
4. 移除对象remove()
场景对象Scene
、组对象Group
、网格模型对象Mesh
的.remove()
方法都是继承自它们共同的基类(父类)Object3D
。
.remove()
方法使用
// 删除父对象group的子对象网格模型mesh1
group.remove(mesh1);
scene.remove(ambient);//移除场景中环境光
5. 模型显示与隐藏
开发web3d项目,有时候需要临时隐藏一个模型,或者一个模型处于隐藏状态,需要重新恢复显示。
模型属性.visible
模型对象的父类Object3D
封装了一个属性.visible
,通过该属性可以隐藏或显示一个模型
mesh.visible =false;// 隐藏一个网格模型,visible的默认值是true
group.visible =false;// 隐藏一个包含多个模型的组对象group
mesh.visible =true;// 使网格模型mesh处于显示状态
材质属性.visible
材质对象的父类Material
封装了一个.visible
属性,通过该属性可以控制是否隐藏该材质对应的模型对象。
// 隐藏网格模型mesh,visible的默认值是true
mesh.material.visible =false;
// 注意如果mesh2和mesh的.material属性指向同一个材质,mesh2也会跟着mesh隐藏
注意:如果多个模型引用了同一个材质,如果该材质.visible
设置为false,意味着隐藏绑定该材质的所有模型。
顶点UV坐标、纹理贴图
1. 创建纹理贴图
通过纹理贴图加载器TextureLoader
的load()
方法加载一张图片可以返回一个纹理对象Texture
,纹理对象Texture
可以作为模型材质颜色贴图.map
属性的值。
//长方形
const geometry = new THREE.PlaneGeometry(200, 100);
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./earth.jpg');
const material = new THREE.MeshLambertMaterial({
// 设置纹理贴图:Texture对象作为材质map属性的属性值
map: texture,//map表示材质的颜色贴图属性
});
//CircleGeometry的顶点UV坐标是按照圆形采样纹理贴图
const geometry = new THREE.CircleGeometry(60, 100);
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
const texture = texLoader.load('./texture.jpg');
const material = new THREE.MeshBasicMaterial({
map: texture,//map表示材质的颜色贴图属性
side:THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
2. 定义顶点UV坐标
顶点UV坐标的作用
顶点UV坐标的作用是从纹理贴图上提取像素映射到网格模型Mesh的几何体表面上。
浏览器控制台查看threejs几何体默认的UV坐标数据。
const geometry = new THREE.PlaneGeometry(200, 100); //矩形平面
// const geometry = new THREE.BoxGeometry(100, 100, 100); //长方体
// const geometry = new THREE.SphereGeometry(100, 30, 30);//球体
console.log('uv',geometry.attributes.uv);
纹理贴图UV坐标范围
顶点UV坐标可以在0~1.0之间任意取值,纹理贴图左下角对应的UV坐标是(0,0)
,右上角对应的坐标(1,1)
。
自定义顶点UVgeometry.attributes.uv
顶点UV坐标geometry.attributes.uv
和顶点位置坐标geometry.attributes.position
是一一对应的,
UV顶点坐标你可以根据需要在0~1之间任意设置,具体怎么设置,要看你想把图片的哪部分映射到Mesh的几何体表面上。
/**纹理坐标0~1之间随意定义*/
const uvs = new Float32Array([
0, 0, //图片左下角
1, 0, //图片右下角
1, 1, //图片右上角
0, 1, //图片左上角
]);
// 获取纹理贴图左下角四分之一部分的像素值
const uvs = new Float32Array([
0, 0,
0.5, 0,
0.5, 0.5,
0, 0.5,
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2); //2个为一组,表示一个顶点的纹理坐标
3. 纹理对象Texture阵列
使用threejs纹理对象Texture
的阵列功能+矩形平面几何体PlaneGeometry
实现一个地面瓷砖效果。
const geometry = new THREE.PlaneGeometry(2000, 2000);
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./瓷砖.jpg');
// 设置阵列模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.set(12,12);//注意选择合适的阵列数量
const material = new THREE.MeshLambertMaterial({
// 设置纹理贴图:Texture对象作为材质map属性的属性值
map: texture,//map表示材质的颜色贴图属性
});
const mesh = new THREE.Mesh(geometry, material);
如果背景为透明png贴图,要打开透明设置
const material = new THREE.MeshBasicMaterial({
map: textureLoader.load('./指南针.png'),
transparent: true, //使用背景透明的png贴图,注意开启透明计算
});
4. UV动画
通过纹理对象的偏移属性.offset
实现一个UV动画效果。
纹理对象Texture的.offset
的功能是偏移贴图在Mesh上位置,本质上相当于修改了UV顶点坐标
texture.offset.x +=0.5;//纹理U方向偏移
texture.offset.y +=0.5;//纹理V方向偏移
纹理贴图阵列 + UV动画
// 设置U方向阵列模式
texture.wrapS = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.x=50;//注意选择合适的阵列数量
// 渲染循环
function render() {
texture.offset.x +=0.1;//设置纹理动画:偏移量根据纹理和动画需要,设置合适的值
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
渲染器和前端UI界面
1. Three.js渲染结果保存为图片
配置webgl渲染器preserveDrawingBuffer:true
// WebGL渲染器设置
const renderer = new THREE.WebGLRenderer({
//想把canvas画布上内容下载到本地,需要设置为true
preserveDrawingBuffer:true,
});
按钮绑定鼠标事件
// 鼠标单击id为download的HTML元素,threejs渲染结果以图片形式下载到本地
document.getElementById('download').addEventListener('click',function(){
})
创建超链接元素a:用于保存下载文件
// 鼠标单击id为download的HTML元素,threejs渲染结果以图片形式下载到本地
document.getElementById('download').addEventListener('click',function(){
// 创建一个超链接元素,用来下载保存数据的文件
const link = document.createElement('a');
// 通过超链接herf属性,设置要保存到文件中的数据
link.href = ;
link.download = 'threejs.png'; //下载文件名
link.click(); //js代码触发超链接元素a的鼠标点击事件,开始下载文件到本地
})
Cavnas方法.toDataURL()
Canvas画布通过.toDataURL()
方法可以获取画布上的像素信息。canvas.toDataURL("image/png");
表示以png格式获取像素数据,可以直接赋值给超链接元素a的.herf
属性下载到本地。
const link = document.createElement('a');
// 通过超链接herf属性,设置要保存到文件中的数据
const canvas = renderer.domElement; //获取canvas对象
link.href = canvas.toDataURL("image/png");
2. 深度冲突(模型闪烁)
对于模型闪烁的原因简单地说就是深度冲突,对应的英文关键词是Z-fighting
。
模型闪烁主要是两个Mesh重合,电脑GPU分不清谁在前谁在后,这种现象,可以称为深度冲突Z-fighting
。
解决方法:
- 两个矩形Mesh拉开距离
- 透视投影相机对距离影响
- webgl渲染器设置对数深度缓冲区
// WebGL渲染器设置
const renderer = new THREE.WebGLRenderer({
// 设置对数深度缓冲区,优化深度冲突问题
logarithmicDepthBuffer: true
});
生成曲线和几何体
1. 生成圆弧顶点
const geometry = new THREE.BufferGeometry(); //创建一个几何体对象
const R = 100; //圆弧半径
const N = 50; //分段数量
const sp = 2 * Math.PI / N;//两个相邻点间隔弧度
// 批量生成圆弧上的顶点数据
const arr = [];
for (let i = 0; i < N; i++) {
const angle = sp * i;//当前点弧度
// 以坐标原点为中心,在XOY平面上生成圆弧上的顶点数据
const x = R * Math.cos(angle);
const y = R * Math.sin(angle);
arr.push(x, y, 0);
}
//类型数组创建顶点数据
const vertices = new Float32Array(arr);
// 创建属性缓冲区对象
//3个为一组,表示一个顶点的xyz坐标
const attribue = new THREE.BufferAttribute(vertices, 3);
// 设置几何体attributes属性的位置属性
geometry.attributes.position = attribue;
// 线材质
const material = new THREE.LineBasicMaterial({
color: 0xff0000 //线条颜色
});
// 创建线模型对象 构造函数:Line、LineLoop、LineSegments
// const line = new THREE.Line(geometry, material);
const line = new THREE.LineLoop(geometry, material);//线条模型对象
2. 几何体方法.setFromPoints()
几何体BufferGeometry
的一个方法.setFromPoints()
。.setFromPoints()
是几何体BufferGeometry
的一个方法,通过该方法可以把数组pointsArr
中坐标数据提取出来赋值给几何体。具体说就是把pointsArr里面坐标数据提取出来,赋值给geometry.attributes.position
属性
const pointsArr = [
// 三维向量Vector3表示的坐标值
new THREE.Vector3(0,0,0),
new THREE.Vector3(0,100,0),
new THREE.Vector3(0,100,100),
new THREE.Vector3(0,0,100),
];
// 把数组pointsArr里面的坐标数据提取出来,赋值给`geometry.attributes.position`属性
geometry.setFromPoints(pointsArr);
console.log('几何体变化',geometry.attributes.position);
3. 曲线Curve简介
threejs提供了很多常用的曲线或直线API,可以直接使用。这些API曲线都有一个共同的父类Curve
。
椭圆、圆
// 椭圆弧线EllipseCurve
EllipseCurve( aX, aY, xRadius,yRadius, aStartAngle, aEndAngle, aClockwise )
// 圆弧线ArcCurve
ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise )
曲线精度
//曲线上取点,参数表示取点细分精度
const pointsArr = arc.getPoints(50); //分段数50,返回51个顶点
// const pointsArr = arc.getPoints(10);//取点数比较少,圆弧线不那么光滑
4. 样条曲线
对于一些不规则的曲线,很难用一个圆、椭圆或抛物线函数去描述,这时候,可以使用threejs提供的样条曲线或贝塞尔曲线去表达。
三维样条曲线CatmullRomCurve3
在三维空间中随意设置几个顶点坐标,然后作为三维样条曲线CatmullRomCurve3
的参数,你就可以生成一条穿过这几个点的光滑曲线。
// 三维向量Vector3创建一组顶点坐标
const arr = [
new THREE.Vector3(-50, 20, 90),
new THREE.Vector3(-10, 40, 40),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(60, -60, 0),
new THREE.Vector3(70, 0, 80)
]
// 三维样条曲线
const curve = new THREE.CatmullRomCurve3(arr);
//曲线上获取点
const pointsArr = curve.getPoints(100);
const geometry = new THREE.BufferGeometry();
//读取坐标数据赋值给几何体顶点
geometry.setFromPoints(pointsArr);
// 线材质
const material = new THREE.LineBasicMaterial({
color: 0x00fffff
});
// 线模型
const line = new THREE.Line(geometry, material);
// 用点模型可视化样条曲线经过的顶点位置
const geometry2 = new THREE.BufferGeometry();
geometry2.setFromPoints(arr);
const material2 = new THREE.PointsMaterial({
color: 0xff00ff,
size: 10,
});
//点模型对象
const points = new THREE.Points(geometry2, material2);
二维样条曲线
二维样条曲线SplineCurve
默认情况下就是在XOY平面生成一个平面的样条曲线。
// 二维向量Vector2创建一组顶点坐标
const arr = [
new THREE.Vector2(-100, 0),
new THREE.Vector2(0, 30),
new THREE.Vector2(100, 0),
];
// 二维样条曲线
const curve = new THREE.SplineCurve(arr);
5. 贝塞尔曲线
二维二次贝塞尔曲线QuadraticBezierCurve
// p1、p2、p3表示三个点坐标
// p1、p3是曲线起始点,p2是曲线的控制点
const p1 = new THREE.Vector2(-80, 0);
const p2 = new THREE.Vector2(20, 100);
const p3 = new THREE.Vector2(80, 0);
// 二维二次贝赛尔曲线
const curve = new THREE.QuadraticBezierCurve(p1, p2, p3);
const pointsArr = curve.getPoints(100); //曲线上获取点
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints(pointsArr); //读取坐标数据赋值给几何体顶点
const material = new THREE.LineBasicMaterial({color: 0x00fffff});
const line = new THREE.Line(geometry, material);
三维二次贝赛尔曲线QuadraticBezierCurve3
三维二次贝赛尔曲线QuadraticBezierCurve3
与二维二次贝赛尔曲线QuadraticBezierCurve
区别就是多了一个维度,参数是三维向量对象Vector3。
// p1、p2、p3表示三个点坐标
const p1 = new THREE.Vector3(-80, 0, 0);
const p2 = new THREE.Vector3(20, 100, 0);
const p3 = new THREE.Vector3(80, 0, 100);
// 三维二次贝赛尔曲线
const curve = new THREE.QuadraticBezierCurve3(p1, p2, p3);
二维三次贝塞尔曲线CubicBezierCurve
二维三次贝塞尔曲线CubicBezierCurve
与二维二次贝赛尔曲线QuadraticBezierCurve
区别就是多了一个控制点。
// p1、p2、p3、p4表示4个点坐标
// p1、p4是曲线起始点,p2、p3是曲线的控制点
const p1 = new THREE.Vector2(-80, 0);
const p2 = new THREE.Vector2(-40, 50);
const p3 = new THREE.Vector2(50, 50);
const p4 = new THREE.Vector2(80, 0);
// 二维三次贝赛尔曲线
const curve = new THREE.CubicBezierCurve(p1, p2, p3, p4);
三维三次贝赛尔曲线CubicBezierCurve3
三维三次贝赛尔曲线CubicBezierCurve3
与二维三次贝塞尔曲线CubicBezierCurve
区别就是多了一个维度,参数是三维向量对象Vector3。
const p1 = new THREE.Vector3(-80, 0, 0);
const p2 = new THREE.Vector3(-40, 50, 0);
const p3 = new THREE.Vector3(50, 50, 0);
const p4 = new THREE.Vector3(80, 0, 100);
// 三维三次贝赛尔曲线
const curve = new THREE.CubicBezierCurve3(p1, p2, p3, p4);
6. 样条、贝塞尔曲线应用(*)
曲线API在大屏可视化中的应用:地图大屏可视化、地球大屏可视化。表示一个飞线曲线轨迹有多重方案,圆弧、椭圆弧、贝塞尔、样条...
三维样条曲线CatmullRomCurve3
实现飞线轨迹
// p1、p3轨迹线起始点坐标
const p1 = new THREE.Vector3(-100, 0, -100);
const p3 = new THREE.Vector3(100, 0, 100);
// 计算p1和p3的中点坐标
const x2 = (p1.x + p3.x)/2;
const z2 = (p1.z + p3.z)/2;
const h = 50;
const p2 = new THREE.Vector3(x2, h, z2);
const arr = [p1, p2, p3];
// 三维样条曲线
const curve = new THREE.CatmullRomCurve3(arr);
三维二次贝赛尔曲线QuadraticBezierCurve3
实现飞线轨迹
// p1、p3轨迹线起始点坐标
const p1 = new THREE.Vector3(-100, 0, -100);
const p3 = new THREE.Vector3(100, 0, 100);
// 计算p1和p3的中点坐标
const x2 = (p1.x + p3.x)/2;
const z2 = (p1.z + p3.z)/2;
const h = 100;
const p2 = new THREE.Vector3(x2, h, z2);
// 三维二次贝赛尔曲线
const curve = new THREE.QuadraticBezierCurve3(p1, p2, p3);
7. 组合曲线CurvePath拼接曲线(*)
通过threejs组合曲线CurvePath
对象,你可以把直线、圆弧、贝塞尔等线条拼接为一条曲线。
const R = 80;//圆弧半径
const H = 200;//直线部分高度
// 直线1
const line1 = new THREE.LineCurve(new THREE.Vector2(R, H), new THREE.Vector2(R, 0));
// 圆弧
const arc = new THREE.ArcCurve(0, 0, R, 0, Math.PI, true);
// 直线2
const line2 = new THREE.LineCurve(new THREE.Vector2(-R, 0), new THREE.Vector2(-R, H));
// CurvePath创建一个组合曲线对象
const CurvePath = new THREE.CurvePath();
//line1, arc, line2拼接出来一个U型轮廓曲线,注意顺序
CurvePath.curves.push(line1, arc, line2);
注意:曲线首尾相接
有一点要注意,组合曲线的坐标顺序和线条组合顺序不能随意写,总的方向,就是先确定整个曲线的起点,然后沿着一个方向依次绘制不同曲线,确保不同曲线首尾相接。
- 直线的起点是直线的第一个参数
- 圆弧线的起点,默认就是从x轴正半轴开始
组合曲线CurvePath
取点
组合曲线CurvePath
和它的父类Curve一样具有.getPoints()
和.getSpacedPoints()
取点方法。
//组合曲线上获取点
const pointsArr = CurvePath.getPoints(16); //方法1
const pointsArr = CurvePath.getSpacedPoints(16); //方法2
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints(pointsArr); //读取坐标数据赋值给几何体顶点
8. 曲线路径管道TubeGeometry(*)
管道TubeGeometry
几何体的功能就是基于一个3D曲线路径,生成一个管道几何体。
构造函数格式:TubeGeometry(path, tubularSegments, radius, radiusSegments, closed)
CurvePath多段路径生成管道案例(*)
CurvePath
组合曲线,也可以作为TubeGeometry
的参数1,用于生成管道几何体。
下面组合曲线CurvePath是由一段三维贝塞尔曲线QuadraticBezierCurve3
加上两段3D直线LineCurve3
拼接组成。
// 创建多段线条的顶点数据
const p1 = new THREE.Vector3(0, 0,100)
const p2 = new THREE.Vector3(0, 0,30);
const p3 = new THREE.Vector3(0, 0,0);
const p4 = new THREE.Vector3(30, 0, 0);
const p5 = new THREE.Vector3(100, 0, 0);
// 1. 3D直线线段
const line1 = new THREE.LineCurve3(p1, p2);
// 2. 三维二次贝塞尔曲线
const curve = new THREE.QuadraticBezierCurve3(p2, p3, p4);
// 3. 3D直线线段
const line2 = new THREE.LineCurve3(p4, p5);
const CurvePath = new THREE.CurvePath();
// 三条线拼接为一条曲线
CurvePath.curves.push(line1, curve, line2);
// CurvePath:路径 40:沿着轨迹细分数 2:管道半径 25:管道截面圆细分数
const geometry = new THREE.TubeGeometry(CurvePath, 50, 2, 25);
9. 旋转成型LatheGeometry
生活中有很多的几何体具备旋转特征, three.js提供了一个类LatheGeometry()
, LatheGeometry
可以利用一个2D轮廓,经过旋转变换生成一个3D的几何体曲面。
格式:LatheGeometry(points, segments, phiStart, phiLength)
通过二维样条曲线SplineCurve
生成一个光滑的曲线旋转轮廓。
// 通过三个点定义一个二维样条曲线
const curve = new THREE.SplineCurve([
new THREE.Vector2(50, 60),
new THREE.Vector2(25, 0),
new THREE.Vector2(50, -60)
]);
//曲线上获取点,作为旋转几何体的旋转轮廓
const pointsArr = curve.getPoints(50);
console.log('旋转轮廓数据',pointsArr);
// LatheGeometry:pointsArr轮廓绕y轴旋转生成几何体曲面
const geometry = new THREE.LatheGeometry(pointsArr, 30);
10. 轮廓填充ShapeGeometry(*)
有些时候已知一个多边形的外轮廓坐标,想通过这些外轮廓坐标生成一个多边形几何体平面,这时候你可以借助threejs提供的轮廓填充ShapeGeometry
几何体实现。
// 一组二维向量表示一个多边形轮廓坐标
const pointsArr = [
new THREE.Vector2(-50, -50),
new THREE.Vector2(-60, 0),
new THREE.Vector2(0, 50),
new THREE.Vector2(60, 0),
new THREE.Vector2(50, -50),
]
// Shape表示一个平面多边形轮廓,参数是二维向量构成的数组pointsArr
const shape = new THREE.Shape(pointsArr);
const geometry = new THREE.ShapeGeometry(shape);
11. 拉伸ExtrudeGeometry(*)
拉伸几何体ExtrudeGeometry
和上节课讲到的轮廓填充几何体ShapeGeometry
一样,都是基于一个基础的平面轮廓Shape
进行变换,生成一个几何体。
// Shape表示一个平面多边形轮廓
const shape = new THREE.Shape([
// 按照特定顺序,依次书写多边形顶点坐标
new THREE.Vector2(-50, -50), //多边形起点
new THREE.Vector2(-50, 50),
new THREE.Vector2(50, 50),
new THREE.Vector2(50, -50),
]);
const geometry = new THREE.ExtrudeGeometry(
shape,{
depth: 20,
}
);
12. 扫描ExtrudeGeometry
通过ExtrudeGeometry
除了可以实现拉伸成型,也可以让一个平面轮廓Shape
沿着曲线扫描成型。
// 扫描轮廓:Shape表示一个平面多边形轮廓
const shape = new THREE.Shape([
// 按照特定顺序,依次书写多边形顶点坐标
new THREE.Vector2(0,0), //多边形起点
new THREE.Vector2(0,10),
new THREE.Vector2(10,10),
new THREE.Vector2(10,0),
]);
// 扫描轨迹:创建轮廓的扫描轨迹(3D样条曲线)
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3( -10, -50, -50 ),
new THREE.Vector3( 10, 0, 0 ),
new THREE.Vector3( 8, 50, 50 ),
new THREE.Vector3( -5, 0, 100)
]);
//扫描造型:扫描默认没有倒角
const geometry = new THREE.ExtrudeGeometry(
shape, //扫描轮廓
{
extrudePath:curve,//扫描轨迹
steps:100//沿着路径细分精度,越大越光滑
}
);
13. 多边形轮廓Shape简介(*)
上节课提到多边形轮廓Shape
,是直接通过一组二维向量Vector2
表示的xy点坐标创建。下面是通过Shape
的一些2D绘图API表达多边形轮廓。
.currentPoint
属性
.currentPoint
属性字面意思是当前点,默认值Vector2(0,0)
。
实例化一个Shape
或Path
对象,查看.currentPoint
属性的默认值。
const shape = new THREE.Shape();
const path = new THREE.Path();
console.log('currentPoint',shape.currentPoint);
.moveTo()
方法
执行和.moveTo()
方法查看.currentPoint
属性变化。
const shape = new THREE.Shape();
shape.moveTo(10,0);
console.log('currentPoint',shape.currentPoint);
除了.moveTo()
方法,Path
其他的直线、圆弧等方法也可能会改变.currentPoint
属性
绘制直线.lineTo()
.lineTo()
绘制直线线段,直线线段的起点是当前点属性.currentPoint
表示的位置,结束点是.lineTo()
的参数表示的坐标。
const shape = new THREE.Shape();
shape.moveTo(10,0);//.currentPoint变为(10,0)
// 绘制直线线段,起点(10,0),结束点(100,0)
shape.lineTo(100,0);
.lineTo()
方法和.moveTo()
方法,一样会改变.currentPoint
属性
shape.lineTo(100,0);//.currentPoint变为(100,0)
console.log('currentPoint',shape.currentPoint);
绘制一个矩形轮廓Shape
const shape = new THREE.Shape();
shape.moveTo(10, 0); //.currentPoint变为(10,0)
// 绘制直线线段,起点(10,0),结束点(100,0)
shape.lineTo(100, 0);//.currentPoint变为(100, 0)
shape.lineTo(100, 100);//.currentPoint变为(100, 100)
shape.lineTo(10, 100);//.currentPoint变为(10, 100)
创建好的多边形轮廓Shape
作为几何体的参数
// ShapeGeometry填充Shape获得一个平面几何体
const geometry = new THREE.ShapeGeometry(shape);
// ExtrudeGeometry拉伸Shape获得一个长方体几何体
const geometry = new THREE.ExtrudeGeometry(shape, {
depth:20,//拉伸长度
bevelEnabled:false,//禁止倒角
});
14. 几何体顶点颜色数数据
下面给大家介绍顶点颜色.attributes.color
数据。
- 顶点位置数据
geometry.attributes.position
- 顶点法向量数据
geometry.attributes.normal
- 顶点UV数据
geometry.attributes.uv
- 顶点颜色数据
geometry.attributes.color
#几何体顶点颜色.attributes.color
几何体BufferGeometry
顶点位置数据.attributes.position
。
const geometry = new THREE.BufferGeometry(); //创建一个几何体对象
const vertices = new Float32Array([
0, 0, 0, //顶点1坐标
50, 0, 0, //顶点2坐标
0, 25, 0, //顶点3坐标
]);
// 顶点位置
geometry.attributes.position = new THREE.BufferAttribute(vertices, 3);
与几何体BufferGeometry
顶点位置数据.attributes.position
一一对应的顶点颜色数据.attributes.color
。
每个点对应一个位置数据,同时对应一个颜色数据。
const colors = new Float32Array([
1, 0, 0, //顶点1颜色
0, 0, 1, //顶点2颜色
0, 1, 0, //顶点3颜色
]);
// 设置几何体attributes属性的颜色color属性
//3个为一组,表示一个顶点的颜色数据RGB
geometry.attributes.color = new THREE.BufferAttribute(colors, 3);
点模型Points
渲染顶点颜色数据
通过点、线、网格模型渲染几何体Geometry
,如果希望顶点颜色.attributes.color
起作用,需要设置材质属性vertexColors:true
,下面以以点模型为例,可以看到geometry的不同点被你设置为了不同颜色。
// 点渲染模式
const material = new THREE.PointsMaterial({
// color: 0x333333,//使用顶点颜色数据,color属性可以不用设置
vertexColors:true,//默认false,设置为true表示使用顶点颜色渲染
size: 20.0, //点对象像素尺寸
});
const points = new THREE.Points(geometry, material); //点模型对象
颜色渐变(颜色插值)
自定几何体顶点颜色数据,然后用线模型Line渲染,你可以看到直线的颜色是渐变的。
下面代码两端直线,分别是红色到蓝色渐变、蓝色到绿色渐变。
const colors = new Float32Array([
1, 0, 0, //顶点1颜色
0, 0, 1, //顶点2颜色
0, 1, 0, //顶点3颜色
]);
geometry.attributes.color = new THREE.BufferAttribute(colors, 3);
const material = new THREE.LineBasicMaterial({
vertexColors:true,//使用顶点颜色渲染
});
const line = new THREE.Line(geometry, material);
几何体顶点颜色.attributes.color
设置的直线颜色渐变效果
网格模型颜色渐变
自定几何体顶点颜色数据,然后用网格模型Mesh渲染,和Line一样,也会产生颜色渐变效果。
const material = new THREE.MeshBasicMaterial({
// color: 0x333333,//使用顶点颜色数据,color属性可以不用设置
vertexColors:true,//默认false,设置为true表示使用顶点颜色渲染
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
15. 一段曲线颜色渐变(*)
可以通过几何体顶点颜色.attributes.color
数据,实现一段曲线颜色渐变效果。
const geometry = new THREE.BufferGeometry(); //创建一个几何体对象
// 三维样条曲线
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-50, 20, 90),
new THREE.Vector3(-10, 40, 40),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(60, -60, 0),
new THREE.Vector3(70, 0, 80)
]);
const pointsArr = curve.getSpacedPoints(100); //曲线取点
geometry.setFromPoints(pointsArr); //pointsArr赋值给顶点位置属性
const pos = geometry.attributes.position;
const count = pos.count; //顶点数量
// 计算每个顶点的颜色值
const colorsArr = [];
for (let i = 0; i < count; i++) {
const percent = i / count; //点索引值相对所有点数量的百分比
//根据顶点位置顺序大小设置颜色渐变
// 红色分量从0到1变化,蓝色分量从1到0变化
colorsArr.push(percent, 0, 1 - percent); //蓝色到红色渐变色
}
//类型数组创建顶点颜色color数据
const colors = new Float32Array(colorsArr);
// 设置几何体attributes属性的颜色color属性
geometry.attributes.color = new THREE.BufferAttribute(colors, 3);
const material = new THREE.LineBasicMaterial({
vertexColors: true, //使用顶点颜色渲染
});
const line = new THREE.Line(geometry, material);
16. Color颜色渐变插值(*)
颜色对象Color
颜色渐变插值方法.lerpColors()
和.lerp()
颜色对象Color
颜色插值方法.lerpColors()
执行.lerpColors(Color1,Color2, percent)
通过一个百分比参数percent
,可以控制Color1和Color2两种颜色混合的百分比,Color1对应1-percent
,Color2对应percent
。
const c1 = new THREE.Color(0xff0000); //红色
const c2 = new THREE.Color(0x0000ff); //蓝色
const c = new THREE.Color();
c.lerpColors(c1,c2, 0); //100% c1 + 0% c2混合
c.lerpColors(c1,c2, 0.5); //c1和c2各取50%
颜色对象Color
颜色插值方法.lerp()
.lerp()
和.lerpColors()
功能一样,只是具体语法细节不同。
c1与c2颜色混合,混合后的rgb值,赋值给c1的.r
、.g
、.b
属性。
const c1 = new THREE.Color(0xff0000); //红色
const c2 = new THREE.Color(0x0000ff); //蓝色
c1.lerp(c2, percent);
相机基础
1.正投影相机
之前介绍了常用的透视投影相机PerspectiveCamera
,下面介绍正投影相机OrthographicCamera
。
正投影相机的长方体可视化空间和透视投影PerspectiveCamera
视锥体相似,只是形状不同。
// 构造函数格式
OrthographicCamera( left, right, top, bottom, near, far )
相机选择
对于大部分需要模拟人眼观察效果的场景,需要使用透视投影相机,比如人在场景中漫游,或是在高处俯瞰整个园区或工厂。
正投影没有透视效果,也就是不会模拟人眼观察世界的效果。在一些不需要透视的场景你可以选择使用正投影相机,比如整体预览一个中国地图的效果,或者一个2D可视化的效果。
2. 包围盒Box3(*)
所谓包围盒Box3
,就是一个长方体空间,把模型的所有顶点数据包围在一个最小的长方体空间中,这个最小长方体空间就是该模型的包围盒Box3
。
计算模型最小包围盒.expandByObject()
模型对象,比如mesh或group,作为.expandByObject()
的参数,可以计算该模型的包围盒。
const box3 = new THREE.Box3();
box3.expandByObject(mesh); // 计算模型包围盒
console.log('查看包围盒',box3);
浏览器控制台你可以通过.min
和.max
属性查看模型的包围盒信息。
包围盒尺寸.getSize()
返回包围盒具体的长宽高尺寸
const scale = new THREE.Vector3()
// getSize()计算包围盒尺寸
// 获得包围盒长宽高尺寸,结果保存在参数三维向量对象scale中
box3.getSize(scale)
console.log('模型包围盒尺寸', scale);
包围盒几何中心.getCenter()
Box3方法.getCenter()
计算返回包围盒几何中心
// 计算包围盒中心坐标
const center = new THREE.Vector3()
box3.getCenter(center)
console.log('模型中心坐标', center);
3. OrbitControls旋转缩放限制(*)
禁止右键平移.enablePan
属性
controls.enablePan = false; //禁止右键拖拽
禁止缩放或旋转
controls.enableZoom = false;//禁止缩放
controls.enableRotate = false; //禁止旋转
OrbitControls.target
属性
相机控件OrbitControls.target
属性对应的就是相机的.lookAt()
观察目标。
执行controls.update();
,相机控件内部会执行camera.lookAt(controls.target)
。
// controls.target默认值是坐标原点
controls.target.set(x, y, z);
//update()函数内会执行camera.lookAt(x, y, z)
controls.update();
透视投影相机缩放范围
.minDistance
表示相机位置.position
和相机目标观察点controls.target
的最小距离。
//相机位置与观察目标点最小值
controls.minDistance = 200;
.maxDistance
表示相机位置.position
和相机目标观察点controls.target
的最大距离。
//相机位置与观察目标点最大值
controls.maxDistance = 500;
正投影缩放范围
// 缩放范围
controls.minZoom = 0.5;
controls.maxZoom = 2;
相机位置与目标观察点距离.getDistance()
//相机位置与目标观察点距离
const dis = controls.getDistance();
console.log('dis',dis);
设置旋转范围
// 上下旋转范围
controls.minPolarAngle = 0;//默认值0
controls.maxPolarAngle = Math.PI;//默认值Math.PI
.maxPolarAngle
属性设置为90度,这样不能看到工厂模型底部
controls.maxPolarAngle = Math.PI/2;
通过.minAzimuthAngle
和.maxAzimuthAngle
属性控制左右的旋转范围。
// 左右旋转范围
controls.minAzimuthAngle = -Math.PI/2;
controls.maxAzimuthAngle = Math.PI/2;
光源和阴影
gui辅助调节光源阴影(*)
阴影范围可视化调节
- 根据工厂尺寸数量级预先设置
.shadow.camera
,然后通过GUI调试选择一个合适的值 .shadow.camera
的位置通过光源的位置调试。.shadow.camera
参数改变后,注意执行cameraHelper.update();
更新
// 阴影子菜单
const shadowFolder = gui.addFolder('平行光阴影');
const cam = directionalLight.shadow.camera;
// 相机left、right等属性变化执行.updateProjectionMatrix();
// 相机变化了,执行CameraHelper的更新方法.update();
shadowFolder.add(cam,'left',-500,0).onChange(function(v){
cam.updateProjectionMatrix();//相机更新投影矩阵
cameraHelper.update();//相机范围变化了,相机辅助对象更新
});
其他参数类似设置
shadowFolder.add(cam,'right',0,500).onChange(function(v){
cam.updateProjectionMatrix();
cameraHelper.update();
});
shadowFolder.add(cam,'top',0,500).onChange(function(v){
cam.updateProjectionMatrix();
cameraHelper.update();
});
shadowFolder.add(cam,'bottom',-500,0).onChange(function(v){
cam.updateProjectionMatrix();
cameraHelper.update();
});
shadowFolder.add(cam,'far',0,1000).onChange(function(v){
cam.updateProjectionMatrix();
cameraHelper.update();
});
精灵模型Sprite(*)
1.创建精灵图
Three.js的精灵模型Sprite
和Threejs的网格模型Mesh
一样都是模型对象,父类都是Object3D
,关于精灵模型对象Sprite
的方法和属性除了可以查看文档Sprite,也可以查看父类Object3D
。
// 创建精灵材质对象SpriteMaterial
const spriteMaterial = new THREE.SpriteMaterial({
color:0x00ffff,//设置颜色
});
// 创建精灵模型对象,不需要几何体geometry参数
const sprite = new THREE.Sprite(spriteMaterial);
const mesh = new THREE.Mesh(geometry, material);
精灵模型标注场景(贴图)
如果希望矩形始终平行于canvas画布,就选择Sprite,如果希望矩形标注姿态角度能跟着场景旋转,就使用矩形Mesh
标注场景。
// 精灵模型设置颜色贴图
const texture = new THREE.TextureLoader().load("./光点.png");
const spriteMaterial = new THREE.SpriteMaterial({
map: texture, //设置精灵纹理贴图
transparent:true,//SpriteMaterial默认是true
const geometry = new THREE.BoxGeometry(25, 100, 50);
geometry.translate(0, 50, 0);
// mesh顶部中心添加标注,顶部中心坐标是(0,100,0)
const mesh = new THREE.Mesh(geometry, material);
});
射线拾取模型
1.射线概念
学习Three.js中的射线Ray
概念,可以类比数学几何中提到的射线,在三维空间中,一条线把一个点作为起点,然后沿着某个方向无限延伸。
// 创建射线对象Ray
const ray = new THREE.Ray()
// 设置射线起点
ray.origin = new THREE.Vector3(1,0,3);
// 表示射线沿着x轴正方向
ray.direction = new THREE.Vector3(1,0,0);
// 表示射线沿着x轴负方向
ray.direction = new THREE.Vector3(-1,0,0);
2.射线拾取模型
射线投射器Raycaster
通过.intersectObjects()
方法可以计算出来与自身射线.ray
相交的网格模型。
const raycaster = new THREE.Raycaster();
raycaster.ray.origin = new THREE.Vector3(-100, 0, 0);
raycaster.ray.direction = new THREE.Vector3(1, 0, 0);
// 射线发射拾取模型对象
const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
console.log("射线器返回的对象", intersects);
// intersects.length大于0说明,说明选中了模型
if (intersects.length > 0) {
console.log("交叉点坐标", intersects[0].point);
console.log("交叉对象",intersects[0].object);
console.log("射线原点和交叉点距离",intersects[0].distance);
}
3. 屏幕坐标转标准设备坐标
addEventListener('click',function(event){
const px = event.clientX;
const py = event.clientY;
const x = (px / width) * 2 - 1;
const y = -(py / height) * 2 + 1;
})
4. Raycaster(鼠标点击选中模型)
- 坐标转化(屏幕坐标转标准设备坐标)
- 计算射线(
.setFromCamera()
方法) - 射线交叉计算(
.intersectObjects()
方法)
下面代码的功能是鼠标单击threejs的canvas画布,通过射线投射器Raycaster
射线拾取网格模型,被选中拾取到的网格模型改变颜色。
renderer.domElement.addEventListener('click', function (event) {
// .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
const px = event.offsetX;
const py = event.offsetY;
//屏幕坐标px、py转WebGL标准设备坐标x、y
//width、height表示canvas画布宽高度
const x = (px / width) * 2 - 1;
const y = -(py / height) * 2 + 1;
//创建一个射线投射器`Raycaster`
const raycaster = new THREE.Raycaster();
//.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
// 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
//.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
// 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素
const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
console.log("射线器返回的对象", intersects);
// intersects.length大于0说明,说明选中了模型
if (intersects.length > 0) {
// 选中模型的第一个模型,设置为红色
intersects[0].object.material.color.set(0xff0000);
}
})
动画库tween.js(*)
TweenJS是一个由JavaScript语言编写的补间动画库,如果需要tweenjs辅助你生成动画,对于任何前端web项目,你都可以选择tweenjs库。
npm安装
npm i @tweenjs/tween.js@^18
import TWEEN from '@tweenjs/tween.js';
tweenjs基本语法
tweenjs功能从语法的角度讲,就是改变自己的参数对象。
const pos = {x: 0,y: 0};
const tween = new TWEEN.Tween(pos);//创建一段tween动画
//经过2000毫秒,pos对象的x和y属性分别从零变化为100、50
tween.to({x: 100,y: 50}, 2000);
//tween动画开始执行
tween.start();
在requestAnimationFrame动画中,tween更新.update()
,tween才能正常执行
function loop() {
TWEEN.update();//tween更新
requestAnimationFrame(loop);
}
浏览器控制台测试查看tweenjs是否逐渐改变pos对象的x和y属性
function loop() {
TWEEN.update();
// 测试tweenjs是否逐渐改变pos对象的x和y属性
console.log(pos.x,pos.y);
requestAnimationFrame(loop);
}
tweenjs改变threejs模型对象位置
three.js模型的位置mesh.position
属性是一个具有.x
、.y
、.z
属性的对象,可以直接使用tweenjs直接改变。
//创建一段mesh平移的动画
const tween = new TWEEN.Tween(mesh.position);
//经过2000毫秒,pos对象的x和y属性分别从零变化为100、50
tween.to({x: 100,y: 50}, 2000);
//tween动画开始执行
tween.start();
最后不要忘记在渲染循环中更新TWEEN.update();
即可。
// 渲染循环
function render() {
TWEEN.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
测试模型缩放动画
模型的缩放属性mesh.scale
和.position
属性一样是一个具有.x
、.y
、.z
属性的对象,你也可以直接用tweenjs动画控制。
new TWEEN.Tween(mesh.scale).to({
x: 100,
y: 50
}, 2000).start();
案例模块
渐变线
createGradualLine(fromVector3, toVector3) {
let arr = [
fromVector3.x,
fromVector3.y,
fromVector3.z,
toVector3.x,
toVector3.y,
toVector3.z,
];
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array(arr);
geometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3)
);
// 将颜色从 0x53c7cb 转换成 0xffffff
var colors = new Float32Array([0.329, 0.78, 0.796, 1, 0, 0]);
var colorAttribute = new THREE.BufferAttribute(colors, 3);
geometry.setAttribute("color", colorAttribute);
// 创建THREE.ShaderMaterial
var material = new THREE.ShaderMaterial({
vertexShader: `
attribute vec3 color;
varying vec3 vColor;
void main() {
vColor = color;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec3 vColor;
void main() {
float distance = distance(vColor, vec3(0.329, 0.780, 0.796));
float t = smoothstep(0.9, 1.0, distance); // 将0.9改为其他数字,调整颜色渐变的距离
gl_FragColor = vec4(mix(vec3(0.329, 0.780, 0.796), vec3(1.0, 1.0, 1.0), t), 1.0);
}
`
});
// 创建THREE.Line
var line = new THREE.Line(geometry, material);
return line;
},
字体渲染的三种方案
- CSS2DRender
这个会出现一个问题就是文字会在其他网格模型之上
function initCityName(cityName, pos) {
// 实例化css2d的渲染器
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(labelRenderer.domElement)
//设置样式
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.left = '0px';
labelRenderer.domElement.style.color = 'white'
labelRenderer.domElement.style.fontSize = '12px'
// labelRenderer.domElement.style.zIndex = '0';//设置层级
// 解决添加CSS2DLoader以后轨道控制器不能用的问题
labelRenderer.domElement.style.pointerEvents = 'none';
const div = document.createElement('div');
div.innerHTML = cityName
const labelObj = new CSS2DObject(div);
labelObj.name = cityName
labelObj.position.set(1, 1, 1)
return labelObj
}
复制代码
- Canvas+Sprite精灵图实现
将getCanvas返回值输送到createSprite中,并将返回值添加到scene中。这种方法可以使得相机无论如何旋转文字始终朝向屏幕。
function getCanvas(text, fontStyle = "Bold 25px Times New Roman") {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
ctx.fillStyle = "#ffffff";
ctx.font = fontStyle;
ctx.fillText(text, 0, 120);//写字
return canvas;
}
function createSprite(canvas, pos) {
let texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
let material = new THREE.SpriteMaterial({
map: texture
});
let mesh = new THREE.Sprite(material);
mesh.position.set( 0.6, 0.1, 0.2);
return mesh;
}
复制代码
- FontLoader + TextGeometry实现
首先引入FontLoader,和字体helvetikerRegular(英文)或者YaHeiRegular(中文) 中文Microsoft YaHei_Regular.json百度网盘地址:
提取码:zfzn
如果需要其他字体,可以自行创建。地址gero3.github.io/facetype.js…,选择任意一个中文字体的ttf文件,然后点击生成即可。
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
import helvetikerRegular from 'three/examples/fonts/helvetiker_regular.typeface.json';
//import YaHeiRegular from './assets/font/Microsoft YaHei_Regular.json';
复制代码
直接根据官网(threejs.org/docs/index.… 的写法会出现问题,所以我使用了一下方法(亲测可用)
TextGeometry()第一个参数需要是字符串,如果需要使用变量,可以使用toString()方法转换一下
var loader = new FontLoader();
var font = loader.parse(helvetikerRegular);
//var font = loader.parse(YaHeiRegular);
const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
const textGeometry = new TextGeometry(cityName, {
font: font,
size: 0.8, //字体大小
height: 0.1 //字体高度
});
const textMesh = new THREE.Mesh(textGeometry, material);
textMesh.position.set(1, 1, 1);
scene.add(textMesh);
飞线部分
- 使用着色器实现
/**
* createFlyCurve:创造飞线
* timerFlyCurve:放到render中让飞线动起来
*/
const uniforms = {
u_time: { value: 0.0 },
};
const clock = new THREE.Clock();
const timerFlyCurve = () => {
uniforms.u_time.value = clock.getElapsedTime();
};
// clamp()函数用于将一个值限制在一个指定的范围内
// 着色器可以写在.glsl文件中使用import引用 比如 import fragmentShader from './glsls/fragmentShader2.frag?raw';
// fract(val) ,意思为1减abs(val)的小数部分
const vertexShader = `
varying vec2 vUv;
attribute float percent;
uniform float u_time;
uniform float number;
uniform float speed;
uniform float length;
varying float opacity;
uniform float size;
void main()
{
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
float l = clamp(1.0-length,0.0,1.0);
gl_PointSize = clamp(fract(percent*number + l - u_time*number*speed)-l ,0.0,1.) * size * (1./length);
opacity = gl_PointSize/size;
gl_Position = projectionMatrix * mvPosition;
}
`;
const fragmentShader = `
#ifdef GL_ES
precision mediump float;
#endif
varying float opacity;
uniform vec3 color;
void main(){
gl_FragColor = vec4(color,1.0);
}
`;
function createFlyCurve(points, speed) {
let curve = new THREE.CatmullRomCurve3(points);
const colors = new THREE.Color(0x53c7cb);
let color = new THREE.Vector3(colors.r, colors.g, colors.b);
let flyLine = initFlyLine(
curve,
{
speed: speed, // 速度
color: color, // 颜色
number: 1, //同时跑动的流光数量
length: 0.4, //流光线条长度
size: 5.5, //粗细
},
150
);
return flyLine;
}
function initFlyLine(curve, matSetting, pointsNumber) {
let points = curve.getPoints(pointsNumber);
let geometry = new THREE.BufferGeometry().setFromPoints(points);
const length = points.length;
let percents = new Float32Array(length);
for (let i = 0; i < points.length; i += 1) {
percents[i] = i / length;
}
geometry.setAttribute('percent', new THREE.BufferAttribute(percents, 1));
const lineMaterial = initLineMaterial(matSetting);
let flyLine = new THREE.Points(geometry, lineMaterial);
return flyLine;
}
function initLineMaterial(setting) {
const number = setting ? Number(setting.number) || 1.0 : 1.0;
const speed = setting ? Number(setting.speed) || 1.0 : 1.0;
const length = setting ? Number(setting.length) || 0.5 : 0.5;
const size = setting ? Number(setting.size) || 3.0 : 3.0;
const color = setting
? setting.color || new THREE.Vector3(0, 1, 1)
: new THREE.Vector3(0, 1, 1);
const singleUniforms = {
u_time: uniforms.u_time,
number: { type: 'f', value: number },
speed: { type: 'f', value: speed },
length: { type: 'f', value: length },
size: { type: 'f', value: size },
color: { type: 'v3', value: color },
};
const lineMaterial = new THREE.ShaderMaterial({
uniforms: singleUniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true,
});
return lineMaterial;
}
- 通过计算位置实现
// 调用
this.createFlyLine([fromVector3, midVector3, toVector3]);
createFlyLine(pointArr) {
// 创建轨迹线
let curve = new THREE.CatmullRomCurve3(pointArr);
let trackMat = new THREE.MeshBasicMaterial({
color: color,
});
let curveSegment = 100
let points = curve.getPoints(curveSegment);
let trackGeo = new THREE.BufferGeometry().setFromPoints(points);
let flyTrack = new THREE.Line(trackGeo, trackMat);
this.trackGroup.add(flyTrack);
// 创建轨迹线上的飞线
let index = 0;
let num = curveSegment * 0.1;
let points2 = points.slice(index, index + num);
let flyCurve = new THREE.CatmullRomCurve3(points2);
let newPoints2 = flyCurve.getSpacedPoints(10); //获取更多的点数
let flyGeo = new THREE.BufferGeometry();
flyGeo.setFromPoints(newPoints2);
// 每个顶点对应一个百分比数据attributes.percent 用于控制点的渲染大小
let percentArr = []; //attributes.percent的数据
for (let i = 0; i < newPoints2.length; i++) {
percentArr.push(i / newPoints2.length);
}
let percentAttribute = new THREE.BufferAttribute(
new Float32Array(percentArr),
1
);
flyGeo.attributes.percent = percentAttribute;
// 点模型渲染几何体每个顶点
let pointMat = new THREE.PointsMaterial({
color: color,
size: 0.2, //点大小
});
let flyPoints = new THREE.Points(flyGeo, pointMat);
flyPoints.num = num;
flyPoints.index = index;
flyPoints.points = points
flyPoints.flyGeo = flyGeo
flyPoints.curveSegment = curveSegment
this.flyGroup.add(flyPoints);
// 修改点材质的着色器源码(注意:不同版本细节可能会稍微会有区别,不过整体思路是一样的)
pointMat.onBeforeCompile = function (shader) {
// 顶点着色器中声明一个attribute变量:百分比
shader.vertexShader = shader.vertexShader.replace(
"void main() {",
[
"attribute float percent;", //顶点大小百分比变量,控制点渲染大小
"void main() {",
].join("\n") // .join()把数组元素合成字符串
);
// 调整点渲染大小计算方式
shader.vertexShader = shader.vertexShader.replace(
"gl_PointSize = size;",
["gl_PointSize = percent * size;"].join("\n") // .join()把数组元素合成字符串
);
};
// if (this.flyGroup.children.length) disposeGroup(this.flyGroup);
},
render() {
this.flyGroup.children.forEach((fly)=>{
let indexMax = fly.curveSegment - fly.num
console.log(fly.index)
if (fly.index > indexMax) fly.index = 0;
fly.index +=1
let points3 = fly.points.slice(
fly.index,
fly.index + fly.num
); //从曲线上获取一段
var curve = new THREE.CatmullRomCurve3(points3);
var newPoints2 = curve.getSpacedPoints(fly.curveSegment); //获取更多的点数
fly.flyGeo.setFromPoints(newPoints2);
})
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.render);
this.stats.update();
},
波动光环
drawWaveMesh(posVec) {
const texLoader = new THREE.TextureLoader();
const wave = require("../assets/img/wave1.png");
var material = new THREE.MeshBasicMaterial({
map: texLoader.load(wave),
transparent: true,
});
var geometry = new THREE.PlaneBufferGeometry(1, 1); //默认在XOY平面上
var mesh = new THREE.Mesh(geometry, material);
mesh.size = 2; //自定义一个属性,表示mesh静态大小
mesh._s = Math.random() * 1.0 + 1.0; //自定义属性._s表示mesh在原始大小基础上放大倍数 光圈在原来mesh.size基础上1~1.5倍之间变化
mesh.scale.set(
mesh.size * mesh._s,
mesh.size * mesh._s,
mesh.size * mesh._s
);
mesh.position.set(posVec.x, posVec.y, 0.01); //设置mesh位置
return mesh;
},
render() {
// 所有波动光圈都有自己的透明度和大小状态,一个波动光圈透明度变化过程是:0~1~0反复循环
if (waveMeshArr.length) {
waveMeshArr.forEach(function (mesh) {
mesh._s += 0.02;
mesh.scale.set(
mesh.size * mesh._s,
mesh.size * mesh._s,
mesh.size * mesh._s
);
if (mesh._s <= 1.5) {
mesh.material.opacity = (mesh._s - 1) * 2;
} else if (mesh._s > 1.5 && mesh._s <= 2) {
mesh.material.opacity = 1 - (mesh._s - 1.5) * 2; //1->0
} else {
mesh._s = 1;
}
});
}
.............
},