[翻译] ogre 2.0 移植手册 - 5 实例化

5 实例化

注意!
 
本节是为Ogre 2.0而不是2.1编写的;它依赖于遗留代码。
 
关于ogre2.1;这部分几乎变得无关紧要,因为2.1可以自动自动实例mesh;并应用实例化,即使mesh使用不同的材料。
 
InstanceManager只有在具有相同网格和相同材质的非常非常多的实例(>50k对象)的情况下才能击败Hlms,这并不常见。
 
如果你正在开发Ogre 2.1;您可以跳过此部分。

5.1 什么是实例化?

实例化是一种渲染技术,只需使用一次渲染调用即可绘制同一网格的多个实例。有两种类型的实例化:

  • 软件:创建两个大顶点和索引缓冲区,网格顶点/索引复制 N 次。渲染时,不可见实例会收到一个填充有 0 的转换矩阵。此技术可能需要大量 VRAM,并且具有有限的剔除功能。

  • 硬件:硬件支持一个额外的参数,允许Ogre告诉GPU重复绘制顶点N次;因此占用的VRAM要少得多。由于 N 可以在运行时进行控制,因此在将数据发送到 GPU 之前,可以剔除单个实例。

硬件技术几乎总是优于软件技术,但软件更兼容,因为硬件技术需要D3D9或GL3,并且在GLES2中不受支持

所有实例化技术都需要着色器。无法将实例化与 FFP(固定函数流水线)一起使用

5.2 实例化 101

一个常见的问题是,为什么我应该使用实例化。最大的原因是性能。如果使用得当,可以有10倍或更多的改进。以下是有关何时应使用实例化的指南:

  1. 你有很多重复的实体,并且基于相同的网格(即岩石,建筑物,树木,松散的树叶,敌人,不相关的人群或NPC)

  2. 这些重复很多的实体也共享相同的材料(或只是少数材料,即3或4)

  3. 您的游戏是 CPU 瓶颈。

如果你的游戏都满足了这三个要求,那么很有可能是为你准备的。在重复次数很少的实体上使用实例化时,或者如果每个实例实际上具有不同的材料,或者如果实体从不重复,它的运行速度可能会更慢,则收益将微乎其微。

如果您的游戏不是 CPU 瓶颈(即是 GPU 瓶颈),则实例化不会产生明显的差异。

5.2.1 每个批次实例化

如上一节所述,实例化将所有实例分组到一个drawcall中。然而,这是一半事实。实例化实际上将一定数量的实例分组到一个批处理中。一个批次 = 一个绘制调用。

如果技术每批使用 80 个实例;那么渲染160个实例将需要2个绘制调用(两批);如果有 180 个实例,则需要 3 次绘制调用(3 个批次)。

每个批次实例设置的良好值是什么?这取决于很多因素,你必须分析这些因素。通常,增加这个数字应该会提高性能,因为系统很可能是 CPU瓶颈。但是,超过一定数量,某些权衡开始出现:

  • 首先在批处理级别执行剔除,然后对于硬件技术,剔除也在每个实例级别执行。如果批处理包含太多实例,则其 Aabb 将变得太大;因此,层次结构剔除将始终通过,ogre将无法跳过整个批次。

  • 如果每个批次的实例为 10000,并且应用程序创建了 10001 个实例;大量的RAM和VRAM将被浪费,因为它设置为20000个实例;硬件技术将花费过多的CPU时间来解析9999非活动实例;和 SW 技术将使总线带宽饱和,将非活动实例的空矩阵发送到 GPU。

实际值很大程度上取决于应用程序,以及所有实例是否经常出现在屏幕上或被剔除,以及实例总数是否可以在生产时知道(即环境属性)。通常,介于 80 和 500 之间的数字效果最好,但在某些情况下,像 5000 这样的大值实际上提高了性能.

5.3 Tchniques

Ogre 支持 4 种不同的实例化技术。不幸的是,它们中的每一个都需要不同的顶点着色器,因为它们的方法不同。此外,它们的兼容性和性能也各不相同。

5.3.1 基于Shader

这是最兼容的技术。它是一种软件实例化技术。世界矩阵通过常量寄存器传递,因此每个批次的最大实例数为 80;如果对象是骨架动画,它很快就会下降。因此,这种技术不能很好地与骨骼动画配合使用,除非骨骼数量非常低(3或更少)。

有关如何编写顶点着色器的示例,请参阅材质示例/实例化/着色器基础。文件:

  • ShaderInstancing.material

  • ShaderInstancing.vert (GLSL)

  • ShaderInstancing.cg (Cg, works with HLSL)

5.3.2VTF (软件)

VTF 代表"顶点纹理提取"。它是一种软件实例化技术。与基于着色器的不同,世界矩阵通过纹理传递到顶点着色器。自Vertex Shader 3.0以来,此类功能仅受支持,并且在Radeon X1xxx卡上不受支持,并且在GeForce 6和7上非常慢。 然而,它在任何现代GPU上都非常快(GeForce 8,9,200,300,400,500,600,700; 所有Radeon HD系列,Intel HD 3000及更高版本)

与ShaderBased相比,VTF的优势在于它支持每个批处理的最大实例数非常高;即使它是骨架动画。

请注意,您需要设置一个texture_unit(为了兼容起见,最好是第一个),包括纹理以外的阴影投射器(例如漫反射,镜面反射,法线贴图),以便Ogre获得放置顶点纹理的位置。

有关如何编写顶点着色器和设置材质的示例,请参阅材质示例/实例化/VTF。 文件:

  • VTFInstancing.material

  • VTFInstancing.vert (GLSL)

  • VTFInstancing.cg(Cg,HLSL)

5.3.3 硬件VTF

这与VTF的技术相同;但通过硬件实例化实现。它可能是最好和最灵活的技术之一。

顶点着色器必须与 SW VTF 版本略有不同。有关如何编写顶点着色器和设置材质的示例,请参阅material Examples/Instancing/HW_VTF文件:

  • HW_VTFInstancing.material

  • HW_VTFInstancing.vert (GLSL)

  • HW_VTFInstancing.cg (Cg,HLSL)

5.3.3.1 硬件 VTF LUT

LUT是硬件VTF的一个特殊功能;这代表Look Up Table。它是专门为吸引大量动画人群而设计的。

该技术的工作原理是:对有限数量的实例(即 16 个动画)进行动画处理,将它们存储在 VTF 的查找表中,然后将这些动画统一地重复到所有实例,从而在大型人群中看到所有实例时都独立动画化。

请参阅材料 Examples/Instancing/HW_VTF_LUT.文件:

  • 与硬件 VTF 相同(定义了不同的宏)

若要启用 LUT,SceneManager::createInstanceManager 的标志必须包含标志IM_VTFBONEMATRIXLOOKUP并将 HW VTF 指定为technique。

mSceneMgr->createInstanceManager( "InstanceMgr", "MyMesh.mesh",
			ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, 
			InstanceManager::HWInstancingVTF,
			numInstancesPerBatch, IM_USEALL|IM_VTFBONEMATRIXLOOKUP );

5.3.4 基于硬件

HW Basic 可能是最快的实例化技术1,但肯定比 HW VTF 更兼容。

世界矩阵数据使用三个TEXCOORDs(GLSL 术语中的属性) 而不是顶点纹理传递到顶点着色器。除了数据传递方式之外,与 HW VTF 的另一大区别是 HW Basic 根本不支持骨架动画,使其成为渲染树木、落叶、建筑物等无生命对象的首选。

有关示例,请参阅材料 Examples/Instancing/HWBasic 文件:

  • HWInstancing.material

  • HWBasicInstancing.vert (GLSL)

  • HWBasicInstancing.cg(Cg,与HLSL一起)

5.4 自定义参数

某些实例化技术允许将自定义参数传递到顶点着色器。例如,RTS游戏中的自定义颜色用于识别玩家单位;用于随机着色植被的单个值,用于渲染延迟着色的光量的光参数(漫反射色,镜面色等)

在撰写本文时,只有 HW Basic 支持传递自定义参数。所有其他技术都将忽略它2

要使用自定义参数,请调用 InstanceManager::setNumCustomParams 来告知用户将需要的自定义参数的数量。创建第一个批次后无法更改此数量(调用 createInstancedEntity)

之后,只需使用要发送的参数调用 InstancedEntity::setCustomParam 即可。

对于 HW Basic 技术,顶点着色器将在额外的TEXCOORD中接收自定义参数。

InstanceManager *instanceMgr; //Assumed to be valid ptr
instanceMgr->setNumCustomParams( 2 );

InstancedEntity *instancedEntity = instanceMgr->createInstancedEntity( "myMaterial" );
instancedEntity->setCustomParam( 0, Vector4( 1.0f, 1.0f, 1.2f, 0.0f ) );
instancedEntity->setCustomParam( 1, Vector4( 0.2f, 0.0f, 0.7f, 1.0f ) );

5.5支持多个submesh

多个submesh意味着不同的实例管理器,因为实例化只能应用于同一submesh。

但是,实际上很容易支持多个submesh。第一步是创建 InstanceManager, 将subMeshIdx参数设置为要使用的submesh数:

std::vector<InstanceManager*> instanceManagers;
MeshPtr mesh = MeshManager::getSingleton().load( "myMesh.mesh" );
for( uint16 i=0; i<mesh->getNumSubMeshes(); ++i )
{
	InstanceManager *mgr =
		mSceneMgr->createInstanceManager( "MyManager" + StringConverter::toString( i ),
					"myMesh.mesh",
				ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME,
					InstanceManager::HWInstancingVTF, numInstancePerBatch,
					flags, i );
	instanceManagers.push_back( mgr );
}

第二步是与其中一个子对象(将命名为"master";即第一个submesh)共享transfrom,以提高性能并减少创建实例化实体时的 RAM 消耗:

SceneNode *sceneNode; //Asumed to be valid ptr
std::vector<InstancedEntity*> instancedEntities;
for( size_t i=0; i<instanceManagers.size(); ++i )
{
	InstancedEntity *ent = instanceManagers[i]->createInstancedEntity( "MyMaterial" );
	
	if( i != 0 )
		instancedEntities[0]->shareTransformWith( ent );
	
	sceneNode->attachObject( ent );
	instancedEntities.push_back( ent );
}

请注意,每个基于不同"submesh"的 InstancedEntity 完全有可能使用不同的材质。选择相同的材质不会导致实例管理器一起批处理(尽管 RenderQueue 将尝试减少状态更改,就像任何普通实体一样)。

由于transform是共享的,因此对主实例实例实例(在此示例中为 instancedEntity[0])进行动画处理将导致所有其他从属实例遵循相同的动画。

要销毁实例化实体,请使用该正常过程:

SceneNode *sceneNode; //Asumed to be valid ptr
std::vector<InstancedEntity*> instancedEntities;
for( size_t i=0; i<instanceManagers.size(); ++i )
{
	instanceManagers[i]->destroyInstancedEntity( instancedEntities[i] );
}
mSceneMgr->getRootSceneNode()->removeAndDestroyChild( sceneNode );

5.6 对批处理进行碎片整理

5.6.1什么是批处理碎片?

有两种类型的碎片:

  1. "删除"碎片是指创建了多个实例,跨越多个批次;其中许多后来被删除,但它们都来自不同的批次。如果每批有 10 个实例,则创建了 100 个实例,然后删除了 90 个实例;现在可能有 10 个批处理,每个批处理有一个实例(等于 10 个绘制调用);而不是只有 1 个批处理和 10 个实例(等于 1 个绘制调用)。

  2. “剔除”碎片也是指不同批次的多个实例都分散在整个场景中。如果对它们进行碎片整理,则它们将放在同一批处理中(按彼此的邻近程度排序的所有实例应在同一批中),以利用分层剔除优化。

对批处理进行碎片整理可以显著提高性能:
假设每个批次有50个实例,总共有100个批次(这意味着使用相同材质的同一网格的5000个实例实体),并且它们都一直在移动。

通常,ogre首先更新所有实例的位置,然后更新它们的AABB;同时,为包含其所有实例的每个批计算AABB。

当平截体剔除时,我们首先剔除批,然后剔除它们的实例3(在那些剔除的批中)。这是典型的分层筛选优化。然后,我们将实例转换上传到GPU。

在世界移动了许多实例之后,它们将使批次的封闭aabb越来越大。最终,每一批的aabb都将如此之大,以至于无论相机看起来在哪里,所有100批最终都将通过截锥剔除测试;因此,必须逐个剔除所有5000个实例。

5.6.2 预防:避免碎片化

如果要创建不会移动的静态对象(即树),请按邻近度创建它们。这有助于两种类型的碎片:

  1. 卸载区域(即开放世界游戏)时,这些对象将一起删除,因此整个批次将不再具有活动实例。

  2. 批处理和实例通常按创建顺序进行分配。这些实例将属于同一批次,从而最大限度地提高剔除效率。

5.6.3解决:动态碎片整理

在某些情况下,可以防止碎片化,例如RTS游戏中的单位。根据设计,所有单位最终都可能分散开来,并在数小时的游戏后从场景的一个极端移动到另一个极端;此外,许多单位可能处于创建和销毁的无限循环中,但是如果某种类型的单位的循环被破坏;最终也有可能出现"删除"碎片。

因此,函数 InstanceManager::defragmentBatches(bool optimizeCulling) 存在。

使用它就像调用函数一样简单。示例 NewInstancing 演示如何以交互方式执行此操作。当 optimizeCulling 为 true 时,将尝试修复这两种类型的碎片。如果为 false,则仅修复"删除"类型的碎片。

请记住,当 optimizeCulling = true 时,根据碎片级别,它需要更多的时间,并可能导致帧速率峰值,甚至失速。谨慎地执行此操作,并分析最佳调用频率。

5.7 故障排除

问:我的mesh未显示。

答:请确认您使用的材质正确,顶点着色器设置正确,并且与所使用的实例化技术匹配。

问:我的动画播放方式与实体或在 Ogre Meshy 中预览时完全不同

答:您的绑定动画必须为每个骨骼使用多个权重。您需要在顶点着色器中添加对它的支持,并确保没有创建带有IM_USEONEWEIGHT或IM_FORCEONEWEIGHT标志的实例管理器。

例如,要修改 HW VTF 顶点着色器,您需要从 VTF 中对其他矩阵进行采样:

float2 uv0		:	TEXCOORD0;
// Up to four weights per vertex. Don't use this shader on a model with 3 weights per vertex, or 2 or 1
float4 m03_0	:	TEXCOORD1; //m03.w is always 0
float4 m03_1	:	TEXCOORD2;
float4 m03_2	:	TEXCOORD3;
float4 m03_3	:	TEXCOORD4;
float4 mWeights	:	TEXCOORD5;

float2 mOffset	:	TEXCOORD6;


float3x4 worldMatrix[4];
worldMatrix[0][0] = tex2D( matrixTexture, m03_0.xw + mOffset );
worldMatrix[0][1] = tex2D( matrixTexture, m03_0.yw + mOffset );
worldMatrix[0][2] = tex2D( matrixTexture, m03_0.zw + mOffset );

worldMatrix[1][0] = tex2D( matrixTexture, m03_1.xw + mOffset );
worldMatrix[1][1] = tex2D( matrixTexture, m03_1.yw + mOffset );
worldMatrix[1][2] = tex2D( matrixTexture, m03_1.zw + mOffset );

worldMatrix[2][0] = tex2D( matrixTexture, m03_2.xw + mOffset );
worldMatrix[2][1] = tex2D( matrixTexture, m03_2.yw + mOffset );
worldMatrix[2][2] = tex2D( matrixTexture, m03_2.zw + mOffset );

worldMatrix[3][0] = tex2D( matrixTexture, m03_3.xw + mOffset );
worldMatrix[3][1] = tex2D( matrixTexture, m03_3.yw + mOffset );
worldMatrix[3][2] = tex2D( matrixTexture, m03_3.zw + mOffset );

float4 worldPos = float4( mul( worldMatrix[0], inPos ).xyz, 1.0f ) * mWeights.x;
worldPos += float4( mul( worldMatrix[1], inPos ).xyz, 1.0f ) * mWeights.y;
worldPos += float4( mul( worldMatrix[2], inPos ).xyz, 1.0f ) * mWeights.z;
worldPos += float4( mul( worldMatrix[3], inPos ).xyz, 1.0f ) * mWeights.w;

float4 worldNor = float4( mul( worldMatrix[0], inNor ).xyz, 1.0f ) * mWeights.x;
worldNor += float4( mul( worldMatrix[1], inNor ).xyz, 1.0f ) * mWeights.y;
worldNor += float4( mul( worldMatrix[2], inNor ).xyz, 1.0f ) * mWeights.z;
worldNor += float4( mul( worldMatrix[3], inNor ).xyz, 1.0f ) * mWeights.w;

如您所见,每个顶点具有 4 个权重的 HW VTF 顶点着色器需要大量的纹理提取。幸运的是,它们非常适合纹理缓存。尽管如此,这是需要不断注意的事情。

实例化用于渲染场景中的大量对象。如果您计划渲染数千或数万个动画对象,每个顶点有4个权重,请不要指望它很快;无论你用什么技术来绘制它们。

尝试说服美术部门降低动画质量,或者只是使用IM_FORCEONEWEIGHT让Ogre为您降级。有许多用于流行建模包(3DS Max,Maya,Blender)的插件可以帮助自动执行此任务。

问:实例未显示,或者在播放动画时网格变形非常奇怪,或者出现其他非常明显的伪影

答:您的装备在每个顶点使用多个权重。使用标志创建实例管理器IM_FORCEONEWEIGHT,或修改顶点着色器以支持每个顶点所需的确切权重量(请参阅前面的问题)。

问:如何查找每个顶点在使用我的模型的权重数?

答:最快的方法是查看VES_BLEND_WEIGHTS的大小并将其除以44
在这里插入图片描述
在上图中,Ogre Meshy 查看器用于快速显示网格信息。可以看出,头发每个顶点使用1个权重,而头部每个顶点需要2个权重。


  1. 它是否真的比硬件VTF快取决于GPU体系结构 ↩︎

  2. 理论上,所有其他技术都可以实现自定义参数,但出于性能原因,只有硬件VTF非常适合实现自定义参数。我们还需要考虑是否应该通过VTF或其他TEXCOORDs将其传递给着色器。 ↩︎

  3. 只有硬件实例化技术醍醐每个实例。软件实例化技术发送其所有实例,将不在场景中的那些实例的矩阵归零。 ↩︎

  4. 一个权重等于一个float。一个浮点数是4字节;因此,权重数*4是顶点元素的大小 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值