繁体   English   中英

帆布颗粒,碰撞和性能

[英]Canvas particles, collisions and performance

我正在创建一个Web应用程序,它具有交互式背景,粒子在弹跳。 在任何时候屏幕上都有大约200个圆形颗粒,最多约800个颗粒。 正在为粒子运行的一些碰撞和效果是以下原型。 我想知道我是否可以通过使用网络工作人员来进行这些计算来提高性能?

/**
*   Particles
*/

Jarvis.prototype.genForegroundParticles = function(options, count){

    count = count || this.logoParticlesNum;

    for (var i = 0; i < count; i++) {
        this.logoParticles.push(new Particle());
    }

}

Jarvis.prototype.genBackgroundParticles = function(options, count){

    count = count || this.backgroundParticlesNum;

    for (var i = 0; i < count; i++) {
        this.backgroundParticles.push(new Particle(options));
    }

}

Jarvis.prototype.motion = {
    linear : function(particle, pIndex, particles){
        particle.x += particle.vx
        particle.y += particle.vy
    },
    normalizeVelocity : function(particle, pIndex, particles){

        if (particle.vx - particle.vxInitial > 1) {
            particle.vx -= 0.05;
        } else if (particle.vx - particle.vxInitial < -1) {
            particle.vx += 0.05;
        }

        if (particle.vy - particle.vyInitial > 1) {
            particle.vy -= 0.05;
        } else if (particle.vx - particle.vxInitial < -1) {
            particle.vy += 0.05;
        }

    },
    explode : function(particle, pIndex, particles) {

        if (particle.isBottomOut()) {
            particles.splice(pIndex, 1);
        } else {
            particle.x += particle.vx;
            particle.y += particle.vy;
            particle.vy += 0.1;
        }

        if (particles.length === 0){
            particles.motion.removeMotion("explode");
            this.allowMenu = true;
        }       

    }
}

Jarvis.prototype.collision = {
    boundingBox: function(particle, pIndex, particles){

        if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) {
            particle.vy *= -1;
        }

        if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) {
            particle.vx *= -1;
        }
    },
    boundingBoxGravity: function(particle, pIndex, particles){
        // TODO: FIX GRAVITY TO WORK PROPERLY IN COMBINATION WITH FX AND MOTION
        if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) {
            particle.vy *= -1;
            particle.vy += 5;
        } 

        if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) {
            particle.vx *= -1;
            particle.vx += 5;
        }

    },
    infinity: function(particle, pIndex, particles){

        if (particle.x > this.WIDTH){
            particle.x = 0;
        }

        if (particle.x < 0){
            particle.x = this.WIDTH;
        }

        if (particle.y > this.HEIGHT){
            particle.y = 0;
        }       

        if (particle.y < 0) {
            particle.y = this.HEIGHT;
        }

    }
}

Jarvis.prototype.fx = {
    link : function(particle, pIndex, particles){

        for(var j = pIndex + 1; j < particles.length; j++) {

            var p1 = particle;
            var p2 = particles[j];
            var particleDistance = getDistance(p1, p2);

            if (particleDistance <= this.particleMinLinkDistance) {
                this.backgroundCtx.beginPath();
                this.backgroundCtx.strokeStyle = "rgba("+p1.red+", "+p1.green+", "+p1.blue+","+ (p1.opacity - particleDistance / this.particleMinLinkDistance) +")";
                this.backgroundCtx.moveTo(p1.x, p1.y);
                this.backgroundCtx.lineTo(p2.x, p2.y);
                this.backgroundCtx.stroke();
                this.backgroundCtx.closePath();
            }
        }
    },
    shake : function(particle, pIndex, particles){

        if (particle.xInitial - particle.x >= this.shakeAreaThreshold){
            particle.xOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.WIDTH);
        } else if (particle.xInitial - particle.x <= -this.shakeAreaThreshold) {
            particle.xOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.WIDTH);
        }

        if (particle.yInitial - particle.y >= this.shakeAreaThreshold){
            particle.yOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.HEIGHT);
        } else if (particle.yInitial - particle.y <= -this.shakeAreaThreshold) {
            particle.yOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.HEIGHT);
        }       

        particle.x += particle.xOper;
        particle.y += particle.yOper;

    },
    radialWave : function(particle, pIndex, particles){

        var distance = getDistance(particle, this.center);

        if (particle.radius >= (this.dim * 0.0085)) {
            particle.radiusOper = -0.02;
        } else if (particle.radius <= 1) {
            particle.radiusOper = 0.02;
        }

        particle.radius += particle.radiusOper * particle.radius;
    },
    responsive : function(particle, pIndex, particles){

        var newPosX = (this.logoParticles.logoOffsetX + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.x;
        var newPosY = (this.logoParticles.logoOffsetY + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.y;

        if (particle.xInitial !== newPosX || particle.yInitial !== newPosY){

            particle.xInitial = newPosX;
            particle.yInitial = newPosY;
            particle.x = particle.xInitial;
            particle.y = particle.yInitial;

        }

    },
    motionDetect : function(particle, pIndex, particles){

        var isClose = false;
        var distance = null;

        for (var i = 0; i < this.touches.length; i++) {

            var t = this.touches[i];

            var point = {
                x : t.clientX,
                y : t.clientY
            }

            var d = getDistance(point, particle); 

            if (d <= this.blackhole) {
                isClose = true;

                if (d <= distance || distance === null) {
                    distance = d;
                }

            }  

        }

        if (isClose){
            if (particle.radius < (this.dim * 0.0085)) {
                particle.radius += 0.25;
            }
            if (particle.green >= 0 && particle.blue >= 0) {
                particle.green -= 10;
                particle.blue -= 10;
            }           
        } else {
            if (particle.radius > particle.initialRadius) {
                particle.radius -= 0.25;
            }
            if (particle.green <= 255 && particle.blue <= 255) {
                particle.green += 10;
                particle.blue += 10;
            }           
        }

    },
    reverseBlackhole : function(particle, pIndex, particles){

        for (var i = 0; i < this.touches.length; i++) {

            var t = this.touches[i];

            var point = {
                x : t.clientX,
                y : t.clientY
            } 

            var distance = getDistance(point, particle);

            if (distance <= this.blackhole){

                var diff = getPointsDifference(point, particle);

                particle.vx += -diff.x / distance;
                particle.vy += -diff.y / distance;
            }

        }
    }
}

此外,万一有人想知道我有3个画布层,我将为所有画布层添加粒子渲染功能和清晰功能

  1. 绘制全屏径向渐变和粒子的背景

  2. 菜单画布

  3. 菜单按钮覆盖选择器(显示哪个菜单处于活动状态等)


Jarvis.prototype.backgroundDraw = function() {

    // particles

    var that = this;

    this.logoParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.logoParticles.motion.forEach(function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
        that.logoParticles.fx.forEach(function(fxType, fxIndex){
            that.fx[fxType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
        that.logoParticles.collision.forEach(function(collisionType, collisionIndex){
            that.collision[collisionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
    });

    this.backgroundParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.backgroundParticles.motion.forEach(function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
        that.backgroundParticles.fx.forEach(function(fxType, fxIndex){
            that.fx[fxType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
        that.backgroundParticles.collision.forEach(function(collisionType, collisionIndex){
            that.collision[collisionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
    });

}

Jarvis.prototype.clearCanvas = function() {

    switch(this.background.type){
        case "radial_gradient":
            this.setBackgroundRadialGradient(this.background.color1, this.background.color2);
            break;
        case "plane_color":
            this.setBackgroundColor(this.background.red, this.background.green, this.background.blue, this.background.opacity);
            break;
        default:
            this.setBackgroundColor(142, 214, 255, 1);
    }

    this.foregroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance);
    this.middlegroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance);
}

Jarvis.prototype.mainLoop = function() {
    this.clearCanvas();
    this.backgroundDraw();
    this.drawMenu();
    window.requestAnimFrame(this.mainLoop.bind(this));
}

任何其他优化技巧将不胜感激。 我已经阅读了几篇文章,但我不确定如何进一步优化此代码。

您可以使用FabricJS Canvas Library。 FabricJS默认支持交互性,当您创建新对象(圆形,矩形等)时,您可以通过鼠标或触摸屏对其进行操作。

var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
    width: 10, height: 20,
    left: 100, top: 100,
    fill: 'yellow',
    angle: 30
});

canvas.add(rect); 

看,我们以面向对象的方式在那里工作。

除了切换到使用硬件加速的技术之外,我不知道你可以做些什么重大改进。

我希望这会有所帮助,但正如问题所述,WebGL会更快。 如果你不知道从哪里开始,这里是一个很好的: webglacademy

我还是看到了一些小东西:

radialWave : function(particle, pIndex, particles){

        // As you don't use distance here remove this line
        // it's a really greedy calculus that involves square root
        // always avoid if you don't have to use it

        // var distance = getDistance(particle, this.center);

        if (particle.radius >= (this.dim * 0.0085)) {
            particle.radiusOper = -0.02;
        } else if (particle.radius <= 1) {
            particle.radiusOper = 0.02;
        }

        particle.radius += particle.radiusOper * particle.radius;
    },

另一件小事:

Jarvis.prototype.backgroundDraw = function() {

    // particles

    var that = this;

    // Declare callbacks outside of forEach calls
    // it will save you a function declaration each time you loop

    // Do this for logo particles
    var logoMotionCallback = function(motionType, motionIndex){
        // Another improvement may be to use a direct function that does not use 'this'
        // and instead pass this with a parameter called currentParticle for example
        // call and apply are known to be pretty heavy -> see if you can avoid this
        that.motion[motionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
    };

    var logoFxCallback = function(fxType, fxIndex){
        that.fx[fxType].call(that, particle, i, that.logoParticles, "foregroundParticles");
    };

    var logoCollisionCallback = function(collisionType, collisionIndex){
        that.collision[collisionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
    };

    this.logoParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.logoParticles.motion.forEach(motionCallback);
        that.logoParticles.fx.forEach(fxCallback);
        that.logoParticles.collision.forEach(collisionCallback);
    });

    // Now do the same for background particles
    var bgMotionCallback = function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
    };

    var bgFxCallback = function(fxType, fxIndex){
        that.fx[fxType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
    };

    var bgCollisionCallback = function(collisionType, collisionIndex){
        that.collision[collisionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
    };

    this.backgroundParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.backgroundParticles.motion.forEach(bgMotionCallback);
        that.backgroundParticles.fx.forEach(bgFxCallback);
        that.backgroundParticles.collision.forEach(bgCollisionCallback);
    });

}

如果您希望加速代码,这里有一些微优化:

  • for(var i = 0, l = bla.length; i < l; i++) { ... }而不是bla.forEach(...)
  • 减少回调使用量。 内联简单的东西。
  • 由于SQRT,与距离的比较是缓慢的。 radius <= distance慢, radius*radius <= distanceSquared很快。
  • 通过计算差异来计算距离。 你现在做2个函数调用,首先得到距离,然后得到差异。 这是一个小的重写:没有函数调用,没有不必要的计算。

reverseBlackhole : function(particle, pIndex, particles) { var blackholeSqr = this.blackhole * this.blackhole, touches = this.touches, fnSqrt = Math.sqrt, t, diffX, diffY, dstSqr; for (var i = 0, l = touches.length; i < l; i++) { t = touches[i]; diffX = particle.x - t.clientX; diffY = particle.y - t.clientY; distSqr = (diffX * diffX + diffY * diffY); // comparing distance without a SQRT needed if (dstSqr <= blackholeSqr){ var dist = Math.sqrt(dstSqr); particle.vx -= diffX / dist; particle.vy -= diffY / dist; } } }

为了加快绘图速度(或在绘图过程中减少绘图):

  • 将计算与绘图分开
  • 只有在更新计算后才请求重绘

而对于整个动画:

  • this.backgroundParticles.forEach(..) :在200个粒子的情况下,这样做
    • 200个粒子次数( this.backgroundParticles.forEach(
      • 200个粒子( that.backgroundParticles.motion.forEach
      • 200个粒子( that.backgroundParticles.fx.forEach
      • 200个粒子( that.backgroundParticles.collision.forEach
  • 同样适用于this.foregroundparticles.forEach(..)
  • 假设我们有200个背景和100个前景,即(200 * 200 * 3)+(100 * 100 * 3)回调,即每个刻度150000个回调。 我们实际上还没有计算过任何东西,也没有显示任何东西。
  • 以60fps的速度运行, 每秒钟可以回收900万次回调。 我想你可以在这里发现一个问题。
  • 停止在这些函数调用中传递字符串。

为了获得更高的性能,删除OOP的东西,并只在有意义的地方寻找丑陋的意大利面条代码。

可以通过不对每个粒子彼此进行测试来优化碰撞检测。 只需查看四叉树。 并不难实现,并且可以使用它的基础来提出自定义解决方案。

由于你正在做一些矢量数学,试试glmatrix库 优化的矢量数学:-)

我想您可能会发现webworker支持与WebGL支持相同:

WebGL支持: http//caniuse.com/#search=webgl
WebWorker支持: http//caniuse.com/#search=webworker

从表面上看,它们看起来可能不同,但实际上并非如此。 你唯一能获得的是暂时的IE10支持。 IE11的市场份额已经超过IE10,并且这一鸿沟将继续增长。 唯一需要注意的是webgl支持似乎也基于更新的显卡驱动程序。

当然,我不知道你的具体需求,所以这可能不起作用。

选项

等什么? 屏幕上有200个项目很慢?

在画布领域做得少,在WebGL中做很酷的事情

许多图书馆都这样做。 画布应该可以使用,只是有点酷。 WebGL通常具有所有很酷的粒子特征。

WebWorkers

您可能需要使用延迟库或创建一个系统来确定所有Web工作者何时完成并拥有工作线程池。

一些警告:

  1. 您无法从主应用程序访问任何内容,并且必须通过事件进行通信
  2. 通过webworker传递的对象不会被共享复制
  3. 在没有单独脚本的情况下设置Web工​​作者可能需要一些研究

未经证实的谣言:我听说您可以通过网络工作人员消息传递的数据量有限。 您应该对此进行测试,因为它似乎直接适用于您的用例。

暂无
暂无

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

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