本文使用 UE 4.26 ActionRPG 示例项目,对 Bounds 进行学习和总结。
一、Bounds 是什么 / 有什么用
??任何的游戏引擎,最终呈现出在屏幕上的效果,都是经过和很多变换和计算从三维世界空间,转成屏幕像素空间才能够被玩家看到的,这个过程中,摄像机的位置,FOV,物体的位置,透明度,深度值等等信息,都能影响物体在最终屏幕上的位置和大小,如果每个物体的每个点都要计算,计算量太过庞大,所以每个物体先进行可见性剔除,对于完全不可见的物体,就可以在流程中很早地舍弃掉,减少很多计算。 ??Bounds 就是用于进行可见性剔除的,对于 Bounds 都不在摄像机的视锥体范围内的物体,直接就不画了。 ??UE 中 Bounds 全都是 AABB(Axis-Aligned Bounding Box)的,即轴平行包围盒,类型是 FBoxSphereBounds 。不管是实时计算的,还是固定(Fixed)的,都会因为旋转而变化,使得始终与世界的 XYZ 轴平行。
??上图中可以看到,蓝色的长方体线框就是实际用于计算的 AABB Bounds,黄色的球形线框是用于显示这个物体是否被剔除的,如果看不到黄色线框,物体就被剔除了。所以可以看到,在镜头转动至看不到蓝色线框的时候,黄色线框也就消失了,物体的 Mesh 也就不绘制了。关于 Bounds,最重要的就是上边这个 GIF 了(武器没有消失是因为和主角是单独的两个 Actor,用不同的 Bounds 计算剔除)。
二、Bounds 的一些设置和代码
2.1 Bounds 的显示命令
??一个 Actor 如果没有 Mesh,空的 Actor,是没有 Bounds 的,一个带有 Mesh 的默认 Character(Mesh 是空的),也是没有 bounds 的。
??可以看到在没有运行时,选中 Actor,是有蓝色框和黄色框的,但是实际运行起来是看不到的,这个因为预览时默认是有一个 Sphere 的,所以有 Bounds。 ??如果给 MeshComponent 添加一个 SkeletalMesh,比如一把剑,Bounds 就有了: ??这里也可以看出来,Bounds 和真正可见的部分,不一定是吻合的,大部分情况下 Bounds 是能完全包住可见部分的,这样就没问题(如果Bounds 太大,会导致不管在场景中怎么转摄像机,都不能剔除这个物体,增加无用计算量);但是如果 Bounds 小了,就会出现,我认为能看到的东西,转了一点点镜头突然就看不到了的情况,这个在 ( 三、Bounds 相关的一些问题)中记录。
2.2 Bounds 的设置
??主要记录 SkeletalMeshComponent 上的配置,以及 Component 上的配置(这些参数都是运行时可调的!可以很方便地看到效果!!):
1. Bounds Scale - Bounds 倍率
- 所在类:
UPrimitiveComponent - 用途:
在 CapsuleComponent 上就可以配,但是实际是不生效的,在 SkeletalMeshComponent 上设置才生效,就是字面意思,Bounds 的倍率,下图是设置成 4 的效果:
2. Use Attach Parent Bounds - 使用父节点 Bounds
- 所在类:
USceneComponent - 用途:
如果勾选了则这个 Component 的 Bounds 不自己计算,而是直接使用他 Parent 的,从注释也可以看到,这个选项如果选了,可以大幅提升一个很多个 Component 在一个 Parent 下的效率。 最常见的就是,一个主角,头、上身、胳膊、下半身、腿、分别是单独的 SkeletalMeshComponent,都挂在一个总的 Component 下,勾选和不勾选的对比如下(其中左图是所有 Mesh 都没有选择使用 Parent,右图五个子 Mesh 以及主 Mesh 都选择了使用 Parent,可以看到减少了很大的计算量,但是由于主 Mesh 是空的,所有 Bounds 大小不太对):
3. Include Component Location Into Bounds
??即: ??在 USkeletalMeshComponent::CalcBounds 中,会判断是否勾选,如果勾选了,则会根据 ComponentLocation 重新计算 Bounds:
FBoxSphereBounds NewBounds = CalcMeshBound(RootBoneOffset, bHasValidBodies, LocalToWorld);
if (bIncludeComponentLocationIntoBounds)
{
const FVector ComponentLocation = GetComponentLocation();
NewBounds = NewBounds + FBoxSphereBounds(ComponentLocation, FVector(1.0f), 1.0f);
}
??把所有 Bounds 都关了,只剩头的,下图左右分别是没有勾选 Include Component Location Into Bounds 以及勾选的效果:
4. Use Bounds from Master Pose omponent
??代码里 USkinnedMeshComponent::CalcMeshBound(...) :
else if (MasterPoseComponentInst && bCanUsePhysicsAsset && bUseBoundsFromMasterPoseComponent)
{
NewBounds = MasterPoseComponentInst->Bounds;
}
??目前没用过,没有效果演示。
5. Skip Bounds Update When Interpolating
??使用的地方,即插值就不更新了:
void USkeletalMeshComponent::FinalizeAnimationUpdate()
{
if(bSkipBoundsUpdateWhenInterpolating)
{
if(AnimEvaluationContext.bDoEvaluation)
{
InvalidateCachedBounds();
UpdateBounds();
}
}
else
{
InvalidateCachedBounds();
UpdateBounds();
}
}
6. Component Use Fixed Skel Bounds - 使用固定 Bounds
??上图左右两个 BP 位移的区别就是 Mesh 上左边勾选了 Component Use Fixed Skel Bounds ,右边没有勾,可以看到区分有两个:
- 顾名思义,使用了 Fixed Bounds,Boungs 不会根据Mesh Pose 的变化而更新,减少了不少的计算量
- 由于使用的是 Fixed,所以默认回比自动计算的大,为了把 Mesh 包住(用的就是默认 Mesh 的 Bounds)
??实际在角色 Mesh 的界面中,原始的 Bounds 是比角色大一圈的(如上边 GIF 左边的),并不贴合(可以通过左侧的 Asset Details 中 Bounds 的设置调整,Positive 就是 XYZ 正方向的延展,单位是 cm,UE 里正常的长度单位都是厘米),这个需要使用 Fixed Bounds 才生效(但是要注意这个改小了,就可能会出现 Mesh 没有被 Bounds 包住的情况)。
注意:
- 使用了 Fixed,且 Bounds 没有把 Mesh 完整包裹住(比如播了一个 Mesh 动画),就会出现文章开头那样,Mesh 在镜头转至特定角度后直接消失的情况。
7. Consider All Bodies for Bounds - 考虑所有部位
2.4 Bounds 相关的代码、计算方式
??所有 Bounds 都是 AABB 轴平行包围盒,所以像第一张图中看到的那样,计算部分代码为:
??实际用于剔除的代码详见:USkinnedMeshComponent::CalcMeshBound ,通过一次断点可以看到堆栈:
- 轴平行
??因为需要始终轴平行,所以可能看起来 Mesh 没有动,但是 Bounds 是变了的,比如默认 Place Actors 中的球(这是因为使用和局部坐标系轴平行的 Bounds 转到与世界坐标系轴平行的 Bounds):
??用 Box 看的更清楚:
三、Bounds 相关的一些问题
??由于 Bounds 直接影响物体是否可见,所以如果 Bounds 设置的有问题,很可能导致在物体应该可见的情况下,却看不到。
3.1 Bounds 过小时的问题
??Bounds 过小,可能是因为距离太远,整个 Bounds 在屏幕中就是一个点了,这时候整个物体就看不见了(这种情况,很少见,且看不见了也并没有什么问题)。 ??还有一种需要注意的,就是 Bounds XYZ 的一个值特效小的时候(比如 Z 很小,就是 Bounds 很扁),如果镜头的正方向,正好与 Bounds 的 XY 平面平行,会由于计算进行精度误差(Bounds 的长方体在屏幕中就是一根线),导致整个物体看不见了。如下图所示(使用的时候 ActionRPG 的 P_Skill_03 特效,稍微改了一下):
??出现这个问题的一个原因是,ParticleSystem 的特效,在更新 MeshRotation 的时候,Bounds 是不更新的(不知道是不是引擎的 Bug,还是设计如此),所以如果使用的 Mesh 是一个片状的形状的话(如上图),那么默认 Bounds 就是很扁的,当 Mesh 旋转成上图所示效果,Bounds 是完全包不住 Mesh 的:
??解决方案就是像 ActionRPG 一样,使用 Fixed Bounds:
- Tips:特效的 Bounds 是整个特效的设置,不是某一个发射器的,想从发射器的设置 Details 回到整个特效的设置,不需要关了重新打开,鼠标点击最右侧空白黑色处即可~~~
3.2 Bounds 过大的问题
??Bounds 大不会出现上述稍微动一下镜头瞬间看不见了的 Bug,但是会增加计算量,比如粒子特效,发射出的粒子是随机的,Bounds 的计算就会根据速度等信息自动计算,但是这样计算出来的 Bounds 可能会很大(如下图,粒子实际飞不了那么远),而且一直在变化,也会有计算量,也可以用一个适当大小的 Fixed Bounds 解决。
3.3 Mesh 没有被 Bounds 包住的情况
??在勾选了 Fixed Bounds 之后,很有可能在 Mesh 播放一些动画的时候 Mesh 从 Bounds 里出去了。就像文章一开头的 GIF 一样,出现镜头转动,Mesh 突然消失的情况。 ??这种问题也常见于头、上肢、下肢为分别不用的 Mesh Component 的时候,角色博了一个动画,同时还附带了一个镜头动画,镜头中角色头没了,或者上半身没了,或者下肢没了等,都是这个问题。
四、参考资料
|