优化 canvas 多个对象渲染
Optimizing canvas multiple objects rendering
我正在尝试制作一个蚂蚁模拟器,我使用基本的 Javascript canvas 渲染器
这是渲染代码的一部分:
render(simulation) {
let ctx = this.ctx;
// Clear previous frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Pheromones
let pheromone;
let pheromoneXPos;
let pheromoneYPos;
for (let i = 0; i < simulation.pheromones.length; i++) {
pheromone = simulation.pheromones[i];
pheromoneXPos = pheromone.position[0];
pheromoneYPos = pheromone.position[1];
ctx.fillRect(pheromoneXPos, pheromoneYPos, 1, 1);
}
// ... rendering other stuff
}
这是一个示例(它运行流畅只是因为我使用的对象减少了约 90%):
simulation.pheromones.length
很大(~25000),渲染 1 帧的时间太长(在我的电脑上是 250ms)
我应该怎么做才能让它渲染得更快?我应该改用渲染引擎(如 PixiJS)吗?还是我用错了?
注意:大部分对象(信息素)与上一帧相同(它们每秒仅更新一次或两次)。
由于您的所有矩形似乎都具有相同的样式,因此您可以将所有这些矩形组合在一个子路径中,并使用这个更复杂的子路径仅调用一次 fill()
。
const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
const makePheromone = () => ({
x: w / 2,
y: h / 2,
dx: Math.random() * 20 - 10,
dy: Math.random() * 20 - 10
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
ctx.fillStyle = "red";
requestAnimationFrame( draw );
function draw() {
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
pheromones.forEach( (obj) => {
obj.x += obj.dx;
if( obj.x < 0 ) {
obj.x += w;
}
else if( obj.x > w ) {
obj.x -= w;
}
obj.y += obj.dy;
if( obj.y < 0 ) {
obj.y += h;
}
else if( obj.y > h ) {
obj.y -= h;
}
ctx.rect( obj.x, obj.y, 1, 1 );
});
ctx.fill();
requestAnimationFrame( draw );
}
<canvas width="5000" height="5000"></canvas>
现在,在你的情况下,你是否真的要为这些 'pheromones' 制作动画还不清楚。
如果你不这样做,那么只绘制一次并存储你将在每一帧渲染的 canvas 中的 ImageBitmap:
const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
getPheromonesBitmap()
.then( (bmp) => {
let x = w / 2;
let y = h / 2;
const circle = new Path2D();
circle.arc( 0, 0, 50, 0, Math.PI * 2 );
function draw( t ) {
x += Math.sin( t / 2000 );
y += Math.cos( t / 2000 );
ctx.setTransform( 1, 0, 0, 1, 0, 0 );
ctx.clearRect( 0, 0, w, h );
// draw all the pheromones as a single bitmap
ctx.drawImage( bmp, 0, 0 );
ctx.translate( x, y );
ctx.fill( circle );
requestAnimationFrame( draw );
}
requestAnimationFrame( draw );
} );
function getPheromonesBitmap() {
const makePheromone = () => ({
x: Math.random() * w,
y: Math.random() * h
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
ctx.fillStyle = "red";
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
pheromones.forEach( (obj) => {
ctx.rect( obj.x, obj.y, 1, 1 );
});
ctx.fill();
const prom = createImageBitmap( canvas );
ctx.beginPath();
ctx.clearRect( 0, 0, w, h );
return prom;
}
<canvas width="5000" height="5000"></canvas>
对于不支持 createImageBitmap
的浏览器(对于这种简单的情况,只支持 Safari),我写了一个可用的 polyfill here。
最后,如果您确实在像素边界绘制 1x1px 矩形,您还可以使用 ImageData 来渲染这些矩形,这在所有实体都有自己的颜色时特别有效:
const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
const makePheromone = () => ({
x: w / 2,
y: h / 2,
dx: Math.random() * 20 - 10,
dy: Math.random() * 20 - 10,
color: Math.random() * 0xFFFFFF
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
const img = new ImageData( w, h );
// we use an Uint32Array to set each pixel in one pass
const arr = new Uint32Array( img.data.buffer );
requestAnimationFrame( draw );
function draw() {
pheromones.forEach( (obj) => {
obj.x += obj.dx;
if( obj.x < 0 ) {
obj.x += w;
}
else if( obj.x > w ) {
obj.x -= w;
}
obj.y += obj.dy;
if( obj.y < 0 ) {
obj.y += h;
}
else if( obj.y > h ) {
obj.y -= h;
}
const index = Math.floor( obj.y * w + obj.x );
arr[ index ] = obj.color + 0xFF000000;
});
ctx.putImageData( img, 0, 0 );
requestAnimationFrame( draw );
}
<canvas width="5000" height="5000"></canvas>
我正在尝试制作一个蚂蚁模拟器,我使用基本的 Javascript canvas 渲染器
这是渲染代码的一部分:
render(simulation) {
let ctx = this.ctx;
// Clear previous frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Pheromones
let pheromone;
let pheromoneXPos;
let pheromoneYPos;
for (let i = 0; i < simulation.pheromones.length; i++) {
pheromone = simulation.pheromones[i];
pheromoneXPos = pheromone.position[0];
pheromoneYPos = pheromone.position[1];
ctx.fillRect(pheromoneXPos, pheromoneYPos, 1, 1);
}
// ... rendering other stuff
}
这是一个示例(它运行流畅只是因为我使用的对象减少了约 90%):
simulation.pheromones.length
很大(~25000),渲染 1 帧的时间太长(在我的电脑上是 250ms)
我应该怎么做才能让它渲染得更快?我应该改用渲染引擎(如 PixiJS)吗?还是我用错了?
注意:大部分对象(信息素)与上一帧相同(它们每秒仅更新一次或两次)。
由于您的所有矩形似乎都具有相同的样式,因此您可以将所有这些矩形组合在一个子路径中,并使用这个更复杂的子路径仅调用一次 fill()
。
const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
const makePheromone = () => ({
x: w / 2,
y: h / 2,
dx: Math.random() * 20 - 10,
dy: Math.random() * 20 - 10
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
ctx.fillStyle = "red";
requestAnimationFrame( draw );
function draw() {
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
pheromones.forEach( (obj) => {
obj.x += obj.dx;
if( obj.x < 0 ) {
obj.x += w;
}
else if( obj.x > w ) {
obj.x -= w;
}
obj.y += obj.dy;
if( obj.y < 0 ) {
obj.y += h;
}
else if( obj.y > h ) {
obj.y -= h;
}
ctx.rect( obj.x, obj.y, 1, 1 );
});
ctx.fill();
requestAnimationFrame( draw );
}
<canvas width="5000" height="5000"></canvas>
现在,在你的情况下,你是否真的要为这些 'pheromones' 制作动画还不清楚。
如果你不这样做,那么只绘制一次并存储你将在每一帧渲染的 canvas 中的 ImageBitmap:
const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
getPheromonesBitmap()
.then( (bmp) => {
let x = w / 2;
let y = h / 2;
const circle = new Path2D();
circle.arc( 0, 0, 50, 0, Math.PI * 2 );
function draw( t ) {
x += Math.sin( t / 2000 );
y += Math.cos( t / 2000 );
ctx.setTransform( 1, 0, 0, 1, 0, 0 );
ctx.clearRect( 0, 0, w, h );
// draw all the pheromones as a single bitmap
ctx.drawImage( bmp, 0, 0 );
ctx.translate( x, y );
ctx.fill( circle );
requestAnimationFrame( draw );
}
requestAnimationFrame( draw );
} );
function getPheromonesBitmap() {
const makePheromone = () => ({
x: Math.random() * w,
y: Math.random() * h
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
ctx.fillStyle = "red";
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
pheromones.forEach( (obj) => {
ctx.rect( obj.x, obj.y, 1, 1 );
});
ctx.fill();
const prom = createImageBitmap( canvas );
ctx.beginPath();
ctx.clearRect( 0, 0, w, h );
return prom;
}
<canvas width="5000" height="5000"></canvas>
对于不支持 createImageBitmap
的浏览器(对于这种简单的情况,只支持 Safari),我写了一个可用的 polyfill here。
最后,如果您确实在像素边界绘制 1x1px 矩形,您还可以使用 ImageData 来渲染这些矩形,这在所有实体都有自己的颜色时特别有效:
const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
const makePheromone = () => ({
x: w / 2,
y: h / 2,
dx: Math.random() * 20 - 10,
dy: Math.random() * 20 - 10,
color: Math.random() * 0xFFFFFF
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
const img = new ImageData( w, h );
// we use an Uint32Array to set each pixel in one pass
const arr = new Uint32Array( img.data.buffer );
requestAnimationFrame( draw );
function draw() {
pheromones.forEach( (obj) => {
obj.x += obj.dx;
if( obj.x < 0 ) {
obj.x += w;
}
else if( obj.x > w ) {
obj.x -= w;
}
obj.y += obj.dy;
if( obj.y < 0 ) {
obj.y += h;
}
else if( obj.y > h ) {
obj.y -= h;
}
const index = Math.floor( obj.y * w + obj.x );
arr[ index ] = obj.color + 0xFF000000;
});
ctx.putImageData( img, 0, 0 );
requestAnimationFrame( draw );
}
<canvas width="5000" height="5000"></canvas>