4

I am currently writing my first Three.js / WebGL application and it runs very well on my PC (Chrome). Sadly, on many other PCs, the framerate often drops bellow 30 frames per second.

Since the application actually isn't to complex, I wanted to ask for some tips related to the application to improve the performance. A version of the app can be found here:

www.wrodewald.de/StackOverflowExample/

The application contains a dynamic (morphing) plane using 64² vertices. A matrix is used to store a static heightmap and a wavemap. The wavemap is updated every frame to recalculate itself, some filters are used to "even out" every vertex compared to their neightbors. So every frame the plane has to be updated (colors and vertex position) which could be a reason for the performance problem

The second object (rhombus) shouldn't be a problem, static, moving around a bit but nothing special.

There are three lights (ambient, directional, spherical), no shadows, a tilt shift shader and a vignette shader.

Here are functions which are called per frame:

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();
}

this is what world.updateWorld(time) does

//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;

These are the "diamond"-functions

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;
}

Do you spot any reason for the framerate to be so inconsistent?

1 Answer 1

5

EDIT:

I have found 2 major things you have to improve:

Planes updates with GPU

speedup: high

Lets pick your code from 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]);
        }   
    }

So you have 4096 vertices you would like to update every 17 ms with CPU. Notice you didnt use any GPU advantage. How it should be done:

  • First you create buffers, for vertex position, for normals, for texture cordinates, indices ... . This together is called mesh.
  • Then you create a model. Model is composed from one or more meshes. And modelViewMatrix. This is super important part, matrix 4x4 is algebraic representation of position, rotation and scale of this model.
  • With each render you do exactly this in vertex shader:

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

    it is from your cg/shaders/VerticalTiltShiftShader.js

  • If you want to rotate your plane, you don't multiply each vertex, but you only multiply your model matrix once (js with THREE.js function):

    projectionMatrix.makeRotationY(dT);

    Then each vertex is multiplied in vertex shader with this matrix, which is like much more faster.

Javascript style

speedup: none - medium, but it will allow you to code faster

Lets pick your plane.js as example.

// 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();

In case you have only one plane in your project, you can consider this as singleton and implementation is ~ok. But if you would like to create 2 or more planes, all functions are recreated again for every instance (new PlaneWorld()). Correct way how to do this is:

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();

or more complicated version with anonymous function:

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();

Then new instance cost is lowered. Now the thing which might be connected, every instance should share same mesh, but has its own modelViewMatrix.

Sign up to request clarification or add additional context in comments.

9 Comments

Hi, the 2nd link looks really interesting. I actually programmed Game of Life with Java once. :D However, I have no clue how I would convert functions like these to the gpu. So the plane morphin is more or less visual, there is no reason for the CPU to get the current state of how the plane looks like but I have to interact with the pleane (-> change the state of a cell) at some times. So how do I do this? Also, at some point, the three.js rendering has to tak place and it needs the current geometry. How do I connect these parts?
Take a tessellated plane, render noise to texture then use it to offset your vertices in your vertexshader(texture2DLod). To get things back to the CPU you may read the texture back from the GPU or depending on your use case, use a shared noise seed between CPU and GPU and calculate a smaller noise map on the CPU to update your spatial structures.
Hi, sorry for the late response.;) I already worked with OpenGL once and I used the general approach of MVP-matrices etc. The problem in this project is, that I want to have a low poly plane which dynamicaly updates every vertices height based on their neighbors position. So what I actually do every frame is update the position of every vertex and then update the buffer. I think one major problem is updating the 64x64 vertex buffer every frame. I don't really use the model in this project since the objects don't really move but morph. ;)
@LJ_1102 One idea would be to write a vertex shader which updates the vertex buffer but I don't even know if this is possible with WebGL. ;D The shader would have to do the following steps: - do the basic stuff (calculating the vertex position based on MVP and vertex pos, normals etc. - calculating a new position based on the vertex neighbors and somehow write the new position in another buffer - after rendering, swap the current vertex buffer and the new vertex buffer I have no clue if this would improve the performance. And I have no clue how to implement this...
@ruhigbrauner what you describe sounds like you want to use transform feedback sadly this is not available in WebGL. Updating a 64x64 vertexbuffer each frame on the CPU is certainly fast enough though.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.