在这里列出Babylon.js官方性能优化文档的中英文对照,并在CardSimulate项目里对其中的一些优化方法进行实践。node
Table of contents 内容列表web
This tutorial will help you find some links and info on how you can improve your scene regarding rendering performance.数据库
这篇教程将帮助你找到一些关于如何提高你的场景的渲染效果的连接和信息canvas
If you need node containers or transform nodes, do not use meshes but TransformNode instead. Use meshes only when associated with content to render.浏览器
若是你须要节点容器或者变换节点,不要使用网格,而是使用变换节点来替代。只在有关联的内容须要绘制时使用网格。缓存
The meshes need to go through an evaluation process where the camera checks if they are in the frustum. This is an expensive process so reducing the number of candidates by using TransformNode when possible is a good practice.性能优化
网格须要通过一个验证流程,在这个流程中相机将检查这些网格是否在视截椎体内部。这是一个性能消耗较大的流程,因此在可能时经过使用变换节点减小这种计算的数量是一个好的实践。app
(使用后1366*720分辨率fps似从20提高到22~24)ide
Starting with Babylon.js v3.3, you can now specify a strategy used to cull a specific mesh with mesh.cullingStrategy
.函数
从Babylon.js v3.3开始,你能够设定一个策略来剔除一个特定的网格,经过设置mesh.cullingStrategy
You can set it to:
你能够将它设置为:
BABYLON.AbstractMesh.CULLINGSTRATEGY_STANDARD
: This is the default value and it will use a combination of bounding sphere culling, bounding box culling and then frustum culling这是默认值,它将结合使用边界球剔除和边界盒剔除,而后是截锥体剔除。
BABYLON.AbstractMesh.CULLINGSTRATEGY_BOUNDINGSPHERE_ONLY
: This strategy will use a bounding sphere culling and then frustum culling. This is faster than the standard one but can imply having more meshes sent to the GPU. Really useful if you are CPU bound.这个策略将使用边界球剔除,而后是截锥体剔除。这种策略比基础的版本更加快速,可是可能引发更多的网格被发送到GPU。若是你的CPU资源有限,这种方法会颇有用。(把更多工做分给显卡作,可是个人CPU和GPU都不富裕啊)
Babylon.js uses an advanced and automatic shaders engine. This system will keep shaders up to date regarding material options. If you are using a static material (ie. an immutable material) then you can let it know to Babylon.js by using the following code:
Babylon.js使用一种先进的而且自动的着色器引擎。这个系统将根据材质的选项保持着色器的更新。若是你使用的是静态材质(好比一个不变的材质)那么你能够经过如下的代码让Babylon.js知道这一点:
material.freeze();
Once frozen, the shader will remain unchanged even if you change material's properties. You will have to unfreeze it to update the inner shader:
一旦被冻结,这个着色器将保持不变,即便你改变这个材质的属性(纹理是否属于着色器??更换其余材质会不会影响??网格的isvisble会不会影响??)。你必须解冻它才能更新内部的着色器:
material.unfreeze();
(建议在完成静态材质的全部设定以后使用,不影响更换其余材质,提高到26~27)
Every mesh has a world matrix to specify its position / rotation / scaling. This matrix is evaluated on every frame. You can improve performances by freezing this matrix. Any subsequent changes to position / rotation / scaling will then be ignore:
每一个网格都有一个世界矩阵用来肯定它的位置、姿态、缩放。这个矩阵在每一帧中被计算。你能够经过冻结这个矩阵来改进性能。这样任何传递(经过父元素继承?)的位置、姿态、缩放改变都将被忽略:
mesh.freezeWorldMatrix();
You can unfreeze a mesh with:
你可使用以下代码解冻一个网格:
mesh.unfreezeWorldMatrix();
infiniteDistance不该冻结世界矩阵,由于天空盒需一直移动,冻结地面以后静止帧数提高至29~30(适合用在父元素固定不变的状况下,或者提早预知哪一帧会发生改变,而后只在这一帧解冻!!!!天空盒若是设置了
)
If you are CPU bound, you can decide to keep the list of active meshes unchanged and then free the time spent by the CPU to determine active meshes:
若是你的CPU有限,你能够考虑保持“活动网格列表”不变,这样能够节省CPU用来判断网格是否活动的时间:
scene.freezeActiveMeshes();
You can unfreeze the active meshes with:
你可使用以下代码解冻活动网格列表
scene.unfreezeActiveMeshes();
Note that you can force a mesh to be in the active meshes before freezing the list with mesh.alwaysSelectAsActiveMesh = true
.
注意你能够在冻结活动网格列表前,强制一个网格进入活动网格列表,经过mesh.alwaysSelectAsActiveMesh = true
.
(好比isVisible==false就是非活动网格??
)
As soon as you can please use instances as they are drawn with one single draw call.
请尽可能快的用instances代替经过单独绘制调用绘制
If sharing the same material is a problem, you can then think about using clones which share the same geometry with mesh.clone("newName")
若是实例对象必须共用同一个材质对象是一个问题,你能够考虑经过语句mesh.clone("newName")
用克隆方法,克隆方法产生的mesh
共用同一个多边形对象
(instances
能够以一个网格为基础,复制出许多形状和材质相同的实例,它们的材质和多边形对象都是共用的)
By default, Babylon.js automatically clears the color, depth, and stencil buffers before rendering the scene. It also clears the depth and stencil buffers after switching to a new camera and before rendering a new RenderingGroup. On systems with poor fill rates, these can add up quickly and have a significant impact on performance.
默认状况下,Babylon.js会在渲染场景以前自动清空颜色、深度和模板缓存。它一样会在切换到新相机以后以及渲染一个新的渲染组以前清空深度和模板缓存。对于填充率较低的系统,这些清空操做能够被快速的汇总而且对性能有巨大的影响。
If your scene is set up in such a way that the viewport is always 100% filled with opaque geometry (if you're always inside a skybox, for instance), you can disable the default scene clearing behavior with:
若是你的场景按这种方式设定:视点一直百分之百的被不透明多边形填满(好比你一直处在一个天空盒内部),你能够用如下方法禁用默认的场景清理:
false//Colorscene.autoClear =;buffer颜色缓存
false//Depthandscene.autoClearDepthAndStencil =;stencil, obviously显然是深度和模板缓存
If you know that the geometry in a particular RenderingGroup will always be positioned in front of geometry from other groups, you can disable buffer clearing for that group with the following:
若是你肯定在某个渲染组中的多边形将一直位于其余组的多边形以前,你能够禁用这个组的缓存清理,经过如下方法:
scene.setRenderingAutoClearDepthStencilrenderingGroupIdxautoCleardepthstencil(,,,);
autoClear
: true
to enable auto clearing. If false
, overrides depth
and stencil
为真则启动自动清理。不然,覆盖深度和模板
depth
: Defaults to true
to enable clearing of the depth buffer
默认为真启动深度缓存清理
stencil
: Defaults to true
to enable clearing of the stencil buffer
默认为真启动模板缓存清理
Go ahead and be aggressive with these settings. You'll know if it's not appropriate for your application if you see any smearing!
尽管大胆测试这些设置。若是你在你的应用中看到任何污渍你将知道这个设置是否适合。
(在场景层面执行两个方法禁用了三种缓存,暂时未发现渲染异常,静止fps增长到37~38,可是在场景中漫游一段时间后fps又下降了,可是若是要在场景中使用多重半透明材质,还须要更谨慎的测试)
When dealing with complex scenes, it could be useful to use depth pre-pass. This technique will render designated meshes only in the depth buffer to leverage early depth test rejection. This could be used for instance when a scene contains meshes with advanced shaders. To enable a depth pre-pass for a mesh, just call mesh.material.needDepthPrePass = true
.
在处理复杂的场景时,使用深度预传递是颇有用的。这项技术将只在深度缓存中(意思是一个不显示的网格,但能影响其余网格的显示?)渲染指定的网格,来影响早期的深度测试剔除。在有网格应用了高级着色器的场景中这会颇有用。要启用一个网格的深度预传递,只须要调用:mesh.material.needDepthPrePass = true
.
By default Babylon.js uses indexed meshes where vertices can be reuse by faces. When vertex reuse is low and when vertex structure is fairly simple (like just a position and a normal) then you may want to unfold your vertices and stop using indices:
默认状况下Babylon.js使用索引的网格,这时顶点能够经过面来引用。当顶点引用较少或者顶点结构至关简单时(好比只有一个位置和一个法线),那么你可能想要展开你的顶点而且中止使用索引:
mesh.convertToUnIndexedMesh();
For example this works very well for a cube where it is more efficient to send 32 positions instead of 24 positions and 32 indices.
例如这种设定对于立方体很是好用,这时发送32个位置以代替24个位置和32个索引是更有效率的。
(实际上是换成了OpenGL更基础的一种顶点绘制方式,给没有精确点击的网格设置这种绘制方式?也许能加快场景加载速度?可是fps反而下降了,改回索引方式后fps恢复,估计多是非索引方式在检测pick时消耗更大)
By default, Babylon.js will adapt to device ratio in order to produce the best possible quality even on high-DPI devices.
默认状况下,Babylon.js会适应设备的比例来产生尽量高的图像质量,即便是在高分辨率设备上。
The drawback is that this could cost a lot on low-end devices. You can turn it off with the fourth parameter of the Engine constructor:
后果是在低端设备上这将形成很大的消耗。你能够经过引擎的构造方法的第四个参数关闭它:
varnewnullfalseengine =BABYLON.Engine(canvas, antialiasing,,);
In the same constructor, you may also want to turn off antialiasing support with the second parameter.
一样是在这个构造函数中,你也可能想经过第二个参数关闭抗锯齿支持。
By default the scene will keep all materials up to date when you change a property that could potentially impact them (alpha, texture update, etc...). To do so the scene needs to go through all materials and flag them as dirty. This could be a potential bottleneck if you have a lot of material.
默认状况下,场景将在你改变任何潜在的可能影响材质的属性时更新全部的材质(透明度、纹理更新等等)。要作到这一点场景须要遍历全部的材质而后将它们标志为脏的。在你有许多材质时这可能成为一个潜在的瓶颈。
To prevent this automatic update, you can execute:
要阻止这种自动的更新,你能够执行:
truescene.blockMaterialDirtyMechanism =;
Do not forget to restore it to false when you are done with your batch changes.
不要忘了将它恢复为false,在你须要批量更改材质时。
(使用后帧数彷佛略有提升,可能和前面的材质冻结存在部分功能重合)
Babylon.js processes speed depending on the current frame rate.
Babylon.js根据当前帧率计算速度。
On low-end devices animations or camera movement may differ from high-end devices. To compensate this you can use:
在低端设备上动画或者相机移动可能和高端设备不一样。要补偿这一点你可使用:
scene.getAnimationRatio();
The return value is higher on low frame rates.
在低帧率的状况下返回值更高
Starting with version 3.1, Babylon.js can handle WebGL context lost event. This event is raised by the browser when the GPU needs to be taken away from your code. This can happen for instance when using WebVR in hybrid scenario (with multiple GPU). In this case, Babylon.js has to recreate ALL low level resources (including textures, shaders, program, buffers, etc.). The process is entirely transparent and done under the hood by Babylon.js.
从3.1版本开始Babylon.js能够处理WebGL上下文丢失事件。这个事件由浏览器在CPU须要从你的代码上移开时触发。例如这种状况会在再混合场景中使用WebVR时发生(具备多个CPU)。在这种状况下,Babylon.js不得不从新创建全部的底层资源(包括纹理、着色器、程序、缓存等等)。这个流程是彻底透明的而且由Babylon.js的底层进行。
As a developer you should not be concerned by this mechanism. However, to support this scenario, Babylon.js may need an additional amount of memory to keep track of resources creation. If you do not need to support WebGL context lost event, you can turn off the tracking by instantiating your engine with doNotHandleContextLost option set to true.
做为一个开发者你不须要了解这个机制。可是,要支持这个机制,Babylon.js可能须要一些额外的内存来追踪资源的创建。若是你不须要支持WebGL上下文丢失事件,你能够经过将引擎的doNotHandleContextLost选项设为true来关闭这个追踪。
If you created resources that need to be rebuilt (like vertex buffers or index buffers), you can use the engine.onContextLostObservable
and engine.onContextRestoredObservable
observables to keep track of the context lost and context restored events.
若是你的已经被创建的资源须要被重建(好比顶点缓存或者索引缓存),你可使用engine.onContextLostObservable
和engine.onContextRestoredObservable
两个标志来追踪上下文丢失和上下文重建事件
(也就是说显卡要去作别的用,或者换成另外一块显卡?)
If you have a large number of meshes in a scene, and need to reduce the time spent in adding/removing thoses meshes to/from the scene, There are several options of the Scene
constructor that can help :
若是你的场景中有大量的网格,而且须要下降向这个场景中添加或者移除网格的时间消耗,场景工做函数的一些选项可能有帮助
useGeometryIdsMap
to true
will speed-up the addition and removal of Geometry
in the scene.将useGeometryIdsMap
选项设为true
将加速场景中几何体的添加和移除
useMaterialMeshMap
to true
will speed-up the disposing of Material
by reducing the time spent to look for bound meshes.将useMaterialMeshMap
选项设为true
将加速材质的回收,经过减小寻找绑定的网格所需的时间作到这一点。
useClonedMeshMap
to true
will speed-up the disposing of Mesh
by reducing the time spent to look for associated cloned meshes.将useClonedMeshMap
选项设为true
将加速网格的回收,经过减小相关的被克隆网格查找时间来实现这一点。
For each of this options turned on, Babylon.js will need an additional amount of memory.
对于开启的每个选项,Babylon.js都会须要额外的内存。(上面三种的原理和数据库经过建索引加快查询速度的原理是同样的)
Also, If you are disposing a large number of meshes in a row, you can save unecessary computation by turnning the scene property blockfreeActiveMeshesAndRenderingGroups
to true just before disposing the meshes, and set it back to false
just after, like this :
一样,若是你要在一行中回收大量的网格,你能够节省一些没必要要的计算,经过在回收这些网格前将场景的blockfreeActiveMeshesAndRenderingGroups
属性(成块释放活动网格和渲染组?)设为true
,而后在回收完毕后将它恢复为false
,就像这样:
truescene.blockfreeActiveMeshesAndRenderingGroups =;
/*
* Dispose all the meshes in a row here在这里一次性回收全部的网格
*/
falsescene.blockfreeActiveMeshesAndRenderingGroups =;
Instrumentation is a key tool when you want to optimize a scene. It will help you figure out where are the bottlenecks so you will be able to optmize what needs to be optimized.
在你想要优化一个场景时,指示器是一种关键的工具。它将帮助你指出哪里是性能瓶颈,因而你能够优化须要被优化的地方。
The EngineInstrumentation class allows you to get the following counters:
引擎指示器类容许你获取以下计数器:
instrumentation.captureGPUFrameTime = true
.GPU帧时间计数器:GPU渲染一帧所花费的时间(以纳秒表示)。须要把instrumentation.captureGPUFrameTime
属性设为true
。
instrumentation.captureShaderCompilationTime = true
.着色器计算时间计数器:CPU计算全部着色器的时间(以毫秒表示)。必须把instrumentation.captureShaderCompilationTime
属性设为true
。
Here is an example of how to use engine instrumentation: https://www.babylonjs-playground.com/#HH8T00#1 -
这是一个如何使用引擎指示器的例子:
1 var createScene = function () { 2 var scene = new BABYLON.Scene(engine); 3 var camera = new BABYLON.ArcRotateCamera("Camera", 0, 0, 10, BABYLON.Vector3.Zero(), scene); 4 var material = new BABYLON.StandardMaterial("kosh", scene); 5 var sphere1 = BABYLON.Mesh.CreateSphere("Sphere1", 32, 5, scene); 6 var light = new BABYLON.PointLight("Omni0", new BABYLON.Vector3(-17.6, 18.8, -49.9), scene); 7 8 camera.setPosition(new BABYLON.Vector3(-15, 3, 0)); 9 camera.attachControl(canvas, true); 10 11 // Sphere1 material 12 material.refractionTexture = new BABYLON.CubeTexture("/textures/TropicalSunnyDay", scene); 13 material.reflectionTexture = new BABYLON.CubeTexture("/textures/TropicalSunnyDay", scene); 14 material.diffuseColor = new BABYLON.Color3(0, 0, 0); 15 material.invertRefractionY = false; 16 material.indexOfRefraction = 0.98; 17 material.specularPower = 128; 18 sphere1.material = material; 19 20 material.refractionFresnelParameters = new BABYLON.FresnelParameters(); 21 material.refractionFresnelParameters.power = 2; 22 material.reflectionFresnelParameters = new BABYLON.FresnelParameters(); 23 material.reflectionFresnelParameters.power = 2; 24 material.reflectionFresnelParameters.leftColor = BABYLON.Color3.Black(); 25 material.reflectionFresnelParameters.rightColor = BABYLON.Color3.White(); 26 27 // Skybox 28 var skybox = BABYLON.Mesh.CreateBox("skyBox", 100.0, scene); 29 var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene); 30 skyboxMaterial.backFaceCulling = false; 31 skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("/textures/TropicalSunnyDay", scene); 32 skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE; 33 skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0); 34 skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0); 35 skyboxMaterial.disableLighting = true; 36 skybox.material = skyboxMaterial; 37 38 var colorGrading = new BABYLON.ColorGradingTexture("/textures/LateSunset.3dl", scene); 39 skyboxMaterial.cameraColorGradingTexture = colorGrading; 40 material.cameraColorGradingTexture = colorGrading; 41 skyboxMaterial.cameraColorGradingEnabled = true; 42 material.cameraColorGradingEnabled = true; 43 44 // Instrumentation初始化指示器 45 var instrumentation = new BABYLON.EngineInstrumentation(engine); 46 instrumentation.captureGPUFrameTime = true; 47 instrumentation.captureShaderCompilationTime = true; 48 49 // GUI 50 var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI"); 51 var stackPanel = new BABYLON.GUI.StackPanel(); 52 stackPanel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP; 53 stackPanel.isVertical = true; 54 advancedTexture.addControl(stackPanel); 55 56 var text1 = new BABYLON.GUI.TextBlock(); 57 text1.text = ""; 58 text1.color = "white"; 59 text1.fontSize = 16; 60 text1.height = "30px"; 61 stackPanel.addControl(text1); 62 63 var text2 = new BABYLON.GUI.TextBlock(); 64 text2.text = ""; 65 text2.color = "white"; 66 text2.fontSize = 16; 67 text2.height = "30px"; 68 stackPanel.addControl(text2); 69 70 var text3 = new BABYLON.GUI.TextBlock(); 71 text3.text = ""; 72 text3.color = "white"; 73 text3.fontSize = 16; 74 text3.height = "30px"; 75 stackPanel.addControl(text3); 76 77 var text4 = new BABYLON.GUI.TextBlock(); 78 text4.text = ""; 79 text4.color = "white"; 80 text4.fontSize = 16; 81 text4.height = "30px"; 82 stackPanel.addControl(text4); 83 84 var text5 = new BABYLON.GUI.TextBlock(); 85 text5.text = ""; 86 text5.color = "white"; 87 text5.fontSize = 16; 88 text5.height = "30px"; 89 stackPanel.addControl(text5); 90 91 var i = 0; 92 scene.registerBeforeRender(function () { 93 colorGrading.level = Math.sin(i++ / 120) * 0.5 + 0.5; 94 //读取指示器 95 text1.text = "current frame time (GPU): " + (instrumentation.gpuFrameTimeCounter.current * 0.000001).toFixed(2) + "ms"; 96 text2.text = "average frame time (GPU): " + (instrumentation.gpuFrameTimeCounter.average * 0.000001).toFixed(2) + "ms"; 97 text3.text = "total shader compilation time: " + (instrumentation.shaderCompilationTimeCounter.total).toFixed(2) + "ms"; 98 text4.text = "average shader compilation time: " + (instrumentation.shaderCompilationTimeCounter.average).toFixed(2) + "ms"; 99 text5.text = "compiler shaders count: " + instrumentation.shaderCompilationTimeCounter.count; 100 }); 101 102 return scene; 103 }
Please note that each counter is PerfCounter object which can provide multiple properties like average, total, min, max, count, etc.
请注意每一个计数器都是一个性能计数器对象,这个对象能够提供多种属性,好比平均值、合计值、最小值、最大值、计数等等。
GPU timer require a special extension (EXT_DISJOINT_TIMER_QUERY) in order to work. This extension has been disabled due to Spectre and Meltdown on all major browsers. This is still possible to use by enabling the flag gfx.webrender.debug.gpu-time-queries on firefox at the moment. This should be re-enabled soon in the browsers.
GPU计数器要起做用须要一个特殊的扩展(EXT_DISJOINT_TIMER_QUERY)。这个扩展由于幽灵与熔毁被全部的主要浏览器禁用了。但这时仍然可能经过在火狐浏览器上设置gfx.webrender.debug.gpu-time-queries标志来启用它。这一功能应该会很快在浏览器中被从新启用。
The SceneInstrumentation class allows you to get the following counters (per scene):
场景计数器类容许你获取以下的计数器(每一个场景都有):
instrumentation.captureActiveMeshesEvaluationTime = true
.活动网格评价时间计数器:评价活动网格(根据活动相机的截锥体)所花费的时间(用毫秒表示)。必须设置instrumentation.captureActiveMeshesEvaluationTime= true
.
instrumentation.captureRenderTargetsRenderTime = true
.渲染目标渲染时间计数器:渲染全部渲染目标纹理所花费的时间(毫秒)必须设置instrumentation.captureRenderTargetsRenderTime = true
.
绘制调用计数器:每一帧里调用绘制的次数(实际上是调用engine.draw的次数)。一个忠告是保持这个数字越小越好。
纹理冲突计数器:纹理不得不被移除来释放纹理槽位的次数。通常的,最新的硬件都有16个纹理槽位。Babylon.js会尝试使用全部的槽位,由于绑定一个纹理的过程是昂贵的。保持这个数字尽可能低是一个好主意。
instrumentation.captureFrameTime = true
.帧时间计数器:计算一整个帧(包括动画、物理效果、渲染目标、特殊效果等等)所花费的时间(毫秒)。必须设置instrumentation.captureFrameTime = true
.
instrumentation.captureRenderTime = true
.渲染时间计数器:渲染一帧花费的时间
instrumentation.captureInterFrameTime = true
.跨帧时间:两帧之间的时间
instrumentation.captureParticlesRenderTime = true
.粒子渲染时间:渲染粒子所花费的时间(包括粒子的动画)
instrumentation.captureSpritesRenderTime = true
.精灵渲染时间:渲染精灵所花费的时间
instrumentation.capturePhysicsTime = true
.物理效果时间:进行物理模拟所花费的时间
instrumentation.captureCameraRenderTime = true
.相机渲染时间:渲染一个相机所花费的时间
Those counters are all resetted to 0 at the beginning of each frame. Therefore it is easier to access them in the onAfterRender callback or observable.
在每一帧的开始全部这些计数器都会被重置为0.所以在onAfterRender的回调里获取它们是更容易的。
How to Use Scene Optimizer How To Optimize Your Scene With Octrees
如何使用场景优化器(是Babylon.js提供的一款自动优化工具,可以根据设置的策略自动将场景优化为设置的帧数)、如何使用八叉树(用来快速判断物体所在的区域,内置的物理引擎就使用了这种技术)优化你的场景
时间有限,并无通过充分的校对与测试,若是发现错误请在评论区指出。