three.js性能研究——第一篇

现在开源的webgl引擎中,three.js是功能最丰富的,而且社区活跃,使用简单,但是它的性能确实不太理想。本系列就和大家一一探究three.js的性能到底如何,原因是什么,以及有什么改进方案。
首先我们模拟一个理论上性能最好的使用场景,scene下创建10000个sphere,每个sphere大概1000个顶点(测试显卡为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大概1000个顶点,webgl差不多是这个帧率。
但是真实情况下,我们很难共用同一个材质,也很难共用同一个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个复杂物体,大概300万三角面(顺便测试了下webgl的性能,1000万三角面的mesh,16帧),性能大概在43帧左右,merge和instance几乎在性能上没有差别,毕竟都是一次drawCall,但是在内存上就是20000倍的差别了,而且instance修改物体颜色,位置,pick等更加灵活,所以能用instance最好使用instance,用不了在采用merge的方案

发布了9 篇原创文章 · 获赞 11 · 访问量 8271

猜你喜欢

转载自blog.csdn.net/zhgu1992/article/details/103553397