gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 08:16:19 24 4
gpt4 key购买 nike

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

为了使游戏最高达到60 FPS,我需要将这些树合并成几个几何体。但是,当我这样做时,Chrome由于使用过多的内存而崩溃。

合并这些树时导致极端内存使用的原因是什么?是因为我要取消使用.clone()函数的积极性吗?

最佳答案

您需要研究实例化,您的用例正是出于此目的。

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

编辑

当合并操作对THREE.Geometry进行操作时,实际上是在消耗您的内存。原因是您必须分配大量JS对象,例如Vector3Vector2Face3等。由于原始几何不再存在,这些对象将被丢弃。这会给一切施加压力,即使您没有遇到崩溃,也可能会因垃圾回收而变慢。缓冲区几何形状更好的原因是因为它使用类型数组。对于初学者,您的所有 float 都是 float 而不是 double ,仅原始元素被复制,没有对象分配等。

不过,您正在gpu上吃更多的内存,因为您现在不将一个几何实例存储并在多个绘制调用中进行引用,而是将N个实例保存在同一缓冲区中(相同的数据仅重复并预先转换)。这就是实例化将提供帮助的地方。总之,您有:

网格(节点,对象)

描述“场景图”中的3d对象。它是某个其他节点的子节点吗,它是否有子节点,其位置(平移),其旋转方式以及缩放比例。这是THREE.Object3D类。从此扩展的是THREE.Mesh,它引用几何图形和 Material 以及其他一些属性。

几何

保存几何数据(在建模程序中实际上称为“网格”),文件格式等,因此不明确。在您的示例中,这将是单个“树”:

  • 描述叶子距根的距离或树干或分支的分割程度(顶点)
  • 叶子或树干查找纹理的位置( UVS ),
  • 对光的反应(显式法线,可选,但是实际上存在使用覆盖/修改/非常规法线渲染树叶的技术)

  • 要理解的重要一点是,这些东西存在于“对象(模型)空间”中。假设有一位建模人员对此建模,这意味着他将对象指定为“垂直”(躯干朝上说Z轴,地面被认为是XY),从而为其赋予了初始 旋转,他将根的位置很好地放在了0,0,0从而为其提供初始 转换,默认情况下,我们可以假设 Scale 部分为1,1,1。

    现在可以在建模程序或three.js中将这棵树散布在3d场景周围。假设我们将其导入Blender之类的东西。它会进入世界的中心,即0,0,0,旋转0,0,0,并且其自身的比例为1,1,1。我们意识到建模器以英寸为单位工作,而我们的世界以米为单位工作,因此我们在三个方向上按一定常数对其进行缩放。现在我们意识到这棵树在地下,因此我们将其向上移动X个单位,直到它位于地形上。但是现在它穿过一所房子,所以我们将其向侧面或三个方向移动,因为房子在山上,现在它就位于我们想要的位置。现在,我们发现该轮廓在美学上并不令人满意,因此我们将其围绕“垂直”轴旋转了N度。

    现在让我们观察发生了什么。我们尚未对树进行单个克隆,而是对其进行了缩放,移动和旋转。 我们没有以任何方式修改几何形状(添加树叶,分支,删除某些东西,更改u​​vs),它仍然是同一棵树。 如果我们说几何是它自己的实体,则我们有一个场景图节点,该节点的TRS集已设置。在three.js的上下文中,这是 THREE.Mesh(继承自 Object3D),其中具有 .rotation.scaleposition(翻译),最后是 .geometry(在您的情况下为“树”)。

    现在,我们需要为森林添加一棵新树。该树实际上将是前一棵树的精确副本,但是它位于另一个位置(T),沿Z轴(R)旋转,并且缩放比例不一致(S)。我们只需要一个具有不同TRS的新节点,将其称为 tree_02,它使用相同的几何图形,将其称为 treeGeometryOption_1。由于树的几何形状具有一组定义的UVS,因此它也具有相应的纹理。纹理进入 Material , Material 描述了属性,叶子有多光泽,树干有多暗淡,是否使用法线贴图,是否有颜色覆盖等。

    这意味着您可以使用某种 TreeMasterMaterial来设置这些属性,然后使用与几何形状相对应的 treeOptionX_material。就是如果一片叶子在某个范围内查找紫外线,那么那里的纹理应该是绿色的,并且更有光泽,那么该树干的范围就会向上查找。

    现在让我们重复整个过程。我们导入了初始树模型,并为其设置了一些比例,旋转和位置。这是一个节点,并附加了几何图形。然后,我们复制了节点的 的多个副本,这些副本均引用相同的几何TreeOption1。由于几何形状相同,因此所有这些克隆都可以具有相同的 Material treeOption1_material,该 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的矩阵),纹理(绘制不同 Material 的树时),这会产生开销。如果将它们组合在一起并减少绘图调用次数,则可以减少开销,并且webgl可以处理许多顶点的转换,因此可以以60fps的速度绘制低多边形对象数千次,但不能进行数千次绘图调用。

    这是造成3 fps结果的原因。

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

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

    完成一千次之后,需要将这数千个单独的树几何合并为一个。轻松自在,它只是在创建一个新的几何图形,并用这千个新几何图形填充其顶点,面和uvs。您可以想象这些数字很高时所涉及的开销,而JS有其局限性,GC可能很慢,并且这是很多数据,因为它是3d。

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

    现在,gpu仅持有一棵低多边形模型,而不是持有一个森林的巨型模型。内存已保存。绘制通话量。

    FPS RIP。

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

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

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

    一棵树,许多节点:
    T记忆
    N次抽奖

    一个林,单节点:
    T * N内存
    1次抽奖

    一棵树,多次实例化:
    T + N内存(有点,但主要是T)
    1次抽奖

    /编辑

    100k相当多,在处理这个问题上可能比以前好了三个,但是每当您有65536个以上的顶点时,它就会消耗fps。我不确定自己的想法,但是现在可以通过在内部将其分解为多个drawcall或webgl可以处理2 ^ 16个以上顶点的事实来解决。

    I save tons of memory by doing this, but the game runs at 3 FPS because of the 100k geometries.



    您仍然只有一个几何,还有100k个“节点”,它们都指向同一几何实例。这是100k 绘制调用的开销,这会减慢速度。

    有很多方法可以给这只猫蒙皮。

    关于javascript - Three.js中的.clone()方法如何节省内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41638745/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com