繁体   English   中英

PIXI.js 性能优化

[英]PIXI.js performance optimization

我使用 canvas 2dContext 构建了一个分形时钟,但发现当层数增加到 6 以上时我遇到了性能问题。为了提高性能并增加层数限制,我使用 PIXI.js 重建了相同的时钟

我遇到的问题是性能改进很小——在我的桌面上,它从 2dContext 的最多 6 层变为 PIXI 的 7 层。

我如何接近可能限制其性能的 PIXI 解决方案有什么明显的错误吗?

我怀疑最好的解决方案是将所有计算移到着色器中,并从样板 webgl 开始。

保存点击链接的代码:

 const config = { numLevels: 7, // bottleneck here, higher means poor performance levelAngleOffset: 0, levelScale: .7, levelOpacityMultiplier: .7, levelHueRotation: 40, initialRadius: .4, initialOpacity: 1, initialHue: 0, dialOpacity: .1, handOpacity: 1, secondHandLength: .9, secondHandWidth: .015, minuteHandLength: .8, minuteHandWidth: .03, hourHandLength: .6, hourHandWidth: .05, // set by code width: 0, height: 0, }; // https://stackoverflow.com/a/44134328/3282374 function hslToHex(h, s, l) { l /= 100; const a = s * Math.min(l, 1 - l) / 100; const f = n => { const k = (n + h / 30) % 12; const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed }; return `0x${f(0)}${f(8)}${f(4)}`; } class Hand extends PIXI.Graphics { constructor(x,y,length, rotation, width, opacity) { super(); this.length = length; this.lineStyle({ width: width, color: 0xFFFFFF, alpha: config.handOpacity * opacity, cap: PIXI.LINE_CAP.ROUND }); this.moveTo(0, 0); this.lineTo(0, -length); this.rotation = rotation; this.x = x; this.y = y; this.endFill(); } setClock(clock) { this.hasClock = true; this.clock = clock; clock.x = 0; clock.y = this.length; this.addChild(clock); } update(rotation, angles) { this.rotation = rotation; if (this.hasClock) { this.clock.update(angles, 0, -this.length, rotation); } } } class Clock extends PIXI.Graphics { constructor(cx, cy, radius, rotation, hue, opacity) { super(); this.lineStyle(0); this.beginFill(hslToHex(hue, 100, 50), opacity * config.dialOpacity); this.drawCircle(0, 0, radius); this.endFill(); this.x = cx; this.y = cy; this.rotation = rotation; this.hourHand = new Hand(0, 0, config.hourHandLength * radius, 0, config.hourHandWidth * radius, opacity); this.minuteHand = new Hand(0, 0, config.minuteHandLength * radius, 0, config.minuteHandWidth * radius, opacity); this.secondHand = new Hand(0, 0, config.secondHandLength * radius, 0, config.secondHandWidth * radius, opacity); this.addChild(this.hourHand, this.minuteHand, this.secondHand); } setChildClocks(hour, minute, second) { this.hourHand.setClock(hour); this.minuteHand.setClock(minute); this.secondHand.setClock(second); } update(angles, cx, cy, rotation) { this.x = cx; this.y = cy; this.rotation = rotation; this.hourHand.update(angles.hour, angles); this.minuteHand.update(angles.minute, angles); this.secondHand.update(angles.second, angles); } } function getTimeAngles() { const time = new Date(); const millisecond = time.getMilliseconds(); const second = time.getSeconds() + millisecond / 1000; const minute = time.getMinutes() + second / 60; const hour = time.getHours() % 12 + minute / 60; const hourAngle = Math.PI * 2 * hour / 12; const minuteAngle = Math.PI * 2 * minute / 60; const secondAngle = Math.PI * 2 * second / 60; return { hour: hourAngle, minute: minuteAngle, second: secondAngle }; } let clock; function initClock() { const center = Math.min(config.width, config.height) / 2; clock = new Clock( center, center, center * config.initialRadius, Math.PI / 2, config.initialHue, config.initialOpacity); let level = 0; let clocks = [clock]; while (level < config.numLevels) { level++; const nextClocks = []; for (const parent of clocks) { const children = []; for (var i = 0; i < 3; i++) { const child = new Clock( center, center, center * config.initialRadius * config.levelScale**level, 0, config.initialHue + config.levelHueRotation * level, config.initialOpacity * config.levelOpacityMultiplier ** level); children.push(child); } parent.setChildClocks(...children); nextClocks.push(...children); } clocks = nextClocks; } } function step() { const angles = getTimeAngles(); clock.update(angles, config.width/2, config.height/2, 0); } function init() { PIXI.utils.skipHello(); const app = new PIXI.Application({ antialias: true, transparent: true }); document.body.appendChild(app.view); const canvas = document.querySelector('canvas'); const resize = () => { const {width, height } = canvas.getBoundingClientRect(); config.width = width; config.height = height; app.renderer.resize(width, height); } window.addEventListener('resize', resize); resize(); initClock(); app.stage.addChild(clock); app.ticker.add(step); } init();
 html, body { padding: 0; margin: 0; width: 100%; height: 100%; } body { display: flex; align-items: center; justify-content: center; background: #222; } canvas { width: 99vw; height: 99vh; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.9/pixi.min.js"></script>

我如何接近可能限制其性能的 PIXI 解决方案有什么明显的错误吗?

您的性能问题来自 CPU,而不是 GPU。 对于深度 8,在我的 PC(英特尔酷睿 i7-10700F)上绘制单个帧需要 60 毫秒,因为 CPU 必须完成大量工作(深入遍历不断扩展的树)。

在此处输入图像描述

切换到 Pixi 将无济于事,因为您在 CPU 上做的工作量相同。 为了让你的分形时钟在更大的深度上工作得很好,你必须在 GPU 上进行编程。 WebGL 允许您通过编写着色器来做到这一点。

您需要重新考虑您的程序顺序算法并将其转换为片段着色器,其指令一次并行执行许多像素(片段)。 这不是一项简单的任务,需要一些练习。

但是,根据您希望分形的外观,您可能仍然使用 canvas。 您可以只绘制一个级别,然后将此图像数据复制到其他位置,以及平移/旋转/缩放,而不是从头开始重新绘制每个分支。 有关详细信息,请参阅getImageData

但是,如果分形的更深节点需要使用仿射变换或简单的基于像素的操作无法实现的操作,则必须使用我描述的自定义片段着色器解决方案。

有关在 WebGL 中可以使用着色器做什么的灵感,请参阅ShaderToy 还有一个分形部分 这是一个与您的时钟相似的时钟(如下图所示)。

在此处输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM