Reputation: 963
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?
Upvotes: 4
Views: 6617
Reputation: 3491
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:
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.
Upvotes: 5