UE4 Lightmap从烘焙到渲染
Posted qiutang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE4 Lightmap从烘焙到渲染相关的知识,希望对你有一定的参考价值。
UE4.26.2移动端使用LightMass烘焙的光照信息。
关于LightMass,知乎上的Jiff大佬的解析非常的详细。
我们重点放在Lightmass烘焙完光照信息后,传回UE4时发生的事情。
首先是量化(Quantize)光照信息,也就是把一个float类型的光照信息,转成uint8(byte)大小的数据,便于之后存放在Lighmap(R8G8B8)中。
这部分代码函数位于项目 UnrealLightmass/Private/Lighting/LightmapData.cpp中:
为了减少量化过程的光照信息损失,UE4采用的特殊的量化函数,其中,移动端的量化函数如下:
Jiff大佬把函数公式和图像都给出来了:
这样,光照图编码时暗部的保留细节更多。
接下来是LightMap的Encode && Saved部分,此时Lighmass光照数据回流到引擎中,进入Engine/Private/Lightmap.cpp:
主要逻辑记录在FLightMap2D::EncodeTextures中:
UE4增加了一个FLightMapPendingTexture结构体来处理这些光照数据,在里面通过StartEncoding来处理Lightmap的编码及压缩:
进入该StartEncoding函数,它包含了所有类型的Lightmap编码函数,比如ShadowMap的编码、SkylightOcclusion的编码、Coefficient的编码。
这下面几个函数非常重要,基本上如果想改UE4的Lightmap编码的话,基本都在这里面改:
以EncodeCoefficientTexture为例走一遍这个流程:
首先在StartEncoding内设置对应的Texture格式:
进入EncodeCoefficientTexture函数,首先注意我下面红框处的代码:
在这里设置了LQ质量的Lightmap使用R8G8B8格式,节省了Alpha通道,相应的,LQ Lightmap因此大小比HQ小了一半:
UE4的LightMap上半部分存的是光照颜色,下半部分存的是最大贡献的光照方向,这样,在渲染时,可以用像素法线点积最大贡献的光照方向,近似模拟一个像素级的SH漫反射。
这个USE_LM_DIRECTIONALITY宏在材质球里可以直接通过去勾的方式取消开启,这样UE就用默认的0.6作为SH经验值了。
另外,存数据的时候,UE4用的最原始的操纵内存的方式。233。
首先申请一块足够大小的MipCoverageData0, 然后计算好上半部分和下半部分的内存偏移。
接下来就是分别为RGBA填入数据了:
如果要自定义Lightmap格式,比如把下半部分的最大贡献光照方向去掉,或者在LQ Lightmap的A通道存放AO信息等,基本都在类似的地方修改。
剩下的Lightmap2D::PostEncode部分基本都是些UE相关的处理,基本没有改动的必要。
接下来是Lightmap的指定部分。
使用到Lightmap的只有静态物体:StaticMeshComponent和LandscapeComponent。
首先来康康StaticMeshComponent是怎么指定Lightmap的:
要想缕清这部分的代码逻辑,必须清楚UE4是一个多线程引擎架构,GameThread和RenderThread是分离开的,而为了两者之间的通讯,一般GameThread中的对象,会备份一个Proxy,当GameThread中该对象发生更改时,必须手动通知Renderer更新数据。
以StaticMesh为例,UStaticMeshComponent类为游戏线程中的StaticMeshComponent,相应的,UE4增加了一个FStaticMeshSceneProxy类:
Lightmap指定发生在FStaticMeshSceneProxy的构造函数中:
注意红框标出部分,我们进入该LOD构造函数:
逻辑很清晰,GameThread中的UStaticMeshComponent作为InComponent,传入对应的FMeshMapBuildData。
溯源到UStaticMeshComponent::GetMeshMapBuildData。
其实这里已经可以看到LightScenario的相关逻辑处理了233。
很简单,获取Component的Level,然后获取Level中对应的MapBuildData并返回,当然这里额外处理了LightScenario的逻辑。
此时,返回之前的LOD构造函数,我们看看那几个Set函数,指定了FLightCacheInterface中的LightMap等信息。
注意一点喔,FLODInfo是继承自FLightCacheInterface的喔。(奇怪的继承)
除开StaticMesh会用到Lightmap,Landscape也会用到Lightmap。
Landscape也是同样的套路,使用FLandscapeLCI代理存储在FLandscapeComponentSceneProxy中,注意它同样继承自FLightCacheInterface:
由于LandscapeComponent没有LOD,因此直接设置LigmapData就可以了。
Shader的Binding部分
UE4有一套独特的MeshDrawProcessor处理流程,根据RenderPass需求的资源格式(比如ShadowDepth Pass仅需顶点坐标和Mask数据、BasePass需要大部分数据、PrePass需要顶点和Mask数据等),于是在FMeshPassProcessor中组织好这些Primitive资源,渲染时调用对应Primitives的Draw:
下面以移动端的Forward渲染管线为例:
首先是UE的Forward渲染函数FMobileSceneRenderer::RenderForward:
在渲染完PrePass后进入MobileBasePass:
在RenderMobileBasePass函数中,先更新完场景相机相关的View UBO后就会调用视角中ParallelMeshDrawCommandPasses的DispatchDraw函数。
而View中的ParallelMeshDrawCommandPasses则是在之前FMobileSceneRenderer::SetupMobileBasePassAfterShadowInit函数中Setup填充的。
UE4专门设计了一个全局函数类,利用它的初始化函数往一个全局表FPassProcessorManager::JumpTable里面填每个Pass对应的创建函数:
注册每个MeshPass时,则会经由该构造函数填入,比如MobileBasePass的注册如下:
其中MobileBasePass的注册函数如下:
FMeshPassProcessorRenderState 这个参数非常重要,标定了该Pass需要的一些渲染资源。
其构造函数分别传入一个FSceneView和一个FRHIUniformBuffer,前者用于填充该视口下公用的ViewUniformBuffer,后者则是该Pass独有的UniformBuffer:
对于MobileBasePass,其需求的列表如下:
这里的参数基本可以每帧修改,比如UE4.26.2移动端Forward管线的GTAO,MobileBasePass用了上一帧的AO Mask,该Mask就在这里指定。
这里仅处理了MobileBasePass网格公用的UniformBuffer,但还没有处理每个网格独有的UniformBuffer。
好比说,MobileBasePass里的所有物体渲染时都需要AmbientOcclusionTexture,而静态的StaticMesh渲染时需要传入Lightmap数据,动态的SkeletonMesh不需要Lightmap数据但是需要SH(ILC)数据。
因此,UE用FMeshPassProcessor::Process函数来单独为每一个网格处理这种Shader变体情况。
还是以移动端的MobileBasePass为例:
以Lightmap的指定为例,Process函数参数需要传入ELightMapPolicyType,然后调用GetShaders找到对应的变体:
UE为了节省移动端的性能,在计算移动端点光源的动态光照时,使用的宏分支来控制计算而不是动态分支,因此还需要在CPU侧处理好点光源的数目:
在GetMobileBasePassShaders函数这里才是Lightmap的变体抉择策略:
在GetUniformMobileBasePassShaders则转入了根据材质选择具体的的Shader时候:
注意下方的模板继承关系:
TUniformLightMapPolicy的Shader变体编译函数根据不同的Lightmap策略选择对应的变体编译函数。
我们移动端就会跳到下面这个:
TUniformLightMapPolicy继承自FUniformLightMapPolicy,在类FUniformLightMapPolicy中:
这里的LightmapResourceCluster就是指定的Lightmap数据了。
走到这里,UE已经把RenderPass对应的UBO Parameter和Shader变体都找到了。
最后剩下的就是Lightmap UniformBuffer的Update了。
先前的MoblieBasePass的Shader变体选择完后会调用GetShaders函数,最终会调用到FUniformLightMapPolicy::GetPixelShaderBindings里,在该GetPixelShaderBindings函数中将会调用SetupLCIUniformBuffers获取FLightCacheInterface*对应的FLightmapResourceCluster。
很前面的时候提到FLodInfo和FLandscapeLCI都继承自FLightCacheInterface。经过MeshPassProcessor的逐物体处理时,该接口的FLightmapResourceCluster便可以直接获取调用了。
而FLightCacheInterface里的FLightmapResourceCluster数据,在StaticMesh在渲染线程时就已经指定了,也就是我们一开始分析的那里,调用的SetResourceCluster初始化。
到目前为止,所有的数据都已经设置完毕,渲染正常进行。
Lightmap的解码
咋编码的就咋解码,非常简单:
友情Tip
如果你正在为UE4开发两套Lightmap Lerp融合的Time Of Day方案,却发现但BlendWeight为1时完全还原不了第二套Lightmap的颜色信息,请检查你是否把第二套Lightmap的LightMapScale和LightMapAdd数据都传进来并参与了插值(否则它永远将为第一套的数据)。
另外,如果你是基于LightingScenario开发的,请立刻放弃换别的方法,因为LightScenario没办法做到Streaming加载,并且!!!要非常小心的处理昼夜过渡时的资源加载卸载,非常操蛋。
如果想为UE4移动端添加昼夜变换效果,用一套Lightmap就足够了,透过对比度和亮度调节,简单方便,包体大小友好。
以上是关于UE4 Lightmap从烘焙到渲染的主要内容,如果未能解决你的问题,请参考以下文章
unity3d lightmap 是需要烘培到单独的第二套uv上么