当前位置:  开发笔记 > 编程语言 > 正文

Three.js中的.clone()方法如何节省内存?

如何解决《Three.js中的.clone()方法如何节省内存?》经验,为你挑选了1个好方法。

对于我正在写的游戏,我正在添加100,000棵树,每棵树都是合并的几何体.当我使用克隆模型添加这些时tree.clone(),我通过这样做可以节省大量内存,但由于100k几何形状,游戏以3 FPS运行.

为了使游戏达到60 FPS,我需要将这些树合并为几个几何总体.但是,当我这样做时,由于使用了太多内存,chrome崩溃了.

合并这些树时极端内存使用的原因是什么?是因为我正在撤消使用该.clone()功能的积极因素吗?



1> pailhead..:

您需要查看实例化,您的用例正是为此而制作的.

或者,使用BufferGeometry而不是常规几何,应该减少内存密集.

编辑

实际占用你的记忆的是合并操作的开销THREE.Geometry.这样做的原因是,你必须分配一吨JS对象,如Vector3,Vector2,Face3,等自原始几何体不存在了,然后被丢弃.这会强调一切,即使你没有遇到崩溃,你也会遇到从垃圾收集中慢下来的问题.缓冲区几何体更好用的原因是因为它使用的是类型化数组.对于初学者来说,所有浮动都是不是双打的浮点数,只复制基元,没有对象分配等.

你在gpu上占用更多的内存,因为你不是存储一个几何实例并在多个绘制调用中引用它,你现在在同一个缓冲区中保存N个实例(相同的数据只是重复,并进行预转换).这是实例化将有所帮助的地方.总之,你有:

网格(节点,对象)

描述"场景图"中的3d对象.它是否是某个其他节点的子节点,它是否有子节点,它的位置(平移),如何旋转,以及它是否缩放.这是THREE.Object3D类.从那里扩展的是THREE.Mesh,它引用了几何和材料,以及一些其他属性.

几何

保存几何数据(在建模程序中实际上称为"网格"),文件格式等因此模糊.在您的示例中,这将是一个"树":

描述叶子距离根有多远或者如何分割树干或树枝(顶点)

叶子或树干在哪里查找纹理(UVS),

它如何对光做出反应(显式法线,可选,但实际上有渲染叶子的方法有覆盖/修改/非常规法线)

重要的是要理解这个东西存在于"对象(模型)空间"中.假设建模者对此建模,这意味着他将对象指定为"垂直"(树干向上说Z轴,地面被认为是XY)从而给它初始旋转,他将根很好地置于0, 0,0因此给它初始翻译,默认我们可以假设比例部分是1,1,1.

这个树现在可以分散在3d场景中,无论是在建模程序中还是三个.js.让我们说我们将它导入像Blender这样的东西.它将位于世界的中心,0,0,0,旋转0,0,0,自身的比例为1,1,1.我们意识到建模者以英寸为单位工作,而我们的世界以米为单位,因此我们将它在所有三个方向上按一定的常数进行缩放.现在我们意识到树在地下,所以我们将它向上移动X个单位,直到它位于地形上.但是现在它经过一个房子,所以我们将它移到侧面,或者可能在所有三个方向,因为房子在山上,现在它就在我们想要的地方.我们现在观察到轮廓在美学上并不令人愉悦,因此我们围绕"垂直"轴旋转N度.

我们现在观察发生了什么.我们没有对树进行单一克隆,我们缩放它,移动它并旋转它.我们没有以任何方式修改几何体(添加树叶,树枝,删除内容,更改uv),它仍然是同一棵树.如果我们说几何是它自己的实体,我们有一个场景图节点,其TRS集.在three.js的上下文中,这是THREE.Mesh(继承自Object3D),有.rotation(.scale,position翻译)和最后.geometry(在你的情况下是"树").

现在让我们说我们需要一个新的森林树.该树实际上将是前一棵树的精确副本,但位于不同的位置(T),沿Z轴(R)旋转,并且非均匀地缩放(S).我们只需要一个具有不同TRS的新节点,让我们调用它tree_02,它使用相同的几何体,让我们调用它treeGeometryOption_1.由于树几何体具有一组定义的UVS,因此它也具有相应的纹理.纹理进入材质,材质描述属性,叶子有多光泽,树干有多沉闷,是否使用法线贴图,是否有颜色叠加等.

这意味着您可以使用某种TreeMasterMaterial设置这些属性,然后treeOptionX_material对应于地理测量.IE浏览器.如果叶片在某个范围内查找紫外线,那么纹理应该是绿色,并且更加有光泽,然后是树干查找的范围.

现在让我们重申整个过程.我们导入了初始树模型,并给它一些比例,旋转和位置.这是一个节点,附有几何图形.然后,我们制作了该节点的多个副本,这些副本都指向相同的几何体TreeOption1.由于几何体是相同的,所有这些克隆可以具有相同的材料treeOption1_material,该材料具有自己的纹理集.

这对于为什么克隆代码如下所示是一个超长的解释:

return 
  new this.constructor( //a new instance of Mesh
    this.geometry ,     //with the sources geometry (reference)
    this.material       //with the sources material (reference)
  ).copy(this)          //utility to copy other properties per class (Points, Mesh...)

另一个答案是误导,使它听起来像:

return 
  new this.constructor( //a new instance of Mesh
    this.geometry.clone() ,  //this would be devastating for memory
    this.material.clone()    //this is actually sort of common to be done, but it would be done after the clone operation
  ).copy(this)          

假设我们想要将树木染成不同的颜色,例如3.

var materialOptions = [];

colorOptions.forEach( color =>{

 var mOption = masterTreeMaterial.clone()
 //var mOption = myImportedTreeMesh.material.clone(); //lets say youve loaded the mesh with a material 

 mOption.color.copy( color ); //generate three copies of the material with different color tints

 materialOptions.push( mOption );
});

scatterTrees( myImportedTreeMesh , materialOptions ); 

//where you would have something like
var newTree = myImportedTreeMesh.clone(); //newTree has the same geometry, same material - the master one
newTree.material = someMaterialOption; //assign it a different material

//set the node TRS
newTree.position.copy( somePosition );
newTree.rotation.copy( someRotation );
newTree.scale.copy( someScale );

现在发生的是,这会产生许多绘制调用.对于要绘制的每个树,需要发送一组低级指令来设置制服(TRS的矩阵),纹理(当绘制具有不同材料的树时)并且这产生开销.如果这些被组合并且绘制调用数减少,则开销减少,并且webgl可以处理变换许多顶点,因此可以以60fps数千次绘制低多边形对象,但是没有数千个绘制调用.

这是你的3 fps结果的原因.

除了花哨的优化之外,蛮力方式是将多个树合并为单个对象.如果我们将多个THREE.Mesh节点合并为一个,我们只有一个TRS的空间.我们如何处理散落在地形上的数千棵树木,它们的TRS发生了什么?它们被烘焙成几何形状.

首先,每个节点现在都需要克隆几何体,因为几何体将被修改.第一步是将每个顶点乘以该节点的TRS矩阵.现在这是一棵树,它不是0,0,0,不再是英寸,而是相对于地形位于XYZ的某个地方,以米为单位.

在完成这一千次之后,需要将这千个单独的树几何合并为一个.它很简单,它只是制作一个新的几何体,并用这千个新几何体填充它的顶点,面和uv.您可以想象当这些数字很高时涉及的开销,JS有其局限性,GC可能很慢,而且这是很多数据,因为它是3d.

这应该回答标题中的问题.如果我们反过来,我们可以说你有一个消耗大量内存的游戏(通过一个模型 - 几何,一个"森林"),但运行速度为60fps.您使用克隆方法来节省内存,方法是将林分成单独的树,在每个根中提取TRS,并为每个节点使用单个树引用.

现在gpu只拥有一棵树的低多边形模型,而不是一个巨大的森林模型.记忆得救了.画出呼叫丰度.

FPS RIP.

两者如何减少绘制调用的数量,同时渲染树的模型,而不是森林?

通过使用称为实例化的功能.Webgl允许发出特殊的绘图调用.它使用属性缓冲区同时设置多个TRS信息.10000个节点将具有10000 TRS矩阵的缓冲区,这是16个浮点数,描述单个三角形没有uv或法线需要9,所以你可以看到它的去向.您在gpu上保存了一个树几何实例,而不是设置数千个绘制调用,而是设置一个包含所有这些数据的数据.如果对象是静态的,则开销很小,因为您一次设置了两个缓冲区.

Three.js用THREE.InstancedBufferGeometry(或类似的东西)很好地抽象了这个.

一棵树,很多节点:T内存N绘制调用

一个森林,单个节点:T*N内存1绘制调用

一棵树,多次实例化:T + N内存(有点,但它主要只是T)1个绘制调用

/编辑

100k是相当多的,三个可能比以前更好地处理它,但它曾用于在你有超过65536个顶点时使用你的fps.我不确定是不是最重要的,但它现在通过在内部将其分解为多个drawcalls或者webgl可以处理超过2 ^ 16个顶点的事实来解决.

通过这样做我节省了大量内存,但由于100k几何形状,游戏以3 FPS运行.

您仍然有一个几何体,您有100k"节点",它们都指向相同的几何实例.这是100k 绘制调用的开销正在减慢这种速度.

有很多方法可以给这只猫上皮.

推荐阅读
女女的家_747
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有