Javascript 画布上大约有120000个粒子?
我有大约120000个粒子(每个粒子的大小为1px),我需要找到最好和最重要的:最快的方式来绘制到我的画布上 你会怎么做 现在我基本上把我的像素放到一个数组中,然后我在这些粒子上循环,做一些x和y计算,然后用fillRect把它们画出来。但是现在的帧速率是8-9 fps 有什么想法吗?请举例说明 多谢各位 最新更新(我的代码)Javascript 画布上大约有120000个粒子?,javascript,html,canvas,html5-canvas,particles,Javascript,Html,Canvas,Html5 Canvas,Particles,我有大约120000个粒子(每个粒子的大小为1px),我需要找到最好和最重要的:最快的方式来绘制到我的画布上 你会怎么做 现在我基本上把我的像素放到一个数组中,然后我在这些粒子上循环,做一些x和y计算,然后用fillRect把它们画出来。但是现在的帧速率是8-9 fps 有什么想法吗?请举例说明 多谢各位 最新更新(我的代码) 函数init(){ addEventListener(“mousemove”,onMouseMove); 设mouseX,mouseY,比值=2; const canva
函数init(){
addEventListener(“mousemove”,onMouseMove);
设mouseX,mouseY,比值=2;
const canvas=document.getElementById(“textCanvas”);
const context=canvas.getContext(“2d”);
canvas.width=window.innerWidth*比率;
canvas.height=window.innerHeight*比率;
canvas.style.width=window.innerWidth+“px”;
canvas.style.height=window.innerHeight+“px”;
context.imageSmoothingEnabled=false;
context.fillStyle=`rgba(255255,1)`;
setTransform(比率,0,0,比率,0,0);
const width=canvas.width;
const height=canvas.height;
context.font=“正常232px EB Garamond”;
context.fillText(“howdy”,01160);
var pixels=context.getImageData(0,0,宽度,高度);
var data32=新的UINT32阵列(像素.缓冲区);
常量粒子=新数组();
对于(变量i=0;i[
Math.round(Math.random()*(宽度-1)),
Math.round(Math.random()*(高度-1))
]);*/
移动鼠标的功能(e){
mouseX=parseInt((e.clientX-canvas.offsetLeft)*比率);
mouseY=parseInt((e.clientY-canvas.offsetTop)*比率);
}
功能帧(时间戳){
clearRect(0,0,宽度,高度);
const imageData=context.getImageData(0,0,宽度,高度);
常量数据=imageData.data;
for(设i=0;i0){
var cursorDX=p.ox-mouseX;
var cursorDY=p.oy-mouseY;
变量cursorDistanceSquared=(cursorDX*cursorDX+cursorDY*cursorDY);
cursorForce=数学最小值(10/cursorDistanceSquared,10);
cursorAngle=-Math.atan2(cursorDY,cursorDX);
}否则{
游标力=0;
橙色=0;
}
p、 xVelocity+=0.2*homeDX+cursorForce*Math.cos(cursorAngle);
p、 yVelocity+=0.2*homeDY+cursorForce*Math.sin(cursorAngle);
p、 xVelocity*=0.55;
p、 yVelocity*=0.55;
p、 x+=p.x速度;
p、 y+=p.yVelocity;
}
请求动画帧(帧);
}
请求动画帧(帧);
}
在webgl
上下文中计算着色器中的这些粒子将提供最高效的解决方案。见e。G举个例子
如果希望继续使用2d
上下文,可以通过在屏幕外执行此操作来加快渲染粒子的速度:
context.getImageData()
context.putImageData()
const output=document.getElementById(“输出”);
const canvas=document.getElementById(“canvas”);
const context=canvas.getContext(“2d”);
const width=canvas.width;
const height=canvas.height;
const particles=Array.from({length:120000},()=>[
Math.round(Math.random()*(宽度-1)),
Math.round(Math.random()*(高度-1))
]);
设previous=0;
功能帧(时间戳){
//每秒打印帧数:
const delta=时间戳-上一个;
先前=时间戳;
output.textContent=`${(1000/delta).toFixed(1)}fps`;
//绘制粒子:
clearRect(0,0,宽度,高度);
const imageData=context.getImageData(0,0,宽度,高度);
常量数据=imageData.data;
for(设i=0;i
每秒移动720万个粒子
不使用webGL和着色器,并且您希望每帧的粒子数为120K
每秒60帧您需要每秒720万点的吞吐量。你需要一台速度快的机器
Web workers多核CPU
快速解决方案。在多核机器上,web工作者为每个硬件核心提供线性性能提升。例如在8芯i上
function init(){
window.addEventListener("mousemove", onMouseMove);
let mouseX, mouseY, ratio = 2;
const canvas = document.getElementById("textCanvas");
const context = canvas.getContext("2d");
canvas.width = window.innerWidth * ratio;
canvas.height = window.innerHeight * ratio;
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
context.imageSmoothingEnabled = false;
context.fillStyle = `rgba(255,255,255,1)`;
context.setTransform(ratio, 0, 0, ratio, 0, 0);
const width = canvas.width;
const height = canvas.height;
context.font = "normal normal normal 232px EB Garamond";
context.fillText("howdy", 0, 160);
var pixels = context.getImageData(0, 0, width, height).data;
var data32 = new Uint32Array(pixels.buffer);
const particles = new Array();
for(var i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
particles.push({
x: (i % width),
y: ((i / width)|0),
ox: (i % width),
oy: ((i / width)|0),
xVelocity: 0,
yVelocity: 0,
a: pixels[i*4 + 3] / 255
});
}
}
/*const particles = Array.from({length: 120000}, () => [
Math.round(Math.random() * (width - 1)),
Math.round(Math.random() * (height - 1))
]);*/
function onMouseMove(e){
mouseX = parseInt((e.clientX-canvas.offsetLeft) * ratio);
mouseY = parseInt((e.clientY-canvas.offsetTop) * ratio);
}
function frame(timestamp) {
context.clearRect(0, 0, width, height);
const imageData = context.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < particles.length; i++) {
const particle = particles[i];
const index = 4 * Math.round((particle.x + particle.y * width));
data[index + 0] = 0;
data[index + 1] = 0;
data[index + 2] = 0;
data[index + 3] = 255;
}
context.putImageData(imageData, 0, 0);
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
var homeDX = p.ox - p.x;
var homeDY = p.oy - p.y;
var cursorForce = 0;
var cursorAngle = 0;
if(mouseX && mouseX > 0){
var cursorDX = p.ox - mouseX;
var cursorDY = p.oy - mouseY;
var cursorDistanceSquared = (cursorDX * cursorDX + cursorDY * cursorDY);
cursorForce = Math.min(10/cursorDistanceSquared,10);
cursorAngle = -Math.atan2(cursorDY, cursorDX);
}else{
cursorForce = 0;
cursorAngle = 0;
}
p.xVelocity += 0.2 * homeDX + cursorForce * Math.cos(cursorAngle);
p.yVelocity += 0.2 * homeDY + cursorForce * Math.sin(cursorAngle);
p.xVelocity *= 0.55;
p.yVelocity *= 0.55;
p.x += p.xVelocity;
p.y += p.yVelocity;
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
// call this just once outside the animation loop.
const imageData = this.context.getImageData(0, 0, this.width * this.ratio, this.height * this.ratio);
// create a 32bit buffer
const data32 = new Uint32Array(imageData.data.buffer);
const pixel = 0xFF000000; // pixel to fill
const width = imageData.width;
// inside render loop
data32.fill(0); // clear the pixel buffer
// this line may be a problem I have no idea what it does. I would
// hope its only passing a reference and not creating a copy
var particles = this.particleTexts[0].getParticles();
var cDX,cDY,mx,my,p,cDistSqr,cForce,i;
mx = this.mouseX | 0; // may not need the floor bitwize or 0
my = this.mouseY | 0; // if mouse coords already integers
if(mX > 0){ // do mouse test outside the loop. Need loop duplication
// But at 60fps thats 7.2million less if statements
for (let i = 0; i < particles.length; i++) {
var p = particles[i];
p.xVelocity += 0.2 * (p.ox - p.x);
p.yVelocity += 0.2 * (p.oy - p.y);
p.xVelocity *= 0.55;
p.yVelocity *= 0.55;
data32[((p.x += p.xVelocity) | 0) + ((p.y += p.yVelocity) | 0) * width] = pixel;
}
}else{
for (let i = 0; i < particles.length; i++) {
var p = particles[i];
cDX = p.x - mx;
cDY = p.y - my;
cDist = Math.sqrt(cDistSqr = cDX*cDX + cDY*cDY + 1);
cForce = 1000 / (cDistSqr * cDist)
p.xVelocity += cForce * cDx + 0.2 * (p.ox - p.x);
p.yVelocity += cForce * cDY + 0.2 * (p.oy - p.y);
p.xVelocity *= 0.55;
p.yVelocity *= 0.55;
data32[((p.x += p.xVelocity) | 0) + ((p.y += p.yVelocity) | 0) * width] = pixel;
}
}
// put pixel onto the display.
this.context.putImageData(imageData, 0, 0);
const interleave = 2; // example only setup for 2 frames
// but can be extended to 3 or 4
// create frameCount outside loop
frameCount += 1;
// do half of all particals
for (let i = frameCount % frameCount ; i < particles.length; i += interleave ) {
var p = particles[i];
cDX = p.x - mx;
cDY = p.y - my;
cDist = Math.sqrt(cDistSqr = cDX*cDX + cDY*cDY + 1);
cForce = 1000 / (cDistSqr * cDist)
p.xVelocity += cForce * cDx + 0.2 * (p.ox - p.x);
p.yVelocity += cForce * cDY + 0.2 * (p.oy - p.y);
p.xVelocity *= 0.55;
p.yVelocity *= 0.55;
// add pixel index to particle's property
p.pixelIndex = ((p.x += p.xVelocity) | 0) + ((p.y += p.yVelocity) | 0) * width;
// write this frames pixel
data32[p.pixelIndex] = pixel;
// speculate the pixel index position in the next frame. This need to be as simple as possible.
p.pixelIndex += (p.xVelocity | 0) + (p.yVelocity | 0) * width;
p.x += p.xVelocity; // as the next frame this particle is coasting
p.y += p.yVelocity; // set its position now
}
// do every other particle. Just gets the pixel index and sets it
// this needs to remain as simple as possible.
for (let i = (frameCount + 1) % frameCount ; i < particles.length; i += interleave)
data32[particles[i].pixelIndex] = pixel;
}
// for each particle after updating position
// get index of pixel
p.pixelIndex = (p.x | 0 + p.y | 0) * width;
// add pixel
data32[p.pixelIndex] = pixel;
// now you get 8 more pixels for the price of one particle
var ind = p.offsetArrayIndex;
// offsetArray is an array of pixel offsets both negative and positive
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
data32[p.pixelIndex + offsetArray[ind++]] = pixel;
// offset array arranged as sets of 8, each set of 8 is a frame in
// looping pre calculated offset animation
// offset array length is 65536 or any bit mask able size.
p.offsetArrayIndex = ind & 0xFFFF ; // ind now points at first pixel of next
// set of eight pixels