HTML5 Canvas滞后

时间:2017-07-04 14:17:04

标签: javascript html5-canvas

我最近在我的页面中添加了一个画布元素,球体上的随机点。它在PC上工作得很好,但在手机和平​​板电脑上渲染非常慢。 如何加快球速并减少滞后? 任何帮助都将非常感谢。GitHub example

1 个答案:

答案 0 :(得分:0)

还有很大的改进空间。

在渲染循环中你有

    for (var p of points) {
        p = rotation.multiplyVector(p);
        ctx.beginPath();
        ctx.arc(p.x + c.width / 2, p.y + c.height / 2, 2, 0, 2 * Math.PI);
        ctx.closePath();
        ctx.fill();
    }

如果要反复渲染相同的样式,则不需要使用beginPath

    ctx.beginPath();
    for (var p of points) {
        p = rotation.multiplyVector(p);
        const x = p.x + c.width / 2;
        const y = p.y + c.height / 2;
        ctx.moveTo(x + 2, y)
        ctx.arc(x, y, 2, 0, 2 * Math.PI);
    }
    ctx.fill();

然后矩阵向量乘法有不必要的审查和不必要的内存分配和对象实例化

你有

Matrix3.prototype.multiplyVector = function (vec) {
    if (vec instanceof Vector3) {
        var x = this.data[0 + 0 * 3] * vec.x + this.data[0 + 1 * 3] * vec.y + this.data[0 + 2 * 3] * vec.z;
        var y = this.data[1 + 0 * 3] * vec.x + this.data[1 + 1 * 3] * vec.y + this.data[1 + 2 * 3] * vec.z;
        var z = this.data[2 + 0 * 3] * vec.x + this.data[2 + 1 * 3] * vec.y + this.data[2 + 2 * 3] * vec.z;

        return new Vector3(x, y, z);
    }
}

这会不会发生if (vec instanceof Vector3) { ???不在你的代码中,为什么要浪费CPU时间呢。

然后this.data[2 + 0 * 3]优化工具可能会为您提供此功能,但手机不会像桌面一样进行优化,我不确定是否可以使用它。使用间接引用时,某些浏览器速度较慢this.data[?]慢于data[?]

为每个圆圈创建一个新的向量以立即丢弃它并不是所有内存友好的。您只需要一个对象,因此将其传递给要设置的函数。

改进

Matrix3.prototype.multiplyVector = function (vec, retVec = new Vector3(0,0,0)) {
    const d = this.data;
    retVec.x = d[0] * vec.x + d[3] * vec.y + d[6] * vec.z;
    retVec.y = d[1] * vec.x + d[4] * vec.y + d[7] * vec.z;
    retVec.z = d[2] * vec.x + d[5] * vec.y + d[8] * vec.z;
    return retVec;
};

然后在循环中

const rp = new Vector3(0,0,0);
ctx.beginPath();
for (var p of points) {
    rotation.multiplyVector(p,rp);
    const x = rp.x + c.width / 2;
    const y = rp.y + c.height / 2;
    ctx.moveTo(x + 2, y)
    ctx.arc(x, y, 2, 0, 2 * Math.PI);
}
ctx.fill();

Vector3Matrix3对象都非常浪费内存,这意味着CPU周期无法控制仅用于分配和删除时应重新使用内存,如上所示。

你有Matrix旋转功能来构建旋转,创建一个新的矩阵来创建一个旋转矩阵,然后需要一个新的矩阵来相乘。您只需创建一个矩阵即可创建6个完整矩阵对象。

旋转函数用x,y,z中的2作为0来调用,这意味着许多乘法和加法只是为零,或者你最终添加等于1的omc + cos或者你乘以1而不做任何改变。

你有

Matrix3.rotate = function (angle, x, y, z) {
    var result = new Matrix3();
    result.setIdentity();

    var cos = Math.cos(angle);
    var sin = Math.sin(angle);
    var omc = 1 - cos;

    result.data[0 + 0 * 3] = x * omc + cos;
    result.data[1 + 0 * 3] = y * x * omc + z * sin;
    result.data[2 + 0 * 3] = x * z * omc - y * sin;

    result.data[0 + 1 * 3] = x * y * omc - z * sin;
    result.data[1 + 1 * 3] = y * omc + cos;
    result.data[2 + 1 * 3] = y * z * omc + x * sin;

    result.data[0 + 2 * 3] = x * z * omc + y * sin;
    result.data[1 + 2 * 3] = y * z * omc - x * sin;
    result.data[2 + 2 * 3] = z * omc + cos;

    return result;
}

通过直接乘以矩阵来创建旋转矩阵,每个轴需要一个。

Matrix3.prototype.rotateX = function(angle, result = new Matrix3()) {
    const r = result.data;
    const d = this.data;
    const c = Math.cos(angle);
    const s = Math.sin(angle));
    const ns = -s;
    r[0] = d[0]
    r[1] = d[1] * c + d[2] * ns;
    r[2] = d[1] * s + d[2] * c;
    r[3] = d[3];
    r[4] = d[4] * c + d[5] * ns;
    r[5] = d[4] * s + d[5] * c;
    r[6] = d[6];
    r[7] = d[7] * c + d[8] * ns;
    r[8] = d[7] * s + d[8] * c;     
    return result;
},

对rotateY,rotateZ(每个与上面不同)执行相同操作

实例矩阵直接设置身份而不需要第二次调用。

function Matrix3() { this.data = [1,0,0,0,1,0,0,0,1] }

设置标识
Matrix3.prototype.setIdentity = function () { 
    const d = this.data;
    d.fill(0);
    d[8] = d[4] = d[0] = 1;
}

然后在你的循环函数中可以访问两个矩阵对象。

const mat1 = new Matrix3();
const mat2 = new Matrix3();
const rp = new Vector3(0,0,0);
const MPI2 = 2 * Math.PI;
function loop(){
    mat1.setIdentity();
    mat1.rotateX(angle.x,mat2);
    mat2.rotateY(angle.y,mat1);
    mat1.rotateZ(angle.z,mat2);
    // your text rendering in here
    const cw = c.width / 2;
    const ch = c.height / 2;
    ctx.beginPath();
    for (var p of points) {
        mat2.multiplyVector(p,rp);
        const x = rp.x + cw;
        const y = rp.y + ch;
        ctx.moveTo(x + 2, y)
        ctx.arc(x, y, 2, 0, MPI2);
    }
    ctx.fill();
}

这会给你一点额外的速度。任何其他改进都将是浏览器/设备特定的。

UPDATE 根据评论中的要求,以下代码段包含围绕X,Y和Z轴的缩短旋转矩阵乘法

// How I find the optimum matrix multiplication via 
// eleminating a[?] * 0 = 0 
// reducing  a[?] * 1 = a[?]
//
// The following are the rotations for X,Y,Z as matrix
//-------------------
// rotate X
//  1       0     0
//  0  cos(r) sin(r)
//  0 -sin(r) cos(r)
//-------------------
// rotate Y
//  cos(r) 0 sin(r)
//  0      1      0 
// -sin(r) 0 cos(r)
//-------------------
// rotate Z
//  cos(r) sin(r) 0
// -sin(r) cos(r) 0 
//       0      0 1
// The matrix indexes 
// [0][1][2]
// [3][4][5]
// [6][7][8]
// Using the indexs and multiply is c = a * b
// c[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]
// c[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]
// c[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]
// c[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]
// c[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]
// c[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]
// c[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]
// c[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]
// c[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]
// Then use the rotations matrix to find the zeros and ones 
// EG rotate X b[1],b[2],b[3],b[6] are zero and b[0] is one
// c[0] = a[0] * 1    + a[1] * 0    + a[2] * 0   
// c[1] = a[0] * 0    + a[1] * b[4] + a[2] * b[7]
// c[2] = a[0] * 0    + a[1] * b[5] + a[2] * b[8]
// c[3] = a[3] * 1    + a[4] * 0    + a[5] * 0   
// c[4] = a[3] * 0    + a[4] * b[4] + a[5] * b[7]
// c[5] = a[3] * 0    + a[4] * b[5] + a[5] * b[8]
// c[6] = a[6] * 1    + a[7] * 0    + a[8] * 0   
// c[7] = a[6] * 0    + a[7] * b[4] + a[8] * b[7]
// c[8] = a[6] * 0    + a[7] * b[5] + a[8] * b[8]
// then eliminate all the zero terms a[?] * 0 == 0 and 
// remove the 1 from 1 * a[?] = a[?]
// c[0] = a[0] 
// c[1] = a[1] * b[4] + a[2] * b[7]
// c[2] = a[1] * b[5] + a[2] * b[8]
// c[3] = a[3]   
// c[4] = a[4] * b[4] + a[5] * b[7]
// c[5] = a[4] * b[5] + a[5] * b[8]
// c[6] = a[6] 
// c[7] = a[7] * b[4] + a[8] * b[7]
// c[8] = a[7] * b[5] + a[8] * b[8]
// And you are left with the minimum calculations required to apply a particular rotation Or any other transform.

Matrix3.prototype.rotateX = function(angle, result = new Matrix3()) {
    const r = result.data;
    const d = this.data;
    const c = Math.cos(angle);
    const s = Math.sin(angle);
    const ns = -s;
    r[0] = d[0];
    r[1] = d[1] * c + d[2] * ns;
    r[2] = d[1] * s + d[2] * c;
    r[3] = d[3];
    r[4] = d[4] * c + d[5] * ns;
    r[5] = d[4] * s + d[5] * c;
    r[6] = d[6];
    r[7] = d[7] * c + d[8] * ns;
    r[8] = d[7] * s + d[8] * c;     
    return result;
}
Matrix3.prototype.rotateY = function(angle, result = new Matrix3()) {
    const r = result.data;
    const d = this.data;
    const c = Math.cos(angle);
    const s = Math.sin(angle);
    const ns = -s;
    r[0] = d[0] * c + d[2] * ns;
    r[1] = d[1];
    r[2] = d[0] * s + d[2] * c;
	
    r[3] = d[3] * c + d[5] * ns;
    r[4] = d[4];
    r[5] = d[3] * s + d[5] * c;
	
    r[6] = d[6] * c + d[8] * ns;
    r[7] = d[7];
    r[8] = d[6] * s + d[8] * c;     
    return result;
}
Matrix3.prototype.rotateZ = function(angle, result = new Matrix3()) {
    const r = result.data;
    const d = this.data;
    const c = Math.cos(angle);
    const s = Math.sin(angle);
    const ns = -s;
    r[0] = d[0] * c + d[1] * ns;
    r[1] = d[0] * s + d[1] * c;
    r[2] = d[2];
	
    r[3] = d[3] * c + d[4] * ns;
    r[4] = d[3] * s + d[4] * c;
    r[5] = d[5];
	
    r[6] = d[6] * c + d[7] * ns;
    r[7] = d[6] * s + d[7] * c;     
    r[8] = d[8];
    return result;
}