/images/avatar.png

Papalqi

Lighting系列1

UE4 Lighting系列1 光照基础 UE4中的所有光源都是通过lightmass和直接照明两种方式作用于物体。同时灯光分直接光和间接光。 各种灯光的Movable和Stationary类型都会对物体产生直接光照明。所有Static类型灯光,自发光材质物体以及Stationary经过lightmass后会对物体产生间接光照明。反射也是一种间接光照明(动态天光产生的天光反射属于直接光)。 如果场景中拥有这两种光照类型,那么我们就说这是全局光照。在默认情况下,光源都是设置为全局光照。 虚幻引擎的间接光照是通过lightmass生成lightmap来完成的 1.1 Direct Light 直接从光源所在处打出射线进行计算,对于有光源半径的光而言会多做一些计算。 直接光都是动态光,可以实时变化颜色,亮度等等 1.2 Indiret Light 间接光照的计算核心是光子映射算法. 所有的间接光都是静态光,但我们仍然可以通过后期以及材质来模拟的实时调整间接光的颜色和亮度。 1.2.1Lightmass Lightmap为HDR,存有灯光方向信息。UE4使用Lightmass对光照进行预计算,以节省动态光照计算的成本。Stationary light build完灯光后还有Shadowmap,如果有Stationary天光会有sky occlusion map(Bent normal信息)。 材质,物体,灯光,后期都可以控制Lightmass。 1.2.1.1Lightmass的设置 最主要的两个参数: Static Lighting Level Scale Indirect Lighting Quality 对时间的影响 Lightmass portal提高lightmap的品质 Light Scenarios提供静态光变化的可能性 1.2.1.2光照贴图需要注意以下问题: 不要有重叠的部分 不要超过0~1的UV空间 Flag-Mapping并不是最好的方式且经常导致光照贴图错误 尽量占满UV空间 如果模型很大而且复杂,最后分成数个物体,这样也能有助于裁剪等机制 尽量减小光照贴图分辨率以减少贴图尺寸 相互不接触的线之间要保持至少2像素的距离,以防止光照污染 展UV的时候注意Min Lightmap resolution, 定义了UV块之间的距离。这个值要小于等于lightmap resolution,否则会出现lightmap像素无法匹配而溢出的问题。 1.2.2ILC (indirect light cache间接光缓存) ILC是对动态物体进行间接光的 球形谐波是一种非直射光cache的编码方式,也可以理解为如何把一张图片IBL编码到一个球体上的方式来表现这个位置光线的方向,强度,遮挡等信息。可以理解成一个非常低分辨率的cubemap(2*2像素每个面);编码的频率多少决定精度的高低, band的数量高低可以理解成LOD的概念,低级别的band可以表达大致的信息,但缺少细节,高级别的band里包含了更细腻的信息。 band的数量越多精度越高。一般游戏里出于实时渲染和内存的考量会使用2 band, 3band 一般使用于high end的渲染,比如Wetadigital在他们的电影渲染流程里应用的那样。 Cache的点的密度在电影渲染里会非常高,当然UE4里也是可以调节cache密度来符合不同性能和品质的要求 1.2.3VLM(Volumetric ligthmap) ILC也是对动态物体进行间接光的 ILC VS VLM

UE4材质系统源码剖析

材质(Material) 是可以应用到网格物体(Mesh)上的资源,用它可控制场景的可视外观。从较高的层面上来说,可能最简单的方法就是把材质视为应用到一个物体的"描画"。但这种说法也会产生一点点误导,因为材质实际上定义了组成该物体所用的表面类型(质感)。您可以定义它的颜色、它的光泽度及您是否能看穿该物体(半透明)等等。 在这里是默认大家对使用材质系统拥有基本的基本操作经验,所以本文并不会对一般的简单操作做任何的解释和说明,如果大家想对Unreal材质系统的基本操作感兴趣的话,参考官方文档: https://docs.unrealengine.com/zh-CN/Engine/Rendering/Materials/index.html 对与某些高端的材质TA向的技巧和经验总结以后会在之后的文档中进行总结和归纳。 因此,本文的重点主要是来分析材质系统的运行逻辑和手段,从自动的代码生成到生成变体的过程,最终到如何和几何信息绑定提交渲染的一整套的流程。 一.简介 由于后文即将阐述的编译功能或者变体功能更加的抽象和难以理解,所以我们需要一个比较直观的切入来审视Unreal的整个材质系统。 我希望呈现一种较为直接的介绍模式,这一切就在我们打开一个Materal.asset开始。其对应于代码中的UMaterial。 1.1 PBR和它的输出 当我们打开材质编辑器,最开始我们能最直接看见的就是我们的材质的输出。如下图的左 右图是迪斯尼的PBR参考,这是unreal 的PBR材质节点。 对应代码中,是对应的各种FXXXMaterialInput。材质输出属性在UE4中的基类叫FMaterialInput,它有一些子类用于表达具体的输出属性的具体数据类型,如FColorMaterialInput,FScalarMaterialInput,FVectorMaterialInput等。 UMaterial中对应的材质输出属性和材质编辑器上的输出节点属性几乎是一一对应的(注,显示出来的名字可能会和变量量不同,因为名字因材质类型,光照模型而显示不同的名字),完整定义如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { FColorMaterialInput BaseColor; FScalarMaterialInput Metallic; FScalarMaterialInput Specular; FScalarMaterialInput Roughness; FVectorMaterialInput Normal; FColorMaterialInput EmissiveColor; FScalarMaterialInput Opacity; FScalarMaterialInput OpacityMask; FVectorMaterialInput WorldPositionOffset; FVectorMaterialInput WorldDisplacement; FScalarMaterialInput TessellationMultiplier; FColorMaterialInput SubsurfaceColor; FScalarMaterialInput ClearCoat;FScalarMaterialInput ClearCoatRoughness; FScalarMaterialInput AmbientOcclusion; FScalarMaterialInput Refraction;FVector2MaterialInput CustomizedUVs[8]; FMaterialAttributesInput MaterialAttributes;FScalarMaterialInput PixelDepthOffset; FShadingModelMaterialInput ShadingModelFromMaterialExpression; } 这里比较重要的是有一个WITH_EDITORONLY_DATA的宏,其关闭和IOS竟然相关!那按道理来说如果平台不同的话确实在最后呈现上会有明显的不同。

UE4变体研究 源码剖析

在编辑器打开时,会从资源包中LoadPackage,我们这里只关心的是材质问题。每一个Obj,都会调用自己的Obj->ConditionalPostLoad() 这里已经是在加载的后端了,在这里面会调用子类的PostLoad() 1 2 3 4 5 6 7 8 9 10 for (int32 i = 0; i < ObjLoaded.Num(); i++) { UObject* Obj = ObjLoaded[i]; check(Obj); #if WITH_EDITOR //一顿操作 #endif //又一顿操作 Obj->ConditionalPostLoad(); } 我们只看 UMaterial::PostLoad,如果调用到这里,也就是说我们加载的是一个材质。这是一个非常长的函数。就不在这里展开了。 这里会调用他父类的PostLoad,而UMaterial的父类就是UMaterialInterface,而他们的区别,和主要的成员分析见Unreal Material类的关系 在UMaterialInterface::PostLoad()中又会调用UMaterial::CacheResourceShadersForRendering Cache resource shaders for rendering. If a matching shader map is not found in memory or the DDC, a new one will be compiled.(关于什么是DDC,听过很多次) The results will be applied to this FMaterial in the renderer when they are finished compiling.

UE4制作材质和打光前的准备——lookdev

为什么我们需要LookDev 我们在工作中多数情况都是分工合作,把比较大的资源拆分开来给不同的人员进行制作,最后每个人对资源进行提交审核并组合到一起。因为资源要求统一,所以正常来说每个人提交的资源表现效果应该基本一致,但是因为制作环境的不同,可能会导致单独渲染出来的图差异不大,但是将两个资源放在同一个光照环境下却效果差异很大的情况。 那我们该怎么杜绝这种情况的发生呢?这就是lookdev环节存在的重要意义之一了。我们先聊聊LookDev在CG流程的哪个环节,以及主要内容吧。“Lookdev,或者Look Development这项工作一般会在模型贴图和灯光之间。一般规模大一些的工作室部门分工比较详细,会有专门的lookdev部门。有些公司是灯光部门的人负责lookdev,也有些公司是贴图部门负责lookdev。但是不管有没有这个部门, lookdev这个步骤是不能少的。 简单来说 lookDev的一个主要内容就是让不同人员制作的不同资产在统一光照环境下有统一的表现效果。 好的HDR贴图 1、要有一个相对明确的光源,这样能更准确的体现出模型的高光情况。 2、明暗交界比较清晰,使模型结构清晰不显得平。 3、光照也没有过于死黑的地方。 上图中,虽然光照比较均匀,但是光源不明确,无法产生准确的高光,会让人感觉整个材质是无高光的,但实际上模型是有较强反射存在的。但是实际使用中,还是要根据项目情况以及项目风格来确定一个项目统一的HDR贴图。 unreal能借鉴的技术 中灰球、镜面球和色卡主要作用就是做绝对的参考。就像做实验一样,制作cg内容也需要控制变量才能确保资产质量的统一,和材质的准确度。通常实景拍摄和素材拍摄的时候也会拍摄当场的球和色卡。进入后期制作时会用色卡统一较对一切内容,确保从贴图到lookdev到灯光渲染 所有人的参考图和素材等等颜色一致。 一个灰球,一个全反射的金属球(铬球),以及一个标准色卡。这三个元素实际上不仅仅是再lookdev环节开始使用,而是基本上从贴图开始就会用到。从矫正贴图素材颜色,到lookdev做材质时的参考,再到灯光部门摆灯等等,都需要这些元素的参考。 灰球使用的参数是线性空间下的0.18,也就是SRGB下的0.5的中灰球,可以让我们比较方便的观察到光源方向以及强度。铬球是一个全反射金属材质,用来观察高光及反射的信息。 最后一个标准色卡,是用来协助我们对图像颜色进行校准,对图像进行标准校色使用的。我 如何打光在准备场景时 ![image5.png][5] 关闭自动曝光 (auto exposure) Guanbi SSAO和SSR 保持默认的tone mapper(色调映射) 关闭Vignetting 关闭bloom 建立两个材质球 第一个Chrome sphere ,第二个Gray 一般是使用一个灰球和一个铬球收集拍摄场景的全局光照信息。 灰球又叫diffuse ball,来判断全局的光照效果,主光方向,阴影的范围和暗度。我们特别熟悉的是50度灰,现在我们要再多记住一个值叫18度灰,为什么是18度。18%就是传说中人眼视觉中间调,定一个测光的标准。 反射光测光,通俗地讲就是在曝光补偿为±0的时候拍一张照片,然后将照片饱和度降到0,定义测光区域的输出总是和百分之十八的灰是一样的。 一米宽,10米厂的板子,最左边是100%反光的白,最右边是0%反光的黑,从左到右平均分11份,亮度依次90%,80%……递减。这样组成的板子和10米长的白板比,反射了其18%亮度的光线。 当拍摄场景很暗的时候,建议把灰球换成白球,因为光照不够的时候就,灰度球的漫反射信息是不够的。

UE4 多线程架构

在UE4中使用多线程的方式非常丰富,在Engine初始化时我们就能看出一些端倪。除了最原始的使用FRunnable和FRunnableThread的方式,我们几乎还可以使用两种方式进行线程操作。 在我们引擎初始化时,我们就可以看到两种线程的创建方式。 一. 标准多线程实现FRunnable FRunnable 是指标准多线程,适合长期连续的操作。我们只需要进行简单的继承,就可以实现一个标准的多线程实例。 1.1 FRunable的细节 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class FSimpleRunnable :public FRunnable { public: FSimpleRunnable(); ~FSimpleRunnable(); private: // 必须实现的几个 virtual bool Init() override; virtual uint32 Run() override; virtual void Stop() override; virtual void Exit() override; }; Runnable对象初始化调用 Init(),并通过返回值确定是否成功。初始化失败,则该线程停止执行,并返回一个错误码;成功,则会执行 Run()。 如果Run()中的方法不是永远循环的,就可以直接退出。如果Run永远循环,线程无法退出。就算游戏主线程停止了,这个线程还在继续运行。 当Run执行完毕后,则会调用 Exit() 执行清理操作。 我们继承FRunnable实现这些接口,但是其FRunnable本身并没有线程功能,其线程本身是FRunnableThread。 1 2 void WaitForCompletion(); // 阻塞调用例程,直到线程执行完毕 bool Kill(bool bShouldWait); // 强制杀掉线程 如果调用 FRunnableThread的Kill(bool bShouldWait=false) 函数,会先执行 runnable 对象的 stop(),然后根据 bShouldWait 参数决定是否等待线程执行完毕。如果不等待,则强制杀死线程,可能会造成内存泄漏。 调用 FRunnableThread的WaitForCompletion() 函数,将阻塞调用线程直到线程执行完毕。 1.

UE4使用datasmith工作流

DataSmith的作用 一比一的还原场景,以厘米为单位 能为所有实例化对象建立单一的虚幻资源 能保持位置和朝向 支持图层查看 能将Vray材质变为[[PBR]],自动转换贴图 能导入如强度颜色和IES文件 自动生成光照贴图UV DataSmith下载 [https://www.unrealengine.com/zh-CN/studio]{.underline} 进入网站,点击: 获取我们的DataSmith。获得资格后我们就在自己的库里能够看到如下。我们选择安装到高于4.20版本的引擎当中。 DataSmith创建 首先是创建unreal studio工程,要求引擎版本必须4.20以上。选择启动 在新建里就可以创建Unreal studio工程。 空项目是不包含与构建的内容和功能。如果我们是建立空项目的话,我们就必须手动的创建所有的交互功能,或者从其他的模板里导入交互功能。这里的交互是指,内置的一些交互功能,但是我们在这里只关心的是工作流的问题,这里只在空项目里就可以了 我们找到插件里的unreal studio ,可以看到所有的插件 从3ds Max导入场景 要想能从3dsMax导入场景,我们必须首先安装Datasmith导出的插件,我们可以在启动器里找到。 点击获取,我们就能得到网址 [https://www.unrealengine.com/zh-CN/studio/downloads]{.underline} 我们可以从这里下载关于任何的软件的插件。 下载完成后,选择自己的版本进行安装。 然后,可以在max的导出文件里找到.uadtasmith的东西。之后我们就能使用 进行导入。 在这里我们可以选择我们能导入的东西都有哪些。下面还有高级设置。 这里是设置DataSmith生成光照贴图时的分辨率。 上面是最小分辨率,下面是最大。最大最小都设成一样的可以保证光照贴图有最大的内存空间,同时保证贴图纹理空间拥有足够多的空隙。 datasmith自动计算。每次导入引擎时,datasmith都自动的在通道5中展开UV然后将UV打包进通道6,之后压缩之前的空通道。 如果我们只有一个通道的话,也就时0通道。那么datasmith会建立通道1 和通道2 并且默认使用通道2。当我们的UV出现问题时,我们导出去自己调节一下就可以了。

UE4Lightmass可能遇到的问题

光照贴图的UV问题是造成lightmass的最大的问题。注意事项是,不能重叠,每个之间要留有空隙从而避免泄露可扩散。最好的方法当然是在max里自己来展开每个UV。 光照UV重叠 如果我们发现例如,角落边缘透光,重叠,接缝瑕疵等问题,那么需要返回3D工具。手动的使用第一种方法自己建立光照贴图UV. 对于UV重叠。只需要进行构建,然后看日志信息,大概率会出现UV重叠的信息,这个时候我们可以手动去创建。 光照贴图扩散 这主要时由于这两个部分在光照贴图里过于靠近,所以我们的光照UV中需要每个部分都要有一定的间隔,要不就会出现这种问题。 接缝问题 我们打开它的光照贴图可以看到,对于圆柱型的东西,是比较容易出问题的,仅有一个接缝的Uv是比较合适的。 这个时候我们也是要手动的调整光照UV。 Indirect Seams 间接光照的运算结果在模型之间的接缝处会出现不自然的裂缝 这种的主要原因是两个Mesh之间虽然是平滑的,但是在间接光照进行阴影计算时并不知道这些信息。可以通过在世界设置中调节间接光照的质量和平滑度来减少这种现象 提高间接光照质量会加重光照构建的成本,而如果过于提高平滑度的话,会导致间接光照的很多细节被丢弃。所以一个更好的解决方案是,在构建关卡时,如果是一个平滑的面的话就直接使用一个整体的模型来做,而不是用好几个模型拼接而成。 Bleeding 光照泄露的主要原因是光照贴图的分辨率造成的 像这样室外的光照感觉上就像直接透过到了室内,显然不符合预期。虽然通过修改光照贴图的分辨率来进行应对,但是这样就相当于绕过了问题的来源。更根本的解决方法是,让"地板"与房间的尺寸匹配,这样在光照计算时,房间的地板就不会接收到外部的光的光照计算。 Emissive Material 自发光颜色的材质是通过HDR来实现泛光效果的,因此它本身并不参与光照运算。 通过在使用了自发光颜色材质的物体上打开 可以让其能够照亮周围环境,但仅限于静态光照。当物体是Movable时,没有办法开启这个选项。视频中实现的类型于动态照亮的效果是通过在物体上绑定一个改变GI的PP来实现的,然后两个PP之间的Blending就会改变空间内的灯光造成的影响,形成类似于被物体本身照亮的效果。 Error Coloring 错误着色可以用于排查静态光照计算时报出的UV方面的错误,因为光照构建时只是提示物体上有UV的Overlapping和Wrapping有时候还是很难找到对应的问题的,尤其是模型并不是自己构建的情况下。 打开这个选项之后要将光照质量调整为预览,才能看到错误着色。 重新构建一次光照,就能看到 橙色的部分是Overlapping而绿色的部分是Wrapping。

UE4 主线程和渲染线程的同步

当我们完全了解UE4的多线程是怎么进行时,我们就需要看一下UE4的主线程和渲染线程到底如何进行的同步的。 UE4多线程架构 GameThread、RenderThread、RHI Thread和GPU之间的渲染器同步是一个非常复杂的主题。简而言之,虚幻引擎4通常配置为"后一帧(single frame behind)“渲染器。这意味着当RenderThread处理第N帧时GameThread处理第N + 1帧,除非RenderThread的运行速度比GameThread快。 添加RHI线程使同步过程更为复杂化,因为当RHI线程处理第N帧时,RenderThread能够通过完成第N+1帧的可视性计算而移动到RHI线程之前。最终结果是,当GameThread处理第N+1帧时,RenderThread可以处理第N帧或第N+1帧的命令,RHI线程也可以平移第N帧或第N+1帧的命令,具体取决于执行时间。 在帧的末尾,我们将执行主线程和渲染线程的同步。通过静态的FFrameEndSync来进行线程间的同步。 1 2 3 4 5 6 7 8 //同步主线程和渲染线程 { SCOPE_CYCLE_COUNTER(STAT_FrameSyncTime); static FFrameEndSync FrameEndSync; static auto CVarAllowOneFrameThreadLag =IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.OneFrameThreadLag")); FrameEndSync.Sync(CVarAllowOneFrameThreadLag -> GetValueOnGameThread() != 0); } 我们来看一下FFrameEndSync的数据结构 1 2 3 4 5 6 7 8 9 10 11 12 13 class FFrameEndSync { /** Pair of fences. */ FRenderCommandFence Fence[2]; /** Current index into events array. */ int32 EventIndex; public: /** * Syncs the game thread with the render thread.

蓝图的分类

蓝图的分类 上期视频我们讲解了蓝图的可视化编程的目的和意义,本期视频将正式跨入蓝图的理论学习,今天所讲的是蓝图的类型。 Level Blueprint 第一个蓝图类型是关卡蓝图Level Blueprint,它并不能在资源管理器中进行创建,强制和一个关卡level绑定,实际上一个实例化的蓝图对象。我们可以在这个位置打开我们的蓝图关卡。 每个关卡都有自己的关卡蓝图,可以引用和操纵关卡内的Actor。 可以引用场景关卡里面的实例,这是蓝图类做不到的 Blueprint Class 第二种叫做Blueprint Class,叫做蓝图类,它类似于一般编程概念里面面向对象的类(class)。我们的打交道的大部门都是这种类型。 它跟我们Gamplay,也就是游戏逻辑有很深的绑定关系,因此我们要完全弄清楚这里面的所有概念还是要花一些时间的。 Widget Blueprint Widget Blueprint是用来做UI的界面显示 蓝图库 蓝图的库主要分为两种,一种是宏库,一种是函数库 1. 蓝图宏库,一组自包含的带节点的集合,宏的特点就是不需要编译,仅仅使用节点替换,就好像你复制粘贴宏所代表的集合到了新的使用处,和C++的宏机制很像。我们其实经常使用但是不自知for each 2. 函数库。可以类比成C++的全局函数。这些对你的实现功能没有任何影响,但是对你的多人合作和蓝图维护是非常重要的。 - 编程概念里面很重要的一条就是不要重复你自己,有时候你想复用一个功能,你是通过复制来完成的,类似于排比句,这对实现功能当然没有任何影响,但是如果你想重构和改变之前的逻辑将会变成一个体力活,并且很容易出错。为了在长代码中实现更好地代码复用,提升可读性,提升可修改的能力,将一整块的逻辑抽象成函数是非常必要的 蓝图接口 第五个类型是蓝图接口:它提供一个通用的编程接口,定义了一组拥有这个接口的蓝图类需要实现的函数的名字,仅仅包含名字而不是实现。主要的作用是对逻辑的剥离,使类之间降低耦合性 蓝图的枚举结构体 枚举类型,是用户定义的若干枚举常量的集合。虽然我们可以直接使用1234代表不同的类型,但是可读性和维护性很差,枚举就相当于助记符,用来帮助我们标识和记忆。 而结构体是一个由程序员定义的数据类型,可以容纳许多不同的数据值在一个结构里。 其他的蓝图类型 其他类型,例如动画蓝图,[[Niagara]]编辑器,材质编辑器等等,虽然都拥有是和蓝图一样可视化的连线逻辑,但是有的比较特殊,有的是底层根本不是运行的相同的虚拟机,因此等到我们真正遇到的时候我们再来详细讨论。

基础shader函数 与对应曲线

Fract 得到这个值的小数部分 1 2 3 4 5 float fract(float x) vec2 fract(vec2 x) vec3 fract(vec3 x) vec4 fract(vec4 x) ![fract(x).png][1] y = fract(x); Mod 得到两个值的模,其实就是余数 1 2 3 4 5 6 7 8 float mod(float x, float y) vec2 mod(vec2 x, vec2 y) vec3 mod(vec3 x, vec3 y) vec4 mod(vec4 x, vec4 y) vec2 mod(vec2 x, float y) vec3 mod(vec3 x, float y) vec4 mod(vec4 x, float y) y = mod(x,1.