Three.js-性能优化

最近做了一个集群目标策略的3D可视化项目,前面花了2周时间出了个Demo来进行演示,性能极差,在我的i7-12700H+RTX2050的笔记本上,也会出现卡顿。后面这个还要迁移到移动端,性能问题就更严重了,因此借助解决这个需求的性能问题,把Three.js的性能优化方面的内容整理下,方便今后参考。

CPU or GPU?

可以通过将场景中所有材质改为普通材质,来确定你的网页是依赖CPU的还是GPU的:

1
scene.overrideMaterial = new MeshBasicMaterial({ color: 'green' });

如果性能有提升,那么说明是依赖GPU的;如果性能没提升,那么就是依赖CPU的。

比如我们做的这个3D目标策略,修改后PC端帧数从80提升到了120,移动端帧数从27提升到了52,那么说明就是依赖GPU的。

内存

模型加载到内存中,占用的内存和显存比硬盘上的模型尺寸往往要大一些。

而像iOS这种,是有单网页内存上限限制的,超过一定数值就会页面崩溃。

Web Worker

web worker 是运行在后台的 JavaScript,它独立于其他脚本,不会影响页面的性能
Physijs物理库就是使用这种方式来保证页面的性能。

我们可以将一些计算类的内容都放到web worker中处理。

控制帧数

默认60帧在某些场景其实用不着,我们人为降低下帧数,也可以提升性能。

不要在render中执行太多操作

UnrealBloom的性能损耗

不要设置冗余的Vue的data属性

Dev-tools分析

渲染阶段

这个不知道是不是和面数有关系,还不知道怎么优化。

2

交互阶段

看起来主要是事件的问题。

1

程序中给renderer设置了intersectRecursive=true,可能有影响。

我设置了这个:

1
<Raycaster @click="onPointerClickEvent" intersect-recursive />

操作的时候基本就20-30FPS。

如果去掉这个,那么基本上稳定60FPS。

1
2
3
4
5
6
7
8
<Renderer
ref="renderer"
antialias
:orbit-ctrl="{ enableDamping: true, target }"
:pointer="{ intersectRecursive: true }"
resize="true"
shadow
>

Renderer中的:pointer=”{ intersectRecursive: true}” 是结合Mesh上的@click来生效的。

Raycaster.intersectObjects(objects, true)导致的性能问题

官方文档

Raycaster.intersectObjects()的第二个参数,标注了是否递归检测被命中的元素及其子元素。

如果是一些独立、数量较少的模型场景,这个参数设置为true是没问题的,可以简化操作;但是如果是加载一些层级结构复杂的模型,则这个计算会很耗费性能,因为这是在每一个渲染循环中都会执行的操作。

解决方案:

模型load成功后,我们对需要执行事件交互的模型做一个引用缓存,然后每次执行Raycaster.intersectObjects()的时候,手动将这些模型作为第一个参数传入进去。

onEvent.js也是按照这个思路做的,当用户给某个Mesh注册事件监听的时候,将其加入缓存列表。这样就按需计算了。

注意:intersectObjects()似乎只会针对Mesh生效?一定会拆散模型?

onEvent.js的做法

1、触发事件

2、将Group逐个拆分,放入容器

3、将容器作为参数传入intersectObjects(),确定被点中的Mesh对象是哪个

4、反向通过被点中的Mesh对象,找到绑定事件的根对象

5、触发跟对象的事件函数

onEvent.js有对点击空白区域做拦截,因此我们完全可以舍弃掉再叠加一层Canvas来监听空白点击事件的方式了。

面数多导致RayCaster计算量巨大的问题

比如我们的3D目标策略图、3D汽车产业链,因为模型面数较多,加上射线检测后,转动一下就很卡(给mousemove事件添加了RayCaster的调用)。

解决方案:

1、压缩模型,降低面数

2、防抖节流

3、仅当用户操作停止时,才触发RayCaster

以汽车模型为例,压缩前大约70MB,压缩后7.18MB,但是发现性能还是一样差,似乎压缩模型对于网格没啥效果。

李少杰:

我目前的方案是,先从sence中筛选可见的,不为组的mesh实体,然后挨个判断
假设有N个模型,对应N个射线,每条射线都需要检测N个模型,才能算出有对应的模型有没有被遮挡,全车的时候N很大,大概160多,再加上模型平均面数比较多,大概3-5s才能出结果

改为GPU拾取方案

three.js manual

想着很简单,然后发现还是有不少要注意的点的,比如要剔除光照等因素的影响、性能优化等。

这个的原理,是用单独的一个scene,并指定相机大小,来检测相机中的像素颜色。

几个关键的API:

camera.setViewOffset()

.setViewOffset ( fullWidth : Float, fullHeight : Float, x : Float, y : Float, width : Float, height : Float ) : undefined

fullWidth — full width of multiview setup
fullHeight — full height of multiview setup
x — horizontal offset of subcamera
y — vertical offset of subcamera
width — width of subcamera
height — height of subcamera

WebGLRender.readRenderTargetPixels()

全屏读取的话,这个会不会有性能问题?哪怕读取没问题,遍历判断也会有问题吧?比如我4K显示器:

3840*2160 = 8,294,400,接近830W次判断,我的天~

关键这个还是每一帧都要判断的。

(误,此路不通)额外收获

这个还可以解决在用户无交互的情况下,判断哪些物体被全部/局部遮挡了,比如用于标记操作引导点:

javascript - Three.js detect when object is partially and fully occluded - Stack Overflow

经测试,这个方案不行,因为计算量太大了。

有性能问题,刚想了下,GPU拾取也不适合这个场景,主要问题还是在于这个是无用户操作接入,要针对全场判断
如果是用户鼠标交互,只需要判断一个像素;但是这种全场判断,比如4k全屏,单次判断就要判断830W次;即使做部分裁剪,也在几百万的级别,性能吃不消。

我自己基于官方的这个Demo修改了下,卡成狗了,根本没法用。
其实也有其他一些trick的方法,不是很完美,但会比老方案好一些,比如还是用老方案,但是采样取点多取几个

方案1:WebGL2.0的Query

three.js webgl - Occlusion Queries

https://github.com/mrdoob/three.js/pull/15450

WebGLQuery - Web APIs | MDN

还没合并到Three.js中,要想用,得自己去他的页面源码里面迁移过来才行。

使用WebGL2:

1
2
3
var canvas = document.createElement( 'canvas' );
var context = canvas.getContext( 'webgl2' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas, context: context } );

Trois默认使用WebGL1.0,问题不大,可以魔改。

但是!WebGL2有个坑:Safari不支持……这样后面移动端没法用。

通过beginQuery制定查询类型,比如遮挡查询:

1
WebGL2RenderingContext.beginQuery(gl.ANY_SAMPLES_PASSED, query)

Babylon.js在 3.1版本就支持遮挡查询了。

另外要考虑一个:阴影问题,不显示了就没影子了。

方案2:react-three-drei的occlude

https://codesandbox.io/s/html-markers-6oei7?file=/src/App.js:1943-1950

https://github.com/pmndrs/drei#html

这个我看了下,还是不行
这个也还是通过raycaster进行检测的:拾取物体和相机的距离 VS. 目标物体和相机的距离,通过这个来判断是否被遮挡
我们这个场景,还是会有性能问题

(TODO)方案3:GPU拾取&增加采样点

比如取每个模型的包围盒的8个点+中心点,或者模型球面上的N个点+中心点。

然后仍然用GPU拾取的方案,这样我们如果有300个模型,每个模型取9个点,那么也就只需要计算2700个点,似乎可行?

但是取像素是2D平面的操作,还得先把模型的这些点的3D坐标转为2D坐标才行。最终性能怎么样还不好说。

步骤:

1、单独创建一个PickingScene

2、clone每个独立模型,设置顶点颜色,构建为一个整的Mesh,加入PickingScene:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 构建拾取用到的对象,注意将id作为取数据的标记
elements.forEach((element, i) => {
// TODO:获取geometry
geometry = geometry.clone();
// give the geometry's vertices a color corresponding to the "id"
applyVertexColors(geometry, color.setHex(i));
geometriesPicking.push(geometry);
pickingData[i] = {
position: position,
rotation: rotation,
scale: scale,
};
};

// 加入场景
pickingScene.add(
new THREE.Mesh(
BufferGeometryUtils.mergeBufferGeometries(geometriesPicking),
pickingMaterial
)
);

3、帧渲染之前,同步位置信息(可优化,封装为一个函数,打开车门的时候调用下即可)

1
2
3
function syncTransform(displayingObjects, pickingObject) {
// 根据id一一对应
}

4、离屏渲染PickingScene

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function pick() {
//render the picking scene off-screen

// set the view offset to represent just a single pixel under the mouse

// 这里可以优化,根据汽车的包围盒确定渲染的范围
camera.setViewOffset(
renderer.domElement.width,
renderer.domElement.height,
0,
0,
renderer.domElement.width,
renderer.domElement.height
);

// render the scene

renderer.setRenderTarget(pickingTexture);
renderer.render(pickingScene, camera);

// clear the view offset so rendering returns to normal

camera.clearViewOffset();

//create buffer for reading single pixel

const pixelBuffer = new Uint8Array(4);

//read the pixel

renderer.readRenderTargetPixels(
pickingTexture,
pointer.x * window.devicePixelRatio,
pointer.y * window.devicePixelRatio,
1,
1,
pixelBuffer
);

//interpret the pixel as an ID

const id =
(pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | pixelBuffer[2];
const data = pickingData[id];

if (data) {
//move our highlightBox so that it surrounds the picked object

if (data.position && data.rotation && data.scale) {
highlightBox.position.copy(data.position);
highlightBox.rotation.copy(data.rotation);
highlightBox.scale.copy(data.scale).add(offset);
highlightBox.visible = true;
}
} else {
highlightBox.visible = false;
}
}

5、获取每个模型的中心点,转为2D坐标,获取该点的颜色,判断是否被遮挡

6、给未被遮挡的对象,进行业务处理

程序设计方面:

1、封装为一个Helper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class GPUPicking{
private _displayingObjects: Array<Object3D>;
private _pickingingObjects: Array<Object3D>;

// 同步展示的物体和用于拾取的物体的位置信息
public syncTransform(){

}

// 拾取
public pick() {

}

}

遇到的坑

Material设置了vertexColors: true之后,不能设置color属性,否则颜色会不生效:

1
2
3
4
const pickingMaterial = new THREE.MeshBasicMaterial({
vertexColors: true,
// color: new THREE.Color(0, 1, 0),
});

(巨坑)模型数量太少,导致setHex(i)生成的颜色值很小,全部都接近黑色,最终渲染出来就是黑屏,让我以为渲染失败了:

1
2
// 总共166个元素,最大的结果:Color {isColor: true, r: 0, g: 0, b: 0.6470588235294118}
applyVertexColors(geometry, color.setHex(i));

颜色值16进制,分三段,每段255,是2的8次方,16的6次方,1677216,除以166=101067

scale问题

另外会不会有缩放问题?屏幕被颜色都填充满了

看了下结构,只有最外层有scale,里面都是1,估计这个确实有问题的,得乘以什么矩阵才行

计算位置是否也要乘以矩阵才行?

1、先将模型坐标转换为世界坐标

2、缩放大小处理下

3、再传入类里面进行计算

(TODO:待整理)欧拉角和四元数的转换:

Three.js欧拉对象Euler和四元数Quaternion

果然,设置scale才行;但是不要计算scale,直接写死0.03,不然算出来的有问题,(TODO)待确认为什么有问题。

定位肯定有问题!

不对,我是将中心点转为2D,那叠加的情况岂不是就错了?

比如安全气囊被玻璃挡住了,安全气囊的中心点传入进去,得到的颜色不是纯黑的,是玻璃的颜色。

(坑)我这个场景和官方的不一样!判断逻辑得修改,我得根据拿到的id,和当前循环的id对比,看是不是同一个

1、我算错了

2、(x)有动画动了

3、模型的定位不正确,导致我转换后错误(也不对,应该一错全部错啊)

4、(X)都是偏左上的?是不是几个位置大小不一致?

【Warning】这个方案不行,比如轮胎1,中间是空心的,就判断有误了

“轮毂盖1”又TM不在可交互的模型范围内

window.devicePixelRatio的问题

这里的x、y不对,是不是默认进来车设置了相机视角?

缺少debug工具

不能动态查看某个模型到底是什么状态

怎么搞个可以动态查看物体的调试工具?点击控制台就可以查看,类似查看DOM元素

待整理学习的几个API

this._renderer.setRenderTarget(null);

this._renderer.setRenderTarget(this._pickingTexture);

 this._camera.setViewOffset()

通过离屏渲染渲染阴影

比如人物脚底随着角色移动的实时阴影,可以离屏渲染实现。

参考2022年D2论坛中,淘宝的分享内容。

(TODO)合并策略-Instanced、Merged、Naive

Instanced、Merged、Naive三种合并策略的区别:

https://threejs.org/examples/?q=instanc#webgl_instancing_performance
(TODO)原理、帧数、CPU、内存、可修改的内容

InstancedMesh

核心是减少DrawCall,减少到1。比如我们的故事森林,假如有7种树,那么就是合并成7个InstancedMesh。

矩阵相关的计算可以(缩放、旋转),顶点相关的不行。可以改颜色,也可以改材质(Three.js不行,但是Unity可以)。

压缩模型

Google的draco(天龙座)算法:

Draco 3D Graphics Compression

和three.js结合的一个示例:draco/README.md at master · google/draco · GitHub

GitHub - CesiumGS/gltf-pipeline: Content pipeline tools for optimizing glTF assets.

移动端帧数低于30帧

3

一个RAF周期的执行时间太长了(166ms),导致丢帧很严重。

需要排查下,我们在RAF中做了哪些操作、是否可以针对移动端降低这些操作的频率。

另外,我们可能可以通过分层(Layer)来大幅度提升移动端渲染性能吧?模型少了,自然面数就少了。

工具

GLTF模型分析工具:

https://gltf.report/

这是一个部分开源的项目:https://github.com/donmccurdy/glTF-Report-Feedback

能看到模型的详细信息,包括GL_PRIMITIVES(几何图元)、VERTICES(顶点数)、INDICES(三角形的顶点索引数)、占用的GPU内存、动画的关键帧数量等。

从这个工具可以看到,Texture占用的GPU倍数可能达到原始文件大小的10-30倍,所以精简纹理挺重要的。

这个工具还提供在线压缩模型的功能,一个5MB的模型,通过Draco算法压缩后精简为了700KB,效果惊人。用的是google的库来实现的。

GLTF模型转换:

https://gltf-transform.donmccurdy.com/

(TODO)3D产业链的性能优化案例

这个项目性能巨差,在设计师的Mac mini上跑起来很卡顿,视觉效果也非常差。

查看网页的任务管理器,发现CPU一直属于较高的使用率状态(后来看了下,策略图也是CPU一直比较高的);再结合dev-tools的性能分析,

怀疑:是不是射线拾取一直在跑?

从耗时较长丢帧的函数调用栈来看,是渲染耗时很长,那么是因为面数多么,还是因为我们用的特效性能很差?

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 获取场景内模型数量、顶点数及面片数
* @param {*} view :需要计算的场景视图即scene
*/
export function getSceneModelFaceNum(scene: THREE.Scene) {
let objects = 0; // 场景模型对象
let vertices = 0; //模型顶点
let triangles = 0; // 模型面片

for (let index = 0; index < scene.children.length; index++) {
let object = scene.children[index];
object.traverseVisible(function (object) {
objects++;
if (object instanceof THREE.Mesh) {
let geometry = object.geometry;
if (
geometry instanceof THREE.BufferGeometry &&
geometry.attributes.position
) {
vertices += geometry.attributes.position.count;
if (geometry.index !== null) {
triangles += geometry.index.count / 3;
} else {
triangles += geometry.attributes.position.count / 3;
}
} else {
console.log('这不是一个几何体', geometry);
}
}
});
}
console.log(
'模型对象数量: ' + objects,
'模型顶点数: ' + vertices,
'模型面片数: ' + triangles
);
}

function printInfo() {
renderer.info.autoReset = false;
console.error("WebGLRenderer.info", renderer.info);
// 必须在打印之后再重置,否则打印的calls全是0
renderer.info.reset();
getSceneModelFaceNum((scene.value as any).scene);
}

window.printInfo = printInfo;

setTimeout(() => {
printInfo();
}, 3000);

查看下统计数据:

1
2
3
4
5
6
7
8
calls: 793287
frame: 1
lines: 46332
points: 0
triangles: 7736279004
模型对象数量: 360 模型顶点数: 1800028 模型面片数: 2699187
# mesh压缩后:
模型对象数量: 354 模型顶点数: 1801599 模型面片数: 2705426

对比之前策略图的统计数据:

1
2
3
4
5
calls: 989487
frame: 2238
lines: 0
points: 0
triangles: 8363818369

看起来也差不多,为什么这次产业链这个这么卡呢?

其他官方demo:

三个机器人:

1
2
3
4
5
calls: 10946
frame: 341
lines: 0
points: 0
triangles: 57473236

webgl_performance:

1
2
3
4
5
calls: 827234
frame: 178
lines: 0
points: 0
triangles: 799935278

webgl_performance_shader:

1
2
3
4
5
calls: 980000
frame: 172
lines: 0
points: 0
triangles: 1764000000

看起来是因为三角面太多的缘故?

找了个丢帧的,发现是射线拾取大约占30ms,渲染占了70ms

渲染里面,反射占了10ms

有大约3个WebGLProgram,是不是对应2个后期处理和一个最终的渲染?

用第三方渲染器展示汽车,非常流畅

那么肯定是我们加的内容导致的卡顿,到底是什么呢?

用删除法试试?

TODOs

待尝试:

  • 关掉镜面反射

  • 去掉高亮

  • raycaster改为GPU拾取

  • 设计师减少面数

  • 采用其他更好的压缩算法

  • traverseVisible代替traverse

  • 检查RAF中的操作,降低帧循环中的计算量(将交互、业务数据处理和RAF完全独立开),写一个RAFHelper类,参考这个文章

  • 延迟渲染,同样参考上面这个文章,通过防抖节流实现。

  • 合并同材质的网格

从任务管理器看,GPU耗费是很低的,CPU高一点,但是百分比也不大啊,为什么就是卡呢?

不对,GPU是很高的,应该是Chrome默认的任务管理器统计有问题,实际上看系统的任务管理器,可以看到GPU基本上占用在80%左右了;关掉这一页,GPU就回归到0了。

设计师的3080Ti测试:GPU使用率21%-25%,操控时帧数在30帧左右,GPU为什么跑不满帧数就上不去了

(TODO)GPU到底在干嘛?为什么占用这么高?

原因1:mousemove高频触发raycaster

这是影响性能最大的因素了。

有个visible检测

另外就是改为GPU拾取。

原因2:单次mousemove耗时很长

3

比如这就是一个mousemove事件,看到耗时接近100ms了,肯定不对。

这里有5个raycast,是不是也有问题?

原因3:多了一次默认的render

我采用了EffectComposer后,Trois默认的render没取消,因此多了一次渲染。

Trois有个onInit()可以注册生命周期函数,看起来似乎可以处理这个问题:

1
2
3
4
5
6
const renderer = this.$refs.renderer
renderer.onInit(() => {
renderer.renderFn = () => {
// do what you want
}
})

但是实测发现,这个函数如果放在onMounted中注册,就太晚了;如果放在onMounted之外注册,此时renderer.value又还是空的,因此并不能用onInit()解决这个问题

最终在onMounted中直接**重写renderFn()**解决了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
renderer.value.renderFn = () => {
let composerRender = false;
if (industryChain) {
const composer = industryChain.getComposer();
if (composer) {
renderer.value?.renderer.render(
scene.value.scene,
camera.value?.camera
);
composer.render();
composerRender = true;
}
}
if (!composerRender) {
renderer.value?.renderer.render(scene.value.scene, camera.value?.camera);
}
};

注意,这里我踩了一个坑,就是为了性能优化,在mouseout模型时,去掉了EffectComposer里面的RenderPass,结果一不小心把默认的RenderPass也删掉了,导致鼠标只要移出模型范围,画面就不渲染了。

下面注释掉的代码,就是之前出问题的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 清除特效
* 注意:别把默认的renderPass清掉了,否则就不执行渲染操作了
*/
clear() {
// this.finalComposer.removePass(this.renderPass);
this.finalComposer.removePass(this.outlinePass);
}

/**
* 恢复特效
* 注意:只添加outline的Pass,别添加默认的Pass
*/
restore() {
if (this.finalComposer.passes.length === 1) {
// this.finalComposer.addPass(this.renderPass);
this.finalComposer.addPass(this.outlinePass);
}
}

(TODO)BVH方案

GitHub - gkjohnson/three-mesh-bvh: A BVH implementation to speed up raycasting and enable spatial queries against three.js meshes.

看描述,就是为了加速RayCaster的,还支持空间查询:

A BVH implementation to speed up raycasting and enable spatial queries against three.js meshes.

Casting 500 rays against an 80,000 polygon model at 60fps!

这个看起来靠谱多了,不会存在中心点透明时点不中的问题。不对,还是会有这个问题,因为中心点是我们自己主动设置的,你设置为空的坐标点,就还是会点不中的。。。。。。

但是BVH这个,可以随机选几个点作为校验点,比我们手动配置要好。因为这个可以自己配置包围盒的大小。

案例

股市森林

  • 改小视锥体范围
  • 替换PointLight 改为HemisphereLight, since this requires the WebGLRenderer to recompile the shader programs
  • disabling AA (antialiasing) 高分辨率屏幕下差距不大
  • MeshStandardMaterial改为其他材质,这个质量高,但是效率低,试试MeshLambertMaterial?
  • 重用资源(geometries, materials, textures) buildForest()
  • 静止状态别渲染,目前静止也有很多draw call
  • 将同材质的物体改为InstancedMesh,比如装饰物,树桩等等 three.js docs
  • 去除效果不明显的物体的动画
  • 视锥体裁剪改为空间分割方式
  • 高分辨率带来的高计算量问题
  • 改为WebGPU 不兼容
  • 交互改为bvh
  • 计算放入Web Worker

资料

Three 之 three.js (webgl)性能优化、提高帧率的思路/方向整理:

https://blog.csdn.net/u014361280/article/details/124285654

https://blog.pig1024.me/posts/621a1fea23cc38439fdbc85d

判断性能问题,第一步都是先区分GPU还是CPU瓶颈。

提到了比如Chrome浏览器参数设置等方法:

threejs性能优化_wodomXQ的博客-CSDN博客_threejs 性能优化

three.js 性能优化的几种方法_weixin_30773135的博客-CSDN博客

three.js使用gpu选取物体并计算交点位置-木庄网络博客

(精)Three.js 拾取之GPU Picking的理解和思考_Webglzhang的博客-CSDN博客_gpu拾取

华为云官网 Web3D 和动效技术的应用与探索:

https://www.infoq.cn/article/qdtc0goyfc17uaorontz

https://attackingpixels.com/tips-tricks-optimizing-three-js-performance/

https://discoverthreejs.com/tips-and-tricks/

https://discourse.threejs.org/t/how-can-i-optimise-my-three-js-rendering/42251

破案了,是Pass的问题

EffectComposer哪怕只加一个基础的Pass,也会很卡:

1
const renderPass = new RenderPass(scene, camera);

改为延迟触发高亮 + 动态加减Pass来进行优化试试。

TODOs

instance实例化创建

React Fiber的优化经验

reqeuestIdleCallback

延迟渲染

分层:3D tiles,Cesium一层场景,BBL二层场景,分宏观微观采用不同的技术

千万级渲染

如果要渲染1000W的数据,你如何做技术选型、如何优化性能?
1、【数据】数据预处理,布局预计算,服务端,静态化
2、【调度】fiber架构,不卡住主线程
3、【CPU】web worker,不卡住渲染线程
4、【??】增量渲染、局部擦除
5、【CPU??】减少drawcall
6、【交互】缩略图,局部显示的方式
7、【GPU】将计算做并行拆解,放入GPU中用Shader处理
8、WebGPU(Electron打包,下一个版本的Electron估计就会嵌入Chrome113版本了,默认支持WebGPU)

附录

手机GPU天梯榜

我的手机是红米K30Pro,高通骁龙865,刚好是参考基准,分值100分。

iPhoneXR是A12,96.5分。

iPhone6是A8,11.6分。

iPhone13是A15,200分。

gpu

手机GPU和PC的显卡几乎没有技术差距,性能表现差距是面积功耗成本不同导致的。