使用 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。
我目前正在编写我的第一个 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。