使用 Three.js 提高性能

Improving performance with Three.js

我目前正在编写我的第一个 Three.js / WebGL 应用程序,它在我的 PC 上运行良好 (Chrome)。可悲的是,在许多其他 PC 上,帧率经常下降到每秒 30 帧以下。

由于应用程序实际上并不复杂,所以我想请教一些与应用程序相关的技巧以提高性能。 可以在此处找到该应用程序的一个版本:

www.wrodewald.de/WhosebugExample/

该应用程序包含一个使用 64² 个顶点的动态(变形)平面。矩阵用于存储静态高度图和波浪图。波形图每帧都会更新以重新计算自身,一些过滤器用于 "even out" 每个顶点与其邻居相比。 所以飞机的每一帧都必须更新(颜色和顶点位置)这可能是性能问题的原因

第二个物体(菱形)应该没有问题,静态的,四处移动,但没什么特别的。

有三种光(环境光、定向光、球形光)、无阴影、倾斜偏移着色器和晕影着色器。

以下是每帧调用的函数:

var render = function() {
    requestAnimationFrame( render );
    var time = clock.getDelta();

    world.updateWorld(time);
    diamond.rotate(time);
    diamond.update(time);
    control.updateCamera(camera, time);
    composer.render();      
    stats.update();
}

这就是 world.updateWorld(time) 所做的

//in world.updateWorld(time) where
// accmap: stores acceleration and wavemap stores position
// this.mapSize stores the size of the plane in vertices (64)

// UPDATE ACCELERATION MAP
for(var iX = 1; iX < (this.mapSize-1); iX++) {
    for(var iY = 1; iY < (this.mapSize-1); iY++) {
        accmap[iX][iY] -=  dT * (wavemap[iX][iY]) * Math.abs(wavemap[iX][iY]);
    }   
}


// SMOOTH ACCELERATION MAP
for(var iX = 1; iX < (this.mapSize-1); iX++) {
    for(var iY = 1; iY < (this.mapSize-1); iY++) {
        tempmap[iX][iY] =     accmap[iX-1][iY-1]    * 0.0625
                            + accmap[iX-1][iY  ]    * 0.125
                            + accmap[iX-1][iY+1]    * 0.0625
                            + accmap[iX  ][iY-1]    * 0.125
                            + accmap[iX  ][iY  ]    * 0.25
                            + accmap[iX  ][iY+1]    * 0.125
                            + accmap[iX+1][iY-1]    * 0.0625
                            + accmap[iX+1][iY  ]    * 0.125
                            + accmap[iX+1][iY+1]    * 0.0625;

        accmap[iX][iY] = tempmap[iX][iY];
    }
}


// UPDATE WAVE MAP
for(var iX = 1; iX < (this.mapSize-1); iX++) {
    for(var iY = 1; iY < (this.mapSize-1); iY++) {
        wavemap[iX][iY] += dT * accmap[iX][iY];
    }   
}

for(var i = 0; i < this.mapSize; i++) {
    for(var k = 0; k < this.mapSize; k++) {
        geometry.vertices[ i * this.mapSize + k ].y =  wavemap[i][k] + heightmap[i][k];
    }   
}

for(var i = 0; i < geometry.faces.length; i++) {

    var vertexA = geometry.vertices[geometry.faces[i].a];
    var vertexB = geometry.vertices[geometry.faces[i].b];
    var vertexC = geometry.vertices[geometry.faces[i].c];

    var val = (vertexA.y + vertexB.y + vertexC.y) / 3.0;
    val = (val / 200.) + 0.5;

    geometry.faces[i].color.r = val;
    geometry.faces[i].color.g = val;
    geometry.faces[i].color.b = val;
}

geometry.colorsNeedUpdate = true;
geometry.verticesNeedUpdate = true;

这些是 "diamond"-函数

this.rotate = function(dT) {
    counter += 0.5 * dT;
    counter % 1;
    var x = 0.0025 * (Math.sin((counter) * 2 * Math.PI));
    var y = 0.0025 * (Math.cos((counter) * 2 * Math.PI));
    this.mesh.rotateOnAxis(new THREE.Vector3(1,0,0), x);
    this.mesh.rotateOnAxis(new THREE.Vector3(0,0,1), y);
}

this.update = function(dT) {
    for(var i = 0; i < geometry.faces.length; i++) {    
        geometry.faces[i].color.lerp(color, dT*(0.9));
    }

    geometry.colorsNeedUpdate = true;
}

你发现帧率如此不一致的原因了吗?

编辑:

我发现有 2 个主要方面需要改进:

使用 GPU 更新飞机

加速:高

让我们从 plane.js

中选择您的代码
    timer += dT;
    if(timer > 0.1) {           
        var x = 2 + Math.floor(Math.random() * (this.mapSize - 4));
        var y = 2 + Math.floor(Math.random() * (this.mapSize - 4));
        //accmap[x][y] += 30000 * Math.random () - 15000
    }


    // UPDATE ACCELERATION MAP
    for(var iX = 1; iX < (this.mapSize-1); iX++) {
        for(var iY = 1; iY < (this.mapSize-1); iY++) {
            accmap[iX][iY] -=  dT * (wavemap[iX][iY]) * Math.abs(wavemap[iX][iY]);
        }   
    }

所以你有 4096 个顶点你想用 CPU 每 17 毫秒更新一次。请注意,您没有使用任何 GPU 优势。应该怎么做:

  • 首先,您为顶点位置、法线、纹理坐标、索引...创建缓冲区。这一起被称为网格。
  • 然后你创建一个模型。模型由一个或多个网格组成。和模型视图矩阵。这是非常重要的部分,矩阵 4x4 是该模型的位置、旋转和比例的代数表示。
  • 对于每个渲染,您都在顶点着色器中执行此操作:

    "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",

    来自你的cg/shaders/VerticalTiltShiftShader.js

  • 如果你想旋转你的平面,你不乘每个顶点,但你只乘你的模型矩阵一次(js with THREE.js function):

    projectionMatrix.makeRotationY(dT);

    然后每个顶点在顶点着色器中与这个矩阵相乘,这样会快得多。

Javascript风格

加速:none - 中等,但它可以让你编码更快

让我们以您的 plane.js 为例。

// this is your interface
function PlaneWorld () {
    this.init = function() {};
    this.updateVertices = function() {};
    this.updateWorld = function(dT) {};
    // ... and more
}

// and somewhere else:
var world = new PlaneWorld();

如果你的项目中只有一架飞机,你可以将其视为单例并且实现是~ok。但是,如果您想创建 2 个或更多平面,则会为每个实例重新创建所有函数 (new PlaneWorld())。正确的做法是:

function PlaneWorld () {
    ...
}

PlaneWorld.prototype.init = function() {};    
PlaneWorld.prototype.updateVertices = function() {};    
PlaneWorld.prototype.updateWorld = function(dT) {};  
// ... and more

var world = new PlaneWorld();

// calling methods works same
world.updateVertices();

或更复杂的匿名函数版本:

var PlaneWorld = (function() {

    // something like private static variables here

    var PlaneWorld = function () {
        ...
    }

    PlaneWorld.prototype = {
        init: function() {},
        updateVertices: function() {},
        updateWorld: function(dT) {} 
        // ... and more
    }

    return PlaneWorld();
})();

var world = new PlaneWorld();
// calling methods works same
world.updateVertices();

然后降低新实例成本。现在可能连接的东西,每个实例都应该共享相同的网格,但有自己的 modelViewMatrix。