最近做在线CAD可视化与编辑,对前端的可视化渲染技术进行了选型,对于二维CAD来说一般用canvas就够了,但是canvas每一次平移,缩放,更新数据都需要重新计算渲染所有的图形数据,数据一多就显得非常卡。如果使用三维webgl,在没有任何第三方的开源封装技术下,通过webgl去实现二维也是会遇到很多问题。基于此,找到了PixiJS这个支持canvas和webgl渲染的引擎,关键是非常快(之前在github看到过一个在线的程序测试,有十几种渲染引擎的测试,我对比过确实是最强的,这个网站忘记在哪里了)
常规的优化策略
1. 使用BatchGeomery
BatchGeomery是一种优化渲染性能的技术,它可以将多个Geomery对象合并成一个渲染批次,从而减少渲染调用次数,是一种实例化技术,要求对象的图形和属性都是一样,其他的Graphics对象都是基于这个基准对象去优化.
const app = new PIXI.Application();
document.body.appendChild(app.view);
const geometry = new PIXI.Geometry()
.addAttribute('aVPos', [-100, 0, 100, 0, 0, -150]);
geometry.instanced = true;
geometry.instanceCount = 5;
const positionSize = 2;
const colorSize = 3;
const buffer = new PIXI.Buffer(new Float32Array(geometry.instanceCount * (positionSize + colorSize)));
geometry.addAttribute('aIPos', buffer, positionSize, false, PIXI.TYPES.FLOAT, 4 * (positionSize + colorSize), 0, true);
geometry.addAttribute('aICol', buffer, colorSize, false, PIXI.TYPES.FLOAT, 4 * (positionSize + colorSize), 4 * positionSize, true);
for (let i = 0; i < geometry.instanceCount; i++) {
const instanceOffset = i * (positionSize + colorSize);
buffer.data[instanceOffset + 0] = i * 80;
buffer.data[instanceOffset + 2] = Math.random();
buffer.data[instanceOffset + 3] = Math.random();
buffer.data[instanceOffset + 4] = Math.random();
}
const shader = PIXI.Shader.from(`
precision mediump float;
attribute vec2 aVPos;
attribute vec2 aIPos;
attribute vec3 aICol;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
varying vec3 vCol;
void main() {
vCol = aICol;
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVPos + aIPos, 1.0)).xy, 0.0, 1.0);
}`,
`precision mediump float;
varying vec3 vCol;
void main() {
gl_FragColor = vec4(vCol, 1.0);
}
`);
const triangles = new PIXI.Mesh(geometry, shader);
triangles.position.set(400, 300);
app.stage.addChild(triangles);
app.ticker.add((delta) => {
triangles.rotation += 0.01;
});
2. 使用WebGL渲染器:WebGL渲染器可以利用GPU加速渲染,从而提高渲染性能
PixiJS默认使用WebGL渲染,但可以通过以下方式强制使用Canvas渲染
// 创建一个使用Canvas渲染的Pixi应用程序
const app = new PIXI.Application({
width: 800,
height: 600,
forceCanvas: true // 强制使用Canvas渲染
});
如果要在运行时切换渲染器,可以使用以下代码
// 获取当前渲染器
const renderer = app.renderer;
// 切换渲染器
if (renderer instanceof PIXI.WebGLRenderer) {
app.renderer = new PIXI.CanvasRenderer();
} else if (renderer instanceof PIXI.CanvasRenderer) {
app.renderer = new PIXI.WebGLRenderer();
}
3. 减少渲染对象数量
减少渲染对象数量可以减少渲染调用次数,从而提高渲染性能。可以通过合并对象、使用图集等方式来实现。
对于CAD数据,如果每个矢量都使用一个Grpahics对象,不仅内存消耗比较大,而且性能下降很厉害。如果整个CAD图面的数据都不做修改,完全可以将所有的图形绘制到一个Graphics对象中,大概方法如下:
var g1=new PIXI.Graphics();
//绘制第一个对象;
g1.beginFill(0xFF0000);
g1.drawCircle(10,10,20);
gl.endFill();
//绘制第二个对象;
g1.beginFill(0x00FF);
g1.drawRect(0,0,10,10);
gl.endFill();
//绘制后面的对象
...
//添加到舞台;
app.stage.addChild(g1);
以下是近10M的原始CAD数据(对象大概是100万左右)使用一个对象一个Graphics的对象的渲染情况,内存消耗11G,帧率基本为0,处于半假死状态
而如果使用将所有的对象使用放在一个Graphics中,可以发现帧率可以达到近40帧,内存消耗在4500M(但是这种方式不太适合对单个对象进行操作,对于纯浏览是没有什么问题的)
![1681627285950-e8e26fbc-018d-4363-9373-106507d0fadc.png])
4.共享几何对象
我们不是单个图形对象,而是创建多个图形,这些图形都共享相同的几何图形。图形的唯一可选构造函数是对另一个图形几何实例的引用。这种方法更新
var g1=new PIXI.Graphics();
g1.beginFill(0xFF0000);
g1.drawRect(0,0,100,100);
g1.endFill();
app.stage.addChild(g1);
var g2=new PIXI.Graphics(g1.geometry); //如果和第一个对象只是位置不同,可以这样使用.
g2.x=g2.x+10;
g2.y=g2.y+10;
app.stage.addChild(g2);
5. 使用位图文本
位图文本可以也可以大幅的减少渲染器的内存使用量,具体用法可以参考 《PixiJs文字模糊处理》
6. 渲染纹理
此方法不使用共享的图形几何体,而是使用 RenderTexture 渲染单个图形对象。然后像使用任何纹理一样使用它,并在精灵之间共享。从本质上讲,这是相同的效果,但没有图形开销和更好的抗锯齿。如果需要缩放圆圈,只需以较大的初始大小渲染渲染纹理即可(在极限缩放情况下,显示还是会比较模糊)
7. 设置为可剔除的
在处理数千个元素时,仅在屏幕上显示基本元素至关重要,当可视区域数据较少时,性能可以大幅提升(可能是由于pixijs外包计算的问题,效率的效率提升并不是非常的明显)