PIXI中同baseTexture不同裁剪创建4万精灵帧率过低优化咨询
问题描述
我在PIXI中绘制了一个圆形,将其生成为纹理后,基于同一个baseTexture创建40000个设置了不同裁剪区域的精灵。当精灵数量为20000时帧率约为60fps,提升至40000时帧率仅为35fps,请问是否有更优的方案实现使用相同纹理但不同裁剪的需求?
原代码示例
JS逻辑代码
window.onload = () => { const app = new PIXI.Application({ width: 800, height: 800 }); const stats = new Stats(); stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom stats.domElement.style.position = 'absolute'; stats.domElement.style.right = '50%'; stats.domElement.style.top = '0px'; document.body.appendChild(stats.domElement); // stats = addStats(document, app); document.body.appendChild(app.view); const lineWidth = 40; const numX = 200; const numY = 200; const width = 600; const height = 600; const stepX = width / numX; const stepY = height / numY; const circleGraphics = new PIXI.Graphics(); circleGraphics.lineStyle(lineWidth, 0xffffff); circleGraphics.drawCircle(400, 400, 300 - lineWidth * 0.5); const circleTexture = app.renderer.generateTexture(circleGraphics); let array = []; for (let y = 0; y < numY; y++) { array[y] = []; for (let x = 0; x < numX; x++) { const texture = new PIXI.Texture(circleTexture.baseTexture); texture.frame = new PIXI.Rectangle(stepX * x, stepY * y, stepX, stepY); const sprite = new PIXI.Sprite.from(texture); app.stage.addChild(sprite); sprite.x = x * stepX + 100; sprite.y = y * stepY + 100; sprite._x = sprite.x; sprite._y = sprite.y; array[y][x] = sprite; } } let c = 0; app.ticker.add(() => { for (let y = 0; y < numY; y++) { for (let x = 0; x < numX; x++) { const item = array[y][x]; item.x = item._x + Math.sin(c) * 100 * (x / numX); item.y = item._y + Math.cos(c) * 120 * (y / numY); } } c += 0.1; stats.update(); }); }
依赖引入代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
优化方案
- 替换普通容器为
PIXI.ParticleContainer:普通Container每个精灵会单独触发一次绘制提交,4万次draw call是帧率下降的核心原因。ParticleContainer针对同纹理的大量精灵做了合批优化,最多只需要1次draw call就能提交所有精灵的渲染,且当前场景仅需要修改精灵位置,完全满足ParticleContainer的使用限制。 - 预创建裁剪纹理复用:原代码中每个精灵都单独创建Texture和Rectangle实例,4万个重复对象会带来额外的内存和初始化开销,可以提前把所有网格对应的裁剪纹理生成好,不需要重复实例化。
- 优化遍历逻辑:把存储精灵的二维数组改为一维数组,减少循环中的寻址开销;也可以直接在创建精灵时记录基准坐标和比例系数,不需要每次循环重复计算比值,进一步降低CPU开销。
- 进阶可选:使用
PIXI.Instances实例化渲染:如果需要更高性能,可以直接用GPU实例化渲染,把所有精灵的位置、UV裁剪信息存在顶点缓冲区,一次提交给GPU渲染,CPU开销会降到最低,4万实例可以轻松跑到60fps以上。
优化后代码示例
window.onload = () => { const app = new PIXI.Application({ width: 800, height: 800 }); const stats = new Stats(); stats.showPanel(0); stats.domElement.style.position = 'absolute'; stats.domElement.style.right = '50%'; stats.domElement.style.top = '0px'; document.body.appendChild(stats.domElement); document.body.appendChild(app.view); const lineWidth = 40; const numX = 200; const numY = 200; const total = numX * numY; const width = 600; const height = 600; const stepX = width / numX; const stepY = height / numY; // 生成基础纹理 const circleGraphics = new PIXI.Graphics(); circleGraphics.lineStyle(lineWidth, 0xffffff); circleGraphics.drawCircle(400, 400, 300 - lineWidth * 0.5); const circleBaseTexture = app.renderer.generateTexture(circleGraphics).baseTexture; // 预生成所有裁剪纹理 const textures = []; for (let y = 0; y < numY; y++) { for (let x = 0; x < numX; x++) { const tex = new PIXI.Texture(circleBaseTexture, new PIXI.Rectangle(stepX * x, stepY * y, stepX, stepY)); textures.push(tex); } } // 使用ParticleContainer,仅开启需要的位置属性 const container = new PIXI.ParticleContainer(total, { position: true, rotation: false, scale: false, uvs: false, tint: false }); app.stage.addChild(container); // 预创建所有精灵,存入一维数组 const sprites = new Array(total); let index = 0; for (let y = 0; y < numY; y++) { for (let x = 0; x < numX; x++) { const sprite = new PIXI.Sprite(textures[index]); sprite._baseX = x * stepX + 100; sprite._baseY = y * stepY + 100; sprite._ratioX = x / numX; sprite._ratioY = y / numY; sprite.x = sprite._baseX; sprite.y = sprite._baseY; sprites[index] = sprite; container.addChild(sprite); index++; } } let c = 0; app.ticker.add(() => { const sinC = Math.sin(c); const cosC = Math.cos(c); // 一维数组遍历,性能更高 for (let i = 0; i < total; i++) { const item = sprites[i]; item.x = item._baseX + sinC * 100 * item._ratioX; item.y = item._baseY + cosC * 120 * item._ratioY; } c += 0.1; stats.update(); }); }
内容的提问来源于stack exchange,提问作者lasse
相关产品推荐
相关产品推荐

