/images/avatar.png

Papalqi

Chaos破碎性能分析

默认情况下,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 结论 就算是调整到比较廉价的方式,其本身依旧还是开销巨大,对整个场景的破坏极具考验。

Chaos代码结构剖析

本片文章深入剖析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

Chaos使用文档

在这里我们将跳过如何开启、编译、下载的基本步骤。并且虽然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。

UE4 SkeletalMesh的实时动态切割

技术概述 本系统提供了一套通用的针对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可能悬空的部分,我们在处理时识别到这一物理数据时,将对其骨骼的子节点物理进行解除物理约束,保证悬空部分的物理的正确。 应用/效果展示

UE4 物理系统实现

虚幻引擎4使用 PhysX 3.3 物理引擎来模拟物理效果。所有物理运动(坠落或受力的物理形体)以及碰撞(物理形体的相互作用)都由 PhysX 管理。 一.Physx 1.1Physx简介 UE4.21前的版本采用的是NVIDIA的PhysX做为其默认的物理引擎,用于计算3D世界的碰撞查询与物理模拟。自4.21版本开始重构了代码调用,兼容使用Chaos物理系统,4.26才会实装,如要使用的话是需要自己构建的。 由于Epic 和NVIDIA的PY交易。Epic为UE4开发者们提供PhysX 3.3.3的基于CPU实现的二进制代码访问权,而且还包括其C++源代码访问权,以及布料库和可破坏物体库。 现在除了可以获得虚幻引擎4的完整C++源代码外,还可以查看和修改此PhysX代码。 参见:https://ue4community.wiki/legacy/physx-integrating-physx-code-into-your-project-ryzw4tj3 Physx文档:https://gameworksdocs.nvidia.com/PhysX/3.3/PhysXGuide/Index.html 1.1.1使用debug 模式检测物理 UE4 使用NVIDIA 的PhysX 3.3物理引擎来模拟物理效果,使用 APEX 模拟 destruction 和 clothing。对于PhyX,它对于UE4来说就是一个提供输入然后获取输出的黑 盒,不过可以通过NVIDIA提供的PhysX Visual Debugger(PVD)来进行可视化调试在编辑器运行游戏 输入pvd connect我们就可以得到实时的物理调试结果。 整个debug软件是比较重的,功能众多,如果大家在项目中遇到了一下物理上的性能问题,强烈建议可以打开pvd看下场景里的动态rigidbody情况,碰撞穿插解算的复杂度,一般严重的性能问题都是在这方面的。 1.1.2基本数据结构 PxPhysics:用来创建Physx各种实用组件,像场景,Shape,Rigidbody,布料,粒子系统等。 PxScene:物理场景,是碰撞盒组件的模拟环境,它的创建是根据PxSceneDesc属性来的。一个大型系统可以有多个场景,不同场景的组件相互不影响,比如UE4,使用了2个场景,用来模拟同步和异步的物理模拟功能。 PxSceneDesc:有如下属性: gravity 重力大小和方向, PxSimulationEventCallback 模拟事件回调, PxContactModifyCallback 碰撞解算修改回调, PxCCDContactModifyCallback CCD的碰撞解算修改回调, filterShader 全局的碰撞分类处理函数, cpuDispatcher Cpu线程分配器 PxCooking:碰撞盒得有形状,我们知道物理引擎里碰撞盒不仅仅是只有胶囊体,长方形,球形,还有凸包体,复杂的静态地表碰撞盒,三角形面片构成的碰撞盒。对于比较复杂的碰撞盒,Phyx支持用PxCooking类接受面片数据,然后create出可以使用的碰撞mesh。 Rigidbody: 它首先是Actor,作为Scene的基础实体,然后可以分成静态的和动态的,静态的如房子,各种场景部件,有自带的预处理功能,处理碰撞等运算性能高很多,动态的像人,车,可以运动,但性能会差一些,动态Rigidbody里用的比较多的是PxRigidDynamic,而Articulation是专门给类似布娃娃这样的系统设计的。 PxShape:SimulationFilterData 这个是做碰撞模拟类别划分的,这边设定好SimulationFilterData,在场景下的filterShader进行区分,可以控制哪些碰撞shape之间是可以相互进行碰撞计算的。 PxMaterial:DynamicFriction,StaticFriction动摩擦,静摩擦,Restitution弹性系数,FrictionCombineMode 摩擦力计算的方式,比如两个物体碰撞了,摩擦力双方不同,可以选择取最小的,最大的,或者平均一下,RestitutionCombineMode,弹力计算模式,摩擦力的一样。 1.1.3几何体 UE4 里面使用的几何体完全匹配于Physx的类型。 球 胶囊体 box 平面 凸包 三角形Mesh 需要注意的问题是,凸包的在Physx里面限定的个数是255. 1.1.3.1Cooking 对于凸包和三角Mesh 的来说,将会发生Cooking。这是PhysX提供的一种转换、序列化批量数据的一种方式。这是一种对于凸包和Mesh 的预处理。Physx需要将你提供的凸包和三角形变成他们自己的结构去加速他们自己的计算。 在UE4中,有复杂碰撞和简单碰撞的区别,复杂碰撞仅仅指的是将整个的Mesh传入的形式,而凸包依旧属于简单碰撞。

UE4RenderingDependencyGraph

什么是RDG 渲染依赖性图表(Rendering Dependency Graph) 也被称为RDG或渲染图表,是一个基于图表的调度系统,用于执行渲染管线的整帧优化。它利用DirectX12这样的现代API,使用自动异步计算调度以及更高效的内存管理和屏障管理来提高性能。 RDG的概念如下:在GPU上并非立即执行通道,而是延迟到整个帧已记录到依赖性图表数据结构之中后再执行。当完成了对所有通道的收集之后,则按照依赖性的排序顺序对图表进行编译和执行。这主要是因为DirectX 12、Vulkan和Metal2之类的现代图形API选择将低级GPU管理的负担转移至应用程序本身。这提供了一个机会,可以利用渲染管线的高级情境来驱动调度,从而提高性能并且简化渲染堆栈。 我们今天的RDG有什么好处,就要看之前的方式有多菜。来看一下传统的下图分别是07年和17年的渲染系统的复杂程度 可以参考: http://www.frostbite.com/2007/04/frostbite-rendering-architecture-and-real-time-procedural-shading-texturing-techniques 为什么需要RDG 这里当然不是非常的具体,不过我们都能看出这里非常的复杂。 当然,我们可以进行一些简单的简化,如下图 战地四的pass数(Feature) 面临的挑战: 显示的代码驱动的即时渲染模式 显示的资源管理 手动的ESRAM管理 每一个不同的游戏团队有着不同的实现方式 不同的渲染系统有紧密的耦合性 对于管线定制的扩展性差 游戏团队必须对这些定制fork/diverge 代码数飞速增长,将产生很多重复性的代码 所以,我们需要将整个的渲染结构进行一个更高层次的抽象,让其拥有更高的扩展性,模块化,更好的可视化和debug,资源的自动管理,解耦模块并使其可自由组合。 这就是新的结构: RDG设计理念 我们将抛弃即时的渲染模式;将渲染代码分离到pass中,大致分为三个阶段 1.Setup phase 2.Compile phase 3.Execute phase Setup phase 我们将定义这个pass是一个render pass, 还是compute pass我们将定义每一个resource,对于输入和输出对于renderpass ,我们要定义资源的使用其是read,write,还是create。当然对于长期贮存的资源我们是要imported进RDG中,例如History buffer for TAA,Backbuffer Compile phase 剔除没有使用的资源和pass 计算资源的生命周期 根据使用情况分配实际的GPU资源 Execute phase 对于每个pass执行回调函数 这个阶段就是跟我们的之前的渲染流程一样的执行draw,dispatch Unreal RDG 实例 由于unreal4.23后,RDG才真正的加入,目前的资料还是比较的稀少,所以我们需要学习如何使用,最好的方式就是仿造引擎中一些已经使用到的地方。 FTAAShaderParameters,FSSDSignalTextures,FDOFGatherInputTextures,FDeferredLightUniformStruct Screen pass pixel shader: ●SSS ●Motion Blur Stenciltest: ●SSR Stencil Setup ●TAA Pixel Shader

AR Kit sample分析

AR Kit sample分析 arkit动画模型规范 关卡蓝图流程 Live Link Plugin 使用Live Link Plugin驱动面部追踪数据,包括当前的面部表情和头部旋转,作为一个运动捕获设备来puppeteer一个onscreen charater. 概览 对比51个独立的面部位姿。这些位姿在ARKit SDK内部,每一个位姿对应面部的一个特定部分。比如左眼,右眼,嘴巴等。位姿的值在[0.0, 1.0]间变化。举例来说,如果一个用户闭上左眼,那么LeftEyeBlink会在[0.0, 1.0]之间变化。 也就是说,用户的面部自然移动时,所有的51个位姿会被SDK评估并分配一个值。UE4 ARKit集成了所有的51个位姿,通过Live Link Plugin导入到UE4中。这51个值能够驱动实时的任务面部表情。 需要做的是捕获并驱动人物的头部来确保角色内容能够使用这51个数据。这51个反馈数据在[0.0, 1.0]之间,因而十分适合驱动人物角色表情。 详情请参见: arkit动画模型规范 设置 角色设置 创建一个blend shape角色模型基于面部动画,包括51个blend shapes.理想的,这些blend shapes的命名也与Apple ARKit一致。 导入到UE4,确保导入了Blend Shapes 在DefaultEngine.ini文件中开启面部追踪。 打开项目,在项目设置中设置AR为On. 创建并应用一个Data Asset来进行面部追踪。 内容浏览器,Miscellaneous > Data Asset 选择ARSessionConfig 双击 new Asset 设置 World Alignment: Camera Session Type: Face Horizontal Plane Detecion: Off Vertical Plane Detection: Off Enable Auto Focus: Off Light Estimation Mode: Off Enable Automatic Camera Overlay\Trakcing :Off 在关卡蓝图中Begin Play后,带动Start AR Session,并设置ARSessionConfig数据。 创建一个动画蓝图,使用LiveLinkPose节点,名称为FaceAR.

arkit动画模型规范

arkit动画模型规范 Neutral-静态的pose 一.创建Blendshape 曲线 苹果通过ARKit面部捕捉技术,提供开发者使用iphoneX以上的机器的前置摄像头,实现面部捕捉。blendshape名称是写死的,总共51个blendshape我们需要去实现,不过我们可以实现一些而不是全部。强烈建议的是,命名和功能需要保持一致。所有的值是在0-1之间. eyeBlinkLeft eyeLookDownLeft eyeLookInLeft eyeLookOutLeft eyeLookUpLeft eyeSquintLeft eyeWideLeft eyeBlinkRight eyeLookDownRight eyeLookInRight eyeLookOutRight eyeLookUpRight eyeSquintRight eyeWideRight jawForward jawRight jawOpen mouthClose mouthFunnel mouthPucker mouthLeft mouthRight mouthSmileLeft mouthSmileRight mouthFrownLeft mouthFrownRight mouthDimpleLeft mouthDimpleRight mouthStretchLeft mouthStretchRight mouthRollLower mouthRollUpper mouthShrugLower mouthShrugUpper mouthPressLeft mouthPressRight mouthLowerDownLeft mouthLowerDownRight mouthUpperUpLeft mouthUpperUpRight browDownLeft browDownRight browInnerUp browOuterUpLeft cheekSquintRight noseSneerLeft noseSneerRight eyeBlinkLeft ;eyeLookDownLeft ;eyeLookInLeft eyeLookOutLeft eyeLookUpLeft eyeSquintLeft eyeWideLeft eyeBlinkRight eyeLookDownRight eyeLookInRight eyeLookOutRight eyeLookUpRight eyeSquintRight eyeWideRight jawForward jawLeft jawRight jawOpen mouthClose mouthFunnel mouthPucker mouthLeft mouthRight mouthSmileLeft mouthSmileRight mouthFrownLeft mouthFrownRight mouthDimpleLeft mouthDimpleRight mouthStretchLeft mouthStretchRight mouthRollLower mouthRollUpper mouthShrugLower mouthShrugUpper mouthPressLeft mouthPressRight mouthLowerDownLeft mouthLowerDownRight mouthUpperUpLeft mouthUpperUpRight browDownLeft browDownRight browInnerUp browOuterUpLeft browOuterUpRight cheekPuff cheekSquintLeft cheekSquintRight noseSneerLeft noseSneerRight 二.

UE4动画系统源码剖析

动画基础概念 3D模型动画的基本原理是让模型中各顶点的位置随时间变化。主要种类有Morph(变形)动画,关节动画和骨骼蒙皮动画(SkinnedMesh)。 (自己搭的blog被黑了,只有本地备份,太伤了,所以文章会有一些格式问题) 从动画数据的角度来说,三者一般都采用关键帧技术,即只给出关键帧的数据,其他帧的数据使用插值得到。但由于这三种技术的不同,关键帧的数据是不一样的。 Morph(渐变,变形)动画是直接指定动画每一帧的顶点位置,其动画关键中存储的是Mesh所有顶点在关键帧对应时刻的位置。 关节动画的模型不是一个整体的Mesh,而是分成很多部分(Mesh),通过一个父子层次结构将这些分散的Mesh组织在一起,父Mesh带动其下子Mesh的运动,各Mesh中的顶点坐标定义在自己的坐标系中,这样各个Mesh是作为一个整体参与运动的。动画帧中设置各子Mesh相对于其父Mesh的变换(主要是旋转,当然也可包括移动和缩放),通过子到父,一级级的变换累加(当然从技术上,如果是矩阵操作是累乘)得到该Mesh在整个动画模型所在的坐标空间中的变换(从本文的视角来说就是世界坐标系了,下同),从而确定每个Mesh在世界坐标系中的位置和方向,然后以Mesh为单位渲染即可。关节动画的问题是,各部分Mesh中的顶点是固定在其Mesh坐标系中的,这样在两个Mesh结合处就可能产生裂缝。 第三类就是骨骼蒙皮动画即SkinnedMesh了,骨骼蒙皮动画的出现解决了关节动画的裂缝问题,而且效果非常酷,发明这个算法的人一定是个天才,因为SkinnedMesh的原理简单的难以置信,而效果却那么好。骨骼动画的基本原理可概括为:在骨骼控制下,通过顶点混合动态计算蒙皮网格的顶点,而骨骼的运动相对于其父骨骼,并由动画关键帧数据驱动。一个骨骼动画通常包括骨骼层次结构数据,网格(Mesh)数据,网格蒙皮数据(skin info)和骨骼的动画(关键帧)数据。下面将具体分析。 SkinnedMesh中文一般称作骨骼蒙皮动画,正如其名,这种动画中包含骨骼(Bone)和蒙皮(Skinned Mesh)两个部分,Bone的层次结构和关节动画类似,Mesh则和关节动画不同:关节动画中是使用多个分散的Mesh,而Skinned Mesh中Mesh是一个整体,也就是说只有一个Mesh,实际上如果没有骨骼让Mesh运动变形,Mesh就和静态模型一样了。 Skinned Mesh技术的精华在于蒙皮,所谓的皮并不是模型的贴图(也许会有人这么想过吧),而是Mesh本身,蒙皮是指将Mesh中的顶点附着(绑定)在骨骼之上,而且每个顶点可以被多个骨骼所控制,这样在关节处的顶点由于同时受到父子骨骼的拉扯而改变位置就消除了裂缝。 Skinned Mesh这个词从字面上理解似乎是有皮的模型,哦,如果贴图是皮,那么普通静态模型不也都有吗?所以我觉得应该理解为具有蒙皮信息的Mesh或可当做皮肤用的Mesh,这个皮肤就是Mesh。 而为了有皮肤功能,Mesh还需要蒙皮信息,即Skin数据,没有Skin数据就是一个普通的静态Mesh了。 Skin数据决定顶点如何绑定到骨骼上。顶点的Skin数据包括顶点受哪些骨骼影响以及这些骨骼影响该顶点时的权重(weight),另外对于每块骨骼还需要骨骼偏移矩阵(BoneOffsetMatrix)用来将顶点从Mesh空间变换到骨骼空间。在本文中,提到骨骼动画中的Mesh特指这个皮肤Mesh,提到模型是指骨骼动画模型整体。骨骼控制蒙皮运动,而骨骼本身的运动呢?当然是动画数据了。 每个关键帧中包含时间和骨骼运动信息,运动信息可以用一个矩阵直接表示骨骼新的变换,也可用四元数表示骨骼的旋转,也可以随便自己定义什么只要能让骨骼动就行。除了使用编辑设定好的动画帧数据,也可以使用物理计算对骨骼进行实时控制。 线性混合蒙皮算法 在骨骼动画的蒙皮算法中,出现最早、最经典,也是应用最为广泛的算法是线性混合蒙皮算法。 根据骨骼动画的基本原理,动画模型之所以能够运动,是由于其骨骼带动了蒙在骨骼之上的皮肤一同动作,实现了动画效果。因此,因首先设置好模型骨架以及各骨骼之间的关联性,当运动数据到来时,计算皮肤顶点的新位置,就可以完成模型的运动。 黑色与白色的皮肤顶点分别与其相同颜色的骨骼相绑定。 方框里的皮肤顶点离两个骨骼关节最近,它们同时受到两个骨骼关节的影响。当骨架运动的时候,对于这些受多个骨骼共同影响的皮肤顶点,我们要计算它们变换后的位置信息,即找到皮肤网格自动变形后的方法,传统一般采用线性混合蒙皮算法。线性混合蒙皮算法是由 Lander 最早提出并实现的一种柔性绑定算法。Lander 利用线性混合蒙皮算法实现了人体上臂的动画,解决了之前的刚性绑定算法在关节处的失真问题。该算法的基本原理可以用下列公式表示 V表示顶点变换前的世界坐标系中的位置,V’表示顶点变换后的位置,i 表示同时影响该顶点的骨骼数量,一般取 2-4 之间的值。W_i表示第 i 个骨骼对该顶点的施加的影响权重,取 0-1 之间的值,M_i表示在模型初始参考姿势下,与顶点相关的第 i 个骨骼由本地坐标转换为世界坐标的转换矩阵(即骨骼变换的绝对矩阵),通过矩阵M_i能将骨骼 i 从初始位置转换到动画数据来到时的新位置上. 综上所述,线性混合蒙皮算法即是求得一个顶点在每个骨骼影响下的一系列新的位置,然后对这些位置数据进行加权平均计算得到最后的结果。 在线性混合蒙皮算法中,顶点的新位置 V′是通过其初始位置V 乘以一个矩阵 C 得到,这个矩阵被称为变换矩阵。 我们可以使用 OFFSET(偏移)的 3 个量来表示子关节相对父关节的偏移量;用 CHANNELS 来表示关节旋转通道数量和旋转顺序,其中根关节有6个通道,其他关节有3个通道,与根关节相比少了XYZ的位置(position)信息,这是因为其他关节都可以根据相对其于父关节的偏移量计算坐标位置。 运动数据对应的是骨架信息中各关节点的层次数据,即CHANNELS 中 Zrotation Xrotation Yrotation 顺序的数据。对于子关节来说,平移信息存储在骨架信息的 OFFSET 中,旋转信息则来自于运动数据部分;对于根关节来说,平移量是 OFFSET 和运动数据部分中定义的平移量之和。要得到蒙皮所需的绝对变换矩阵,首先需要根据 BVH 文件中的旋转数据分别创建三个方向轴(Y 轴,X 轴,Z 轴)对应的旋转矩阵,然后将它们按顺序相乘得到矩阵R (也称相对矩阵): 绝对变换矩阵是由关节的相对矩阵乘上它的父关节的绝对矩阵得到的,其中,根关节的绝对变换矩阵就是它的相对矩阵。因此,根据骨架各关节之间的关系,可以计算出每一个关节的绝对变换矩阵,用来将关节的本地坐标变换为世界坐标。 在骨骼动画中,一般使用正向运动学和逆向运动学将运动数据作用到动画模型上。正向运动学是从模型的根节点开始(对人体模型来说,髋关节就是根节点),根据骨骼的拓展顺序,逐个计算各关节在动画数据下的偏移和旋转量,直至到达末端节点为止。 蒙皮算法中变换矩阵的计算实际上就是插值的计算。对于动画中发生动作的骨骼,应根据该骨骼的数据找出其前后两个关键帧,根据时间差进行插值计算。对于使用四元数表示旋转的情况,可以使用四元数线性插值或四元数球面插值。将插值得到的四元数转换成变换矩阵(旋转矩阵部分),最后更新骨骼之间的层次关系,计算出各个骨骼的绝对变换矩阵,完成顶点的新位置计算。 线性混合蒙皮算法的缺陷 线性混合蒙皮算法需要手工设置骨骼对皮肤顶点影响的权重值,这项工作繁琐耗时,并且要求设计者对模型的构成要比较熟悉。不过随着建模软件的日趋完善,现在已经有很多建模软件简化了权重设置这项工作,比如常用的 3DMax、Maya 等大型 3D 建模软件,为骨骼与皮肤的绑定提供了很多便捷的操作功能,能大大节省该工作的时间,提高工作效率。

UE4 lightmass数据流程

构建 在编辑器中,我们可以找到build的按钮,当然,我们这里讨论的并不是构建所有的东西,而仅仅讨论在构建灯光的时候。关于构建参数,请参考光照设置 编辑器构建系统 当我们点击build的时候,我们将触发下面的函数, 1 2 3 4 5 6 FEditorBuildUtils::EditorBuild(); //其会调用StopRenderingThread(); if (GUseThreadedRendering) { StartRenderingThread(); } 其中,关于build的内容定义为 目前我们仅关心的是BUILDTYPE_Lighting,看buildLighting的分支 这里会对BSP进行特殊处理,如果场景中没有BSP的话,我们就不需要进行 geometryrebuild。之后,我们会进行BuildLighting。 我们看一下传入的数据LightingBuildOptions的填充,其是取的config里面的数据 在这里,收前强制关闭所有的texture property windows,因为这些在构建灯光时是无效的。 之后有一个回调这个目前来看并没有东西,我们暂且先不去管他。 建立构建系统 首先FStaticLightingManager,其为单例模式,用来进行管理静态光照的所有系统和子系统。其只是一个管理功能,而不是一个真正的执行build的类。 其最开始的入口就是CreateStaticLightingSystem。如果目前已经有了StaticLightingSystems那么会发生警告说 如果目前没有残存的余孽的话,我们会有进行接下来的流程。这里会有一个逻辑,如果我们的配置中没有选择bOnlyBuildVisibility,也就是是由只是构建可见的,其会在结束后build ReflectionCaptures。 之后,对整个world的所有level进行判断,看是否有lighting scenario,这个概念是说同一个level可以有不同的光照图。这里是说,这个level是否是一个lightingscenario。 我们会把需要构建的level进行添加,如果我们对应的level,我们添加空的。 我们会将每一个level添加入FStaticLightingManager内部中的真正处理的类为StaticLightingSystem其进行处理的单元是一个level,这从它的构造函数可以很明显的看出来。 在FStaticLightingManager中有一个针对class FStaticLightingSystem*的指针。 之后获取列表中StaticLightingSystems的指针,然后进行真正的构建,BeginLightmassProcess,如果成功,SendProgressNotification进行显示,如果失败DestroyStaticLightingSystems通知显示。 每一个level的构建 因此,我们进入FStaticLightingSystem,来进行每一个level的构建 初始化。对所有的UPrimitiveComponent,里面的VisibilityId进行赋值INDEX_NONE初始化。对所有的level,如果不是当前的PersistentLevel,那就FindStreamingLevel。并且在设置中,会有设置,如是否只build CurrentLevel,bOnlyBuildSelectedLevels等会有不同的变化。那些被Skipped的Levels会被收集。这里还会有TextureStreaming的配置的搜集。在进行load一些其他的LoadGlobalIniFile里面的东西。 收集灯光,剔除所有的AGeneratedMeshAreaLight(在逻辑上是自发光物体)。搜集场景中所有的ULightComponentBase存储在TArray Lights; 收集mesh,Gather static lighting info from BSP.Gather HLOD primitives Gather static lighting info from actors.Recurse through HLOD trees, group actors and calculate child ranges。 这里面非常重要的结构 显而易见,这里一个是有排序的,一个是没有排序的。 我们将看到FStaticLightingMapping在世界的平面和static lighting cache的一个映射。