/images/avatar.png

Papalqi

从UProperty到FProperty

UE4 在4.25中,进行了一些非常重要的改进。 其中就有FProperty。 一.老旧的结构体系 UE4中带有前缀U的类是UObject派生类。每个UObject都有一个与之对应的UClass,这个UClass保存了所有的反射系统相关的信息,描述了各个Property之间的内存布局和关系;通过UClass可以遍历所有的序列化属性;通过Object对象的实例化地址就可以将相应的Property的值取出来,进行读写操作。 UClass保留了这个对象的结构信息。它在内部拥有成员的元素列表,例如UFunction,UEnum,UProperty等。它们一起作用来提供“反射”机制。从命名不难看出UClass 和下文中的UProperty,都是从UObject继承而来,主要是为了使用Object的GC功能。 UProperty将变量定义为类的组成部分。这也是我们这次的重点内容。 不管我们是在蓝图中还是在C++中,属性都是由我们的UProperty来指定的。这些属性包括蓝图变量和节点,以及执行节点的返回值。 二.反射 我们将C++编译成exe文件。编译器将C++中的信息翻译成了机器语言。我们本身的函数名称在编译时将会被替换和省略。因此你不能在自己的程序中使用字符串"HogeHogeFunc"来处理任何事情。反射是静态或者动态的去构造这一映射信息,并且在运行时使用他们。UE4是静态反射的。 在UE4中,我们在通过他特殊的宏,将所需要反射的数据进行标记,从而自动的生成供运行时查询的信息。 从而其生成了一些额外的信息。 三.反射案例 四.FProperty 在创建类时,会生成大量属性,包括蓝图类。有些类型将可能有几十万多个UProperty,因为所有的UProperty继承自UObject,因此会产生各种各样的开销。这一特性对整个引擎影响及其广泛,包括额外的内存空间,UProperty构建/销毁成本,垃圾回收性能。 从4.25开始,此UProperty已完全更改为FProperty。 前缀“F”说明其不再继承UObject。UProperty类的定义仍然存在,但它只用于在将早期版本中创建的资源转换为FProperty做一次。 这是4.24的继承关系。 这是4.25的继承关系 由于Field与UObject和UField大多数函数拥有相同的名称,因此当您更新引擎时,只需将其从UxxProperty重命名为FxxProperty即可。 五.改善点 内存消耗减少 在整个应用程序中使用的UObject中内存占用中,Property占40%以上,根据情况也有超过60%的程度。特别是在包含多个函数调用的蓝图很多的情况下。在ThirdPersonTemplate中,UE4.24的UObject合计有5万多个,其中70%左右是Property。这些消耗直接没了。 由于继承了UObject,因此每个属性不再需要超过100个字节的额外内存。 虽然每个容量都是微乎其微的,但由于数量众多,有时会减少数十MByte或更多的内存使用量。 使用宏调用8个BoolProperty返回值的函数2304次。 如前所述,由于不适用NewObject而是C++的New和Delete,因此生成和销毁特性也变得简单和快速。同样,您还可以缩短应用程序的启动时间。 六.结论 4.25 UProperty变成了FProperty 继承UObject后的开销不见了 提高了引擎启动时间 GC处理时间明显改善 内存使用量的减少 打包后包体体积减少

UBT

一. 调试UBT 打开项⽬的默认的构建命令⾏ 可以获得如下的 BuildCommandLine 1 ..\..\Build\BatchFiles\Build.bat -Target="ActionRPGEditor Win64 Debug" -Target="ShaderCompileWorker Win64 Development -Quiet" -WaitMutex - FromMsBuild 这⾥的 Build.bat,是可以直接换成 UnrealBuildTool.exe 的,如果你打开 Build.bat 查看内容,会发现⾥⾯的多数内容就是如何⽤ C# 构建⼀个 UnrealBuildTool.exe,然后调⽤这个 UnrealBuildTool.exe。 所以我们把 UnrealBuildTool 作为启动⼯程,然后启动命令⾏改成上⾯的 Build.bat 的命令⾏,就可以启动 UnrealBuildTool 的调试,如下图: 1.1启动调试 因为不知道 main 函数在哪⾥,这⾥有两种常⻅的调试⽅式 ⼀、直接按下 F11 启动,代码会⾃动停在 Main 函数的第⼀⾏,如下图 ⼆、通过 Attach 的⽅式调试,这种⽅式⼀般⽤于⼆段甚⾄三段启动的程序(也就是⼀个 exe 启动另⼀个exe,我们⽆法控制 F11 的位置,典型的就是UBT会启动 UHT的exe) 我们找到Main函数,然后在写下如下代码和断点,这样任何⽅式启动程序(包括⼆段启动)都⼀定会进⼊死循环。 Attach到⼀个进程上,此时断点⼀定会停在死循环⾥⾯。 通过运⾏时修改内存的⽅式,把死循环解开,⽐如这⾥的把 i 改成 1。接下来就可以愉快的调试了。 二. 文件结构 Module 是构成 Unreal 的基本元素,每一个 Module 封装和实现了一组功能,并且可以供其他的 Module 使用,整个 Unreal Engine 就是靠各个 Module 组合驱动的。我们创建的游戏项目本身,就是一个单独的Module。

VirtualTexture

一.基础、原理和使用 Unreal最早从2017年就开始进行VirtualTexture功能的研发工作。4.17版本初次的提交,4.23 版本正式放出SVT和RVT,4.26 版本推出Adaptive VT。 按照使用上的不同,在引擎中一共有四种不同的VT类型 UVirtualTexture2D直接是继承于UTexture2D的,是最普遍上的的VT类型。 ULightMapVirtualTexture2D跟 UVirtualTexture2D没有什么太大的区别,主要是使用在LightMap中使用。 URuntimeVirtualTexture,RVT,后面会讲到其最终的用法。 如果RVT勾选了Adaptive,那么RVT则会变成AdaptiveRVT。 大致上我们按照生成的时机不同,主要分为两种VT的格式。UVirtualTexture2D和ULightMapVirtualTexture2D叫做SVT(Streaming Virtual texture)。而URuntimeVirtualTexture和他的衍生类别AdaptiveRVT称之为RVT((Runtime Virtual texture))。 1.1Streaming Virtual Texture VT功能并不是UE4引擎默认开启的,如果我们想要使用Virtual texture ,需要在项目设置中设置Enable Virtual Texture Support。而勾选Enable Virtual texture lightmaps,则会将我们的Lightmap变为VT形式。 1.1.1 普通贴图转换为Virtual Texture 当我们重启编辑器后,我们可以把之前普通的贴图右键点击转换为VirtualTexture。使用这种方式编辑器将检索与之相关的材质使用。会将会改变使用到这张贴图的材质的采样方式。 多选批量转换当然也是支持的。 我们也可以在贴图的设置中去设置这张贴图是否是VirtualTexture。 不过,这样我们就必须手动的去调整材质里对于VT的采样方案。不然材质会报编译错误 1.1.2 导入为VirtualTexture 之前的操作是把项目中已知的贴图进行转换,当我们想要新增贴图时,由于贴图的大小限制,我们可以把一张超大的贴图切割开,然后同时导入多张texture ,如果他的命名规则如下图所示那么他就会按照UDIM多象限UV进行排列并且识别为VirtualTexture。 当然,如果我们只是导入一张贴图的话,会根据项目中的配置来进行判断,分辨率如果大于这个设置,将会转换为VirtualTexture。如果比这个小的话,将视为普通的贴图处理。 1.2 Virtual Texture的目的 在这里我们希望去讨论VT的实现目的。假设我们的游戏画面是静止不动的,并且如果我们能够实时创建这么一张贴图来显示我们的场景,我们假设屏幕分辨率是1920x1080 ,用RGB8来进行存储。 920x1080x3 =6075KB≈5.93MB,(显存利用率) 也就是说,我们显卡的显存只需要用 6MB 就完全够了。这种极端的情况用现有的硬件实现起来恐怕不太现实。这种情况很难在一个动态的场景中实时的去做到,但是即使是有一些cache的冗余数据,就算到了100倍的冗余,其空间使用也小于我们目前的Texture Pool的大小设置。 因此,Virtual Texture的基本思路就是:把能看到的贴图才加载进来,没看到的不加载;进的精度高,远处的精度低。如下图所示,如果我们只把看到的部分(高亮的部分加载进来)那么内存占用上将大大减少。 从中我们可以看出Virtual Texture实则是解决我们空间存储的问题。世间所有的炼金术都遵循等价交换,你既然节省了空间,那么你的时间消耗则会增加,这是一个以时间换空间的技术:在每个Tile动态加载卸载的过程中和VT采样时都将产生额外的性能开销。因此我们是否需要开启,将哪些贴图进行开启,都是一个值得权衡的问题。而不是无脑的去使用。 既然解决的是贴图的内存管理问题,其跟传统的基于Mipmap的贴图和可见性的Texture Streaming管理是不同的。传统的StreamMgr只会根据可见性和对应的Mipmap来进行流送的处理,也就是说我们并不能对单张贴图的某一部分进行更为精确的控制,也就是有了过多的冗余信息。例如我只看到了一个贴图的一个角落,却要把他整个都加载进来。 而VirtualTexture 这种将整个Texture分为Tile 的方案,则考虑了这一冗余数据的处理。VT的劣势也很明显,在镜头剧烈变化的时候,其会不停的进行加载卸载的操作,如果配置不当会导致明显的帧率问题。 1.3 Runtime Virtual Texture 首先我们将解释一下RVT(Runtime Virtual Texure) 和普通的SVT(streaming Virtual Texture)的区别。RVT是一张动态在场景中获取的VT,因此所有的数据并不需要存储在硬盘中,而是在我们需要某一块时(相机看到的时候)直接渲染出来。

Niagara 源码架构解析

从git上的信息其实可以看到,Niagara其实在引擎中存在的时间非常久,在4.0版本就已经存在。但是直到2018年的4.17中公开出来。4.17~4.24 为实验版本,4.25 之后正式版本。但是从目前官方的对于Niagara fix的代码提交上看,还是有相当多的编辑器问题修复。 17年才将Niagara从Engine内部移植到插件中。 随着长久的历史变迁,Niagara已经进行了多次的文件位置上的转移,目前的位置是在引擎的插件中。 Niagara在UE4开始就已经在构思做了,目的就是重构继承于UE3的Cascade。Niagara当时的设计目的就是data driven并且扩展性更高的粒子系统,解锁模拟和渲染,所以有图形化的节点能不需要程序来实现模拟的逻辑功能。所以2014年时引擎实现了向量化(指令并行)的虚拟机。 后续为了进一步提高了并行性,加入了compute shader的GPU支持。 本文主要设计Niagara 的主要代码框架结构,对其内部可能的其他一些知识体系。本接下来文章介绍Niagara 的编译和反射系统。因此本文就不在提及过多关于这方面的内容。 我们照例观察一下整个代码的文件组织。 Niagara:所有的运⾏和逻辑构成 。 NiagaraAnimNotifies:可以在动画的过程中进行事件通知播放Niagara。 NiagaraCore:⼀些通⽤的类 NiagaraEditor:编辑器内的可视化编辑节点。 NiagaraEditorWidgets:Niagara 的UI 。 NiagaraShader:Niagara 的shader 。 NiagaraVertexFactories:组成MeshBatch的顶点⼯⼚。 一.基本概念 1.1system 和Emitter 我们通常在编辑器中使用的是Niagara资源主要有两种,一种是我们的Niagara发射器Emitter,一种是我们的Niagara系统System。并且我们发射器是由我们的Module共同组成,从而完整的形成我们的整个Niagara系统。其设计理念相比与Cascade,拆分的更加的细致,从而能偶比较细致的组合和复用。 我们本次并不关心Module所能代表的发射和编译模块,我们重点关注的是Emitter和System所对应的运行时数据。 所有使用Niagara 的地方都需要我们的UNiagaraComponent来进行,其主要是进行控制和Niagara系统的交互。 我们在编辑器中资源对应的是UNiagaraSystem和UniagaraEmitter。一个UNiagaraComponent会拥有指定的一个UNiagaraSystem对应。但是UNiagaraSystem并不是我们运行时直接使用的数据。我们会在UNiagaraComponent初始化时构建对应UNiagaraSystem的FNiagaraSystemInstance,同样System里面的UNiagaraEmitter会生成对应的FNiagaraEmitterInstance。这如同类和实例的关系一般。 对于每个FSystemInstance,会有⼀个相对应这个System类型的的Simulation,它和Instance并不是⼀对⼀的 关系。如果场景中存在同⼀个UNiagaraSystem的多个实例,其只对应⼀个 FNiagaraSystemSimulation。也就是说,对于数据结构⼀致的实例,会使⽤同⼀个Simulation进⾏模拟。因为相同的System的Spawn和Update与数据存储的大小和类型是相同的。提高缓存一致性和数据的存储效率。 所有的FNiagaraSystemSimulation都由FNiagaraWorldManager统一进行管理。 1.2 运转流程 Niagara将有Initial和Update两个重要阶段。其中Initial只在一开始运行一次,Update则每帧运行。 而根据发生的位置不同分为SystemUpdate,EmitterUpdate和ParticleUpdate,分别代表每个System,每个Emitter,和每个粒子的更新。 例如我们spawn一个NiagaraActor,对应的NiagaraSystem中有两个Emitter,每个Emitter发射1000个粒子。 我们排除一些粒子间事件这种情况。那么当我们在初始化时,会调用一次SystemSpawn,调用各自的Emitter的EmitterSpawn,调用各自粒子的共两千次的ParticleSpawn。之后除非有新的Particle生成会调用particleSpawn,否则不会再调用任何Spawn。 之后,我们每帧都会调用我们的各种Update去更新数据。 1.3 渲染策略 这里忽略Light和component对应的Renderer。我们拥有多种渲染器可供选择。 我们其他MeshDraw一样会拥有⼀个渲染的代理Proxy帮助我们来完成最终的渲染,对于Component来说我们持有的该System 的所有Emiter带有的Renderer都会我们会赋值给Proxy。因此其实我们的最小MeshBatch单位是与Emitter数量相关,其数据被Proxy持有,最终交付渲染,这⾥的渲染和UE4普通的渲染架构并没有什么不同,都是统⼀的框架。 由于Niagara的可编程性,其实现功能的潜力是巨大的。它可以非常复杂,但是我们剖析其本质上是一种空间位置模拟的系统之后,我们就能够抓住其关键就是更新它需要的关键数据。更新FNiagaraDataSet。 二.模拟数据 不论Niagara Emitter有多少的Module,Module里有多少的脚本,脚本中写了多少东西。我们当然要抓住本质数据,其都是更新某个渲染器所需要的必要参数,也就是如下图所示的信息。 也就是说,只有这些信息是每个渲染器所需要的,其会传递给渲染线程并最终提交渲染。任何其他参数仅仅为中间变量。因此我们首先关心的是这个数据是存储在什么地方的。由于一个Emitter可以拥有多个不同的Renderer。因此其本身存储在Emitter级别,而不是在Renderer级别。 因此我们最终传递给渲染线程的数据存储在每个Emitter实例FNiagaraEmitterInstance的FNiagaraDataSet身上。不论是GPU粒子还是CPU粒子。 我们将FNiagaraDataSet的数据作为每帧更新的数据继续传递。FNiagaraDataSet最主要的数据是两个buffer。 其中一个是当前使用的buffer,一个是我们将要写入的buffer。FNiagaraDataBuffer拥有提供CPU粒子或者GPU粒子的数据。如果是CPU粒子,我们将在CPU做计算写入操作,如果是GPU粒子,我们在CPU端只需要把对应的buffer指定就可以了。 从传递的数据来看,如果我们是CPU粒子,将直接把最终的模拟结果对应的buffer传递给renderer。而GPU粒子则没有最终的数据,所以需要传递给渲染线程的数据则更完整一些。 其封装成FNiagaraComputeExecutionContext。 2.1 CPU粒子模拟数据更新 最开始我们有一个全局的管理器FNiagaraWorldManager。每一个System类型对应的唯一FNiagaraSystemSimulation都将在这里进行注册并且收到它的支配和管理。 我们的FNiagaraSystemSimulation的数据更新分成两个部分。 Tick_GameThread。这里主要是更新一下系统时间等参数,然后调用FNiagaraSystemInstance的Tick_GameThread。在FNiagaraSystemInstance主要是更新System的Parameters和DataInterface。 Tick_Concurrent,这个阶段可以不在Game线程完成。 对于那些新生成的SystemInstance调用它的spawn script。 是调用system update script进行System的更新。 将system模拟的结果注入到emitter的绑定数据中。 调用FNiagaraSystemInstance的Tick_Concurrent。 在FNiagaraEmitterInstance::PreTick中,最主要的是执行Emitter的Spawn和Update脚本。

UE4 渲染代码解析系列1——主渲染函数

从今天起,我将从主渲染函数入手,来整理整个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 152 //更新需要添加到scene和需要删除的物体。 Scene->UpdateAllPrimitiveSceneInfos(RHICmdList, true); //抗锯齿策略,分辨率大小比例,窗口大小,屏幕百分比策略 PrepareViewRectsForRendering(); //场景中是否存在天空大气的渲染需求,进行准备工作 PrepareSunLightProxy(*Scene->GetSkyAtmosphereSceneInfo(),LightIndex, *Scene->AtmosphereLights[LightIndex]); //如果存在多GPU,计算GPUMask ComputeViewGPUMasks(RenderTargetGPUMask); //将RT池中的rt设置为可写入 GRenderTargetPool.

100TipForUnreal

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.