three.js性能研究

·  阅读 1961

现在开源的webgl引擎中,three.js是功能最丰富的,而且社区活跃,使用简单,但是它的性能确实不太理想。本系列就和大家一一探究three.js的性能到底如何,原因是什么,以及有什么改进方案。 首先我们模拟一个理论上性能最好的使用场景,scene下创建10000个sphere,每个sphere大概6000个顶点(测试显卡为mac Radeon Pro 555X 4 GB,相当于GTX1050左右) 代码如下:

var geometry =  new THREE.SphereBufferGeometry( 5, 32, 32 );
var material = new THREE.MeshBasicMaterial({transparent:false, color:new THREE.Color(1,0,0)});
 for ( var i = 0; i < 8000; i ++ ) {
            var mesh = new THREE.Mesh( geometry, material );
            mesh.position.x = Math.random() * 1600 - 800;
            mesh.position.y = 0;
            mesh.position.z = Math.random() * 1600 - 800;
            mesh.updateMatrix();
            mesh.matrixAutoUpdate = false;
            scene.add( mesh );
        }  
复制代码

我们共用同一个材质,共用同一个geometry,不产生多余的节点,关掉scene的autoUpdate,使用basic材质,排除灯光的影响(理论上应该是three.js最佳的性能),下面看看帧率: 在这里插入图片描述 帧率也只不过31帧左右,这个性能也差不多是webgl的性能了,10000次drawCall,每次drawCall大概6000个顶点。 但是真实情况下,我们很难共用同一个材质,也很难共用同一个geometry,模拟三个不同的材质,帧率掉了5帧(如果是同一个材质对象,shader相同,three.js会有优化,掉帧可以忽略不计,当然define不同相当于使用了不同材质): 在这里插入图片描述 我们分析一下为什么:

	if ( material.id !== _currentMaterialId ) {

				_currentMaterialId = material.id;

				refreshMaterial = true;

			}
复制代码

首先,如果创建不同的材质对象,则refreshMaterial会一直是true,这样会让每个材质的uniform全部上传,由于three.js没有实现uniformBlock(webgl2),因此这个开销也是有个2-3帧的差距的,当然如果我创建的是不同材质,除了uniform要更新外还要切shader,这样的开销更大,创建3个不同材质会导致5帧的开销。 对于这种情况,我们在设计渲染引擎的时候最好能使用uniformBlock,把世界矩阵这些放在一个layer中,灯光放在一个layer中,其他的放在一个layer中,如果设置了材质的相关参数,直接更新buffer中的值,不要像three.js这种只是设置了属性,每帧还要去遍历属性计算最终的值,这样无疑又是一笔开销。 如果材质公用,geometry每次创建一个新的对象:这样直接掉了7帧,这个帧率的开销非常大 在这里插入图片描述 我们看看为什么,

	var updateBuffers = false;

		if ( _currentGeometryProgram.geometry !== geometry.id ||
			_currentGeometryProgram.program !== program.id ||
			_currentGeometryProgram.wireframe !== ( material.wireframe === true ) ) {

			_currentGeometryProgram.geometry = geometry.id;
			_currentGeometryProgram.program = program.id;
			_currentGeometryProgram.wireframe = material.wireframe === true;
			updateBuffers = true;

		}
复制代码

我们发现只要geometry的id不一样,则就会执行updateBuffers,创建attribute的buffer,然后上传,这个开销确实是巨大的,不仅影响显存,而且影响帧率,所以需要尽量能使用同一个geometry对象。 真实情况下,geometry很多不能公用,材质也有多个的情况,只有18帧了,掉了13帧 : 在这里插入图片描述 所以如果要绘制超过10000个物体,必须使用merge或者instancedMesh,下面是使用merge后的结果(满帧,instanced的性能和merge基本能差不多,而且能节省很多显存的开销,所以能使用instance尽量使用) 在这里插入图片描述下面我们来测试和对比一下merge与instance 在这里插入图片描述在这里插入图片描述创建20000个复杂物体,性能大概在43帧左右,merge和instance几乎在性能上没有差别,毕竟都是一次drawCall,但是在内存上就是20000倍的差别了,而且instance修改物体颜色,位置,pick等更加灵活,所以能用instance最好使用instance,用不了在采用merge的方案

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改