Starfield画布程序占用太多CPU



我使用canvas创建了一个星形字段,它按预期工作:

<!DOCTYPE HTML5>
<html>
<head>
<title>StarField</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
width: 100%;
height: 100%;
}
</style>
</head>
<body onLoad="startGame()"></body>
<script type="text/javascript">
var NO_OF_STARS = 512;
var stars = [];
function startGame() {          
gameArea.start(); /* Makes the canvas */
gameRun = requestAnimationFrame(gameArea.update); /* Starts the game and coordinates all the animations */
window.addEventListener("keydown", function(e) {
if(e.keyCode == 27) { /* ESC stops everything */
stopEverything();
}
});
}
var gameArea = {
canvas : document.createElement("canvas"),
start : function() {
document.body.appendChild(this.canvas);
this.canvas.width = document.body.clientWidth;
this.canvas.height = document.body.clientHeight;
},
update : function() {               
gameArea.clear(); /* Fills the canvas with #000000 */
gameArea.drawStars(); /* Draws the stars */
gameRun = requestAnimationFrame(gameArea.update); /* Repeat the whole thing */
},
drawStars : function() {
var ctx = gameArea.canvas.getContext("2d");
if(stars.length == 0) {
for(var i = 0; i < NO_OF_STARS; i++) {
var opacity = ((Math.floor(Math.random() * 10) / 10) + .1);
stars.push([getRandomInt(0, gameArea.canvas.width - 1), getRandomInt(0, gameArea.canvas.height - 1),opacity]);
ctx.beginPath();
ctx.strokeStyle = "rgba(255, 255, 255, " + opacity + ")";
ctx.moveTo(stars[i][0], stars[i][1]);
ctx.lineTo(stars[i][0] + 1, stars[i][1] + 1);
ctx.stroke();
}
} else {
for(var i = 0; i < NO_OF_STARS; i++) {
ctx.strokeStyle = "rgba(255, 255, 255, " + stars[i][2] + ")";
stars[i][0] -= ((stars[i][2] == 1.0) ? 5 :
(stars[i][2] >= 0.8) ? 4 :
(stars[i][2] >= 0.5) ? 3 :
(stars[i][2] >= 0.3) ? 2 :
1);
if(stars[i][0] < 0) {
var opacity = ((Math.floor(Math.random() * 10) / 10) + .1);
stars.splice(i, 1, [gameArea.canvas.width, getRandomInt(0, gameArea.canvas.height - 1), opacity]);
}
ctx.beginPath();
ctx.moveTo(stars[i][0], stars[i][1]);
ctx.lineTo(stars[i][0] + 1, stars[i][1] + 1);
ctx.stroke();
}
}
},
clear : function() {
var ctx = this.canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
};
/**
* Returns a random integer between min (inclusive) and max (inclusive)
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function stopEverything() {
cancelAnimationFrame(gameRun);
}
</script>
</html>

这里的问题是它占用了大量的CPU(在配备AMD A8四核处理器的笔记本电脑上占60%到65%)。我希望这个canvas程序也能在其他拥有低端处理器的计算机上运行。

我试过减少NO_OF_STARS,但这并没有改变CPU的使用。然而,当增加它时,动画会大大减慢,CPU使用率也会降低(不过我不认为我会增加它,所以这并不是真正相关的)

我还注意到画布的大小在CPU使用率中起着重要作用。(我上面提到的笔记本电脑的分辨率是1366x768)但我想让画布占据整个视口。

那么,如何减少CPU使用量?

为每个星定义路径、笔划样式并光栅化它是非常昂贵的。尝试收集一些操作来减少负载-这一切都是为了做出妥协:

  • 选择3-5个预定义的不透明度级别
  • 绘制按预定义的不透明度级别划分的星星数
  • 循环前使用单个beginPath()
  • 使用rect()而不是moveTo+LineTo
  • fill()循环结束后,继续下一个不透明度级别
  • 获取一次3D上下文,而不是每次调用
  • 对星形位置使用整数值(强制执行整数步,在这种情况下可能不理想,但值得一试)
  • 回收/重复使用星形条目,而不是拼接和创建新条目
  • 尽可能减少计算次数和条件
  • 将帧速率降低到30 FPS(切换RAF以每隔一次绘制)。60 FPS是不错的,但我们也可以像电影一样使用30 FPS(不过,它们受益于运动模糊;我们可以通过使用嵌入"运动模糊"的精灵而不是绘制矩形来作弊)
  • 可选:将每个字段层存储为单独的画布,绘制为图像(速度更快,但需要更多内存)。可以通过平铺旋转和/或翻转的画布来进行变化

我会亲自对不透明度级别进行硬编码,但我将下面的调整后的代码留给了随机生成(多次点击运行按钮)。

通常情况下,CPU/GPU仍然会受到一些影响,但这些提示应该会提高性能(或者在这种情况下会降低负载)。

var NO_OF_STARS = 500; // divisable by 5 (due to num. of opacities - see below)
var stars = [];
function startGame() {
gameArea.start(); /* Makes the canvas */
gameRun = requestAnimationFrame(gameArea.update); /* Starts the game and coordinates all the animations */
window.addEventListener("keydown", function(e) {
if (e.keyCode == 27) { /* ESC stops everything */
stopEverything();
}
});
}
var gameArea = {
canvas: document.createElement("canvas"),
ctx: null,
opacities: [],
start: function() {
document.body.appendChild(this.canvas);
this.canvas.width = document.body.clientWidth;
this.canvas.height = document.body.clientHeight;
// store context once
this.ctx = this.canvas.getContext("2d");

// opacity levels
for (var t = 0; t < 5; t++) this.opacities.push(((Math.floor(Math.random() * 10) / 10) + .1));
},
update: function() {
gameArea.clear(); /* Fills the canvas with #000000 */
gameArea.drawStars(); /* Draws the stars */
gameRun = requestAnimationFrame(gameArea.update); /* Repeat the whole thing */
},
drawStars: function() {
var ctx = this.ctx;
if (!stars.length) {
for (var i = 0; i < NO_OF_STARS; i++) {
stars.push({
x: getRandomInt(0, gameArea.canvas.width - 1)|0, 
y: getRandomInt(0, gameArea.canvas.height - 1)|0
});
}
}
for (t = 0, pos = 0; t < 5; t++) {
var opacity = this.opacities[t];
ctx.beginPath();
for (var i = 0; i < NO_OF_STARS / 5; i++) {

stars[pos].x -= opacity * opacity * 4;
if (stars[pos].x < 0) {
stars[pos].x = gameArea.canvas.width;
stars[pos].y = getRandomInt(0, gameArea.canvas.height - 1)|0;
}
ctx.rect(stars[pos].x, stars[pos].y, 1, 1);
pos++;  // total position
}
ctx.strokeStyle = "rgba(255, 255, 255, " + opacity + ")";
ctx.stroke();
}
},
clear: function() {
var ctx = this.canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
};
/**
* Returns a random integer between min (inclusive) and max (inclusive)
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function stopEverything() {
cancelAnimationFrame(gameRun);
}
startGame();
* {
margin: 0;
padding: 0;
}
body {
width: 100%;
height: 100%;
}

最新更新