最近有些忽悠大侠出来,说他的技术利用“视觉残留”欺骗用户的眼睛,以达到性能优化,有多么牛逼,那么今天我们来研究一下所谓视角欺骗是怎么一回事。
第一点:人的眼睛有一种视觉暂留的特性。眼睛的一个重要特性是视觉惰性,即光象一旦在视网膜上形成,视觉将会对这个光象的感觉维持一个有限的时间,这种生理现象叫做视觉暂留性。对于中等亮度的光刺激,视觉暂留时间约为0.1至0.4秒。中国古代的走马灯,就是通过这个特性来实现视觉的动画效果。让我颇为怀念的是前跟着我大伯用翻斗车接着十大麻袋的电影胶卷去周边村子播放李连杰的《少林寺》,电影胶卷是静态的,通过一秒24帐的播放却可以给人以连续的画面。
视角欺骗 其实就是帧率下降,还不涉及到帧率提升,没有太多的技术含量。Three.js 渲染器的渲染方法.render()
每执行一次,WebGL帧缓冲区就得到一帧像素数据,canvas画布就会显示当前渲染的一帧像素数据。通过requestAnimationFrame()
可以周期性调用执行渲染方法.render()
,帧率就是指渲染方法.render()
每秒钟执行次数,比如帧率是 60,可以表示为 60FBS,FBS 对应的英文是 Frame per Second 每秒帧数。每一帧消耗的性能都是很高的,持续渲染,CPU和GPU都会以比较高的负荷工作。其实降到30,人眼直觉感觉不出来,只不过做动画的要注意帧率快慢。
帧率下降,下降还是比较容易的,在循环中间增加休息时间即可。
在一些特定的应用中没有必要保持Threejs渲染频率为60FPS,那么可以通过Threejs渲染时间判断来控制Threejs渲染频率,比如设置为30FPS。
// 创建一个时钟对象Clockvar clock = new THREE.Clock();// 设置渲染频率为30FBS,也就是每秒调用渲染器render方法大约30次var FPS = 30;var renderT = 1 / FPS; //单位秒 间隔多长时间渲染渲染一次// 声明一个变量表示render()函数被多次调用累积时间// 如果执行一次renderer.render,timeS重新置0var timeS = 0;function render() {requestAnimationFrame(render);//.getDelta()方法获得两帧的时间间隔var T = clock.getDelta();timeS = timeS + T;// requestAnimationFrame默认调用render函数60次,通过时间判断,降低renderer.render执行频率if (timeS > renderT) {// 控制台查看渲染器渲染方法的调用周期,也就是间隔时间是多少console.log(`调用.render时间间隔`,timeS*1000+'毫秒');renderer.render(scene, camera); //执行渲染操作...//renderer.render每执行一次,timeS置0timeS = 0;}}render();
帧率的提升却实在太难了,这和眼睛的残留特性并没有什么关系。但是,这才是难点,可以说任何的模型优化手段都用上都不为过。但是我们这次只说几个小手段。
(1)分时加载算法(大数组)。本质上渲染一个模型,可能包含了5万个物体。但是渲染了一部分的时候,用户交互触发,前面一个渲染已经可以被放弃了(可能是相机已经转走了,再渲染已无意义)。这时候要需要中断渲染,如何把优先权让给用户交互呢?这可以引入大数组分断的概率。比如5万个物体,分10次渲染,每次渲染完的间隙时间让给响应鼠标。当鼠标触发新渲染,则中断前一次渲染。此时交互过程的帧率可以调到比较高的水平。timeout是灵魂!调查显示100ms内的响应能让用户感觉非常流畅。50ms是Nicholas
针对JavaScript
得出的最佳经验值。
setTimeout 延时25ms,25ms 保证主流浏览器都顺畅。
//Copyright Nicholas C. Zakas. All rights reserved.//MIT Licensedfunction timedChunk(items, process, context, callback){var todo = items.concat(); setTimeout(function(){var start = +new Date();do {process.call(context, todo.shift());} while (todo.length > 0 && (+new Date() - start < 50));if (todo.length > 0){setTimeout(arguments.callee, 25);} else {callback(items);}}, 25);};
(2)几何合并。
在webgl里面,降低drawcall来的的收益十分巨大。
geometries 为所有几何不重复的,材质相同的物体
geometries 为所有几何不重复的,材质相同的物体
geometries 为所有几何不重复的,材质相同的物体,重要的事情说三遍。几何相同的,请用instance复用。利用的收益比合并更巨大。材质不相同的物体,合并了也没有用。别看那些误导人的,说通过索引能够把材质不同的也进行合并,合是能合,合起来没鸟用,drawcall不会降低,还失去了独立控制权。
//geometries 为所有几何不重复的,材质相同的物体var myOne = THREE.BufferGeometryUtils.mergeBufferGeometries(geometries, useGroup)var xmesh = new THREE.InstancedMesh(myOne, myMtrs, 1);
合并前的
合并后的
有些手段如果说得不够详细,还请大家多多包涵。都已经21节了,大家自研的引擎应该差不多做出来了吧?做不出来就别做了,费事费力,不如直接用我们的。再做个广告吧。
自研引擎产品试用,demo下载:
QModel-BIM模型浏览器