2018-12-29 08:38:58 jxw167 阅读数 2140
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2372 人正在学习 去看看 杨石兴

BRDF作为比较流行的渲染算法,但凡成熟的引擎的都有此功能,我们自己写引擎也不例外,我在编写引擎时也会参考网上的一些资料,这也是学习的过程,先介绍BRDF,它是双向反射分布函数,说白了它是对于物体表面上光的处理,BRDF有很多模型,我们在编写Shader的时候也是基于这些模型。BRDF使用的数学模型是Cook-Torrance公式,如下所示:
在这里插入图片描述

我们实现的BRDF渲染算法会参考Unreal Engine 4中所使用的函数,其中D使用Trowbridge-Reitz GGX,F使用Fresnel-Schlick近似(Fresnel-Schlick Approximation),而G使用Smith’s Schlick-GGX,下面我们就重点实现DFG,先看D的实现算法:

在这里插入图片描述
我们根据上述公式可以写出Shader代码,如下所示:
在这里插入图片描述其实我们就是用编程语言写出算法而已,接下来再看F的算法实现,它主要是处理反射的,公式如下所示:
在这里插入图片描述
数学公式有了,下面我们将其应用到Shader编程中,我们用一个函数实现,代码如下所示:
在这里插入图片描述
另外,我们还可以在此基础上扩展,参考网址:
https://seblagarde.wordpress.com/2011/08/17/hello-world/
提供的代码,拿过来用到我们自己的引擎中,它的公式如下所示:
在这里插入图片描述
对应实现的代码如下所示:

float3 FresnelSchlickWithRoughness(float3 SpecularColor,float3 E,float3 N,float Gloss)
{
    return SpecularColor + (max(Gloss, SpecularColor) - SpecularColor) * pow(1 - saturate(dot(E, N)), 5);
}

再看最后一个G,再求G之前,我们先求Smith’s Schlick-GGX的算法公式,如下所示:
在这里插入图片描述
而UE4引擎做了一个小的改动,如下所示:
在这里插入图片描述
对应的Shader代码如下所示:

在这里插入图片描述我们的几何为了有效的估算几何部分,需要将观察方向(几何遮蔽(Geometry Obstruction))和光线方向向量(几何阴影(Geometry Shadowing))都考虑进去。我们可以使用史密斯法(Smith’s method)来把两者都纳入其中:

G(n,v,l,k)=Gsub(n,v,k)Gsub(n,l,k)

这样我们的G就完成了,对应的脚本代码如下所示:
在这里插入图片描述到这里,我们的FGD就完成了,下面将其带入我们第一步所列公式中,得到我们的BRDF渲染算法,实现的代码如下所示:
在这里插入图片描述
Epic Games使用了基于square roughness会产生更好的视觉效果,可以参考网址:
https://learnopengl.com/PBR/IBL/Specular-IBL
将其移植到我们的引擎中,对应的Shader代码如下所示:
在这里插入图片描述
对应着BRDF脚本如下所示:
在这里插入图片描述其在我们引擎中实现的效果图如下所示:
在这里插入图片描述

总结:
我们在实现某个技术时,会查阅大量资料,将其领会贯通后应用到我们自己的项目中,其实我们在项目开发时也是一样的,这个就是自我学习的能力,每个开发者都要有这样的能力。利用别人的技术实现自己的东西,这本身就是一种能力,有多年项目经验的人,经常做的事情就是复制粘贴,人家在复制粘贴的过程中已经对这些代码烂熟于心,出问题了能够立马定位到。最后给读者强调一点,渲染技术并不时鼓励存在的,多种渲染技术能够互相结合起来使用,BRDF可以跟forward和deferred结合起来使用。

2020-01-12 23:48:46 weixin_43958768 阅读数 5
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2372 人正在学习 去看看 杨石兴

关于UE shader 查询了一些别人的观点,自己做个笔记

材质=shader+贴图

unreal是否使用shader?

官方建议:不要使用,基础不好的话优化不行
如果是为了完成项目(效率高),使用材质编辑器的內置函数(足够支持99%的材质需求)
高级一点的渲染,如 烟雾,流体,使用shader(现在也可以用材质或者粒子表现出来)
材质编辑器中不能使用循环,但是可以使用时间函数time
事实上也只有在特定情况下,比如实现很复杂的材质效果,或者要用于手机平台,对性能损耗要求极高的情况下,才会自己用 hlsl 写 shader

unity什么情况下用shader?----不太清楚没学过unity
要想表现特殊的材质,比如镜面反射、水面、塑料等,就需要编程shader(Unity中有一些默认的shader)。

渲染管线
在这里插入图片描述
shader三大语言 HLSL / GLSL / CG
HLSL:基于 OpenGL的OPENGL Shading Language —unreal
GLSL:基于 DirectX的 High LevelShading Language —微软游戏99%使用
CG: NVIDIA公司的 C for Graphic —unity

在这里插入图片描述

GPU优越性
1:由于GPU具有高并行结构,所以GPU在处理图形数据和复杂算法方面拥有比CPU更高的效率。CPU大部分面积为控制器和寄存器,与之相比,GPU拥有更多的ALU( Arithmetic Logic Unit,逻辑运算单元)用于数据处理,这样的结构适合对密集型数据进行并行处理
在这里插入图片描述
2:GPU采用流式并行计算模式,可对每个数据进行独立的并行计算,所谓“对数据进行独立计算”,即,流内任意元素的计算不依赖于其它同类型数据,例如,计算一个顶点的世界位置坐标,不依赖于其他顶点的位置,所谓“并行计算”是指“多个数据可以同时被使用,多个数据并行运算的时间和1个数据单独执行的时间是一样的”。所以,在顶点处理程序中,可以同时处理N个顶点数据。

GPU缺陷
由于“任意一个元素的计算不依赖于其它同类型数据”,导致“需要知道数据之间相关性的”算法,在GPU上难以得到实现个典型的例子是射线与物体的求交运算。GPU中的控制器少于CPU,致使控制能力有限。另外,进行GPU编程必须掌握计算机图形学相关知识,以及图形处理API
gpu就是并行处理强大, cpu很多功能gpu都没有。 什么指令流水化, 多进程管理之类的。GPU工作原理是cpu 处理指令,遇到需要gpu的地方, 比如矩阵处理, 图像渲染, 会在显存中开辟一个小空间, 然后把这个矩阵打成很多小数据给gpu的流水线来加工, gpu的行为基本上是靠cpu调动的, 本身的指令不够强大(数据处理方面除外)

GPU运用
科学可视化计算:绘制光线投射算法
通用算法:线性代数,物理仿真,光线追踪

2019-10-24 10:33:36 qq826364410 阅读数 14
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2372 人正在学习 去看看 杨石兴

一、对象销毁

 

纯C++类销毁

尽量不要使用new、delete方案,可以使用智能指针。智能指针会使用引用计数来完成自动的内存释放。

使用MakeShareable函数可以来转化普通指针为智能指针。

TSharedPtr<YourClass> YourClassPtr = MakeShareable(new YourClass());

 

UObject类

无法使用智能指针来管理UObject对象。

UObject类采用自动垃圾回收机制。当一个类的成员变量包含指向UObject的对象,同时又带有UPROPERTY宏定义,那么这个成员变量将会触发引用计数机制。

垃圾回收器会定期从根节点Root开始检查,当一个UObject没有被别的任何UObject引用,就会被垃圾回收。可以通过AddToRoot函数来让一个UObject一直不被回收。

Actor类

Actor类对象可以通过调用Destroy函数来请求销毁。

 

二、从C++到蓝图

 

UPROPERTY

当需要将一个UObject类的子类的成员变量注册到蓝图中时,只需要借助UPROPERTY宏即可。

UPROPERTY(BlueprintReadWrite, VisibleAnywhere,Category="Object")

 

  • BlueprintReadWrite 可从蓝图读取或写入此属性。此说明符与 BlueprintReadOnly 说明符不兼容。
  • VisibleAnywhere 说明此属性在所有属性窗口中可见,但无法被编辑。此说明符与“Edit”说明符不兼容。
  • Blueprintable - 该类可以由蓝图扩展。
  • BlueprintReadOnly - 该属性可以从蓝图读取,但不能写入蓝图。
  • EditAnywhere - 该属性可以在原型和实例上的属性窗口中编辑。
  • Category - 定义该属性将出现在编辑器“细节(Details)”视图下面的哪个部分。这对于整理结构而言十分有用。
  • BlueprintCallable - 该函数可以从蓝图调用。仅用于组播委托。应公开属性在蓝图代码中调用。

 

UFUNCTION

UFUNCTION(BlueprintCallable, Category="Object")

可选的还有:

BlueprintImplementEvent:表示这个成员函数有其蓝图的子类实现,不应该尝试在C++中给出函数的实现,这会导致错误。

BlueprintNativeEvent:表示这个成员函数提供一个“C++的默认实现”,同时也可以被蓝图重载。需要提供一个“函数名_Implement”的函数实现,放置于.cpp中。

 

说明符众多,不便在此一一列出,但可以参考下面的链接:

UCLASS说明符列表

UPROPERTY说明符列表

UFUNCTION说明符列表

USTRUCT说明符列表

 

三、引擎相关的类

 

1. FPaths类

FPaths::GameDir() 游戏根目录

FPaths::FileExists() 文件是否存在

FPaths::ConvertRelativePathToFull() 转变相对路径为绝对路径

 

2.使用Log类

FString dir = FPaths::ProjectPluginsDir(); UE_LOG(LogTemp, Log, TEXT("%s"), *dir);

 

第一个参数是Log的分类,第二个参数是Log的类型,分为Log,Warning,Error。Log灰色,Warning黄色,Error红色。具体输出内容为TEXT宏,有三种常用符号:

1.%s字符串(Fstring)

2.%d整形数据(int32)

3.%f浮点型(float)

 

3.文件的读写与访问

FString str = FPaths::ProjectDir() + TEXT("Setting.xml"); if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*str)) { }

FPlatformFileManager::Get().GetPlatformFile()获得一个IPlatformFile类型的引用,这个接口提供了通用的文件访问接口。

 

4.字符串处理

FName 是无法被修改的字符串,不管出现多少次,在字符串表中只被存储一次。

FText 提供本地化支持。

FString 是唯一提供修改操作的字符串类。

 

5.ImageWrapper

转换图片类型;从硬盘导入图片作为贴图。

 

图片文件自身的数据是压缩后的数据,称为CompressedData

图片对应的真正的RGBA数据,是没有压缩的,与格式无关,称为RawData

所有的图片格式,都可以抽象为一个CompressedData和RawData的组合。

 

读取JPG图片:

  1. 从文件中读取为TArray的二进制数据
  2. 使用SetCompressData填充为压缩数据
  3. 将压缩后的数据借助ImageWrapper的GetRaw转换为RGB数据
  4. 填充RGB数据到UTexture的数据中

 

转换PNG图片到JPG:

  1. 从文件中读取为TArray的二进制数据
  2. 使用SetCompressData填充为压缩数据
  3. 将压缩后的数据借助ImageWrapper的GetRaw转换为RGB数据
  4. SetRaw将RGB数据填充到JPG类型的ImageWrapper中
  5. 使用GetCompressData获得压缩后的JPG数据
  6. 最后用FFileHelper写入到文件中

 

6.模块机制

虚幻源码目录包含四大部分:Runtime,Development,Editor,Plugin

每个部分有包含了很多个模块。

一个模块文件夹包含:Public文件夹,Private文件夹,模块构建文件.build.cs

只有通过XXX_API宏暴露的类和成员函数才能被其他模块访问。

 

1. 模块包含文件结构:

 

模块名.build.cs (模块构建文件,告知UBT如何配置自己的编译和构建环境)

public文件夹:

模块名.h

private文件夹:

模块名.cpp

模块名PrivatePCH.h (模块预编译头文件,加速代码的编译,当前模块公用的头文件可以放置于这个头文件中,当前模块所有的.cpp文件,都需要包含预编译头文件)

 

2. 引入模块

对于游戏模块,引入当前模块的方式是在游戏工程目录下的Source文件夹中,找到工程名.Target.cs文件。修改SetupBinaries函数,添加引入的模块。

 

对于插件模块,修改当前插件的.uplugin文件,在Modules数组中引入新的模块。

 

3. 虚幻引擎初始化

 

初始化分为预初始化和初始化。

  • 首先加载的是FPlatformFileModule,读取文件
  • 调用FEngineLoop::PreInit,预初始化PreInit:
  1. 设置路径:当前程序路径,当前工作目录路径,游戏工程路径
  2. 设置标准输出:GLog系统输出
  3. 初始化游戏主线程GameThread,把当前线程设置为主线程
  4. 初始化随机数系统
  5. 初始化TaskGraph任务系统,并按照当前平台核心数量设置TaskGraph的工作线程数量
  • 加载核心模块,FEngineLoop::LoadCoreModules,加载CoreUObject
  • 在初始化引擎之前,加载模块,FEngineLoop::LoadPreInitModules,包括:引擎模块、渲染模块、动画蓝图、Slate渲染模块、Slate核心模块、贴图压缩模块和地形模块。
  • 加载这些模块后,AppInit函数会被调用,进入引擎正式的初始化阶段。
  • 所有被加载到内存中的模块,如果有PostEngineInit函数,都会被调用从而初始化。借助IProjectManager完成。

 

游戏主循环

GEngine->Tick:最重要的任务是更新当前的World。无论是编辑器中正在编辑的World,还是游戏模式下只有一个的World。此时所有World中持有的Actor都会被得到更新。

很多任务无法在一次Tick中完成,就会分在多次Tick函数中完成。

 

8.虚幻内存分配

《游戏引擎架构》书中,对内存分配方案重点提到两个方面:

1. 通过内存池降低malloc消耗

2. 通过对齐降低缓存命中失败消耗。

 

在虚幻引擎中,主要使用还是Intel TBB内存分配器提供的scalable_allocator:不在同一个内存池中分配内存,解决多线程竞争带来的无谓消耗;cache_aligned_allocator:通过缓存对齐,避免假共享。

 

9.多线程

 

FRunnable

1. 声明一个继承自FRunnable的类FRunnableTestThread,并实现三个函数:Init、Run和Exit。

2. 借助FRunnableThread的Create方法,第一个参数传入FRunnable对象,第二个参数传入线程的名字。

FRunnableThread::Create(new FRunnableTestThread(0), TEXT("TestThread"));

 

Task Graph任务系统

由于采用的是模板匹配,不需要每个Task继承自一个指定的类FTestTask,只要具有指定的几个函数,就能够让模板编译通过。

GetTaskName:静态函数,返回当前Task的名字

GetStatId:静态函数,返回当前Task的ID记录类型,可以借助RETURN_QUICK_DECLARE_CYCLE_STAT宏快速定义一个并返回。

GetDesiredThread:指定Task在哪个线程执行

GetSubsequentsMode:用来进行依赖检查的前置标记

DoTask:最重要的函数,Task的执行代码

 

TGraphTask<FTestTask>::CreateTask(NULL, ENamedThreads::GameThread). ConstructAndDispatchWhenReady(0);

 

Std::Thread

C++11的特性

 

 

10.UObject

对象初始化分为:内存分配和对象构造阶段。

 

内存分配阶段:

获取当前UObject对象对应的UClass类的信息,根据类成员变量的总大小,加上内存对齐,然后在内存中分配一块合适的区域存放。

对象构造阶段:

获取FObjectInitializer对象持有的、指向刚刚构造出来的UObject指针,调用以FObjectInitializer为参数的构造函数(ClassConstructor),完成对象构造。

 

序列化

虚幻引擎序列化每个继承自UClass类的默认值,然后序列化对象与类默认对象的差异。节约了大量子类对象序列化后的存储空间。

反序列化:先实例化对应类的对象,然后还原原始对象数据

如果成员变量没有被UPROPERTY标记,不会被序列化。

如果成员变量与默认值一致,也不会进行序列化。

 

11.垃圾回收

垃圾回收算法

引用计数法

给每个东西保持一个引用计数。用时加1,不用减1。一旦减为0,进行回收。

优点:

引用计数不用暂停,是逐渐完成的。将垃圾回收的过程分配到运行的过程中。

缺点:

指针操作开销,每次使用都要调整引用计数,频繁使用的物品,频繁修改计数是很大的一笔开销。

环形引用,互相引用的对象,锅与锅盖配套,互相引用,导致两者引用计数都是1。但是实际上需要把锅和锅盖一起垃圾回收。

 

标记-清扫算法

是追踪式GC的一种。追踪式引用计数算法会寻找整个对象引用网络,从应用程序的root出发,利用相互引用关系,遍历其在Heap(堆)上动态分配的所有对象,没有被引用的对象不被标记,即成为垃圾;存活的对象被标记,即维护成了一张“根-对象可达图”。

优点:

没有环形引用问题,即使锅盖和锅互相引用,也可以垃圾回收。

缺点:

必须暂停,执行完垃圾回收算法后,才能继续做其他事情,导致系统有延迟。

如果只是丢掉垃圾而不整理,就会导致可用空间越来越细碎,最终导致大对象无法被分配。

整理内存:

C#中,启用Compact算法,对内存中存活的对象进行移动,修改它们的指针,使之在内存中连续,这样空闲的内存也就连续了,这就解决了内存碎片问题,当再次为新对象分配内存时,CLR不必在充满碎片的内存中寻找适合新对象的内存空间,所以分配速度会大大提高。但是大对象(large object heap)除外,GC不会移动一个内存中巨无霸,因为它知道现在的CPU不便宜。通常,大对象具有很长的生存期,当一个大对象在.NET托管堆中产生时,它被分配在堆的一个特殊部分中,移动大对象所带来的开销超过了整理这部分堆所能提高的性能。

 

Compact算法除了会提高再次分配内存的速度,如果新分配的对象在堆中位置很紧凑的话,高速缓存的性能将会得到提高,因为一起分配的对象经常被一起使用(程序的局部性原理),所以为程序提供一段连续空白的内存空间是很重要的。

 

虚幻引擎的智能指针系统

采用引用计数算法,使用弱指针方案来(部分)解决环形引用问题。

问题:往往忘记去判断哪些是强指针,哪些是弱指针,从而导致内存释放的问题。

智能指针系统管理非UObject对象。

 

 

UObject的标记清扫算法

UClass包含了类的成员变量信息,类的成员变量包含了“是否是指向对象的指针”,因此具备选择精确式GC的客观条件。利用反射系统,完成对每一个被引用的对象的定位。故采用追踪式GC。

虚幻在回收过程中,没有搬迁对象,应该是考虑到对象搬迁过程中修正指针的庞大成本。

选择了一个非实时但是渐进式的垃圾回收算法,将垃圾回收的过程分步、并行化,以削弱选择追踪式GC带来的暂停等消耗。

 

虚幻引擎的GC

是追踪式、非实时、精确式,非渐近、增量回收(时间片)。

 

垃圾回收函数 CollectGarbage

  1. 锁定
  2. 回收:标记和清除
  3. 解锁

 

虚幻的GC入口是CollectGarbage()

COREUOBJECT_API void CollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge = true);
void CollectGarbage(EObjectFlags KeepFlags, bool bPerformFullPurge)
{
    // No other thread may be performing UOBject operations while we're running
    GGarbageCollectionGuardCritical.GCLock();
    
    // Perform actual garbage collection
    CollectGarbageInternal(KeepFlags, bPerformFullPurge);

    // Other threads are free to use UObjects
    GGarbageCollectionGuardCritical.GCUnlock();
}

锁定/解锁

借助GGarbageCollectionGuardCritical.GCLock/GCUnLock函数,在垃圾回收期间,其他线程的任何UObject操作都不会工作,从而避免出现一边回收一边操作导致各种问题。

回收

回收过程对应函数CollectGarbageInternal中的FRealtimeGC::PerfomReachablilityAnanlysis函数,可以看做两个步骤:标记和清除。不过,增加了簇和增量清除,簇是为了提高回收效率,增量清除是为了避免垃圾回收时导致的卡顿。

 

标记过程:全部标记为不可达,然后遍历对象引用网络来标记可达对象。

清除过程:直接检查标记,对没有被标记可达的对象调用ConditionalBeginDestroy函数来请求删除。

 

标记过程的实现原理:

全部标记为不可达:虚幻引擎的MarkObjectsAsUnreachable函数就是用来标记不可达的。借助FRawObjectIterator遍历所有的Object,然后设置标记为Unreachable即可。

MarkObjectsAsUnreachable(ObjectsToSerialize, KeepFlags);

遍历对象引用网络来标记可达对象:

FGCReferenceProcessor ReferenceProcessor;
TFastReferenceCollector<FGCReferenceProcessor, FGCCollector, FGCArrayPool> 
    ReferenceCollector(ReferenceProcessor, FGCArrayPool::Get());
ReferenceCollector.CollectReferences(ObjectsToSerialize, bForceSingleThreaded);

 

这里有几个重要的对象TFastReferenceCollector、FGCReferenceProcessor、以及FGCCollector,分别介绍一下。

 

TFastReferenceCollector:用于可达性分析。

如果是单线程就调用ProcessObjectArray()函数,遍历UObject的记号流(token stream)来查找存在的引用,如果没有记号流,调用UClass::AssembleReferenceTokenStream()函数就是用生成记号流(token steam,其实就是记录了什么地方有UObject引用),用CLASS_TokenStreamAssembled来保存。

如果是多线程,创建几个FCollectorTask来处理,最终还是调用ProcessObjectArray()函数来处理。

 

UClass::AssembleReferenceTokenStream()函数

如果没有创建token stream,那么就会遍历当前UClass的所有UProperty,对每个UProperty调用EmitReferenceInfo()函数,这是一个虚函数,如果它有父类,那么就会调用父类的AssembleReferenceTokenStream()函数,并把父类添加到数组的前面,最后加上GCRT_EndOfStream到记号流中,并设置CLASS_TokenStreamAssembled来保存。

 

FGCReferenceProcessor

处理由TFastReferenceCollector查找得到的UObject引用。

如果Object->IsPendingKill()的返回值为true且允许引用消除,那么把Object的引用设置为NULL

否则,调用ThisThreadAtomicallyClearedRFUnreachable()清除不可达标记,标记为可达,如果这个UObject是簇的根,调用MarkReferencedClustersAsReachable函数,把当前簇引用的其他簇标记为可达,当这个UObject簇根不可达,整个簇都会被回收。

 

基于簇的垃圾回收

其中跟Cluster相关的几个函数在UObjectBaseUtility中,如下图所示: 

 

用于加速Cook后的对象的回收,所以编辑器下不会使用簇来GC。能够作为簇根的为UMaterial和UParticleSystem,基本上所有的类都可以在簇中。当垃圾回收阶段检查到一个簇根不可达,整个簇都会被回收,加速回收的效率,节省了再去处理簇的子对象的时间。

 

FGCCollector

继承自FReferenceCollector,HandleObjectReference()和HandleObjectReferences()都调用了FGCReferenceProcessor的HandleObjectReference()方法来进行UObject的可达性分析。

 

清除过程的实现原理:

为了减少卡顿,虚幻增加了增量清除的概念(IncrementalPurgeGarbage()函数),就是一次删除只占用固定的时间片,一段段进行销毁的触发。

  • 需要注意的是,由于会在两次清除时间内产生新的UObject,故在每次进入清除时,需要检查GObjCurrentPurgeObjectIndexNeedsReset,如果为true,那么重新创建一个FRawObjectIterator用于遍历所有的UObject。
  • 通过GObjCurrentPurgeObjectIndex来循环遍历所有的FUObjectItem(记录了UObject相关的信息,比如ClusterIndex,Flags等),如果对象不可达且IsReadyForFinishDestroy()为true,那么我们就调用ConditionalFinishDestroy();
  • 而如果IsReadyForFinishDestroy()为false,那么把它添加到GGCObjectPendingDestruction中去。待到下一次增量清除时,如果GGCObjectPendingDestructionCount不为0且IsReadyForFinishDestroy()为true,那么我们就调用ConditionalFinishDestroy()。
  • 当然如果有时间限制,到了时间限制,也会退出。

 

虚幻的渲染流程

虚幻引擎在FEngineLoop::PreInit中对渲染线程进行初始化。

渲染线程的启动位于StartRenderingThread全局函数中。

  1. 创建渲染线程类实例
  2. 通过FRunnableThread::Create函数创建渲染线程
  3. 等待渲染线程准备好,从TaskGraph取出任务并执行
  4. 注册渲染线程
  5. 创建渲染线程心跳更新线程

渲染线程的主要执行在全局函数RenderingThreadMain中,游戏线程会借助EQUEUE_Render_COMMAND宏,向渲染线程的TaskMap中添加渲染任务。渲染线程则不断的提取这些任务去执行。

这里需要注意,渲染线程并非直接向GPU发送指令,而是将渲染命令添加到RHICommandList,也就是RHI命令列表中。由RHI线程不断取出指令,向GPU发送。并阻塞等待结果。此时,RHI线程虽然阻塞,但是渲染线程依然正常工作,可以继续处理向RHI命令列表填充指令,从而增加CPU时间的利用率,避免渲染线程凭空等待GPU的处理。

 

渲染架构

虚幻引擎对于场景中所有不透明物体的渲染方式,是延迟渲染

对于半透明物体的渲染方式,是前向渲染

 

在虚幻引擎中,先进行延迟光照计算不透明物体,然后借助深度排序,计算半透明物体。

FDeferedSceneRender::Render函数

 

1、初始化视口 InitViews

进行必要的可见性剔除。分为三步:预设置可见性,可见性计算,完成可见性计算。

 

预设置可见性 PreVisibilityFrameSetup

1.根据当前画质,设置TemporalAA的采样方式,同时确定采样位置。采样位置用来微调视口矩阵。TemporalAA采样,每一帧渲染的时候,让这个像素覆盖的位置进行微弱的偏移,然后混合前面几帧的渲染结果。

2.设置视口矩阵,包括视口投影矩阵和转换矩阵。

 

可见性计算 ComputeViewVisibility

1.初始化用于可视化检测的缓冲区,位数组,用0和1表示是否可见。翻译为位图BitMap

2.视椎体剔除,对应函数FrustumCull,该函数内部使用ParallelFor函数的线性剔除,进行并行化的异步剔除。

3.遮挡剔除

4.根据可见性位图,设置每个需要渲染对象的可见性,即Hidden flags

5.开发者控制对象可见

6.获取所有对象的渲染信息,对应函数是每个RenderProxy的GetDynamicMeshElements函数。

 

网格物体组件对应的容器是RenderProxy,材质对象的容器是MaterialRenderProxy

 

完成可见性计算 PostVisibilityFrameSetup

1.对半透明的对象进行排序。半透明对象的渲染由于涉及互相遮挡,必须按照从后往前的顺序来渲染。

2.对每个光照确定当前光照可见的对象列表

3.初始化雾与大气的常量值。

4.完成对阴影的计算。包括对覆盖整个世界的阴影,对固定光照的级联阴影贴图和对逐对象的阴影贴图的计算。

 

虚幻引擎的剔除方式是借助ParallelFor的线性剔除,并行化的线性结构剔除在性能上优于基于树的剔除。

 

2、PrePass 预处理阶段

降低Base Pass的渲染工作量。通过渲染一次深度信息,如果某个像素点的深度不符合要求,这个像素点就不会进行工作量最大的像素渲染器计算。

不是基于分块的GPU,渲染器的EarlyZPassMode参数不为DDM_None,或GEarlyZPassMovable不为0,才会进行PrePass计算。

 

对象的渲染按照设置渲染状态,载入着色器,设置渲染参数,提交渲染请求,写入渲染目标缓冲区的步骤进行。

 

设置渲染状态 SetupPrePassView

关闭颜色写入,打开深度测试与深度写入。PrePass不需要计算颜色,只需要计算每个不透明物体像素的深度。

 

渲染静态数据

三个绘制列表由静态模型组成,通过可见性位图控制是否可见。

  1. 只绘制深度的PositionOnlyDepthDrawList
  2. 主要绘制不透明物体的DepthDrawList
  3. 带蒙版的深度绘制列表MaskedDepthDrawList,蒙版对应材质系统中的Mask类型

渲染动态数据

通过ShouldUseAsOccluder函数询问Render Proxy是否被当做一个遮挡物体,是否为可移动,决定是否需要在这个阶段绘制。

 

写入渲染目标缓冲区

通过RHI的SetRenderTarget设置。

 

TStaticMeshDrawList::DrawVisible函数

绘制可见对象

绘制可见对象的基础是可见对象列表,在绘制之前,每个绘制列表已经进行了排序,尽可能共用同样的绘制状态。

每个绘制列表都共用以下着色器状态,区别只是在于具体参数不同:

  1. 顶点描述 Vertex Declaration
  2. 顶点着色器 Vertex Shader
  3. 壳着色器 Hull Shader
  4. 域着色器 Domain Shader
  5. 像素着色器 Pixel Shader
  6. 几何着色器 Geometry Shader

 

载入公共着色器的信息 SetBoundShaderState 和SetSharedState

SetBoundShaderState 载入需要的着色器

SetSharedState 对于TBasePass,设置顶点着色器和像素着色器的参数。

 

逐元素渲染

1.对于每个DrawingPolicy调用SetMeshRenderState函数,设置渲染状态。包括调用每个着色器的SetMesh函数,以设置与当前Mesh相关的参数

2.调用Batch Element的DrawMesh函数,完成绘制。调用RHICmdList的DrawIndexedPrimitive函数,指定顶点缓冲区和索引缓冲区的位置。

 

3、BasePass

极为重要的阶段,通过对逐对象的绘制,将每个对象和光照相关的信息都写入到缓冲区中。

BasePass和PrePass的过程非常接近,分为设置渲染状态,渲染静态数据和渲染动态数据。

 

设置渲染状态

1.如果PrePass已经写入深度,则深度写入被关闭,直接使用已经写入的深度结果。

2.通过RHICmdList.SetBlendState,打开前4个渲染目标的RGBA写入。TStaticBlendStateWriteMask用模板参数定义渲染目标是否可写入,最高支持8个渲染目标。

RHICmdList.SetBlendState(TStaticBlendStateWriteMask<CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA>::GetRHI());

3.设置视口区域大小。这个大小会因为是否开启InstancedStereoPass而有所变化。

 

渲染静态数据

如果PrePass已经进行深度渲染,那么会先渲染Masked蒙版对象,然后渲染普通不透明对象。否则,先渲染不透明对象,再渲染蒙版对象。

 

渲染动态数据

与PrePass基本相同

 

BasePass采用MRT(Multi_Render Target)多渲染目标技术,从而允许Shader在渲染过程中向多个渲染目标进行输出。

渲染目标来自哪里?

渲染目标由当前请求渲染的视口(Viewport)分配,对应FSceneViewport::BeginRenderFramw函数。

如何写入?输出到何处?

并没有在C++代码中,而是在Shader着色器代码中。打开Engine/Shader/BasePassPixelShader.usf文件,大体过程:

  • 通过GetMaterialXXX函数,获取材质的各个参数,比如BaseColor基本颜色,Metallic金属等。
  • 然后,填充到GBuffer结构体中
  • 最后,通过EncodeGBuffer函数,把GBuffer结构体压缩、编码后,输出到SV_Target。

 

 

RenderOccusion渲染遮挡

虚幻引擎的遮挡计算,实质上是在PrePass中直接进行基于并行队列的硬件遮挡查询。除非在r.HZBOcclusion这个控制台变量被设置为1的情况下,或者有些特效需要的情况下,才会开启Hierarchical Z-Buffer Occlusion Culling 作用遮档查询。

全平台默认关闭

总体来说,这个步骤是为了尽可能剔除处于屏幕内但是被其他对象遮挡的对象。在视口初始化阶段,剔除了处于视锥体之外的对象。但是依然有大量对象处于视锥体内,却被其他对象遮挡。比如一座山背面的一大堆石头,这些石头能够正常通过我们的视锥体遮挡测试,却并不需要渲染。

因此, HZB渲染遮挡技术被用于解决这个问题,通常的HZB步骤如下:

  • (1)预先准备屏幕的深度缓冲区,这个缓冲区将会作为深度测试的基础数据。因此,这个步骤必须在PrePass之后,如果没有PrePass,则必须在BasePass 之后。
  • (2)逐层创建缓冲区的Mipmap级联贴图。层级越高,贴图分辨率越低,对应的区域越大。而每个层级对应这个区域“最远”元素到屏幕的距离(深度最大值)。
  • (3)计算所有需要进行测试的对象的包围球半径,根据这个半径,选择对应的深度缓冲区层级进行深度测试,判断是否被遮挡。这个的用意在于,如果对象较大,我们可以直接用更高的层级进行测试,这个对象的深度若比这个层级对应的距离还远,那么该对象一定被遮挡,因为层级对应的是这一片区域中可见元素的最远距离。

需要注意的是, OpenGL平台下不会进行这个测试。这个步骤中的第二步可以使用像素着色器多次绘制完成级联贴图层级,第三步则可以使用计算着色器ComputeShader,或者使用顶点着色器进行计算,将结果写入到一个渲染目标中。从而借助GPU的高度并行化来加速这个遮挡剔除过程。

这个步骤输出的结果会被用于下一帧计算,而不是在本帧。

 

光照渲染

对应函数RenderLights,光照渲染与阴影渲染是分离的,阴影渲染是在视口初始化阶段完成的,光照渲染大体步骤如下:

  1. 收集可见光源。对可见性的判断,利用视口初始化阶段保存的VisibleLightInfos信息,以当前Id查询即可获得结果。对每个光源构建FLightSceneInfo结构,然后通过ShouldRenderLights对光源是否需要渲染进行计算。
  2. 对收集好的光源进行排序。将不需要投射阴影、无光照函数的光源排在前面。
  3. 如果是TiledDeferredLighting,则通过RenderTiledDeferredLighting对光照进行计算。如果是PC平台,使用RenderLight函数进行光照计算。
  4. 如果平台支持Shader Model 5,则会计算反射阴影贴图与PLV信息。

 

核心光照渲染RenderLight函数

每个光源都会调用这个函数,遍历所有视口,计算光照强度,并叠加到屏幕颜色上。

 

1. 设置混合模式为叠加

2. 判断光源类型

平行光源

  • 载入延迟渲染光照对应的顶点着色器(TDeferredLightVS)和像素着色器(TDeferredLightPS)
  • 设置光照参数
  • 绘制一个覆盖全屏幕的矩阵,调用着色器。

非平行光源

  • 判断摄像机是否在光源范围内
  • 如果是,关闭深度测试,从而避免背面被遮盖部分不进行光照渲染
  • 否则,打开深度测试,以加速渲染
  • 载入着色器
  • 设置光照参数
  • 根据是点光源还是聚光灯,绘制一个对应的几何体,从而排除几何体外对象的渲染,加速光照计算。

 

ShaderMap

顶点工厂:负责抽象顶点数据以供后面的着色器获取,从而让着色器忽略由于顶点类型造成的差异。

当前着色器继承自FMaterialShader,则对每个材质类型编译出一组对应渲染管线的着色器

当前着色器继承自FMeshMaterialShader,则对每个材质类型的每个顶点工厂类型编译出一组顶点着色器和像素着色器。

通过GetMaterialXXX,可以获取材质的参数。

 

2019-03-30 18:41:40 lf2007hf 阅读数 218
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2372 人正在学习 去看看 杨石兴

使用自定义字体

在这里插入图片描述
未使用过LGUI插件的同学可先看看之前的博客在UE4里制作3D UI(一)

这一节我们来学习一下如何在让LGUI插件显示自定义的字体。
LGUI使用Freetype来渲染字体,所以Freetype支持的字体格式都可以拿来用,比如ttf,ttc,otf。
首先我们准备个字体文件。从windows自带的字体目录中找个字体,比如我用的华文新魏:
在这里插入图片描述
把字体文件复制到ue4的工程目录。

然后在ue4的ContentBrowser中空白处点右键,在弹出的菜单上选择LGUI/LGUIFont:
在这里插入图片描述
这样就创建了LGUI字体,重命名为MyLGUIFont:
在这里插入图片描述
双击MyLGUIFont打开编辑窗口,在FontSourceFile属性后面点击…,在弹出的文件选择窗口中选中刚才复制的字体文件:
在这里插入图片描述
这样我们的字体就创建完成了,下面我们来创建个UIText显示这个字体看看。

点击LGUI Tools/CreateUIPanel/UIPanelActor创建个UIPanel,然后创建个UITextActor作为UIPanel的子对象:
在这里插入图片描述
选中UIPanelActor,调整位置为(0,0,400),旋转为(-90,0,90):
在这里插入图片描述
选中UITextActor,修改Size为40,Color为黑色,文字内容为“中国智造”,这个时候看视口中显示的文字还是默认字体的样子:
在这里插入图片描述
重点来了,找到Font属性,把刚才创建的MyLGUIFont拖拽到Font属性上,然后看文字是不是就变成新的字体了?:
在这里插入图片描述

2017-09-12 10:44:31 OnafioO 阅读数 667
  • 从这里开始虚幻4-Editor介绍 v4.18

    本课程系列取名英译系列,是录制人员参考国外英文原版经典教程,结合中国人的习惯录制而成。希望能够给大家以帮助。从这里开始虚幻4系列教程,是Unreal的官方发布的入门教学,非常经典,是学习Unreal的佳入口。

    2372 人正在学习 去看看 杨石兴


原文地址:   https://www.unrealengine.com/products/unreal-engine-4     unreal enginer介绍


我的UE4学习(一)


你曾想过用连线的形式来编写程序么;
你曾想过通过编辑工具就可以来创建类,并在vs中自动生成类和函数,头文件和cpp文件么;
你曾想过粒子效果可以成千上万的渲染,并且牛逼的粒子还可以反射光线么;
你做的效果可以能会超越电影画质的游戏;
你曾想象和意淫一下,C++不用编译可以实时的在编辑图形界面上调试和添加断点么;

我看了UE4代码的一个小小的角落,就觉得自己来到了一个无知的世界。你的想象,别人已经实现了。
自惭形秽的同时,希望能学到人家的一二。

Welcome To  The Future! 牛逼闪闪的UE4 来了!

ue4需要的基本电脑配置:
Win7 64位系统或Mac OSX10.9.2以上版本
Intel 或AMD处理器 2.5GHZ以上。
兼容DX11 的GPU。
8G内存,不够的请自动升级!
 

一、特性说明

特性

Ue4是一款专业开发高质量游戏的平台开发工具。Ue4的渲染加快可以满足开发者完成出色的画面,同时也可以匹配低端系统。

全新工作流的特性和深度的工具箱可让开发者快速的实现想法,并可以看到效果,同时c++开源带来了全新的体验。

Ue技术支持几百种游戏,实时3D电影,训练仿真,可视化等。在过去的15年中,成千上万的个人和团体围绕者ue开发技术建立了公司,成为了一种职业。

二、 具体特性

(一)

UE4支持更高级的DX11的渲染特性,比如:全屏的HDR映像,每个场景上千种动态光照,artis-可编程镶嵌技术和disPlacement(位移)技术,基于物理特性的阴影和材质,IES光照轮廓等等。

(二)

颗粒特效·

颗粒特效编辑器VFX为我们提供了创建精细的火、烟、血、尘土、污垢、碎石等的工具。颗粒管线包括的了快速低成本GPU粒子仿真,可与深度缓冲交互的碰撞系统。场景中数百万的动态粒子可以接受和反射光线,你可以控制各种粒子效果参数,包括有大小,颜色,密度,下降散开和反弹。

 

(三)

新材质管道

UE4通过使用基于物理模型的shading,提供了前所未有的控制方式,你可以观察和感受角色和对象。快速创建一个宽阔场景面,它是经得起对细节的考验。在像素级别上,层材质和微调值,可以满足你的任何想象。

  

(四) 

蓝图

蓝图可视脚本带把你有创造力的想法,展示到可视化的游戏中。使用蓝图,任何人不需要写一行代码,可以快速原型和建立玩家内容。蓝图可以在创建级别、对象和玩家行为,修改用户界面,调整输入控制等操作中发挥作用。

 

(五) 

实时的蓝图调试debugging

在游戏测试的中,通过蓝图可视化脚本内置了一个debugger,你可以与可视化的游戏流程交互并检查属性值。通过在蓝图的可视节点上设置断点,来随意的暂停游戏,查看游戏的各种状态。逐步的通过事件和功能,随意的变化,优化你的游戏。

你之前有想象过,不通过代码来调试程序么?你想过你能在编辑工具的界面上设置断点和调试么?

 

(六) 

内容浏览

使用UE4内容浏览器,用来导入,组织,搜索,标记,过滤,修改在unreal编辑器中的游戏的对象。实时动态预览图使用截屏功能,可以被修改保存。创建任何的对象集合,都可以被个人或共享给其他开发者。

 

(七) 

角色动画

使用角色动画工具集,可以来编辑骨骼动画,骨骼网格,sockets,动画蓝图等。这个是多用途工具,你可以预览动画序列,变形目标体,也可以设置动画混合空间和蒙太奇剪切。当然,也可以使用PHAT(物理编辑工具集)用来修改骨骼网格的物理和碰撞属性。

 

(八) 

过场动画

UE4的过场动画工具集提供了在场景级别的直接的级别控制,动态玩家镜头和电影。通过一个类似的非线性编辑器,能够是你在场景中设置精细的动作细节和动画场景属性,产生很棒的电影片段。

 

 

(九) 

地形和枝叶

我们使用景观系统可以创建大型开放的环境,然而平铺地形的方法的命令条数是巨大的,多亏了强大的LOD系统和高效的内存使用。使用枝叶工具,快速绘制或删除所有排序的地形组件,从而达到修改大型室外场景的目的。


(十)

后处理效果

UE4的后处理特性可以使你完美的体验对于场景的观感。电影般的效果,举几个例子:环境立方贴图,环境遮挡,bloom,颜色分级,景深,眼适应,镜头耀斑,光轴,抗锯齿,色调映射等。

 

 (十一) 

完全源码接入

UE4带有全部的C++代码,你可以修改和扩展UE编辑工具和UE的子系统,包括物理系统,声音系统,在线系统,动画系统,渲染系统和slate 界面系统。全部控制引擎和角色代码,你可以干任何事情。

 

(十二) 

专业的源码控制

UE4委托GitHub来源码开源,这样由很清晰的版本控制文档和版本跟踪。另,Perforce的版本控制和ApacheSubversion支持,对开发者而言都是用户条款的。不管,你的团队规模,均享有在UE基础版本上开发和设计的权利。

 

(十三) 

C++代码可视

在游戏角色和对象,UE4代码允许直接浏览c++函数功能,直接跳转到在vs的源代码行来做更改,从而节约了时间。

 

(十四)  

热重载功能

使用UE很受欢迎的热重载特性,你可以在你游戏运行的过程中,更新你的角色代码。这个工具你开业用C++编写代码,并且可以不用暂停游戏来观看代码的运行效果。

 

  

(十五) 

模拟和仿真

使用模拟仿真模式,可以快速调试和更新角色行为,这样你在编辑视口中,可以运行一下程序的逻辑,观察角色的表现。在模拟仿真的编辑环境下,全屏化运行游戏,你可以忽略UI,全身心的关注角色的变化。

 

(十六) 

即时游戏预演

更新游戏和使用即时预演系统可以立即在游戏中任意地方产生一格玩家,并且不用等待文件保存。

 

(十七) 

获取和丢失特性

获取和丢失特性,允许你在编辑的游戏中,可以在任何时间来以玩家视角来编辑和控制相机以便观察那些行为不正确。

 

(十八) 

人工智能

UE4的角色框架和AI系统,可以使角色拥有世界周围的空间意识,从而使他们更智能的行为。当你的运行对象在总是在优化路径,动态导航实时的更新网格

 

(十九) 

音频

使用UE4的音频编辑可以建立音频管线,定义游戏的重复音效。

 

(二十) 

 领先的中间件集成

UE4的主要的配件程序提供了很多的引领工业级的中间件技术,包括英伟达的PhysX,Autodesk的Gameware,Enlighten,暗影,Oculus VR等软件库。

 

三、 

样例使用说明

虚幻4的样例展示了引擎使用高要求粒子效果的能力。在运行样例之前,你需要安装vs2013runtimes.

使用批处理文件可以进行设置,比如:分辨率x=1920,分辨率y=1080等。

 

-------------------------------------

UE4中蓝图宏的使用

阅读数 3007

球谐函数光照小结

阅读数 2408

没有更多推荐了,返回首页