【问题标题】:JS Canvas get pixel value very frequentlyJS Canvas 非常频繁地获取像素值
【发布时间】:2015-02-14 05:18:58
【问题描述】:

我正在创建一个基于 Node.js/WebGL/Canvas/PIXI.js 的视频游戏。

在这个游戏中,方块有一个通用的尺寸:它们可以是圆形、多边形或任何东西。所以,我的物理引擎需要知道东西到底在哪里,哪些像素是墙,哪些像素不是。因为我认为 PIXI 不允许这样做,所以我创建了一个不可见的画布,在其中放置了所有墙上的地图图像。然后,我使用函数 getImageData 在 (x, y) 处创建函数“isWall”:

function isWall(x, y):
    return canvas.getImageData(x, y, 1, 1).data[3] != 0;

但是,这非常慢(根据 Chrome 分析,它占用了游戏 CPU 时间的 70%)。另外,由于我引入了这个功能,我有时会在没有任何额外建议的情况下收到错误“糟糕,WebGL 崩溃”。

有没有更好的方法来访问像素的值?我考虑将所有内容存储在静态位数组中(墙有固定大小),1 对应于墙,0 对应于非墙。内存中包含 1000 万个单元的数组是否合理?

【问题讨论】:

  • 现在对像素进行碰撞几乎是一种反模式。为了在现代 GPU 上读取像素,整个 GPU 管道必须停止(就像猛踩汽车刹车一样),这样 CPU 才能读取像素。 GPU 然后缩小以做更多的事情。想象一辆赛车,每次你想看它时,它都必须在休息时猛烈撞击并完全停下来。这就是现代 GPU 读取像素的作用。关键是,还有其他方法可以进行碰撞吗?多边形? AABB?原语?或者,在 CPU 上渲染您自己的碰撞像素?
  • 第二个想法不需要调用GPU,因为墙壁是静态的,可以存储在与GPU无关的数组中。然而,有趣的是,您说这是如今的反模式。但是当人们想要进行像素级碰撞时,他们会怎么做呢?我的游戏会有很多复杂的表格,我不想手动为每个表格创建一个看起来像表格的多边形,因为这会花费我很多时间。

标签: javascript canvas webgl pixi.js


【解决方案1】:

一些想法:

  • 第一次检查:为所有对象使用碰撞区域。甚至可以根据形状(即复杂形状)为每一侧定义区域。仅检查相交区域内的碰撞。
  • 对命中测试位图使用半分辨率(如果您的方案允许,甚至可以使用 25%)。我们的大脑无法在物体移动时检测到像素精确的碰撞,因此可以利用这一点。
  • 对于复杂的形状,pre-为其存储整个位图(基于其区域),但将其转换为单值类型数组,如 Uint8Array高值和低值(重复使用它而不是通过上下文获取一个和一个像素)。减去对象的位置并将结果用作形状区域的增量,然后对“位图”进行命中测试。如果形状旋转,则相应地转换传入的检查点(这里可能有一个最佳点,更新位图比转换一堆点等更快。您需要针对您的场景进行测试)。
  • 对于接近方形的对象,请折中并使用简单的矩形检查
  • 对于圆形和椭圆形,使用非平方值来检查距离的半径。
  • 在某些情况下,您也许可以使用 碰撞预测,您可以在游戏开始之前以及在知道所有对象的位置、方向和速度时计算(计算完整的运动路径,找到这些路径的交点,计算到这些路口的时间/距离)。如果您的对象在其路径中由于其他事件而改变方向等,这当然不会那么好(或尝试看看重新计算是否有益)。

我确定您为什么需要将 10m 存储在内存中,但这是可行的 - 但您需要使用四叉树之类的东西并将数组拆分,因此查找像素状态变得高效。 IMO 您只需要为复杂形状存储“位”,您可以通过为每个形状定义多个区域来进一步限制它。对于更简单的形状,只需使用向量(矩形、半径/距离)。经常进行性能测试以找到合适的平衡点。

无论如何 - 这类事情必须针对特定场景进行手动优化,所以这只是一个一般性的看法。其他因素会影响方法,例如高速、旋转、反射等,并且会很快变得非常广泛。希望这能提供一些意见。

【讨论】:

  • 为碰撞的有用想法点赞!你提到了碰撞预测……去年圣诞节,我做了一些工作,通过确定 objectA 可能需要与 objectB 碰撞的最小移动周期数来减少所需的碰撞测试总数。结果证明(令人惊讶!)对于需要相互碰撞测试的大量射弹非常有效。
【解决方案2】:

我使用位数组来存储0 || 1 信息,效果很好。

信息存储紧凑,获取/设置速度非常快。

这是我使用的位库:

https://github.com/drslump/Bits-js/blob/master/lib/Bits.js

我没有尝试过 10m 位,所以你必须在自己的数据集上尝试。

您提出的解决方案非常“扁平化”,这意味着每个像素必须有一个对应的位。这会导致需要大量的内存——即使信息是以比特的形式存储的。

替代测试数据范围而不是测试每个像素:

如果墙壁像素的数量与像素总数相比较小,您可以尝试将每面墙壁存储为一系列“运行”。例如,墙跑可能会存储在这样的对象中(警告:未经测试的代码!):

// an object containing all horizontal wall runs
var xRuns={}

// an object containing all vertical wall runs
var yRuns={}

// define a wall that runs on y=50 from x=100 to x=185
// and then runs on x=185 from y=50 to y=225
var y=50;
var x=185;

if(!xRuns[y]){ xRuns[y]=[]; }

xRuns[y].push({start:100,end:185});

if(!yRuns[x]){ yRuns[x]=[]; }

yRuns[x].push({start:50,end:225});

然后你可以像这样快速测试一个 [x,y] 靠墙运行(警告未经测试的代码!):

function isWall(x,y){

    if(xRuns[y]){
        var a=xRuns[y];
        var i=a.length;
        do while(i--){
            var run=a[i];
            if(x>=run.start && x<=run.end){return(true);}
        }
    }

    if(yRuns[x]){
        var a=yRuns[x];
        var i=a.length;
        do while(i--){
            var run=a[i];
            if(y>=run.start && y<=run.end){return(true);}
        }
    }

    return(false);

}

这应该需要很少的测试,因为 x & y 准确地指定了需要测试的 xRuns 和 yRuns 数组。

它可能(也可能不会)比测试“平面”模型更快,因为到达平面模型的指定元素存在开销。您必须使用这两种方法进行性能测试。

wall-run 方法可能需要更少的内存。

希望这会有所帮助...请记住,壁挂式替代方案只是我的想法,可能需要调整 ;-)

【讨论】:

  • 谢谢,这很有趣,我将来可能会使用它。我仍在寻找其他答案,以了解这是否是正确的解决方案(其他人不同意)。
  • 很公平!正如我在帖子中提到的,您将不得不尝试这些方法,以确定哪一种方法可以以您最理想的方式最大化性能。祝你的游戏好运!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-10
  • 2017-01-06
  • 2010-10-14
  • 1970-01-01
  • 1970-01-01
  • 2021-09-06
相关资源
最近更新 更多