虚幻图像渲染引擎有哪几种为什么靠一张纹理就能实现模型上色模块化,是什么原理?

The Unreal Engine Game Framework: From int main() to BeginPlay - YouTube你可以完全不看这篇文章,而看上面这个视频。如果你看不懂英语,可以尝试看一下文章。这只是我的笔记。UE5代码还是发生了一些改变,但是变化不是太大。这个视频非常好,没有一句废话,全是干货。但是就是有一点难,看完下来晕头转向的。稍不留神就不知道在说什么了。我画了一个很长很长的流程图去说明这个过程,但是这个流程图我感觉依然很难被理解,就像视频作者那个视频总结一样。一开始我都是自己在虚幻源码中找这些关键代码过程,但是后来因为扭伤不能去用我的台式电脑,只能截取视频作者的代码。这篇笔记有可能漏洞百出,希望网友多多包涵。我认为虚幻的很多权威资料只有通过源码才能找到,虚幻源码注释要好于虚幻文档。虚幻引擎运行原理
ProcessOn免费在线作图,在线流程图,在线思维导图 虚幻引擎运行原理
ProcessOn免费在线作图,在线流程图,在线思维导图 https://www.processon.com/embed/6388857c7d9c086a817bdc55游戏循环游戏编程中最常见的,最基础的概念就是游戏循环。int main() {
init();
while (!g_exit_requested) {
poll_input();
update();
render();
}
shutdown();
}
初始化游戏,然后玩家输入,根据玩家输入更新世界,然后将世界渲染到屏幕上。当关闭游戏时,会做一些清洗工作。但是虚幻不一样,你并不能直接参与游戏循环。如果你写过虚幻的游戏代码,你就会知道你其实是通过继承一些像GameMode,Character这样的类来自定义自己的功能。常用类结构虚幻引擎中的游戏循环根据游戏引擎版本不同,代码有可能不一样。你可以在Launch.cpp里找到虚幻的游戏循环,他叫FEngineLoop GEngineLoop;
初始化游戏引擎,tick,退出/**
* PreInits the engine loop
*/
int32 EnginePreInit( const TCHAR* CmdLine )
{
int32 ErrorLevel = GEngineLoop.PreInit( CmdLine );
return( ErrorLevel );
}
/**
* Inits the engine loop
*/
int32 EngineInit()
{
int32 ErrorLevel = GEngineLoop.Init();
return( ErrorLevel );
}
/**
* Ticks the engine loop
*/
LAUNCH_API void EngineTick( void )
{
GEngineLoop.Tick();
}
/**
* Shuts down the engine
*/
LAUNCH_API void EngineExit( void )
{
// Make sure this is set
RequestEngineExit(TEXT("EngineExit() was called"));
GEngineLoop.Exit();
}
preInit过程preInit其实在决定加载什么包。比如uplugin,uproject里就有一些要加载的包。rider插件的upluginModuledescriptor.h的ELoadingPhase决定了哪个module需要在开始阶段被加载。/**
* Phase at which this module should be loaded during startup.
*/
namespace ELoadingPhase
{
enum Type
{
/** As soon as possible - in other words, uplugin files are loadable from a pak file (as well as right after PlatformFile is set up in case pak files aren't used) Used for plugins needed to read files (compression formats, etc) */
EarliestPossible,
/** Loaded before the engine is fully initialized, immediately after the config system has been initialized.
Necessary only for very low-level hooks */
PostConfigInit,
/** The first screen to be rendered after system splash screen */
PostSplashScreen,
/** Loaded before coreUObject for setting up manual loading screens, used for our chunk patching system */
PreEarlyLoadingScreen,
/** Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers */
PreLoadingScreen,
/** Right before the default phase */
PreDefault,
/** Loaded at the default loading point during startup (during engine init, after game modules are loaded.) */
Default,
/** Right after the default phase */
PostDefault,
/** After the engine has been initialized */
PostEngineInit,
/** Do not automatically load this module */
None,
// NOTE: If you add a new value, make sure to update the ToString() method below!
Max
};
}
搞明白虚幻引擎到底在初始化什么,我们必须知道虚幻引擎的一些包,如下图。你的一些设置文件决定了以下有些包需要被加载。一些包比另一些包要重要,一些包只能加载在特定的平台上加载,一些包在特定情况下加载。虚幻的一些模块这是一个加载顺序大致加载顺序首先加载底层的一些模块,一些底层的关键系统被初始化和关键类型被定义。然后你的项目和插件的模块需要被提前加载的在此时被加载。接着一些更高级的关键系统被初始化,然后你的项目和插件模块在此时被加载。最后一步才是你的代码被加载的地方。你的模块如何被加载?首先虚幻引擎注册任何你的模块里的UObject,这使得虚幻反射系统能意识到这些类并为这些类建立CDO,class default object(类的默认对象?)。在默认状态下,CDO是你类的记录和用做以后继承的原型。因此,如果你定义了一个自定义的actor类型或者gamemode,或者任何被UClass所声明的类,engine loop都会分配一个默认的实例,然后运行其类的构造函数并传入CDO的父类作为模板。这也是为什么构造函数不应该有任何玩法代码的原因之一。构造函数只是建立类的通用细节,而不是去修改类的任何内容。在这些你的类全部被注册后,引擎调用你的start module方法(startmodule与你的shutdownmodule相匹配),做一些与模块生命周期相联系的初始化。所以在preinit阶段,虚幻引擎加载了一切必须的引擎,项目,插件的模块。虚幻引擎注册了来自这些模块的类,初始化必须的底层系统。GameModeBase的构造函数start moduleInit过程Init相对非常直接。如果我们将其简化一点点,它通过一个叫Uengine的类来处理事情。它是目录UE_5.1\Engine\Source\Runtime\Engine\Classes\Engine\Engine.h文件下的一个类。用rider的ctrl+左键点击一下看看。创建Gengine实例EngineLoop在init函数中检查到底哪一个GameEngine类需要被使用。然后EngineLoop创建了一个这个GameEngine类的实例和将它作为全局的Uengine实例。这个Uengine实例可以通过全局变量Gengine去访问,这个变量同样在Engine.h里被声明。Engine.h声明的GEngine当Gengine被创立,然后就被初始化了。一旦初始化完成,engine loop就会用一个全局委托去表明这个Engine已经被初始化了。然后就会加载被设置为晚加载的项目或者插件模块。最终,engine开始运行,初始化也被完成。初始化方法全局委托去广播加载模块引擎开始运行Engine.h里的Browse和LoadMapengine这个类做了很多事情,但主要的方法是Browse和LoadMapengine主要功能我们现在去看看这个进程是如何启动和如果让所有的引擎系统初始化。UnrealEngine.cpp里的LoadMap这些都在UnrealEngine.cpp里实现。引擎能够浏览url,这个url可以是一个客户端的服务器地址,也可以是一个本地地图的地址。这个url也可以有些参数加到他们身上。含有参数的url当你设置了一个默认地图在你的项目的DefaultEngine.ini。你就告诉游戏引擎去浏览这个地图,当你的引擎启动时。默认地图GameEngine.cpp里的初始化方法我们说到,在gameloop类中初始化调用了Gengine的init方法。也就是在GameEngine.cpp中的UGameEngine::Init中,创建了一些较为重要的实例,GameInstance,GameViewport Client,和LocalPlayer。注意下面的注释,只是唯一的gameinstance创建的地方。GameInstanceGameViewport ClientLocalPlayer你可以把GameViewport Client想象成一个屏幕,LocalPlayer想象成正在盯着屏幕的人。对于渲染,声音,和输入系统,UGameViewportClient是高级接口。所以你可以认为这个是User和Uengine之间的接口。而GameInstance是从Uengine分离的一个类,去处理关于你的项目的一些特殊功能。在过去这些功能是放在Uengine之中的。而且LocalPlayer是由GameInstance创建的。GameInstance.cpp游戏运行时蓝图使用“GetGameInstance”节点获取,C++ 使用“UGameplayStatics::GetGameInstance()”函数获取。4个重要类init初始化全过程当这些类被创建后,就开始下一步工作了,见下图。初始化用的一些方法在这些的最后一步LoadMap会创建很多东西。比如创建一个Uworld,这个UWorld包含了所有被存储在这个World上的Actor。还有游戏框架等。下图就是那些类在loadMap之前创建,哪些类在loadMap创建。上面是引擎的类的初始化,下面是游戏类的初始化。初始化不同的阶段但是虚幻引擎提供了不同map之间的无缝穿越,而且可以保持actor的完整。如果你真正去了一个“新地图”,或者连上了一个新服务器,或者返回到了主菜单,你的所有actor都将被摧毁,世界被清理,这些类将不见了,直到下一个地图。loadMap基本过程一开始这个游戏引擎去发送一个全局委派去暗示这个地图将要加载。全局委派如果之前有地图在内存已经被加载好了,就会摧毁这个地图。摧毁之前的地图当我们到达上图中的starttime时,已经要确保没有旧地图了。LoadMap方法是传入WorldContext的参数。WorldContext是在引擎初始化之时,被gameInstance所创建。WorldContext的是一个永久的类,worldContext用来追踪在此时哪些world被加载。world context参数在任何事情被加载之前,gameinstance都有机会加载它想要的资产。GameInstance加载资产world和map我感觉是可以互换的词,而world可以包含多个level或者一个level。Map, Level and World, whats the difference - Development / World Creation - Unreal Engine Forums如果你的编辑器上有个地图,那这个Uworld已经被载入内存之中了。其中这个Uworld有些level,而这个level有些actor,当你需要把这个Uworld持久化时,它会和他的level还有actor一起被序列化放到磁盘上。也就是你在文件夹里看到的umap文件。而loadmap会从磁盘把umap中的持久化的level,level中的actor,还有worldsettings全部加载到内存之中。序列化到磁盘上将磁盘上的umap载入内存中下面是world一些代码,设置类型,addtoroot防止被垃圾回收。而initworld方法中,则是设置系统像physics,navigation,AI,audio等系统初始化world设置世界的gamemode actor设置gamemode加载地图,意味着任何“总是加载”的子level还有附带的资产都被加载进去。加载永久level为level里的actor初始化gameplay,告诉world游戏要开始了。initialzeActorForPlayUWorld::InitializeActorsForPlay更新关卡组件,这个在UpdateWorldComponents中。这个会注册在这个世界所有actor的component。更新关卡组件UActorComponent::RegisterComponentWithWorld注册actor的component注册actor组件将actor的世界引用为加载的世界。引用世界注册actor的component,并给了component一些早期初始化。UActorComponent::ExecuteRegisterEvents。如果是一个primitiveComponent,在注册之后,componnet将有一个FPrimitiveSceneProxy,FPrimitiveSceneProxy将被创建和加入Fscene,Fscene是Uworld的渲染线程。注册componentscene和add primitive component一旦注册完成之后,world就会调用gamemode的初始化。gamemode的初始化gamemode的init方法会使gamemode去放置一个gamesession actorgamesession actor注册world里的所有level的所有actor多初始化actor类初始化level里的actor。有两个阶段,preinit actor component,init actor component。preinit在注册之后,但是在正式初始化之前。actor初始化的两个阶段gamemode也是一个actor,所以它也有preinit actor component。gamemod component 生成了一个GameSTate,GameNetworkManager,最终也初始化了gamestate。生成gamestate,game network manager激活gamemode的component和初始化gamemode的component激活和初始化 componentpost initialize是actor完全准备好状态的最早时候,所以在这个方法之中,就可以写一些代码在游戏之前初始化这个component。AGameModegamemode定义了游戏的规则,它也生成了很多gameplay的关键actor。在游戏之中,对于发生什么,gamemode有绝对的权限,而且它只发生在server端。AGameSession gamesession用来处理登陆请求,也是在线服务(比如steam和psn)的接口。只在server端。AGameNetworkManagerAGameNetworkManager是用来设置一些名叫cheat direction和move prediction的东西。这个是方向预测?只在server端。AGameStateBaseGameState在服务器上创建,只有服务器有权限去改变它。但是它会复制给每个客户端,你想要所有的玩家都知道当前游戏状态。gamestate是你存储与游戏状态相关数据的地方。我们当前缺失的是我们player actor。用loadmap的gameinstance的GetLocalPlayerIterator迭代器遍历了gameinstance里的所有actor,一般来说迭代器只有一个。对于一个localplayer,它调用了自己的spawnplayeractor方法。在spawnplayeractor这个方法中,play actor和playercontroller是可以互换的概念。比如在spawnplayeractor这个函数中,就可以生成一个playercontroller。迭代localPlayerControllerUlocalPlayer和PlayerControllerlocal player是引擎里player的代表,它在UEngine::init里创建。与local player相反的是playerController是player在地图里的代表。local player实际上是player base class的具体化。还有一种player叫net connection,它是一个连接远程进程的player。为了让任何进程加入游戏,无论是本地还是网络的。他都不得不经历一个登陆过程。这个过程被game mode把持着。gamemodebase:prelogin只有远程的连接请求才会被调用。它的责任就是同意或者拒绝登陆请求。一旦我们将player加入游戏中,要么它被同意登陆,要么就是个本地player。Login返回一个playercontrollergamemodebase:login
生成了一个player controller actor,然后将它返回给世界。我们生成了一个actor,所以我们的world已经为游戏开始准备好了。在生成actor的过程中,我们要初始化这个player controller actor。所以APlayerController::PostInitializeComponents()被调用,然后这个方法会生成一个PlayerState。生成一个PlayerStatePlayerController和PlayerState和Gamemode和Gamestate非常相似,GameMode只能在服务器,而playerController只能是服务器授权,只对拥有的客户端复制。Gamestate服务器授权,对所有客户端复制。而playerstate同样也是如此。Uworld的spawnplayeractorplayer进入过程我们通过代码发现,这个过程无疑是new player要加入,如果player申请加入并成功,gamemode就会生成一个playerController,playerController初试化自己的网络,然后再把这个player和controller联系在一起。然后调用gamemode的postLogin来设置一些东西。比如生成一个pawn给playercontroller控制。Pawn和PlayerController一个actor在controller代表了运行actor的大脑,而pawn则是在世界里的实际运行体。因此每当有一个新玩家加入游戏,game mode都会为其分配一个pawn给player controller去拥有或者说控制。游戏框架同样支持game spectator(旁观者),你的player state可以被用来设置表明你的player是否必须旁观。gamemode去把player全部作为旁观者启动。在那种情况下,game mode就不会生成一个pawn,相反,player controller会生成一个spectator pawn,这个spectator pawn可以在游戏世界自由飞行,而不需要与游戏世界交流。如果不生成game pawn,游戏框架会重启player。这个什么情况?还要重启player?想象你在一个多人射击游戏中,如果一个玩家被杀了,他的pawn就死了,变成了一个尸体躺在那。这个pawn不需要再被控制。但是player controller依然在那。当player已经准备好复活时,pawn又一次要被重新生成。这就是restartPlayer这个方法的作用。restartplayer给一个playercontroller,它就会找到一个代表pawn在哪生成的actor。然后再找到哪个pawn class需要被使用。然后就会生成这个pawn class的实例。在默认情况下,gamemode会浏览所有的在地图上的play start actor,并选择其中一个。但是这些都可以在你的gamemode里重写。在任何事件中,一旦一个pawn被生成,它都会和一个player controller相联系,这个playercontroller将控制这个pawn。world beginplay接下来,就是最后时刻了。游戏开始。引擎告诉world游戏开始,然后world告诉gamemode游戏开始,gamemode告诉worldsettings游戏开始。worldsettings告诉所有actor游戏开始,同样在蓝图中的begin play也开始了。Gamemode我们去看一看AGameModeBase和AGameStateBase,而不是AGameMode和AGameState。这些base类在ureal 4.14中加入。这些好像来自虚幻竞技场备受喜爱的功能。Gamemode包含了一种Match state的概念,这就像游戏中的回合。这个match state在回合开始之前,监控所有的玩家,直到他们准备好,或者决定是否到一个新地图去开始另一个回合。gamemode继承关系match state
原文链接:https://blog.uwa4d.com/archives/Study_Unreal4_Animation_3.html 上周UWA介绍了Unreal 4引擎动画模块中的动画融合功能,今天我们将继续介绍Unreal 4引擎动画模块中的其他进阶功能,其中主要包括:动画重定向、逆向运动学和顶点动画。 在此,特别感谢Unreal中国团队对于本篇文章中Unreal引擎相关内容的审核,并在UWA团队学习其引擎的道路上提供的大力支持。 一、动画重定向 游戏引擎动画模块中的动画重定向(Animation Retargeting)可用于在不同模型上重用动画文件,该功能类似于Unity引擎中Humanoid模式下的Retargeting功能。Unreal 4引擎对使用相同Skeleton Asset的Skeletal Mesh支持动画重定向,并且支持不同尺寸角色动画的重定向,其细节把握非常到位,具体如下图所示:上图显示了高、中、低三个不同的模型,并以骨骼长度中等模型的动画做重定向。在不做骨骼缩放处理的情况下直接做重定向使得较矮的模型被拉伸,较高的模型出现了穿插。 Unreal 4引擎对该问题提供了解决方案。打开两个Skeletal Mesh共用的Skeleton Asset,并勾选Show Retargeting Options,即可看到骨骼结构图右边显示骨骼Retargeting设置,如下图所示:点开向下箭头会显示四个选项:Animation、Skeleton、Animation Scaled以及Animation Relative,如下图所示:该选项表示了骨骼的Transform数据来源。其中,Animation选项表示Transform来自动画文件,Skeleton选项表示Transform来自骨骼文件(即静态骨骼),Animation Scaled表示Transform来自动画文件,但根据原始Skeletal Mesh中的骨骼长度进行缩放。对于一般情况,只需要将Root、IK和Weapon骨骼设置成Animation,Pelvis骨骼(一般为根节点)设置成Animation Scaled,其他骨骼均设置成Skeleton即可。设置完成后即可输出经过骨骼缩放后的动画重定向结果,如下图所示:其中,黄色线表示动画文件原始模型的骨骼,白色线表示进行动画重定向的模型骨骼。 对于使用不同Skeleton Asset的Skeletal Mesh,Unreal 4引擎支持使用Rig文件将两者的骨骼进行映射,过程类似于Unity引擎中对Humanoid类型骨骼的模型进行标准人形骨骼映射。然后,即可支持动画重定向功能。有兴趣的读者可参考Unreal 4官方网站动画重定向教程【1】,进一步了解。 二、逆向运动学 游戏引擎中逆向运动学(Inverse Kinematics)可以弥补普通动画中不精确的情况,从而生成更加真实的角色动画。IK在游戏角色动画中有多种应用,其中比较常用的情况是使得角色双脚能贴合在高低不平的地面或是当角色触碰某些物品时能精确地碰到物品的位置,如下图所示:其中,左图是没有使用IK的情况,在楼梯上由于角色两脚地面高低不一致,导致右脚穿插入楼梯。右图是使用了IK的情况,角色的右脚被抬起到与楼梯相同的高度。 Unreal 4和Unity引擎都支持IK,本节主要介绍Unreal 4引擎中IK的使用和设置。Unreal 4引擎中设置IK通常需要设置两个地方:Character的Blueprint和Animation Blueprint。在Character Blueprint中,需要处理获取Socket节点的位移信息,用于在Animation Blueprint中使用。Socket节点即为IK中需要触碰到指定位置的节点,例如:在本节例子中角色脚底可以设置成Socket节点,如下图所示: 其中,上图的Character Blueprint中,红框表示了每一帧Tick函数,蓝框表示计算角色右脚的Socket节点与地面的高度并记录在IKOffsetRightFoot变量中,绿框表示计算角色左脚的Socket节点与地面的高度并记录在IKOffsetLeftFoot变量中。然后,在Animation Blueprint的Event Graph中获取该变量值并设置为Effector Location,如下图所示: 其中,红色框表示利用IKOffsetRightFoot变量的值计算右脚Effector Location(脚底位置),蓝色框表示利用IKOffsetLeftFoot变量的值计算左脚Effector Location。 最后,在Animation Blueprint的Anim Graph中,使用Two Bone IK节点设置IK节点,如下图所示:其中,红色框表示用Two Bone IK节点计算右脚的IK解,蓝色框表示Two Bone IK节点计算左脚的IK解。其中,Joint Target Location参数是为了限定膝盖节点的弯曲方向。 由此,IK的设置已完成。其中,主要步骤分为三步:(1)计算获取Socket节点的位移;(2)在Event Graph中设置该值为Effector Location;(3)在Anim Graph中使用Two Bone IK节点设置IK骨骼。 三、顶点动画 Unreal 4与Unity引擎都支持顶点动画,它常被用于制作面部表情以及布料等无法用骨骼驱动的动画。在Unreal 4引擎中被称为Morph Target,在Unity引擎中被称为Blend Shape。在Unreal 4引擎中,Morph Target可以被叠加到Skeletal Mesh上,形成顶点动画。其使用通常需要在Maya等3D建模工具中把顶点动画制作完成,并导入Unreal 4编辑器。然后,通过Event Graph中的Set Morph Target节点进行设置,如下图所示: 其中,Target表示Skeletal Mesh,Morph Target Name表示该网格中的某一个Morph Target,Value表示Morph Target与 Skeletal Mesh的插值系数,其取值为[0, 1]。当取值为0时,显示原始网格形状,当取值为1时显示Morph Target形状,取值0~1显示插值结果。其形变效果如下图所示:上图显示了角色左边眉头被挤压的Morph Target叠加到Skeletal Mesh上的结果。 四、Aim Offset Aim Offset在Unreal 4引擎中是动画模块中的一个特殊功能,其主要用于制作射击游戏的瞄准动画。本节UWA将重点介绍其使用方法。 Aim Offset本质上是一个动画融合的工具,它的操作方式类似Blend Space。不同的是Aim Offset用于融合的是角色在不同方向瞄准的Pose,Blend Space则是将不同的动画进行融合。在使用Aim Offset之前需要先创建角色在不同方向的瞄准动作,如下图所示: 然后,基于角色骨骼创建Aim Offset资源,如下图所示: 双击Aim Offset资源并设置好Yaw和Pitch坐标轴,即可将之前创建好的瞄准Pose拖入其中对应的位置,如下图所示: 其中,上图显示了设置好上下左右各方向的瞄准Pose后的坐标轴。使用Aim Offset时可以直接将其拖入Anim Graph中并与Locomotion输出Pose融合,如下图所示: 转动角色到不同方向即可融合出不同的瞄准动画,如下图所示: 五、动画模块小结 Unreal 4引擎提供了丰富的动画融合、重定向、IK以及顶点动画等工具,方便制作出逼真的动画效果。同时,其提供的功能同样非常强大,且在细节处足见其深厚的技术功力。其中,Animation Montage提供了将动画自由切分、组合、循环以及跳转的功能;Blend Space以及Aim Offset提供了根据二维参数以及动画样本进行动画融合的功能;Post Process Animation Blueprint提供了对动画骨骼信息进行访问和使用的功能,并且配合AnimDymanic可以支持角色身上配件基于物体的动画;IK和Morph Target功能则添加了角色动画的精准度和细节。 参考文献 【1】https://docs.unrealengine.com/latest/INT/Engine/Animation/AnimHowTo/Retargeting/index.html#retargetingusingthesameskeleton

我要回帖

更多关于 图像渲染引擎有哪几种 的文章

 

随机推荐