[{"data":1,"prerenderedAt":1718},["ShallowReactive",2],{"blog-page-14":3,"blog-count":1717},[4,413,781,1058,1225],{"id":5,"title":6,"body":7,"date":388,"description":13,"extension":389,"meta":390,"navigation":393,"path":405,"seo":406,"stem":407,"tags":408,"__hash__":412},"blogs\u002F_legacy\u002F2018\u002F2018-01-02-ue4-rendering-code-view-02.md","UE4渲染代码逻辑总结（下）",{"type":8,"value":9,"toc":368},"minimark",[10,14,17,22,25,28,31,36,39,72,75,78,81,84,90,93,96,101,104,107,111,114,118,121,124,135,138,144,147,150,153,159,162,165,168,171,174,177,181,184,187,190,196,199,205,208,212,215,218,221,224,227,230,233,236,328,331,335,338,341,344,347,350,353,356,359,362,365],[11,12,13],"p",{},"前面的内容都集中在C++端了，这边的内容会往Shader靠的更近一些。",[11,15,16],{},"由于感觉上很多东西要全部梳理明白会花费很多时间却又没有多大用处，所以这里的内容实际上进行了精简，因此比预定的短了很多。",[18,19,21],"h2",{"id":20},"shader端","Shader端",[11,23,24],{},"虚幻使用HLSL作为Shader的语言，引擎核心的Shader都可以在引擎目录的Shader文件夹下找到。",[11,26,27],{},"其中ush为Shader头文件，而usf为Shader的源文件。由于Shader部分的代码基本属于引擎渲染的核心部分，要全部理解起来就有些费时间了。",[11,29,30],{},"所以这里只是按照其与C++部分接洽的结构进行粗略的探索。",[32,33,35],"h3",{"id":34},"vertexfatory","VertexFatory",[11,37,38],{},"VertexFatory是C++端将数据推送到Shader端的途径，在Shader文件夹中能够看到以下的几个：",[40,41,42,45,48,51,54,57,60,63,66,69],"blockquote",{},[11,43,44],{},"VectorFieldVisualizationVertexFactory.ush",[11,46,47],{},"ParticleSpriteVertexFactory.ush",[11,49,50],{},"ParticleGPUSpriteVertexFactory.ush",[11,52,53],{},"ParticleBeamTrailVertexFactory.ush",[11,55,56],{},"NiagaraMeshVertexFactory.ush",[11,58,59],{},"NiagaraSpriteVertexFactory.ush",[11,61,62],{},"MeshParticleVertexFactory.ush",[11,64,65],{},"LocalVertexFactory.ush",[11,67,68],{},"LandscapeVertexFactory.ush",[11,70,71],{},"GpuSkinVertexFactory.ush",[11,73,74],{},"分别对应不同的使用情况，从命名上基本就能看出其用途。",[11,76,77],{},"Niagara是UE4的下一代粒子系统，目前版本可以在插件中打开，但是功能似乎并不完全，也没有文档，没有办法使用。",[11,79,80],{},"另外，这里的Shader与FVertexFatory并不是一一对应的关系。使用相同的渲染路径的类会在这里共用Shader。",[11,82,83],{},"如LocalVertexFactory.ush就被FLocalVertexFactory、FEmulatedInstancedStaticMeshVertexFactory、FInstancedStaticMeshVertexFactory、FGPUSkinPassthroughVertexFactory和FSplineMeshVertexFactory共同使用。",[11,85,86],{},[87,88,89],"strong",{},"FVertexFactoryInput",[11,91,92],{},"这个数据结构是来自C++端的数据输入。在不同的Shader头文件中可能会有不同的定义，使用方式上自然也会有不同。",[11,94,95],{},"也有一些通用的Shader函数被定义来处理输入数据，如GetVertexFactoryIntermediates, VertexFactoryGetWorldPosition, GetMaterialVertexParameters。",[11,97,98],{},[87,99,100],{},"FBasePassVSOutput",[11,102,103],{},"这个是另一个比较特殊的结构，由于不同的渲染路径可能在Vertex Shader结束后使用的路径是不同的。",[11,105,106],{},"所以也能看到对这个结构的不同定义。",[32,108,110],{"id":109},"material","Material",[11,112,113],{},"所有的材质最终都会被编译成Shader，在材质编辑器中也能够看到材质的Shader预览。",[115,116,117],"h4",{"id":117},"材质蓝图",[11,119,120],{},"用于将材质蓝图编译成Shader的模板在MaterialTemplate.ush中，查看这个文件的话，会看到有很多地方都是直接写成%s的。",[11,122,123],{},"这些都是由引擎将材质蓝图中的节点填充到这里的，例如",[125,126,131],"pre",{"className":127,"code":129,"language":130},[128],"language-text","\u002F**\n* Parameters calculated from the pixel material inputs.\n*\u002F\nstruct FPixelMaterialInputs\n{\n%s\n};\n","text",[132,133,129],"code",{"__ignoreMap":134},"",[11,136,137],{},"可能会被填写成",[125,139,142],{"className":140,"code":141,"language":130},[128],"\u002F**\n* Parameters calculated from the pixel material inputs.\n*\u002F\nstruct FPixelMaterialInputs\n{\nMaterialFloat3 EmissiveColor;\nMaterialFloat Opacity;\nMaterialFloat OpacityMask;\nMaterialFloat3 BaseColor;\nMaterialFloat Metallic;\nMaterialFloat Specular;\nMaterialFloat Roughness;\nMaterialFloat3 Normal;\nMaterialFloat4 Subsurface;\nMaterialFloat AmbientOcclusion;\nMaterialFloat2 Refraction;\nMaterialFloat PixelDepthOffset;\n\n};\n",[132,143,141],{"__ignoreMap":134},[11,145,146],{},"想要详细的了解的话可以在材质编辑器中修改材质，然后预览HLSL代码并与MaterialTemplate.ush对比以了解更多的内部工作原理。",[115,148,149],{"id":149},"数据获取",[11,151,152],{},"在材质完成编译之后，渲染路径中就可以在需要时对材质中定义的相应的属性进行获取了。",[125,154,157],{"className":155,"code":156,"language":130},[128],"half3 BaseColor = GetMaterialBaseColor(PixelMaterialInputs);\nhalf  Metallic = GetMaterialMetallic(PixelMaterialInputs);\nhalf  Specular = GetMaterialSpecular(PixelMaterialInputs);\n",[132,158,156],{"__ignoreMap":134},[11,160,161],{},"不同的shader根据不同的渲染路径将数据最终填充到GBuffer中，以便进行进一步的计算。",[115,163,164],{"id":164},"计算",[11,166,167],{},"在GBuffer的生成过程前、过程中、过程后，都有很多复杂的计算。",[11,169,170],{},"这些过程包括各种裁剪、光照以及PostProcess，由于并非是要进行这些逻辑的修改或者扩展，便不再深究下去了。",[18,172,173],{"id":173},"渲染逻辑",[11,175,176],{},"有了渲染用的C++端和Shader端代码之后，终于可以开始进行渲染工作了。",[32,178,180],{"id":179},"fdeferredshadingscenerenderer","FDeferredShadingSceneRenderer",[11,182,183],{},"对于PC端的延迟渲染，最终负责进行渲染工作的就是这个类了。",[11,185,186],{},"渲染的调用来源为FRendererModule::BeginRenderingViewFamily，可以看到有些编辑器的缩略图也会调用这个函数进行渲染，和玩家看到的游戏界面有关的渲染调用来自UGameViewportClient::Draw。",[11,188,189],{},"BeginRenderingViewFamily这个函数内在进行一些渲染的准备后，将实际渲染的函数扔到渲染线程",[125,191,194],{"className":192,"code":193,"language":130},[128],"ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(\nFDrawSceneCommand,\nFSceneRenderer*,SceneRenderer,SceneRenderer,\n{\n  RenderViewFamily_RenderThread(RHICmdList, SceneRenderer);\n  FlushPendingDeleteRHIResources_RenderThread();\n});\n",[132,195,193],{"__ignoreMap":134},[11,197,198],{},"然后就实际执行渲染",[125,200,203],{"className":201,"code":202,"language":130},[128],"SceneRenderer->Render(RHICmdList);\n",[132,204,202],{"__ignoreMap":134},[11,206,207],{},"基本上渲染的主要逻辑就在这个函数中。",[32,209,211],{"id":210},"rendershadowdepthmaps","RenderShadowDepthMaps",[11,213,214],{},"这个是SceneRenderer->Render中对深度贴图生成，从中可以看出渲染是如何最终使用Shader的。",[11,216,217],{},"比较关键的调用之一是ProjectedShadowInfo->RenderDepth(RHICmdList, this, SetShadowRenderTargets, ShadowDepthRenderMode_Normal);",[11,219,220],{},"这个函数进一步调用FProjectedShadowInfo::RenderDepth并继而调用FProjectedShadowInfo::RenderDepthInner",[11,222,223],{},"而这之中会有SceneRenderer->Scene->WholeSceneReflectiveShadowMapDrawList.DrawVisible",[11,225,226],{},"而这个WholeSceneReflectiveShadowMapDrawList就是一张DrawingPolicy列表了，到了这里就能与Shader相关的类型联系上了。",[32,228,229],{"id":229},"渲染流程",[11,231,232],{},"关于渲染的流程，到了这一步其实就和之前看到的差不多了，因此这里不做赘述。",[11,234,235],{},"下面的内容直接引用自官方文档，所以不保证与当前版本的内容匹配：",[237,238,239,252],"table",{},[240,241,242],"thead",{},[243,244,245,249],"tr",{},[246,247,248],"th",{},"操作",[246,250,251],{},"描述",[253,254,255,264,272,280,288,296,304,312,320],"tbody",{},[243,256,257,261],{},[258,259,260],"td",{},"GSceneRenderTargets.Allocate",[258,262,263],{},"按需要重新分配全局场景渲染目标，使其对当前视图足够大。",[243,265,266,269],{},[258,267,268],{},"InitViews",[258,270,271],{},"‭通过多种剔除方法为视图初始化基元可见性，设立此帧可见的动态阴影、按需要交叉阴影视锥与世界场景（对整个场景的阴影或预阴影）。",[243,273,274,277],{},[258,275,276],{},"PrePass \u002F Depth only pass",[258,278,279],{},"RenderPrePass \u002F FDepthDrawingPolicy。渲染遮挡物，对景深缓冲区仅输出景深。该通道可以在多种模式下工作：禁用、仅遮蔽，或完全景深，具体取决于活动状态的功能的需要。该通道通常的用途是初始化 Hierarchical Z 以降低 Base 通道的着色消耗（Base 通道的像素着色器消耗非常大）。",[243,281,282,285],{},[258,283,284],{},"Base pass",[258,286,287],{},"RenderBasePass \u002F TBasePassDrawingPolicy。渲染不透明和遮盖的材质，向 GBuffer 输出材质属性。光照图贡献和天空光照也会在此计算并加入场景颜色。",[243,289,290,293],{},[258,291,292],{},"Issue Occlusion Queries \u002F BeginOcclusionTests",[258,294,295],{},"提出将用于下一帧的 InitViews 的延迟遮蔽查询。这会通过渲染所查询物体周围的相邻的框、有时还会将相邻的框组合在一起以减少绘制调用来完成。",[243,297,298,301],{},[258,299,300],{},"Lighting",[258,302,303],{},"阴影图将对各个光照渲染，光照贡献会累加到场景颜色，并使用标准延迟和平铺延迟着色。光照也会在透明光照体积中累加。",[243,305,306,309],{},[258,307,308],{},"Fog",[258,310,311],{},"雾和大气在延迟通道中对不透明表面进行逐个像素计算。",[243,313,314,317],{},[258,315,316],{},"Translucency",[258,318,319],{},"透明度累加到屏外渲染目标，在其中它应用了逐个顶点的雾化，因而可以整合到场景中。光照透明度在一个通道中计算最终光照以正确融合。",[243,321,322,325],{},[258,323,324],{},"Post Processing",[258,326,327],{},"多种后期处理效果均通过 GBuffers 应用。透明度将合成到场景中。",[11,329,330],{},"直接阅读FRendererModule::BeginRenderingViewFamily就可以看到UE4是如何对渲染通路进行处理的，其中有的较为简单的就会直接调用Shader进行处理，较为复杂的就会有相应的过程封装。",[32,332,334],{"id":333},"rendering-paths","Rendering paths",[11,336,337],{},"根据官方文档的描述，渲染路径分为Dymaic和Static两种。其中动态的速度会更慢些但是拥有更多的控制选项。",[11,339,340],{},"FPrimitiveSceneProxy会在GetViewRelevance中返回相关性标志，这样在渲染时引擎就会决定是否调用DrawDynamicElements和DrawStaticElements。",[11,342,343],{},"大致看来，static rendering path会在物体被加入FScene的时候，就将自己加入到绘制列表中。而dynamic rendering path由于可以做一些动态处理，并在DrawDynamicElements中提供了回调，所以就没有办法利用缓存机制了。",[11,345,346],{},"可以看到很多类似这样的调用",[11,348,349],{},"PrimitiveSceneInfo->Proxy->GetDynamicMeshElements(InViewFamily.Views, InViewFamily, ViewMask, Collector);",[11,351,352],{},"而SceneRender中也能看到GatherDynamicMeshElements这样的函数。",[11,354,355],{},"感觉上Static的渲染路径指的应该是FScene中大量存在的模板化的列表TStaticMeshDrawList。",[11,357,358],{},"不过由于这方面几乎找不到资料，文档中没有更加详细的说明，社区也基本看不到讨论，要从源码中回溯其意图就比较费时了，所以便没有进一步深究。",[18,360,361],{"id":361},"总结",[11,363,364],{},"到了这里，这个UE4的渲染逻辑就能有一个大致的草图了。",[11,366,367],{},"虽然其中还有更多的细节和详细是实现，也只有到了需要的时候再深入了解了。毕竟这部分已经是引擎开发者的工作，而太过于深入就没有意义了。",{"title":134,"searchDepth":369,"depth":370,"links":371},2,3,[372,381,387],{"id":20,"depth":369,"text":21,"children":373},[374,375],{"id":34,"depth":370,"text":35},{"id":109,"depth":370,"text":110,"children":376},[377,379,380],{"id":117,"depth":378,"text":117},4,{"id":149,"depth":378,"text":149},{"id":164,"depth":378,"text":164},{"id":173,"depth":369,"text":173,"children":382},[383,384,385,386],{"id":179,"depth":370,"text":180},{"id":210,"depth":370,"text":211},{"id":229,"depth":370,"text":229},{"id":333,"depth":370,"text":334},{"id":361,"depth":369,"text":361},"2018-01-02","md",{"layout":391,"status":392,"published":393,"author":394,"author_login":396,"author_email":397,"wordpress_id":398,"wordpress_url":399,"date_gmt":400,"excerpt":401},"post","publish",true,{"display_name":395,"login":396,"email":397,"url":134},"风铃","flinkor","flinkor@foxmail.com",2209,"\u002F\u002F?p=2209","2018-01-01 16:02:45 +0000",{"type":8,"value":402},[403],[11,404,13],{},"\u002F2018-01-02-ue4-rendering-code-view-02",{"title":6,"description":13},"_legacy\u002F2018\u002F2018-01-02-ue4-rendering-code-view-02",[409,410,411],"shader","UE4","Rendering","mZ30HsJT42pZP836dw4bOQTLX2RfTXQDvouudBp_UO8",{"id":414,"title":415,"body":416,"date":766,"description":420,"extension":389,"meta":767,"navigation":393,"path":776,"seo":777,"stem":778,"tags":779,"__hash__":780},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-note-lightmass-deep-dive.md","深入LightMass系列笔记",{"type":8,"value":417,"toc":743},[418,421,432,435,438,441,445,448,452,456,459,466,470,473,478,481,489,492,496,499,504,507,511,514,519,522,525,530,533,538,542,545,548,551,554,559,563,566,569,573,576,579,582,586,589,592,595,598,603,606,611,614,617,620,624,627,632,635,639,642,645,650,654,657,662,666,669,674,677,682,685,690,693,696,699,704,707,710,729,732,735,737,740],[11,419,420],{},"UE4使用Lightmass对光照进行预计算，以节省动态光照计算的成本。",[11,422,423,424,431],{},"本文是EpicJapan的LightMass Deep Dive系列的总结，与之前更新的文章有重叠的地方可能会略过，如有需要可以参看[",[425,426,430],"a",{"href":427,"rel":428},"https:\u002F\u002Fwww.slideshare.net\u002FEpicGamesJapan\u002Flightmass-lightmap-epic-games-japan",[429],"nofollow","原始的PPT","]。",[11,433,434],{},"内容本身的UE4版本为4.13，当前UE4版本4.18。",[11,436,437],{},"其实这个系列讲的内容并不多，只是使用了很多配图，让人能非常直观的对Light Mass的计算进行理解。",[11,439,440],{},"预计算光照主要的组成部分有3个：Light Map、Shadow Map和Precomputed Light Volume。",[18,442,444],{"id":443},"light-map","Light Map",[11,446,447],{},"光照贴图是静态光照最重要的部分，当前关卡生成的光照贴图可以在World Settings一栏里面看到。",[32,449,451],{"id":450},"pointspotdirectional-light","Point\u002FSpot\u002FDirectional Light",[115,453,455],{"id":454},"direct-light","Direct Light",[11,457,458],{},"直接从光源所在处打出射线进行计算，对于有光源半径的光而言会多做一些计算。",[11,460,461],{},[462,463],"img",{"alt":464,"src":465},"image","\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb.png",[115,467,469],{"id":468},"indiret-light","Indiret Light",[11,471,472],{},"间接光照的计算核心是光子映射算法，详细的实现分为以下步骤",[474,475,477],"h5",{"id":476},"photon-mapping","Photon Mapping",[11,479,480],{},"首先从光源向周围放射光子，对命中表面的光子进行记录，并按照一定的条件进行光子反射。",[11,482,483,486],{},[462,484],{"alt":464,"src":485},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-1.png",[462,487],{"alt":464,"src":488},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-2.png",[11,490,491],{},"这个反射过程会重复多次，最终形成在物体表面的很多光子数据。",[474,493,495],{"id":494},"final-gathering","Final Gathering",[11,497,498],{},"从纹素出发，沿着光子的反射路径回溯性的收集光照信息。",[11,500,501],{},[462,502],{"alt":464,"src":503},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-3.png",[11,505,506],{},"在收集过程中并不会从自己周围收集，因为这部分的光照已经在直接光照中计算过了。",[474,508,510],{"id":509},"irradiance-caching","Irradiance Caching",[11,512,513],{},"要对场景中所有的纹素进行收集的话，不仅花费时间，且由于反弹并不是均匀的所以会有很多噪点。",[11,515,516],{},[462,517],{"alt":464,"src":518},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-4.png",[11,520,521],{},"所以这个时候就需要将收集的光照缓存起来，然后以插值的方式来收集光照数据。",[11,523,524],{},"当然上面的只是大致过程，其中还有很多细节上的优化，例如：",[11,526,527],{},[87,528,529],{},"Adpative Samping",[11,531,532],{},"如果在收集时，光子路径的相邻点间隔较大的情况，会增加额外的射线进行光照数据收集。",[11,534,535],{},[462,536],{"alt":464,"src":537},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-5.png",[32,539,541],{"id":540},"sky-light","Sky Light",[11,543,544],{},"天空光照稍微有些不同，并不会进行Photon Mapping。",[115,546,455],{"id":547},"direct-light-1",[11,549,550],{},"在Final Gathering时直接将SkyLight当作二次反射的光源进行光照收集。",[11,552,553],{},"对于场景中窗口过小导致收集到的间接光照不足时，可以添加LightMass Portal以放出更多的收集射线。",[11,555,556],{},[462,557],{"alt":464,"src":558},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-6.png",[115,560,562],{"id":561},"indirect-light","Indirect Light",[11,564,565],{},"天空光的间接光照则是对光照进行一个更小规模的MiniFinalGather。",[11,567,568],{},"不过这个说法可能不适用于当前版本，因为从4.18开始，天空光也可以接收多次反弹的间接光照了。",[18,570,572],{"id":571},"shadow-map","Shadow Map",[11,574,575],{},"这个系列并没有介绍阴影贴图，Shadow Map并不是为Static Light准备的，而是为Stational Light准备的。",[11,577,578],{},"在进行光照计算时，每一个动态的物体，都会根据自己的bound box与stational light的朝向关系，将Shadow Map合成到光照运算结果中去。",[11,580,581],{},"因此，如果Stational Light影响的动态物体特别多的时候，其效率是非常低下的。有时候考虑直接换为Dynamic Light反而更好。",[18,583,585],{"id":584},"precomputed-light-volume","Precomputed Light Volume",[11,587,588],{},"这个主要是在场景中生成预计算的间接光照缓存点，这样动态的物体就可以从中取得数据并插值得到自己的间接光照。",[11,590,591],{},"插值的方式中，ILCQ Point是根据物体当前的位置进行的，所以如果物体有一定的大小且移动速度较慢的话，就容易产生可见的阴影跳变。相对的ILCQ Volume由于使用多个点进行插值处理，运算成本会变高，尤其是对于非常大的物体。",[32,593,594],{"id":594},"缓存数据",[11,596,597],{},"PLV的数据是具有方向性的球谐函数",[11,599,600],{},[462,601],{"alt":464,"src":602},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-7.png",[11,604,605],{},"在计算的时候分为上下两个半球进行计算",[11,607,608],{},[462,609],{"alt":464,"src":610},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-8.png",[11,612,613],{},"计算方法是，在进行光照预计算的时候，对指定的缓存点，将直接光照与Final Gathering的值缓存起来。",[32,615,616],{"id":616},"缓存点的生成",[11,618,619],{},"生成方式总共分为三种",[115,621,623],{"id":622},"surface-light-sample","Surface Light Sample",[11,625,626],{},"这个是最单纯的，从每个Mesh的表面沿着法线方向进行缓存点添加。",[11,628,629],{},[462,630],{"alt":464,"src":631},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-9.png",[11,633,634],{},"可以通过配置文件进行行为修改。",[115,636,638],{"id":637},"detail-volume-sample","Detail Volume Sample",[11,640,641],{},"在世界中拖放Lightmass Character Indirect Detail Volume生成的缓存点。",[11,643,644],{},"这个Volume拖放之后会在内部生成缓存点。",[11,646,647],{},[462,648],{"alt":464,"src":649},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-10.png",[115,651,653],{"id":652},"uniform-volume-sample","Uniform Volume Sample",[11,655,656],{},"Lightmass Importance Sample这个Volume是在场景内全局性的生成缓存点",[11,658,659],{},[462,660],{"alt":464,"src":661},"\u002Fwp-content\u002Fuploads\u002F2018\u002F01\u002Fimage_thumb-11.png",[18,663,665],{"id":664},"usage","Usage",[11,667,668],{},"演讲中介绍了不少静态光照的配置技巧，这里只列出之前没有列出过的。",[11,670,671],{},[87,672,673],{},"反光板",[11,675,676],{},"直接将聚光灯打到一块板子上，只让间接光照计算影响到静态光照计算。",[11,678,679],{},[87,680,681],{},"Compress Lightmaps",[11,683,684],{},"这个光照贴图的压缩是有损的，如果关掉的话效果会变好，但是贴图体积会放大4倍左右。",[11,686,687],{},[87,688,689],{},"Lightmap Resolution",[11,691,692],{},"光照贴图的分辨率在配置时要注意，比最小的纹素小的光照细节无法被捕捉。",[11,694,695],{},"Static Lighting Level Scale",[11,697,698],{},"这个配置会影响到整个LightMass的计算，同样的如果导致小于RecordRadius的光照细节无法被捕捉。",[11,700,701],{},[87,702,703],{},"Use Emissive For Static Lighting",[11,705,706],{},"这个的光照计算和天空光一样是不会进行Photon Mapping的。",[11,708,709],{},"以及最后给出的配置和性能影响",[711,712,713,717,720,723,726],"ul",{},[714,715,716],"li",{},"Static Lighting Level Scale = 1→ 0.1",[714,718,719],{},"Num Indirect Lighting Bounces 👨→20",[714,721,722],{},"Indirect Lighting Quality = 1→10",[714,724,725],{},"Indirect Lighting Smoothness=1.0→1.0",[714,727,728],{},"Lighting Quality = Production Build Time",[11,730,731],{},"光照计算时间15m57s →4h10m24s",[11,733,734],{},"不过这个的演讲者是做建筑展示的，所以对静态光照的要求比较高，并没有考虑到游戏运行的成本。",[18,736,361],{"id":361},[11,738,739],{},"了解了静态光照的计算方法之后，在对其进行调整，尤其是要修改config.ini的时候非常的有帮助。",[11,741,742],{},"至少比盲目的调节选项然后等待漫长的光照计算之后来观察结果要好很多。",{"title":134,"searchDepth":369,"depth":370,"links":744},[745,755,756,764,765],{"id":443,"depth":369,"text":444,"children":746},[747,751],{"id":450,"depth":370,"text":451,"children":748},[749,750],{"id":454,"depth":378,"text":455},{"id":468,"depth":378,"text":469},{"id":540,"depth":370,"text":541,"children":752},[753,754],{"id":547,"depth":378,"text":455},{"id":561,"depth":378,"text":562},{"id":571,"depth":369,"text":572},{"id":584,"depth":369,"text":585,"children":757},[758,759],{"id":594,"depth":370,"text":594},{"id":616,"depth":370,"text":616,"children":760},[761,762,763],{"id":622,"depth":378,"text":623},{"id":637,"depth":378,"text":638},{"id":652,"depth":378,"text":653},{"id":664,"depth":369,"text":665},{"id":361,"depth":369,"text":361},"2018-01-01",{"layout":391,"status":392,"published":393,"author":768,"author_login":396,"author_email":397,"wordpress_id":769,"wordpress_url":770,"date_gmt":771,"excerpt":772},{"display_name":395,"login":396,"email":397,"url":134},2239,"\u002F\u002F?p=2239","2018-01-01 15:59:33 +0000",{"type":8,"value":773},[774],[11,775,420],{},"\u002F2018-01-01-note-lightmass-deep-dive",{"title":415,"description":420},"_legacy\u002F2018\u002F2018-01-01-note-lightmass-deep-dive",[410,300],"lNxcndA-W2UxZRHbVV_MbKunnp78cQrGi-WboMRX8J8",{"id":782,"title":783,"body":784,"date":766,"description":788,"extension":389,"meta":1044,"navigation":393,"path":1053,"seo":1054,"stem":1055,"tags":1056,"__hash__":1057},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-ue4-material-deepdive-note.md","深入UE4材质管理",{"type":8,"value":785,"toc":1025},[786,789,792,795,798,801,805,808,811,815,818,821,824,827,830,832,835,838,841,844,847,851,854,859,862,865,868,871,875,878,883,887,890,895,898,901,904,908,911,915,918,923,926,931,936,939,942,945,950,953,956,961,964,969,972,977,980,982,987,990,995,998,1003,1006,1010,1013,1016,1019,1022],[11,787,788],{},"UE4的材质系统使用起来虽然很方便，但是如果不进行有效的管理，很快就会导致项目的体积变得不可控制，材质编译时间也会变成痛苦的折磨。",[11,790,791],{},"本文是EpicJapan UE4 DeepDive系列中材质管理部分的总结，对应UE4版本为4.12~14。部分截图来自4.18。",[18,793,794],{"id":794},"抽离共通逻辑",[11,796,797],{},"为了减少材质的编译尺寸，首先要做的就是将共通的逻辑都抽出来。",[11,799,800],{},"在材质系统中一共有以下两种做法",[32,802,804],{"id":803},"material-function","Material Function",[11,806,807],{},"材质函数可以将一些共同的操作逻辑抽取出来，这样在进行材质的编译的时候，就不会产生重复的部分了。",[11,809,810],{},"UE4本身就提供了大量的材质函数来方便进行材质的表现。",[32,812,814],{"id":813},"material-instance","Material Instance",[11,816,817],{},"材质实例通过将通用的材质统合起来，有效的减少材质的数量。",[11,819,820],{},"使用上，如果材质之间就只有贴图或者参数不同的话，就可以构建一个基础的材质，然后将需要变化的材质进行参数化。",[11,822,823],{},"并创建材质实例以应对不同的情况。",[18,825,826],{"id":826},"选项优化",[11,828,829],{},"材质在使用时有的选项需要进行优化，否则就会生成很多不会使用到的部分。导致编译时间变长且无端的占用发布体积。",[32,831,665],{"id":664},[11,833,834],{},"材质的选项中有一个Usage的选项，通常会自动勾选上Automatically Set Usage in Editor。",[11,836,837],{},"这个选项会在材质被特定的Usage使用的时候自动打开，针对每一种Usage会生成新的Shader来进行对应。",[11,839,840],{},"但是，Usage被打开后，如果不再在对应的Usage中使用的话，引擎并不会自动的关闭这个Usage，所以需要手动的进行关闭。",[11,842,843],{},"这个选项的作用在极端情况下特别大：使用全部Usage时1837K的材质在仅使用Static Light时却只有97K的。",[11,845,846],{},"因此在使用的时候还是需要进行额外的注意的，如果默认去掉这个选项的话，就可以有效的避免误操作导致的勾选了。当然使用起来就会有些麻烦。",[32,848,850],{"id":849},"shader-permutation-reduction","Shader Permutation Reduction",[11,852,853],{},"在项目设置>渲染中，能够看到",[11,855,856],{},[462,857],{"alt":464,"src":858},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb.png",[11,860,861],{},"这个也是类似的选项，在没有使用对应的功能的时候去掉就好了。",[11,863,864],{},"这个如果去掉了却在使用对应的功能的话，编辑器内会有屏幕输出提示，可以放心使用。",[18,866,867],{"id":867},"材质实例关系",[11,869,870],{},"材质实例的继承关系会很大的影响到材质的编译过程，部分继承的属性重写是会导致材质实例需要生成新的Shader的。",[32,872,874],{"id":873},"override-properties","Override properties",[11,876,877],{},"材质属性覆盖会导致需要为实例生成新的Shader",[11,879,880],{},[462,881],{"alt":464,"src":882},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-1.png",[32,884,886],{"id":885},"static-switch-parameter","Static Switch Parameter",[11,888,889],{},"开关是需要额外的生成Shader的",[11,891,892],{},[462,893],{"alt":464,"src":894},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-2.png",[11,896,897],{},"当然也包括Static Component Mask Parameter。",[11,899,900],{},"因此在进行材质实例的继承的时候，应当进行规划，首先分支出Switch等需要重新生成Shader的材质实例后，再继承其他的如贴图、颜色这些不会导致重新生成的材质实例的继承。",[11,902,903],{},"同时，由于材质实例不携带Usage属性，所以这个问题上只能一开始就进行分开。",[18,905,907],{"id":906},"tips","Tips",[11,909,910],{},"这个是SE的演讲者给出的一些操作建议。",[32,912,914],{"id":913},"textrue","Textrue",[11,916,917],{},"尽量减少图形的分辨率，在使用单色的Textrue时，使用1x1的图片即可，在需要过渡特效时，使用一个小条就可以了。",[11,919,920],{},[462,921],{"alt":464,"src":922},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-3.png",[11,924,925],{},"而有对称性的图片，可以通过调整Tilling Method来减小尺寸。",[11,927,928],{},[462,929],{"alt":464,"src":930},"\u002Fwp-content\u002Fuploads\u002F2017\u002F12\u002Fimage_thumb-4.png",[11,932,933],{},[87,934,935],{},"Mipmap",[11,937,938],{},"有一个ComputeMipLevel的节点可以用于将Mipmap进行可视化调节。",[11,940,941],{},"Mipmap虽然可以在贴图的选项中关掉，但是这样会导致原始贴图常驻内存，运行时消耗很大。",[11,943,944],{},"Texture的Uv入口被用于缩放时并不会对应到MipLevel上，使用时需要注意。",[11,946,947],{},[87,948,949],{},"Power",[11,951,952],{},"不要使用Power来进行2.2次方的运算而是直接相乘。也可以直接将同一个数相乘写成一个材质函数，方便使用。",[11,954,955],{},"因为两个的计算几乎无法目视区分，却有着明显的性能消耗。",[11,957,958],{},[87,959,960],{},"vector displacement map",[11,962,963],{},"矢量置换贴图采用BC7压缩可以大幅减小尺寸？",[11,965,966],{},[87,967,968],{},"Maximum Texture Size",[11,970,971],{},"比LOD Bias能够更好的对贴图尺寸进行控制。",[11,973,974],{},[87,975,976],{},"Never Stream",[11,978,979],{},"会一次性载入所有的MipLevel贴图，在使用频率很高的粒子的贴图上可以打开。",[32,981,110],{"id":109},[11,983,984],{},[87,985,986],{},"除0対策",[11,988,989],{},"为了防止0被传到除法运算上，可以考虑加上0.0001，虽然严格意义上应当使用Max或者Min来进行控制，但是Add的效率更高",[11,991,992],{},[87,993,994],{},"Texture参数",[11,996,997],{},"即便两个参数指定的是同一个Texture，也会进行两次采样。",[11,999,1000],{},[87,1001,1002],{},"SamplerType",[11,1004,1005],{},"SamplerType选择为Alpha时在有的平台实际上用的确实R通道",[32,1007,1009],{"id":1008},"trick","Trick",[11,1011,1012],{},"DynamicParameter 对于float2、float3、float4，如果实际上需要的参数是一样的话，是可以用float来代替的，会自动的被变为对应的类型。",[11,1014,1015],{},"ParticleColor 可以在EmissiveColor不需要通过这个参数指定时用于其他用途",[11,1017,1018],{},"ParticleRandom是GPU粒子独有的节点，值在0~1之间，可以用于添加随机特效",[11,1020,1021],{},"Static Component Mask Parameter可以为1张贴图提供四种变数",[11,1023,1024],{},"UV使用Time进行滚动时，可以先滚动再Tilling，这样的话Tilling进行调整时就不会影响UV动画的速度了。",{"title":134,"searchDepth":369,"depth":370,"links":1026},[1027,1031,1035,1039],{"id":794,"depth":369,"text":794,"children":1028},[1029,1030],{"id":803,"depth":370,"text":804},{"id":813,"depth":370,"text":814},{"id":826,"depth":369,"text":826,"children":1032},[1033,1034],{"id":664,"depth":370,"text":665},{"id":849,"depth":370,"text":850},{"id":867,"depth":369,"text":867,"children":1036},[1037,1038],{"id":873,"depth":370,"text":874},{"id":885,"depth":370,"text":886},{"id":906,"depth":369,"text":907,"children":1040},[1041,1042,1043],{"id":913,"depth":370,"text":914},{"id":109,"depth":370,"text":110},{"id":1008,"depth":370,"text":1009},{"layout":391,"status":392,"published":393,"author":1045,"author_login":396,"author_email":397,"wordpress_id":1046,"wordpress_url":1047,"date_gmt":1048,"excerpt":1049},{"display_name":395,"login":396,"email":397,"url":134},2192,"\u002F\u002F?p=2192","2017-12-31 16:02:08 +0000",{"type":8,"value":1050},[1051],[11,1052,788],{},"\u002F2018-01-01-ue4-material-deepdive-note",{"title":783,"description":788},"_legacy\u002F2018\u002F2018-01-01-ue4-material-deepdive-note",[410,110],"fKGSuTdtw5DEXqYUD_-VBIoQmLlq-aYwVtmxYL8eN0Q",{"id":1059,"title":1060,"body":1061,"date":766,"description":1065,"extension":389,"meta":1209,"navigation":393,"path":1218,"seo":1219,"stem":1220,"tags":1221,"__hash__":1224},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-ue4-project-notes.md","UE4项目笔记汇总",{"type":8,"value":1062,"toc":1198},[1063,1066,1069,1072,1075,1078,1081,1084,1092,1095,1098,1102,1105,1108,1111,1114,1117,1120,1123,1129,1132,1135,1138,1141,1144,1150,1153,1157,1160,1164,1167,1170,1176,1179,1183,1186,1189,1195],[11,1064,1065],{},"这里是过去一段与项目有关的笔记汇总，把一些零零碎碎的给合在了一起。",[11,1067,1068],{},"由于项目一直停留在4.15，所以本文所有内容都是基于UE4.15.3的。",[18,1070,1071],{"id":1071},"物理相关",[11,1073,1074],{},"由于做的项目对运动的精度要求比较高，所以和物理打了很长一段时间的交道。",[11,1076,1077],{},"在用UE4之前基本没接触过3D的物理引擎。好在之前有用过Box2D，一些基本的物理引擎原理还是有些了解的。",[32,1079,1080],{"id":1080},"球体运动",[11,1082,1083],{},"遇到的第一个问题是球体运动相关的，Physx对球体运动的模拟有些糟糕。",[11,1085,1086,1087,1091],{},"主要表现是Friction对球体的速度影响相当的微妙，导致球的运动很难停下来。而如果使用LinearDumping和AngularDumping来进行速度制御的话，由于其并不是匀减速的，而且即便与Friction没有关系，运动也不真实。这部分的解决方案之前已经记录在[",[425,1088,1090],{"href":1089},"\u002F2017-05-03-ue4-physx-and-substepping\u002F","UE4中的Physx物理","]这篇文章中了，总之就是自己接管物理的部分运算。",[11,1093,1094],{},"更加区域的注册自己添加上摩擦力和反弹。途中遇到过很多问题，现在回头看的话，发现代码的实现还是不够简洁。",[11,1096,1097],{},"而且虽然一开始坚持想要存依靠公式来进行运动控制，但最后还是往里面添加了不少“黑魔法”……",[32,1099,1101],{"id":1100},"foliage碰撞","Foliage碰撞",[11,1103,1104],{},"后面出现的一个需求是关于Foliage的碰撞的，总体而言就是需要树的不同部分表现出不同的碰撞特性。",[11,1106,1107],{},"比如树叶区域进行Overlap，而树干区域进行Block。但是由于Foliage本身是一种Instanced的Actor，UE4在实现时，对于同一个FoliageType只能使用一个碰撞配置。",[11,1109,1110],{},"不过Instance主要是针对渲染而进行的一种优化方案，既然Foliage是可以进行碰撞的，那么在Physx world中肯定是有注册碰撞形态的。",[11,1112,1113],{},"几经探索之后终于找到解决方案，通过UE4提供的Physx底层Api，将Foliage Type实例化之后的碰撞形态取出，单独将Capsule的碰撞形态重新注册到Physx中去。",[11,1115,1116],{},"这里本来是想直接对PxActor的碰撞配置进行修改的，但是UE4在上层有封装，无法将Overlap的物体的碰撞响应传递出去。所以只有采取取出重新注册的方案。",[11,1118,1119],{},"但是由于场景中的Foliage实在是太多，在地图载入时进行整体性操作会导致不可接受的时间消耗，最后采用动态转化的方案，将玩家周围的Foliage取出，并拉取出Capsule来实现。",[11,1121,1122],{},"这里面有遇到一个困扰了很久的问题，那就是Capsule取出之后怎么都无法找到正确的对应Roation。几经周折才发现UE4中的Capsule和Physx中的Capsule是有不同的默认朝向的，需要手动进行一次转化才行：",[125,1124,1127],{"className":1125,"code":1126,"language":130},[128],"static const PxQuat CapsuleRotator(0.f, 0.707106781f, 0.f, 0.707106781f);\n\nPxQuat ConvertToPhysXCapsuleRot(const FQuat& GeomRot)\n{\n  \u002F\u002F Rotation required because PhysX capsule points down X, we want it down Z\n  return U2PQuat(GeomRot) * CapsuleRotator;\n}\n\nFQuat ConvertToUECapsuleRot(const PxQuat & PGeomRot)\n{\n return P2UQuat(PGeomRot * CapsuleRotator.getConjugate());\n}\n",[132,1128,1126],{"__ignoreMap":134},[18,1130,1131],{"id":1131},"屏幕偏移",[11,1133,1134],{},"就是要让FOV的计算有一个Offset，当时查了很多资料。只在AnswerHub找到一个旧版本的不是很完全的实现。",[11,1136,1137],{},"好在通过那个页面里的讨论找到了解决的方向，而不用自己深入到UE4的渲染代码中去找Hack Point。",[11,1139,1140],{},"最主要的问题是，讨论最后给出的Viewport的转换矩阵是错的，转换之后的结果并不能让人满意。UE4的渲染实现并不“标准”，所以使用通用的摄像投影矩阵还是无法实现类似视点中心偏移的效果。",[11,1142,1143],{},"最后终于在代码深处的BlendCamera中找到了UE4本身对视角偏移的处理，得出了“正确”的偏移矩阵",[125,1145,1148],{"className":1146,"code":1147,"language":130},[128],"\u002F** 计算投影矩阵 *\u002F\nfloat t_fRatio = InCamera->AspectRatio;\nif (!InCamera->bConstrainAspectRatio)\n{\n t_fRatio = t_ScreenSize.X \u002F t_ScreenSize.Y;\n}\n\nfloat t_fFov = InCamera->FieldOfView;\nfloat t_fNear = GNearClippingPlane;\n\nresult = FReversedZPerspectiveMatrix(t_fFov * PI \u002F 360.0f, t_fRatio, 1.0f, t_fNear);\n\nif (Offset.IsZero())\n{\n return true;\n}\n\n\u002F** 将Offset规范到百分比 *\u002F\nOffset.X \u002F= (t_ScreenSize.X \u002F 2.0f);\nOffset.Y \u002F= (t_ScreenSize.Y \u002F 2.0f);\n\n\u002F** Clamp以避免“过度”偏移 *\u002F\nOffset = FMath::Clamp(Offset, FVector2D(-1, -1), FVector2D(1, 1));\n\nconst float Left = -1.0f + Offset.X;\nconst float Right = Left + 2.0f;\nconst float Bottom = -1.0f + Offset.Y;\nconst float Top = Bottom + 2.0f;\n\nresult.M[2][0] = (Left + Right) \u002F (Left - Right);\nresult.M[2][1] = (Bottom + Top) \u002F (Bottom - Top);\n",[132,1149,1147],{"__ignoreMap":134},[11,1151,1152],{},"不过，当时由于实现的比较急切，直接沿用讨论的方向，对Viewport类进行了重载，也使用了一些“黑魔法，虽然至今没有观测到副作用，但是现在想来，其实还是有其他的解决方案的。",[18,1154,1156],{"id":1155},"ui相关","UI相关",[11,1158,1159],{},"UMG只实现了默认的几种常见的Widget，所以有很多需要实现的自定义控件。这个也算是UI开发的常态了，但是当时有两个问题还是困扰了一段时间。",[32,1161,1163],{"id":1162},"slate的序列帧","Slate的序列帧",[11,1165,1166],{},"UMG的序列帧实现在社区可以找到，但是当需要在LoadingScreen上播放序列帧时就会有麻烦。因为那个时候UMG系统还没有初始化完成，只能使用Slate。",[11,1168,1169],{},"在参考了UE4的进度条实现之后，找到了相关的接口：",[125,1171,1174],{"className":1172,"code":1173,"language":130},[128],"FVector2D Min(FrameSize.X * Column, FrameSize.Y * Row);\nFVector2D Max = Min + FrameSize;\nFBox2D UVCoordinates(Min \u002F TextureSize, Max \u002F TextureSize);\nUVCoordinates.bIsValid = true;\n\nBrush.SetUVRegion(MoveTemp(UVCoordinates));\n",[132,1175,1173],{"__ignoreMap":134},[11,1177,1178],{},"这样就可以实现序列帧的切换了。",[32,1180,1182],{"id":1181},"_2d画线","2D画线",[11,1184,1185],{},"FSlateDrawElement::MakeLines有一个问题，那就是不知为何最后实现的时候Thickness这个参数是不起作用的。",[11,1187,1188],{},"因此要画粗线就必须自己想办法，虽然试过很多方法，包括参考UE4内部的线段AA实现，但是最后还是采用了最不优雅的解决方案：多画几次。",[125,1190,1193],{"className":1191,"code":1192,"language":130},[128],"FVector2D DrawPosition;\nfor (; aTimes > 0; --aTimes)\n{\n DrawPosition = FVector2D::ZeroVector;\n if (m_bIsVert) DrawPosition.Y = aTimes;\n else DrawPosition.X = aTimes;\n\n FSlateDrawElement::MakeLines(\n  InContext.OutDrawElements,\n  InContext.MaxLayer,\n  InContext.AllottedGeometry.ToPaintGeometry(DrawPosition, FVector2D(1.0f,1.0f), 1.0f),\n  tLine,\n  InContext.MyClippingRect,\n  ESlateDrawEffect::None,\n  color,\n  true,\n  0.1f);\n}\n",[132,1194,1192],{"__ignoreMap":134},[11,1196,1197],{},"因为这样画出来的才是最平滑的，好在对粗细的要求并不高，否则可能会加大绘制的负担。",{"title":134,"searchDepth":369,"depth":370,"links":1199},[1200,1204,1205],{"id":1071,"depth":369,"text":1071,"children":1201},[1202,1203],{"id":1080,"depth":370,"text":1080},{"id":1100,"depth":370,"text":1101},{"id":1131,"depth":369,"text":1131},{"id":1155,"depth":369,"text":1156,"children":1206},[1207,1208],{"id":1162,"depth":370,"text":1163},{"id":1181,"depth":370,"text":1182},{"layout":391,"status":392,"published":393,"author":1210,"author_login":396,"author_email":397,"wordpress_id":1211,"wordpress_url":1212,"date_gmt":1213,"excerpt":1214},{"display_name":395,"login":396,"email":397,"url":134},2170,"\u002F\u002F?p=2170","2017-12-31 16:01:50 +0000",{"type":8,"value":1215},[1216],[11,1217,1065],{},"\u002F2018-01-01-ue4-project-notes",{"title":1060,"description":1065},"_legacy\u002F2018\u002F2018-01-01-ue4-project-notes",[410,1222,1223],"UMG","物理","8GIMwwB_ylWoPujHfm3zYlq3ZQOiZ_ohYp777ox0kVk",{"id":1226,"title":1227,"body":1228,"date":766,"description":1232,"extension":389,"meta":1703,"navigation":393,"path":1712,"seo":1713,"stem":1714,"tags":1715,"__hash__":1716},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-ue4-rendering-code-view-01.md","UE4渲染代码逻辑总结（上）",{"type":8,"value":1229,"toc":1677},[1230,1233,1236,1239,1242,1246,1249,1252,1257,1260,1263,1266,1271,1274,1277,1280,1283,1288,1291,1294,1299,1302,1305,1308,1311,1315,1318,1323,1326,1331,1334,1339,1342,1347,1350,1354,1357,1360,1406,1409,1414,1417,1420,1425,1428,1431,1435,1439,1442,1445,1451,1454,1457,1463,1466,1469,1473,1476,1482,1485,1488,1494,1497,1501,1504,1507,1510,1513,1517,1520,1523,1527,1530,1533,1536,1540,1546,1549,1552,1558,1561,1564,1570,1573,1579,1582,1586,1592,1595,1598,1604,1607,1610,1614,1617,1626,1629,1632,1635,1641,1644,1647,1653,1656,1662,1665,1668,1674],[11,1231,1232],{},"本文是对UE4中渲染代码逻辑的总结。",[11,1234,1235],{},"当前使用的UE4版本为4.18.3。",[11,1237,1238],{},"本文内容是之前提到的这段时间的笔记的总结，内容主要来自于社区文章、官方文档以及引擎源码的阅读。",[11,1240,1241],{},"由于渲染系统比较复杂，没有时间遍历所有的代码，很多地方掺杂了自己的臆测，如有错误，欢迎指正。",[18,1243,1245],{"id":1244},"fshader","FShader",[11,1247,1248],{},"FShader是负责Shader的代码端基类，继承自FDeferredCleanupInterface。",[32,1250,1251],{"id":1251},"结构",[11,1253,1254],{},[87,1255,1256],{},"FDeferredCleanupInterface",[11,1258,1259],{},"FDeferredCleanupInterface这个基类是用于游戏线程与渲染线程的同步的，只有一个纯虚函数：FinishCleanup。",[11,1261,1262],{},"由于渲染用的资源在两边都有在使用，所以当要删除一个资源时，会向渲染线程推送资源删除请求，同时通过BeginCleanup将其加入待删除列表，等到渲染线程结束时FinishCleanup就会被调用，这时候就可以安全的在游戏线程中完全释放资源了。",[11,1264,1265],{},"除了FShader之外，其他与渲染有关的资源如FLightMap以及FShadowMap等都有继承自这个类。",[11,1267,1268],{},[87,1269,1270],{},"FShaderResource",[11,1272,1273],{},"这个类也继承自FDeferredCleanupInterface，如其名称，它负责保管Shader编译之后的资源。",[11,1275,1276],{},"为了减小材质编译等对资源的占用，同一个FShaderResource会被多个FShader引用。例如Material Function就利用了这个机制。",[32,1278,1279],{"id":1279},"使用",[11,1281,1282],{},"FShader共有两种类型的之类，分别是FGlobalShader与FMaterialShader，对应不同的使用用途。",[11,1284,1285],{},[87,1286,1287],{},"FGlobalShader",[11,1289,1290],{},"这个是全局的Shader，只允许存在一个实例。",[11,1292,1293],{},"渲染的核心Shader部分有许多就是FGlobalShader。",[11,1295,1296],{},[87,1297,1298],{},"FMaterialShader",[11,1300,1301],{},"这个是用于具体材质的Shader，会拥有很多实例。",[11,1303,1304],{},"进一步被实现为FMeshMaterialShader，可以将Mesh的顶点数据引入到Shader端。",[18,1306,1307],{"id":1307},"渲染数据",[11,1309,1310],{},"渲染数据通过FPrimitiveSceneProxy经由结构FVertexFactory绑定到Shader上。",[32,1312,1314],{"id":1313},"fvertexfactory","FVertexFactory",[11,1316,1317],{},"这个类负责将顶点数据从C++端带到Shader端，继承自FRenderResource，是渲染资源的一种。",[11,1319,1320],{},[87,1321,1322],{},"FLocalVertexFactory",[11,1324,1325],{},"提供本地空间到全局空间的转换，Static Mesh以及Cables、Procedual Mesh等都使用的是它。",[11,1327,1328],{},[87,1329,1330],{},"FGPUBaseSkinVertexFactory",[11,1332,1333],{},"这个是Skeletel Mesh用的，因为需要一些更多的数据。但是似乎还要配合继承自LocalVertexFactory的FGPUSkinPassthroughVertexFactory。",[11,1335,1336],{},[87,1337,1338],{},"FLandscapeVertexFactory",[11,1340,1341],{},"Landscape是基于VTF(Vertex Texture Fetch)，使用高度图来修改顶点位置实现的，所以需要额外的处理。",[11,1343,1344],{},[87,1345,1346],{},"FParticleVertexFactoryBase",[11,1348,1349],{},"粒子系统用的。",[32,1351,1353],{"id":1352},"fprimitivesceneproxy","FPrimitiveSceneProxy",[11,1355,1356],{},"这个类似UPrimitiveComponent在渲染线程中的对应版本，负责维护每个Component在渲染线程上需要的数据。",[11,1358,1359],{},"UE4的核心类大多有自己在渲染线程中的对应",[237,1361,1362,1372],{},[240,1363,1364],{},[243,1365,1366,1369],{},[246,1367,1368],{},"游戏线程",[246,1370,1371],{},"渲染线程",[253,1373,1374,1382,1390,1398],{},[243,1375,1376,1379],{},[258,1377,1378],{},"UWorld",[258,1380,1381],{},"FScene",[243,1383,1384,1387],{},[258,1385,1386],{},"UPrimitiveComponent",[258,1388,1389],{},"FPrimitiveSceneProxy \u002F FPrimitiveSceneInfo‬",[243,1391,1392,1395],{},[258,1393,1394],{},"ULocalPlayer",[258,1396,1397],{},"FSceneViewState",[243,1399,1400,1403],{},[258,1401,1402],{},"ULightComponent",[258,1404,1405],{},"FLightSceneProxy \u002F FLightSceneInfo",[11,1407,1408],{},"不同的UPrimitiveComponent类通过重载CreateSceneProxy()来创建自己的FPrimitiveSceneProxy。",[11,1410,1411],{},[87,1412,1413],{},"UCableComponent",[11,1415,1416],{},"非常的直观，在CreateSceneProxy()中直接创建FCableSceneProxy，然后进行初始化。",[11,1418,1419],{},"然后在SendRenderDyamicData_Concurrent()中将数据发送到渲染线程并借由SetDynamicData_RenderThread进行数据构造。",[11,1421,1422],{},[87,1423,1424],{},"UImagePlateFrustumComponent",[11,1426,1427],{},"由于始终只是在渲染面向摄像的2D材质，所以不需要VertexFactory。",[11,1429,1430],{},"所以FImagePlateFrustumSceneProxy只是在GetDynamicMeshElements时返回演算的结果。",[18,1432,1434],{"id":1433},"drawing-policy","Drawing Policy",[32,1436,1438],{"id":1437},"fdepthdrawingpolicy","FDepthDrawingPolicy",[11,1440,1441],{},"这个Drawing Policy工作于depth-only通道时，负责将Mesh的opaque和masked的深度信息写出。",[11,1443,1444],{},"通过调用",[125,1446,1449],{"className":1447,"code":1448,"language":130},[128],"VertexShader = InMaterialResource.GetShader\u003CTDepthOnlyVS\u003Cfalse> >(VertexFactory->GetType())\n",[132,1450,1448],{"__ignoreMap":134},[11,1452,1453],{},"DrawingPolicy便找到了对应的shader，并将vertex factory传了进去。",[11,1455,1456],{},"在需要Tessellation的情况下，还会另外获取HullShader和DomainShader",[125,1458,1461],{"className":1459,"code":1460,"language":130},[128],"HullShader = InMaterialResource.GetShader\u003CFDepthOnlyHS>(VertexFactory->GetType());\nDomainShader = InMaterialResource.GetShader\u003CFDepthOnlyDS>(VertexFactory->GetType());\n",[132,1462,1460],{"__ignoreMap":134},[11,1464,1465],{},"代码的分支很多，但是作用还是比较明显的。",[11,1467,1468],{},"其中还有SetSharedState和SetMeshRenderState两个函数负责传递参数。",[32,1470,1472],{"id":1471},"fbasepassdrawingpolicy","FBasePassDrawingPolicy",[11,1474,1475],{},"这个是在basepass通道时处理Mesh，根据不同的光照类型会有不同的处理",[125,1477,1480],{"className":1478,"code":1479,"language":130},[128],"template\u003Ctypename LightMapPolicyType>\nclass TBasePassDrawingPolicy : public FBasePassDrawingPolicy\n",[132,1481,1479],{"__ignoreMap":134},[11,1483,1484],{},"这个类才算是本体的感觉吧。",[11,1486,1487],{},"会根据不同的光照类型获取不同的Base pass的shader，这里的光照类型并不是单纯的编辑器中设置的类型，而是实际在Shader中用于计算的光照模型",[125,1489,1492],{"className":1490,"code":1491,"language":130},[128],"enum ELightMapPolicyType\n{\nLMP_NO_LIGHTMAP,\nLMP_PRECOMPUTED_IRRADIANCE_VOLUME_INDIRECT_LIGHTING,\nLMP_CACHED_VOLUME_INDIRECT_LIGHTING,\nLMP_CACHED_POINT_INDIRECT_LIGHTING,\nLMP_SIMPLE_NO_LIGHTMAP,\nLMP_SIMPLE_LIGHTMAP_ONLY_LIGHTING,\nLMP_SIMPLE_DIRECTIONAL_LIGHT_LIGHTING,\nLMP_SIMPLE_STATIONARY_PRECOMPUTED_SHADOW_LIGHTING,\nLMP_SIMPLE_STATIONARY_SINGLESAMPLE_SHADOW_LIGHTING,\nLMP_SIMPLE_STATIONARY_VOLUMETRICLIGHTMAP_SHADOW_LIGHTING,\nLMP_LQ_LIGHTMAP,\nLMP_HQ_LIGHTMAP,\nLMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP,\n\u002F\u002F Mobile specific\nLMP_MOBILE_DISTANCE_FIELD_SHADOWS_AND_LQ_LIGHTMAP,\nLMP_MOBILE_DISTANCE_FIELD_SHADOWS_LIGHTMAP_AND_CSM,\nLMP_MOBILE_DIRECTIONAL_LIGHT_AND_SH_INDIRECT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_AND_SH_INDIRECT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM_AND_SH_INDIRECT,\nLMP_MOBILE_DIRECTIONAL_LIGHT_CSM_AND_SH_INDIRECT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_WITH_LIGHTMAP,\nLMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM_WITH_LIGHTMAP,\n\u002F\u002F LightMapDensity\nLMP_DUMMY\n};\n",[132,1493,1491],{"__ignoreMap":134},[11,1495,1496],{},"GetUniformBasePassShaders负责完成差分，最终通过LightMapPolicyType就会得到不同的basepass的shader，不同的basepass的shader中，使用的vertex factory也就不同了。",[32,1498,1500],{"id":1499},"drawingpolicyfactory","DrawingPolicyFactory",[11,1502,1503],{},"这是一组负责生成DrawingPloicy的工厂类，但是他们都没有各自的基类，而是对应自己的功能有稍微有些不同的实现。",[11,1505,1506],{},"在FDepthDrawingPolicyFactory::AddStaticMesh能够看到一个FStaticMesh是如何被注册到FScene中去的。",[11,1508,1509],{},"而这个调用来自FStaticMesh::AddToDrawLists，在其中能够看到FStaticMesh在通过各种DrawingPolicyFactory来进行Shader的关联注册。",[11,1511,1512],{},"而之后，在渲染线程中，FDepthDrawingPolicyFactory::DrawStaticMesh之类的函数就会被调用来进行渲染。",[32,1514,1516],{"id":1515},"fprimitivesceneinfo","FPrimitiveSceneInfo",[11,1518,1519],{},"对DrawingPolicyFactory的调用最终来自FPrimitiveSceneInfo，这个类与FPrimitiveSceneProxy 是一对一的关系，是最终注册到FScene的数据结构。",[11,1521,1522],{},"在FScene::UpdatePrimitiveTransform_RenderThread中能够看到渲染线程是如何通过FPrimitiveSceneProxy ::GetPrimitiveSceneInfo来更新与FScene的关系的。",[18,1524,1526],{"id":1525},"c到shader的绑定","C++到Shader的绑定",[11,1528,1529],{},"上面这些类是在C++中负责渲染逻辑的，而为了最终代码与Shader之间能够相互交流需要进行绑定。",[32,1531,1532],{"id":1532},"绑定帮助宏",[11,1534,1535],{},"UE4中有一组宏来帮助绑定C++类与Shader代码。",[115,1537,1539],{"id":1538},"fshader绑定","FShader绑定",[125,1541,1544],{"className":1542,"code":1543,"language":130},[128],"IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)\n",[132,1545,1543],{"__ignoreMap":134},[11,1547,1548],{},"这个是实际将C++的类与Shader进行绑定的宏。",[11,1550,1551],{},"在引擎中能够看到很多这个宏，例如",[125,1553,1556],{"className":1554,"code":1555,"language":130},[128],"IMPLEMENT_MATERIAL_SHADER_TYPE(,FVelocityVS,TEXT(\"\u002FEngine\u002FPrivate\u002FVelocityShader.usf\"),TEXT(\"MainVertexShader\"),SF_Vertex);\n",[132,1557,1555],{"__ignoreMap":134},[11,1559,1560],{},"这个宏将FVelocityVS绑定到VelocityShader.usf中，而MainVertexShader是shader端的入口函数。",[11,1562,1563],{},"最后一个ShaderFrequency感觉上更加接近Shader的类型：",[125,1565,1568],{"className":1566,"code":1567,"language":130},[128],"enum EShaderFrequency\n{\nSF_Vertex            = 0,\nSF_Hull                = 1,\nSF_Domain            = 2,\nSF_Pixel            = 3,\nSF_Geometry            = 4,\nSF_Compute            = 5,\n\nSF_NumFrequencies    = 6,\n\nSF_NumBits            = 3,\n};\n",[132,1569,1567],{"__ignoreMap":134},[11,1571,1572],{},"第一个参数用于宏的进一步模板化，在上面的BasePass进行绑定的时候就能看到",[125,1574,1577],{"className":1575,"code":1576,"language":130},[128],"IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE\n",[132,1578,1576],{"__ignoreMap":134},[11,1580,1581],{},"这个宏对这个参数的使用。",[115,1583,1585],{"id":1584},"vertexfactory绑定","VertexFactory绑定",[125,1587,1590],{"className":1588,"code":1589,"language":130},[128],"IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly)\n",[132,1591,1589],{"__ignoreMap":134},[11,1593,1594],{},"这个宏将VertexFactory绑定到对应的shader中去",[11,1596,1597],{},"例如",[125,1599,1602],{"className":1600,"code":1601,"language":130},[128],"IMPLEMENT_VERTEX_FACTORY_TYPE(FGPUSkinPassthroughVertexFactory, \"\u002FEngine\u002FPrivate\u002FLocalVertexFactory.ush\", true, false, true, false, false);\n",[132,1603,1601],{"__ignoreMap":134},[11,1605,1606],{},"这个绑定使得FGPUSkinPassthroughVertexFactory与LocalVertexFactory.ush进行关联，能够看到有很多不同的VertexFactory绑定到了这里，而且VertexFatory在各个不同的ush里面都有定义。",[11,1608,1609],{},"这是因为前面有提到MeshShader是有很多实例，每一个实例会根据自己的光照类型、数据类型进行不同的绑定。",[32,1611,1613],{"id":1612},"shader-plugin","Shader Plugin",[11,1615,1616],{},"从UE4.17开始，已经可以在插件中自己定义Shader并进行调用了。不过UE4的核心渲染流程依然没有开放，所以想要自定义Shader Model之类的话还是必须对源码进行修改。",[11,1618,1619,1620,1625],{},"详细的操作可以参考[",[425,1621,1624],{"href":1622,"rel":1623},"https:\u002F\u002Fdocs-origin.unrealengine.com\u002Flatest\u002FINT\u002FProgramming\u002FRendering\u002FShaderInPlugin\u002FQuickStart\u002Findex.html",[429],"官方文档","]，不过官方文档的操作没有进行充分的解释，只是教你怎么把引擎插件的LensDistortion插件给拷贝并修改成自己的插件，不过刚好可以方便对Shader绑定进行理解。",[115,1627,1628],{"id":1628},"基础重载",[11,1630,1631],{},"ShouldCache用于定义是否在指定的平台要编译这个材质。",[11,1633,1634],{},"ModifyCompilationEnvironment用于在特定的平台上添加自己的定义，但是示例中直接就加进了两个自己的定义",[125,1636,1639],{"className":1637,"code":1638,"language":130},[128],"OutEnvironment.SetDefine(TEXT(\"GRID_SUBDIVISION_X\"), kGridSubdivisionX);\nOutEnvironment.SetDefine(TEXT(\"GRID_SUBDIVISION_Y\"), kGridSubdivisionY);\n",[132,1640,1638],{"__ignoreMap":134},[115,1642,1643],{"id":1643},"参数绑定",[11,1645,1646],{},"可以看到FLensDistortionUVGenerationShader继承自FGlobalShader，然后添加了",[125,1648,1651],{"className":1649,"code":1650,"language":130},[128],"FShaderParameter PixelUVSize;\nFShaderParameter RadialDistortionCoefs;\nFShaderParameter TangentialDistortionCoefs;\nFShaderParameter DistortedCameraMatrix;\nFShaderParameter UndistortedCameraMatrix;\nFShaderParameter OutputMultiplyAndAdd;\n",[132,1652,1650],{"__ignoreMap":134},[11,1654,1655],{},"几个成员，然后在构造中绑定",[125,1657,1660],{"className":1658,"code":1659,"language":130},[128],"PixelUVSize.Bind(Initializer.ParameterMap, TEXT(\"PixelUVSize\"));\nRadialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT(\"RadialDistortionCoefs\"));\n",[132,1661,1659],{"__ignoreMap":134},[11,1663,1664],{},"第二个参数是参数在Shader中的名称。",[11,1666,1667],{},"之后就可以通过",[125,1669,1672],{"className":1670,"code":1671,"language":130},[128],"SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue);\nSetShaderValue(RHICmdList, ShaderRHI, DistortedCameraMatrix, CompiledCameraModel.DistortedCameraMatrix);\n",[132,1673,1671],{"__ignoreMap":134},[11,1675,1676],{},"来进行修改了。",{"title":134,"searchDepth":369,"depth":370,"links":1678},[1679,1683,1687,1693],{"id":1244,"depth":369,"text":1245,"children":1680},[1681,1682],{"id":1251,"depth":370,"text":1251},{"id":1279,"depth":370,"text":1279},{"id":1307,"depth":369,"text":1307,"children":1684},[1685,1686],{"id":1313,"depth":370,"text":1314},{"id":1352,"depth":370,"text":1353},{"id":1433,"depth":369,"text":1434,"children":1688},[1689,1690,1691,1692],{"id":1437,"depth":370,"text":1438},{"id":1471,"depth":370,"text":1472},{"id":1499,"depth":370,"text":1500},{"id":1515,"depth":370,"text":1516},{"id":1525,"depth":369,"text":1526,"children":1694},[1695,1699],{"id":1532,"depth":370,"text":1532,"children":1696},[1697,1698],{"id":1538,"depth":378,"text":1539},{"id":1584,"depth":378,"text":1585},{"id":1612,"depth":370,"text":1613,"children":1700},[1701,1702],{"id":1628,"depth":378,"text":1628},{"id":1643,"depth":378,"text":1643},{"layout":391,"status":392,"published":393,"author":1704,"author_login":396,"author_email":397,"wordpress_id":1705,"wordpress_url":1706,"date_gmt":1707,"excerpt":1708},{"display_name":395,"login":396,"email":397,"url":134},2198,"\u002F\u002F?p=2198","2017-12-31 16:01:54 +0000",{"type":8,"value":1709},[1710],[11,1711,1232],{},"\u002F2018-01-01-ue4-rendering-code-view-01",{"title":1227,"description":1232},"_legacy\u002F2018\u002F2018-01-01-ue4-rendering-code-view-01",[409,410],"rDtA9FsRgIkV52wCHpGUtcAfohNBZfb4k6UKJoiKUyo",222,1788763183000]