Interfaces优先于Cast。通常,我们将Actor或组件强制转换为能够访问其方法所需的Class类型,但是强制转换不仅在性能方面比Interfaces昂贵,而且实现起来也很烦人。尝试一下Interfaces。 使用组件共享行为。很多时候我们过多地依赖继承,以便在参与者之间共享行为。继承不一定全是错误的,但是比如游戏有两种类型的施加伤害的Actor:炮塔和角色。在这种情况下,您可以创建一个名为Damager的组件,并将其添加到两个Actor中。两个Actor都能够施加伤害,而不必担心他们是谁继承的。 隐藏对象的不可自定义属性。当Actor或Components具有变量时,您可以将它们从“详细信息窗格”中隐藏。只需单击要隐藏的变量,然后查找它们公开的“Advanced Display”属性并进行检查。之后,调整该Actor或Component或任何其他对象时,您将不再在“详细信息”窗格中看到此变量。 自动对齐连线。您创建了一个很棒的蓝图,里面到处都是节点。但是由于连线,它看起来像一团糟。快速改善这种情况的一件事是选择两个连接的节点并按“ Q”。 按Q前:
按Q后:
避免将持久状态存储在组件(Component)中。这更多地与有存档的游戏有关。无论使用哪种插件,还是想出自己的保存方法/解决方案,持久存储组件的变量都可能成为负担,因为大多数时候您必须在加载游戏之前重新创建组件并重新添加保存的组件。相反,应该将持久化变量(如HP、Money等)存储在Actors中。
尽可能使用球体而不是“胶囊和Box”。您有99%的时间希望在Actor中使用碰撞,而不必将它们设为Capsule或者Box。请改用Sphere,因为球的物理计算最简单。
使用数学表达式来描述您的数学公式。你可以像在论文中描述的那样描述它们!只需右键单击“蓝图”的网格,然后键入“添加数学表达式” 然后单击显示的选项。然后,像在纸上一样写下公式,并连接点! 8. 使用标签(Tags)!UE4为游戏中几乎每个对象都提供了一个被低估的内置功能,称为“Tags”。只需转到“对象详细信息/默认值”并搜索“标签”,就会显示一个数组。单击“加号”按钮并开始对其进行标记。这将很清楚的区分所有物体属性。当然,大型项目手动输入这些几乎是噩梦,小范围使用吧。 9. 不要滥用Event Tick。我知道这是陈词滥调,但这是严肃的事情。Event Tick是大多数问题的懒惰解决方案。假设您想在角色跳跃时做点什么。与其在事件Tick中放置“ if”以检查变量“ Is Jumping”是否为真,不如在按下按钮进行跳跃时创建一个名为“ OnCharacterJumpStart”(或任何您喜欢的事件)的事件分派器。然后,在对象处对跳转做一些事情。
你可能需要SetTimerByEvent而不是Event Tick。Event Tick在CPU的每一帧上运行。如果你需要以大于0.01s的时间间隔运行某项内容,则最好使用SetTimerByEvent。
在Replicated 事件(或RPC)中传递Actor是可行的。因为没办法传递一个Actor指针到服务器,Actor通过NetUID在RPC中传输,只有4个字节。然后,使用NetPackageMap将NetUID转换为指针,然后将其传递给对象。整个Actor仅发送一次。Actor的各个属性通过该Actor的ActorChannel更新。但是其他的指针,你最好想清楚。
使用SetGlobalTimeDilation。你是否知道在玩游戏时死亡或杀死某物时一切都会慢动作如何做到的?好吧,这是您执行此操作的方法之一。
经常打包游戏。“我希望我早点知道”这件事。你在打包时可能会面临噩梦,这是构建时在开发时看不到的一大堆错误。您等待的打包时间越长,发生错误的机会就越大。因此,请经常打包,经常解决问题,并保持整洁和安全。
在蓝图里使用Ctrl + Shift + F查找所需内容。
Papalqi published on included in 渲染 从今天起,我将从主渲染函数入手,来整理整个UE4的渲染内容和框架,这将是一个非常漫长和艰巨的任务,因为其内容实在是太多,虚幻的更新速度也是超乎寻常的快,但是以后的版本应该不会有大的调整,毕竟新的框架直接用在UE5就可以了。
这里我们只是笼统的将Render函数那一个层级所出现的内容进行概况和整理,每一个概念的内部原理将会在之后的该系列文章中进行讲解。
虚化中目前的渲染基类为FSceneRenderer,其一共有两个派生类,一个是FDeferredShadingSceneRenderer,另一个是FMobileSceneRenderer。当然我们就算是在编辑器里选择为前向渲染,其本身还是会走FDeferredShadingSceneRenderer,FMobileSceneRenderer确实是移动平台才会创建的类型。我们讲讨论feature更完备的FDeferredShadingSceneRenderer。
关于延迟渲染和前向渲染这种问题到底还需不需要讨论,如果有讨论的必要请在评论区留言,否则就默认大家都会的。因为实在是没办法照顾到所有人的学习阶段,是统一天坑还是我讲我的,前期很多概念没有达成共识的话,基本上很难理解剩下的内容。
在这里我将去掉与头发、光线追踪,虚拟纹理相关内容,因为他们比较凌乱,后面如果有可能会出专题。
看代码之前我们可以使用RenderDoc来抓帧unreal,只要安装了RenderDoc并且你启用了对应的插件。之后你就会看到对应的渲染列表。
这可以帮助你快速的对应代码,图形化的帮助你理解每一步的用处。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 //更新需要添加到scene和需要删除的物体。 Scene->UpdateAllPrimitiveSceneInfos(RHICmdList, true); //抗锯齿策略,分辨率大小比例,窗口大小,屏幕百分比策略 PrepareViewRectsForRendering(); //场景中是否存在天空大气的渲染需求,进行准备工作 PrepareSunLightProxy(*Scene->GetSkyAtmosphereSceneInfo(),LightIndex, *Scene->AtmosphereLights[LightIndex]); //如果存在多GPU,计算GPUMask ComputeViewGPUMasks(RenderTargetGPUMask); //将RT池中的rt设置为可写入 GRenderTargetPool.
Papalqi published on included in 技巧 1.Paste Here(复制到这个位置) 我们打开编辑器的设置,然后可以设置这个快捷键,例如我们可以设置为左括号
之后我们在场景中复制的时候,就可以直接使用这个快捷键,将之前你Ctrl C的东西复制到你鼠标的位置。
2. 快速对齐——按住V键拖动物体顶点对齐 我们在拖动一个物体的时候,按住V键,可以显示它周围的物体的顶点并且在移动的时候吸附上去。场景摆放的时候利器!。 3. 鼠标中键移动物体中心 我们知道所有的物体中心是制作模型的时候决定的,但是我们在编辑器摆放物体的时候,希望能够边缘点对齐,我们就可以临时移动中心点。当然这个移动后中心点是临时的,不会保存。
4. 自定义项目类别 我们可以通过修改.uproject file的category来实现
5. 使用高级的文件检索方式 例如我们所有定点数小于1000的模型 6. 输入计算 7.选择log类型 8. 修改文件夹颜色 可以自定义颜色,明显的标记出自己常用的文件夹 9.修改缩略图 我们可以转动编辑器里面的缩略图,进行更改 10.Ctrl +鼠标中间+ 拖动 =快速切换视图 我们使用Ctrl +鼠标中间+ 拖动可以快速切换视图,拖动的方向决定你的视图模式。
11.测量距离 在平行投影的视图中使用鼠标中键可以测试距离,单位是厘米。 12.你可以打开4个Content 这样你就不用每次翻阅资源都跳转了,你需要提前锁住。
13.你可以打开4个Detail面板 14.你可以复制任意属性 15.你在编辑器复制的所有东西,其实都文本 所以我们是可以直接使用文本传递的。
推荐的网站https://blueprintue.com/,可以共享蓝图。
16. 你能够在color picker保存颜色 17.你能够任意拖动数组改变里面的位置 18.蓝图全搜索 使用CTRL + SHIFT + F 在蓝图编辑器中
19. 使用Ctrl 移动节点 20. 修改网络样式 1 2 3 r.Editor.NewLevelGrid 0 r.Editor.NewLevelGrid 1 r.Editor.NewLevelGrid 2 21.修改网格材质 /Engine/EditorMaterials/LevelGridMaterial2
22. r.
一.使用工具 1.1使用 HLOD https://www.youtube.com/watch?v=WhcxGbKWdbI
由于HLOD是减少[[DrawCall]]的方案,所以其实他会对渲染性能的提高能够起到一定的帮助作用。因为它是将场景中互相独立的静态Mesh通过顶点和材质合并的方式聚合在一起,本来由于材质和顶点Buffer的不同而没有办法形成一个统一的[[DrawCall]],现在可以迎刃而解。很显然那些被合并的顶点和索引buffer再也不能被复用了,而且pack到一起的纹理也是一样,它们都变成了场景中独一无二的资源,所以理论上讲HLOD是一种用空间来换取时间的方法。 所以HLOD是会增加内存使用的,如果是巨量的HLOD,不仅适得其反,会影响渲染性能的使用,cook的时候一个HLOD的文件大小是及其夸张的。压缩这些HLOD文件可能花费了数个小时的时间,它大大降低了研发迭代的效率。
将世界场景设置 中的 Enable Hierarchical LODSystem打开。 我们首先在windows 界面打开HLOD管理器 我们点击 Generate Cluster, 他会把所有level里面的空间位置临近的Mesh分组为一个Cluster。如果觉得分的不好的话是可以自己手动修改的。 然后我们点击加号增加HLOD的个数,例如我们增加两个一共三个LOD
确定好之后,点击Generate Proxy Meshs 将会分组的HLOD 数据。等待一段时间。 1.1.1HLOD调试 我们选择HLOD 的视图模式
我们由近到远就可以看到HLOD的变化。
1.1.2参数设置 距离每个cluster越近,使用的LOD的越数值越小,Mesh的数据越精确。每个LOD上面都有特定的参数。
参考: https://docs.unrealengine.com/zh-CN/BuildingWorlds/HLOD/Reference/index.html
1.2使用LOD和Merge Actor 1.2.1使用LOD 所以其实我们并不需要所有的物理都变为HLOD的,当然这个部分没有踩过坑是没有办法预知的,因此我们对于某些Mesh(待定)使用LOD应该就ok。我们的每个Mesh ,都需要设置LODs。
这里我们有了标准是可以使用脚本刷的。
相关调试
1 2 r.StaticMeshLODDistanceScale r.forceLOD 由于我们的LOD是可以使用简化材质的。
1.2.2使用Merge Actor 由于HLOD的内存问题,所以另一种方案是合并actor,因为他是生成一个新的Mesh和material,所以他并不会产生额外开销。
1.3使用 Virtual Texture 首先开启 virtual texture
然后,我们将所使用的贴图进行转换
也可以在纹理编辑器里进行转换
若未使用上述转换菜单选项,将立即导致引用转换纹理的所有现有材质失效。应打开引用违规纹理的所有材质,并将纹理取样节点设为使用正确 虚拟(Virtual) 采样器类型。例如,虚拟纹理应使用 虚拟颜色(Virtual Color) 而非 颜色(Color) 采样器类别
1.3.1调试 1 r.VT.Borders 1 1.4使用Level Streaming https://www.youtube.com/watch?v=GkDg9GPpzXE&ab_channel=UnrealEngine
1.5使用纹理流送 此处用来保证MipMap的正确性。我们构建纹理流数据:
Papalqi published on included in 动画 首先我们打开物理资产:
影响物理动画的是两个,一个是Profile里面物理动画的配置,还有一个是本身Body 上面的配置
一. Body的设置 Mass ,默认是根据你Body上的体积自动计算的,我们可以更改。最终影响的是惯性 LinearDamping:线性阻尼。控制Body的线性速度减缓强度。值越大,速度减小越快。 AngularDamping:角速度阻尼。控制Body的角速度减缓强度。值越大,速度减小越快。 (以上两个数值,若启用PhysicalAnimationProfile,一般不再设置这两个数值,Profile的值也影响该效果)
Enable Gravity:是否启用重力。关闭后不受重力影响,只受Body的惯性影响。
CollisionReponse:是否和外部物体碰撞。需要和外部物体碰撞时打开,如在人群中穿行和碰墙壁
二. Limit的设置 原则上是根据人体骨骼活动范围设置limit,并且所有动作都应在limit中运动。为了方便,可把所有limit设为free,通过Physical Animation Profile来约束,Profile无法满足的,再去设置个别骨骼的limit,如Spine。
三. Physical Animation Profile 打开Profile
选择一个已经有的prfile,这个名字对应的配置是可以在蓝图里指定的。
选择完成后,就可以看不同的刚体对应的物理参数 Is Local Simulation:是在世界空间还是在局部空间,判断移动Kinematic时是全局还是相对父Body的。目前从代码来看我们用世界空间的比较好。 Orientation Strength:用于更正方向误差的力。值越大,越靠近动画的角度 Angular Velocity Strength:用于更正角速度误差的力。值越大,速度稳定(向动画靠近)的越快。 Position Strength:用于更正线性位置误差的力。仅可用于非局部模拟。值越大,越靠近动画的位置。 Velocaty Strength:用于更正线性速度的力。仅可用于非局部模拟。值越大,速度稳定(向动画靠近)的越快。 Max Linear Force:用于更正线性误差的最大力。(设为0) Max Angular Force:用于更正角度误差的最大力。(设为0) 四.Constraint Profile 功能:局部控制。关节驱动身体靠近动画,靠近动画的姿势,而不影响Body的属性。如果靠PhysicalAnimationProfile驱动,会导致Body过于僵硬无法移动,ConstraintProfile解决了这一个问题,但运动时需要PhysicalAnimationProfile去稳定Body的位置(全局的)。
原理:设置Constraint的Motor,setDrive驱动关联的两个Body到达动画的相对Rotation。
SkeletalMeshComponent需要设置Update Joints From Animation为true.
Angular Drive Mode需要设置为Twist and Swing,Motor才会生效。
Target Orientation: 不必设置,Update Joints From Animation会取动画来设置该Orientation。
Strength:向动画靠近的强度。值越大越靠近,10000的值基本和动画一致。
Target Velocity: 设为0即可。可理解为停下的快慢。 待做:添加Update Velocity From Animation,从动画中取得目标速度。
Papalqi published on included in 物理 默认情况下,Chaos没有开启多线程,我们将两种方式对比.开启TaskGraph没有明显变化
一.基础测试 1.1 没有碎块 没有碎块的话,我们的性能开销是0.1ms
开启TaskGraph没有明显变化
1.2 100块碎块 当我们100块的时候,上升到了5ms 1.3 500块碎块 上升到了20ms
1.41000块碎块 上升到了60ms 测试案例,将一个box分为1000块。
二.修改参数 2.1修改Iterations 我们看到,绝大部分的物理性能的消耗不出所料是在计算物理位置的EvolutionAndKinematicUpdate上面。
集中在AdvanceOneTimeStep上面。在物理引擎中,我们的帧数其实要比物理引擎的帧数低很多,物理引擎将每帧分成更小的单位Step,每一步来判断是否发生了碰撞,如果发生了碰撞则会发生反弹等其他操作。
对于1000块,我们修改
当Iterations变为5时,48ms。为2时,28ms,为1时,25ms。
2.2增加cluster 我们Iterations的基础上,将1000块分组。增加Cluster的效果非常好,虽然我们破碎分为10个Cluster的话和没分的时候差不多。但是我们分为100个Cluster时 我们分为200个:
这里的分簇操作控制碎裂网格体的视觉质量和性能。破坏建筑物等对象时,级别1通常需要少量簇(或较大碎片),随着级别的增加会发生更多破坏(较小碎片)。 最初碎裂时无需数千碎片以进行优化,而是在建筑物倒下时落下单独碎片。 优化的另一选择是定义要碎裂的 最大簇级别(Max Cluster Level)
可以看出来,分Cluster对整个的破碎非常的有帮助。
合理的分簇能够极大的减少性能开销,我们进行多级的分组
帧数能稳定在70fps
结论 就算是调整到比较廉价的方式,其本身依旧还是开销巨大,对整个场景的破坏极具考验。
Papalqi published on included in 物理 本片文章深入剖析Chaos的代码结构。
一.代码结构 Chaos的核心代码存储在Source/Runtime/Experimental中
而一些之外的代码都是以插件的形式存在于引擎之中。InteractiveToolsFramework并不属于Chaos的模块。
ChaosCore封装了一些基本的数据结构,有Array,Vector,Matrix,其考虑到了没有Unreal基本数据结构支持时应当如何处理。 Voronoi是计算几何术语,在二维空间中的一种空间划分的方案,对于离散点的空间划分中,其具有每条边到点的距离是相等的特点。 GeometryCollectionEngine,实现资源GeometryCollection的声明和应用,还有Gameplay中使用的Component和Actor实现了GeometryCollectionActor\GeometryCollectionComponent\GeometryCollectionComponent等功能。其作为UE的破碎功能和Chaos的结合实体。 FieldSys,实现控制破碎过程中使用的场的功能。如力场,sleep场,kill场等等。 ChaosVehiclesEngine,使用Chaos实现的载具功能,目前处于实验状态。 ChaosSolverEngine,每个破碎功能都必须有一个解算器,如果我们不声明的话,我们会使用默认的解算器。 Chaos,所有的物理计算,进阶和高端的操作都在这个模块当中 在引擎中,physx和chaos是无法兼容进行使用的,在编译阶段就使用宏来进行了所有的隔离操作,以保证上层的使用对底层的实现无法感知。PHYSICS_INTERFACE_PHYSX和WITH_CHAOS是完全互斥的两个宏。
当然,一些核心的调度代码存在Engine/Physics/Experimental中。
主要数据结构 我们知道,我们使用所有破碎都需要通过使用UGeometryCollectionComponent来实现,而我们的UGeometryCollectionComponent对应的破碎数据则是存储在 UGeometryCollection 中,我们在编辑器中指定和配置的都是这个数据。除了UGeometryCollection外,还有一个UGeometryCollectionCache的数据,我们可以使用UGeometryCollectionCache的数据进行与烘培减少在运行时的实时计算。 FPhysScene_Chaos,作为物理世界的物理线程与主线程的桥梁,所有与物理世界的交互都在这里进行。 TPBDRigidsSolver,真正进行物理计算的数据结构,自己玩自己的,只经过FPhysScene_Chaos进行同步交流。如果仅有一个Solver的话,我们所有的物理计算都是在一个空间中和之前是没有区别的;如果我们对场景中不同的物体指定使用不同的物理解算器,不同解算器之间是没有办法进行通信的,可能这在某些情况下会有这种特殊需求。 建立物理状态 对于破碎来讲,UGeometryCollectionComponent与一般的Component一样,也是在注册时进行物理数据的注册和提交。而其他的物理和Phx一样,上层接口上是无感知的。
从UActorComponent开始,会在注册时调用CreatePhysicsState。根据是否需要生成OverlapEvent,分为两个不同的分支。 如果不需要OverlapEvent,我们获得Wolrd 的PhysicsScene的DeferPhysicsStateCreation,延迟的创建
1 WorldPrivate->GetPhysicsScene()->DeferPhysicsStateCreation(Primitive); 对于需要的,我们将调用OnCreatePhysicsState来进行。
对于每个拥有Mesh 的Component来说,必须含有物理数据,也就是UBodySetup才能够确定其物理世界的表示方案,所以只有保证有其正确的BodySetup。 之后,会加入到PhysScene的DeferredCreate PhysicsStateComponents中。对于其后续的操作则在PhysScene阶段再进行介绍。值得说明的是,我们的初始化操作其实是针对其内部的BodySetup,我们初始化完成BodySetup后,在Scene中依旧会调用OnCreatePhysicsState来生成PhysicsState。
跟渲染一样,由于都是不同线程间进行,根据Unreal 的设计原则,不同线程之间都需要一个Proxy来进行通信和代指,从而保证线程间的安全。
对于Chaos物理系统而言,拥有自己的物理代理。 所有的物理代理都是继承于TPhysicsProxy。其类型有
FSkeletalMeshPhysicsProxy,对SkeletalMesh 的物理代理 FStaticMeshPhysicsProxy,对StaticMesh 的物理代理 TGeometryCollectionPhysicsProxy,对破碎物体的物理代理 TJointConstraintProxy,对约束节点的的物理代理 等等。 每个物体的物理状态的更新OnCreatePhysicsState的时候。在OnDestroyPhysicsState的时候销毁,这两个也分别是在组件创建和销毁的时候进行的。
当我们创建完成一个PhysicsProxy,我们将其加入到PhysicsScene中来。使用FPhysScene_Chaos::AddObject来进行,物理代理和Component的对应关系将会被记录下来。但是其物理真正加入到解算器中并一定是在这里。
在UPrimitiveComponent中首先会根据UBodySetup建立BodyInstance之后调用InitBody,将自己的物理代理加入物理世界。当然我们的破碎由于不是BodyInstance能代表的,其实我们在生成TGeometryCollectionPhysicsProxy加入Scene的时候,注册到物理世界的。
普通的BodyInstance注册到解算器 对于一般的BodyInstance,不管是StaticMesh还是SkeletalMesh,都是使用BodyInstance来表示的,所以他们都是在BodyInstance的InitBody,添加到物理世界中。
调用InitDynamicProperties_AssumesLocked。
GeometryCollection注册到解算器 对于TGeometryCollectionPhysicsProxy来说,我们需要定义三个不同的函数:
1 2 3 FInitFunc InInitFunc; FCacheSyncFunc InCacheSyncFunc, FFinalSyncFunc InFinalSyncFunc 初始化函数,这里主要是处理拥有Cache的破碎,Chaos是支持预先烘培的Cache模式,如果我们指定了Cache模式,我们将会在这里进行初始化操作. 缓存同步函数,这里定义我们每帧从物理线程到底同步哪些东西,这是我们可以自行定义的部分。 最终同步函数,这里都是编辑器的内容,暂时不是很重要。 除了三个函数之外,建立Proxy还有一些数据需要我们注意
FSimulationParameters,这个数据是从Component上扒下来的 FCollisionFilterData InitialSimFilter;这是对应的物理资产里面的物理模拟的掩码 FCollisionFilterData InitialQueryFilter;这是对应的物理资产里面的查询的掩码 FGeometryDynamicCollection DynamicCollection;用来做破碎的网络同步 虽然对于所有的代理都有对应AddObject,但是对于GeometryCollection来说,我们有单独注册的操作RegisterObject。在物理线程初始化代理数据和完成注册InitializeBodiesPT
Papalqi published on included in 物理 在这里我们将跳过如何开启、编译、下载的基本步骤。并且虽然Chaos是一整套的物理引擎,本文则不对其与Phx的通用功能做出特殊说明而只针对其本身的破碎系统
资源与编辑器 GeometryCollection 我们进行破碎的第一步就是将Mesh转化为Chaos可识别的GeometryCollection资源,GeometryCollection是Chaos的物理资源。
Mesh要求 生成GeometryCollection并不是所有的Mesh都符合要求,并且为了达到更高的效率,我们依旧需要Mesh制作中符合某些特定的标准才可以。
模型需要封闭(Watertight),无交叉(Non-Intersecting)。 对于凸物体来说其分割将会更加的灵活能施加更多的控制,所以对于复炸的模型我们需要将他的Mesh拆分开,分别配置其破碎,最后通过蓝图将这些东西聚集成整体。 我们的轴向选择需要符合Unreal的Gridding Ststem的要求,这样我们控制Width和Height的时候才能更加的高效。 要考虑的另一个概念是 材质 指定,即使模块的材质相同,也应每个材质类型应用唯一的材质ID。这样在碎裂时可生成第二个材质ID,然后即可指定适当的内部材质。 在下方视频中,几何体集由两个网格体组成,而这两个网格体共享相同纹理,但使用两个不同的材质类型。因此,石料和混凝土的内面均可使用唯一材质;如果一种材质已被使用,则可将一个材质ID指定到内面。 将多个不同的Mesh整合在一个GeometryCollection中,将会使得如果有相同材质的话会共享材质并且降低DC。 多个Mesh是可以结合成为一个GeometryCollection,虽然小的物体在空间中的拜访非常灵活,但是会造成高的DrawCall,可见的接缝,并且十字交叉的重复。大型的Mesh会导致灵活性降低,但是会隐藏接缝。 创建GeometryCollection 我们使用的所有的Chaos的破碎都需要存储在Geometry Collection的资源内。 Collision Settings,
Collision Type:我们的Collision Type一共有两种,一种是Implicit-Implicit一种是Particle-Implicit。他是和Implicit Type有着密不可分的关系。使用Level Set Volumes时,应将 Collision Type设为Particle-Implicit。也可将其设为 隐式-隐式(Implicit-Implicit),但其在后台仍会检查隐式表面的碰撞粒子。 Implicit type:总共有四种,盒体、球体或胶囊体和Level Set Volumes。盒体以类似于边界框的形式包裹形体,而球体和胶囊体则放置在刚体内部来适应空间。 在模拟期间,这能加快计算速度并降低内存占用率,但会降低精度。Level Set Volumes,此类体积使用体素化网格对刚体采样,并生成几何体的有向距离场。Level Set Volumes精度更高,可调整的性能,不过内存使用更高 Min Level Set Resolution Max Level Set Resolution MinCluster Level Set Resolution MaxCluster Level Set Resolution Collision object Reduction percentage:如Box之类的碰撞,其类似于外包围盒的那种类型,一般来说包围盒都会比破碎物体本身要打,所以我们在模拟一开始包围盒之间就会交叉,调整整个参数将会降低。 破碎系统 我们将在这里来选择我们的破碎系统 碎裂方法有多种不同类型,结合不同技巧便能产生更为有趣的破坏效果。可尝试使用不同选项和设置来实现理想效果。
使用Shift-B 可以看到Bone Color,当你选择了一个GeometryCollection. 使用Shift-Q 和Shift-E可以看分割后的场景 使用Shift-S 和Shift-W切换Levels 破碎层级 分簇 我们可以将破碎划分层级。一组相邻的碎片将会组成一个Cluster,每个Cluster也可以组成一个新的Custer。
Papalqi published on included in 物理 在这里我们将跳过如何开启、编译、下载的基本步骤。并且虽然Chaos是一整套的物理引擎,本文则不对其与Phx的通用功能做出特殊说明而只针对其本身的破碎系统。源码分析可以看[[Chaos源码剖析]]。
资源与编辑器 GeometryCollection 我们进行破碎的第一步就是将Mesh转化为Chaos可识别的GeometryCollection资源,GeometryCollection是Chaos的物理资源。
Mesh要求 生成GeometryCollection并不是所有的Mesh都符合要求,并且为了达到更高的效率,我们依旧需要Mesh制作中符合某些特定的标准才可以。
模型需要封闭(Watertight),无交叉(Non-Intersecting)。 对于凸物体来说其分割将会更加的灵活能施加更多的控制,所以对于复炸的模型我们需要将他的Mesh拆分开,分别配置其破碎,最后通过蓝图将这些东西聚集成整体。 我们的轴向选择需要符合Unreal的Gridding Ststem的要求,这样我们控制Width和Height的时候才能更加的高效。 要考虑的另一个概念是 材质 指定,即使模块的材质相同,也应每个材质类型应用唯一的材质ID。这样在碎裂时可生成第二个材质ID,然后即可指定适当的内部材质。 在下方视频中,几何体集由两个网格体组成,而这两个网格体共享相同纹理,但使用两个不同的材质类型。因此,石料和混凝土的内面均可使用唯一材质;如果一种材质已被使用,则可将一个材质ID指定到内面。 将多个不同的Mesh整合在一个GeometryCollection中,将会使得如果有相同材质的话会共享材质并且降低DC。 多个Mesh是可以结合成为一个GeometryCollection,虽然小的物体在空间中的拜访非常灵活,但是会造成高的DrawCall,可见的接缝,并且十字交叉的重复。大型的Mesh会导致灵活性降低,但是会隐藏接缝。 创建GeometryCollection 我们使用的所有的Chaos的破碎都需要存储在Geometry Collection的资源内。 Collision Settings,
Collision Type:我们的Collision Type一共有两种,一种是Implicit-Implicit一种是Particle-Implicit。他是和Implicit Type有着密不可分的关系。使用Level Set Volumes时,应将 Collision Type设为Particle-Implicit。也可将其设为 隐式-隐式(Implicit-Implicit),但其在后台仍会检查隐式表面的碰撞粒子。 Implicit type:总共有四种,盒体、球体或胶囊体和Level Set Volumes。盒体以类似于边界框的形式包裹形体,而球体和胶囊体则放置在刚体内部来适应空间。 在模拟期间,这能加快计算速度并降低内存占用率,但会降低精度。Level Set Volumes,此类体积使用体素化网格对刚体采样,并生成几何体的有向距离场。Level Set Volumes精度更高,可调整的性能,不过内存使用更高 Min Level Set Resolution Max Level Set Resolution MinCluster Level Set Resolution MaxCluster Level Set Resolution Collision object Reduction percentage:如Box之类的碰撞,其类似于外包围盒的那种类型,一般来说包围盒都会比破碎物体本身要打,所以我们在模拟一开始包围盒之间就会交叉,调整整个参数将会降低。 破碎系统 我们将在这里来选择我们的破碎系统 碎裂方法有多种不同类型,结合不同技巧便能产生更为有趣的破坏效果。可尝试使用不同选项和设置来实现理想效果。
使用Shift-B 可以看到Bone Color,当你选择了一个GeometryCollection. 使用Shift-Q 和Shift-E可以看分割后的场景 使用Shift-S 和Shift-W切换Levels 破碎层级 分簇 我们可以将破碎划分层级。一组相邻的碎片将会组成一个Cluster,每个Cluster也可以组成一个新的Custer。
技术概述 本系统提供了一套通用的针对SkeletalMesh的动态切割的解决方案,包括在UE4中动态的切割模型顶点,补充侧面顶点,增加切割后的截面,增加新增顶点的骨骼权重,动态改变模型的物理碰撞体。使得切割后的模型能够继续播放动画或者进行物理模拟。
问题背景 角色拥有特殊技能能够对场景中物体进行切割,并希望对可移动的敌人骨骼物体进行切割。由于需要多次切割并且要补充切割截面所以并不能使用shader方式去处理。目前市面上的主流解决方案是将SkeletalMesh变为可动态改变的ProceduralMesh,将当前pose下的顶点信息拷贝出来,转化为对ProceduralMesh进行切割。另一种是对整个人物做预处理,事先划分好不同的切割点和模块,切割时动态选择对应的切割点,播放相应的动画。第一种方案由于转化为没有关节的ProceduralMesh,无法继续播放动画或者进行真实的物理模拟,非常生硬;第二种方案对工作流影响太大,并且不支持多次对一个物体的切割。
因此,本方案支持直接对skeletalMesh进行切割,补充动态切割面截面,增加新增顶点的骨骼权重,动态改变模型的物理碰撞体。使得切割后的模型能够继续播放动画或者进行物理模拟。
技术思路 我们主要改变SkeletalMesh的两个部分,一个是它的渲染数据,另一个是它的物理数据。
对于渲染数据的更改,我们首先会将SkeletalMesh渲染数据拿到,由于拿到的顶点处于原始姿势,我们还要将顶点数据变换到pose下的数据,然后按照顶点索引组成的三角形为单位,根据顶点在平面两侧的位置判断,将数据分为两部分。如果三角形中的三个顶点同时处于一侧,我们将直接保留整个结果,如果三角形三个顶点隶属于平面的两侧,说明整个三角形处于边界,我们将增加新的顶点,并对三角形进行重新划分。之后我们将之前存储的重组面上的顶点进行三角剖分,得到我们的截面数据。 对于物理数据的更改,与渲染数据类似,将skeletalMesh身上的物理碰撞体与平面进行检测,如果完全处于平面一侧则完全保留,如果处于相交面上,我们则对它的大小进行简单的增减。 技术实现 顶点划分与侧边补面 我们所有的判断标准都是根据顶点位置是否存在于我的切割平面一侧,如果在我需要的一侧,我们将保留,如果在另一侧我们将丢弃掉这个顶点。当然由于我们是基于三角面的渲染,所以我们判断的准测应该是三角形而不是单一的顶点。所以触发丢弃,应该是整个三角形中所有的顶点都处于丢弃的一侧。对于顶点的所处位置判断其实并不困难。困难的主要原因是我们如何丢弃不需要的顶点。我们并不想在实时的情况下去真的从一整块内存中将其中的某些元素删除,因为这会造成 大量的内存移动的操作。
目前采用的方案是将整个三角形的Index都标记为0来实现最终的渲染隐藏的操作。
我们假设点为有顶点$P=(x_1,y_1,z_1)$,平面的法线为${N}=(A,B,C)$,平面上的点为$Q=(x_0,y_0,z_0)$所以平面的公式则可写为$A(x-x_0)+B(y-y_0) +C(z-z_0) = 0$,我们可以将公式变换成$Ax+By+Cz+D=0$的形式,其中$D=-Ax_0-By_0-Cz_0$,我们将点带入公式,如果大于0说明该点与法线同侧,如果小于0,则与法线异侧。
虽然我们前面进行了简单的丢弃,但是这并不完美,因为我们目前仅处理了完全在一侧的三角面,而对相交的三角形还没有做处理。我们需要的是一个平整的切割平面,而不是一个犬牙交错的切割面。
所以我们将进行切割面的补面,对于那些有些点在保留侧有些点在丢弃侧的那些三角形。我们拿到这个三角形,对其中每个边进行比较,如果其中的某条边上两个点都在同侧,我们添加两个到对应的堆栈中;如果某条边穿过了切割平面,我们将新增一个中间顶点。
我们假设处于Normal 的方向点的集合为A,反方向为B,我们假设三角形三个顶点为$a,b,c$三个点,并且他们的顺序也是abc。
遍历三角形,找到未处理的第一个点$p$,假设顶点$p$处于集合A/B,将$p$加入集合A/B。 .对$p$点的下个顶点$p’$进行判断 如果$p$和$ p’$的集合相同则不做处理,跳转到第一步继续执行。 如果$p$和$ p’$的集合不相同,说明$p$和$ p’$间存在点$o$处于切割平面上。我们根据两点的位置方向适量,计算出新增点的位置。新增点为两侧功能拥有的点,所以我们将点分别加入A,B集合。 最后,每个三角形都划分为两个三角形,增加对应的Index。
增加截面 我们需要对切割的两部分中间进行补齐截面。我们将保存上一部分我们将采集到之前所有补面过程当中得到的新增点,由于在上一部分中,我们对于中间部分的每个三角形,都会生成两个新的点。这两个点也有机会成为最后截面的边,所以我们在记录的时候,是将两个新的点连接成为一个Edge来进行记录。这些点都处于切割平面上,所以我们可以顶点的三维数据进行投影到切割平面上,然后使用二维平面的算法去计算。
我们根据法线方向作为投影平面,将所有数据边进行投影。我们将寻找能够组成封闭多边形的集合。对于每个边,我们根据其中顶点位置,寻找最近的点作为它的临边,然后便利查找,知道回到自己的另一个顶点为止。至此,我们将得到一组多个多边形集合。 之后我们使用隔耳法进行三角剖分。将得到的顶点数据和index数据变换回3D空间。
由于需要对新增的点继续做动作,所以我们必须填充新增点和三角形的骨骼权重。对于新增顶点,我们直接取三角面中距离它最近的顶点的骨骼权重就可以了。
切割并且修改物理数据 我们将动态改变物理数据以保证物理模拟的近似准确。我们将对所有的bodyInstace进行甄别,看是否与我们的切割平面有交叉,并判断隶属于那一部分。对于交叉的物理数据,我们将根据质心与交叉面的切割距离,判断其应当属于那一部分。如果两方的差距不是很大处于中心位置,那么两边将都会保留这一物理碰撞体。
例如手臂,武器,我们如果不做特殊处理,将会导致物体悬空,我们会通过配置的方式,标记出每个skeletalMesh可能悬空的部分,我们在处理时识别到这一物理数据时,将对其骨骼的子节点物理进行解除物理约束,保证悬空部分的物理的正确。
应用/效果展示