[{"data":1,"prerenderedAt":527},["ShallowReactive",2],{"\u002F2018-01-01-ue4-rendering-code-view-01":3},{"id":4,"title":5,"body":6,"date":503,"description":12,"extension":504,"meta":505,"navigation":508,"path":520,"seo":521,"stem":522,"tags":523,"__hash__":526},"blogs\u002F_legacy\u002F2018\u002F2018-01-01-ue4-rendering-code-view-01.md","UE4渲染代码逻辑总结（上）",{"type":7,"value":8,"toc":474},"minimark",[9,13,16,19,22,27,30,34,40,43,46,49,54,57,60,63,66,71,74,77,82,85,88,91,94,98,101,106,109,114,117,122,125,130,133,137,140,143,195,198,203,206,209,214,217,220,224,228,231,234,245,248,251,257,260,263,267,270,276,279,282,288,291,295,298,301,304,307,311,314,317,321,324,327,330,335,341,344,347,353,356,359,365,368,374,377,381,387,390,393,399,402,405,409,412,423,426,429,432,438,441,444,450,453,459,462,465,471],[10,11,12],"p",{},"本文是对UE4中渲染代码逻辑的总结。",[10,14,15],{},"当前使用的UE4版本为4.18.3。",[10,17,18],{},"本文内容是之前提到的这段时间的笔记的总结，内容主要来自于社区文章、官方文档以及引擎源码的阅读。",[10,20,21],{},"由于渲染系统比较复杂，没有时间遍历所有的代码，很多地方掺杂了自己的臆测，如有错误，欢迎指正。",[23,24,26],"h2",{"id":25},"fshader","FShader",[10,28,29],{},"FShader是负责Shader的代码端基类，继承自FDeferredCleanupInterface。",[31,32,33],"h3",{"id":33},"结构",[10,35,36],{},[37,38,39],"strong",{},"FDeferredCleanupInterface",[10,41,42],{},"FDeferredCleanupInterface这个基类是用于游戏线程与渲染线程的同步的，只有一个纯虚函数：FinishCleanup。",[10,44,45],{},"由于渲染用的资源在两边都有在使用，所以当要删除一个资源时，会向渲染线程推送资源删除请求，同时通过BeginCleanup将其加入待删除列表，等到渲染线程结束时FinishCleanup就会被调用，这时候就可以安全的在游戏线程中完全释放资源了。",[10,47,48],{},"除了FShader之外，其他与渲染有关的资源如FLightMap以及FShadowMap等都有继承自这个类。",[10,50,51],{},[37,52,53],{},"FShaderResource",[10,55,56],{},"这个类也继承自FDeferredCleanupInterface，如其名称，它负责保管Shader编译之后的资源。",[10,58,59],{},"为了减小材质编译等对资源的占用，同一个FShaderResource会被多个FShader引用。例如Material Function就利用了这个机制。",[31,61,62],{"id":62},"使用",[10,64,65],{},"FShader共有两种类型的之类，分别是FGlobalShader与FMaterialShader，对应不同的使用用途。",[10,67,68],{},[37,69,70],{},"FGlobalShader",[10,72,73],{},"这个是全局的Shader，只允许存在一个实例。",[10,75,76],{},"渲染的核心Shader部分有许多就是FGlobalShader。",[10,78,79],{},[37,80,81],{},"FMaterialShader",[10,83,84],{},"这个是用于具体材质的Shader，会拥有很多实例。",[10,86,87],{},"进一步被实现为FMeshMaterialShader，可以将Mesh的顶点数据引入到Shader端。",[23,89,90],{"id":90},"渲染数据",[10,92,93],{},"渲染数据通过FPrimitiveSceneProxy经由结构FVertexFactory绑定到Shader上。",[31,95,97],{"id":96},"fvertexfactory","FVertexFactory",[10,99,100],{},"这个类负责将顶点数据从C++端带到Shader端，继承自FRenderResource，是渲染资源的一种。",[10,102,103],{},[37,104,105],{},"FLocalVertexFactory",[10,107,108],{},"提供本地空间到全局空间的转换，Static Mesh以及Cables、Procedual Mesh等都使用的是它。",[10,110,111],{},[37,112,113],{},"FGPUBaseSkinVertexFactory",[10,115,116],{},"这个是Skeletel Mesh用的，因为需要一些更多的数据。但是似乎还要配合继承自LocalVertexFactory的FGPUSkinPassthroughVertexFactory。",[10,118,119],{},[37,120,121],{},"FLandscapeVertexFactory",[10,123,124],{},"Landscape是基于VTF(Vertex Texture Fetch)，使用高度图来修改顶点位置实现的，所以需要额外的处理。",[10,126,127],{},[37,128,129],{},"FParticleVertexFactoryBase",[10,131,132],{},"粒子系统用的。",[31,134,136],{"id":135},"fprimitivesceneproxy","FPrimitiveSceneProxy",[10,138,139],{},"这个类似UPrimitiveComponent在渲染线程中的对应版本，负责维护每个Component在渲染线程上需要的数据。",[10,141,142],{},"UE4的核心类大多有自己在渲染线程中的对应",[144,145,146,159],"table",{},[147,148,149],"thead",{},[150,151,152,156],"tr",{},[153,154,155],"th",{},"游戏线程",[153,157,158],{},"渲染线程",[160,161,162,171,179,187],"tbody",{},[150,163,164,168],{},[165,166,167],"td",{},"UWorld",[165,169,170],{},"FScene",[150,172,173,176],{},[165,174,175],{},"UPrimitiveComponent",[165,177,178],{},"FPrimitiveSceneProxy \u002F FPrimitiveSceneInfo‬",[150,180,181,184],{},[165,182,183],{},"ULocalPlayer",[165,185,186],{},"FSceneViewState",[150,188,189,192],{},[165,190,191],{},"ULightComponent",[165,193,194],{},"FLightSceneProxy \u002F FLightSceneInfo",[10,196,197],{},"不同的UPrimitiveComponent类通过重载CreateSceneProxy()来创建自己的FPrimitiveSceneProxy。",[10,199,200],{},[37,201,202],{},"UCableComponent",[10,204,205],{},"非常的直观，在CreateSceneProxy()中直接创建FCableSceneProxy，然后进行初始化。",[10,207,208],{},"然后在SendRenderDyamicData_Concurrent()中将数据发送到渲染线程并借由SetDynamicData_RenderThread进行数据构造。",[10,210,211],{},[37,212,213],{},"UImagePlateFrustumComponent",[10,215,216],{},"由于始终只是在渲染面向摄像的2D材质，所以不需要VertexFactory。",[10,218,219],{},"所以FImagePlateFrustumSceneProxy只是在GetDynamicMeshElements时返回演算的结果。",[23,221,223],{"id":222},"drawing-policy","Drawing Policy",[31,225,227],{"id":226},"fdepthdrawingpolicy","FDepthDrawingPolicy",[10,229,230],{},"这个Drawing Policy工作于depth-only通道时，负责将Mesh的opaque和masked的深度信息写出。",[10,232,233],{},"通过调用",[235,236,241],"pre",{"className":237,"code":239,"language":240},[238],"language-text","VertexShader = InMaterialResource.GetShader\u003CTDepthOnlyVS\u003Cfalse> >(VertexFactory->GetType())\n","text",[242,243,239],"code",{"__ignoreMap":244},"",[10,246,247],{},"DrawingPolicy便找到了对应的shader，并将vertex factory传了进去。",[10,249,250],{},"在需要Tessellation的情况下，还会另外获取HullShader和DomainShader",[235,252,255],{"className":253,"code":254,"language":240},[238],"HullShader = InMaterialResource.GetShader\u003CFDepthOnlyHS>(VertexFactory->GetType());\nDomainShader = InMaterialResource.GetShader\u003CFDepthOnlyDS>(VertexFactory->GetType());\n",[242,256,254],{"__ignoreMap":244},[10,258,259],{},"代码的分支很多，但是作用还是比较明显的。",[10,261,262],{},"其中还有SetSharedState和SetMeshRenderState两个函数负责传递参数。",[31,264,266],{"id":265},"fbasepassdrawingpolicy","FBasePassDrawingPolicy",[10,268,269],{},"这个是在basepass通道时处理Mesh，根据不同的光照类型会有不同的处理",[235,271,274],{"className":272,"code":273,"language":240},[238],"template\u003Ctypename LightMapPolicyType>\nclass TBasePassDrawingPolicy : public FBasePassDrawingPolicy\n",[242,275,273],{"__ignoreMap":244},[10,277,278],{},"这个类才算是本体的感觉吧。",[10,280,281],{},"会根据不同的光照类型获取不同的Base pass的shader，这里的光照类型并不是单纯的编辑器中设置的类型，而是实际在Shader中用于计算的光照模型",[235,283,286],{"className":284,"code":285,"language":240},[238],"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",[242,287,285],{"__ignoreMap":244},[10,289,290],{},"GetUniformBasePassShaders负责完成差分，最终通过LightMapPolicyType就会得到不同的basepass的shader，不同的basepass的shader中，使用的vertex factory也就不同了。",[31,292,294],{"id":293},"drawingpolicyfactory","DrawingPolicyFactory",[10,296,297],{},"这是一组负责生成DrawingPloicy的工厂类，但是他们都没有各自的基类，而是对应自己的功能有稍微有些不同的实现。",[10,299,300],{},"在FDepthDrawingPolicyFactory::AddStaticMesh能够看到一个FStaticMesh是如何被注册到FScene中去的。",[10,302,303],{},"而这个调用来自FStaticMesh::AddToDrawLists，在其中能够看到FStaticMesh在通过各种DrawingPolicyFactory来进行Shader的关联注册。",[10,305,306],{},"而之后，在渲染线程中，FDepthDrawingPolicyFactory::DrawStaticMesh之类的函数就会被调用来进行渲染。",[31,308,310],{"id":309},"fprimitivesceneinfo","FPrimitiveSceneInfo",[10,312,313],{},"对DrawingPolicyFactory的调用最终来自FPrimitiveSceneInfo，这个类与FPrimitiveSceneProxy 是一对一的关系，是最终注册到FScene的数据结构。",[10,315,316],{},"在FScene::UpdatePrimitiveTransform_RenderThread中能够看到渲染线程是如何通过FPrimitiveSceneProxy ::GetPrimitiveSceneInfo来更新与FScene的关系的。",[23,318,320],{"id":319},"c到shader的绑定","C++到Shader的绑定",[10,322,323],{},"上面这些类是在C++中负责渲染逻辑的，而为了最终代码与Shader之间能够相互交流需要进行绑定。",[31,325,326],{"id":326},"绑定帮助宏",[10,328,329],{},"UE4中有一组宏来帮助绑定C++类与Shader代码。",[331,332,334],"h4",{"id":333},"fshader绑定","FShader绑定",[235,336,339],{"className":337,"code":338,"language":240},[238],"IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)\n",[242,340,338],{"__ignoreMap":244},[10,342,343],{},"这个是实际将C++的类与Shader进行绑定的宏。",[10,345,346],{},"在引擎中能够看到很多这个宏，例如",[235,348,351],{"className":349,"code":350,"language":240},[238],"IMPLEMENT_MATERIAL_SHADER_TYPE(,FVelocityVS,TEXT(\"\u002FEngine\u002FPrivate\u002FVelocityShader.usf\"),TEXT(\"MainVertexShader\"),SF_Vertex);\n",[242,352,350],{"__ignoreMap":244},[10,354,355],{},"这个宏将FVelocityVS绑定到VelocityShader.usf中，而MainVertexShader是shader端的入口函数。",[10,357,358],{},"最后一个ShaderFrequency感觉上更加接近Shader的类型：",[235,360,363],{"className":361,"code":362,"language":240},[238],"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",[242,364,362],{"__ignoreMap":244},[10,366,367],{},"第一个参数用于宏的进一步模板化，在上面的BasePass进行绑定的时候就能看到",[235,369,372],{"className":370,"code":371,"language":240},[238],"IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE\n",[242,373,371],{"__ignoreMap":244},[10,375,376],{},"这个宏对这个参数的使用。",[331,378,380],{"id":379},"vertexfactory绑定","VertexFactory绑定",[235,382,385],{"className":383,"code":384,"language":240},[238],"IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly)\n",[242,386,384],{"__ignoreMap":244},[10,388,389],{},"这个宏将VertexFactory绑定到对应的shader中去",[10,391,392],{},"例如",[235,394,397],{"className":395,"code":396,"language":240},[238],"IMPLEMENT_VERTEX_FACTORY_TYPE(FGPUSkinPassthroughVertexFactory, \"\u002FEngine\u002FPrivate\u002FLocalVertexFactory.ush\", true, false, true, false, false);\n",[242,398,396],{"__ignoreMap":244},[10,400,401],{},"这个绑定使得FGPUSkinPassthroughVertexFactory与LocalVertexFactory.ush进行关联，能够看到有很多不同的VertexFactory绑定到了这里，而且VertexFatory在各个不同的ush里面都有定义。",[10,403,404],{},"这是因为前面有提到MeshShader是有很多实例，每一个实例会根据自己的光照类型、数据类型进行不同的绑定。",[31,406,408],{"id":407},"shader-plugin","Shader Plugin",[10,410,411],{},"从UE4.17开始，已经可以在插件中自己定义Shader并进行调用了。不过UE4的核心渲染流程依然没有开放，所以想要自定义Shader Model之类的话还是必须对源码进行修改。",[10,413,414,415,422],{},"详细的操作可以参考[",[416,417,421],"a",{"href":418,"rel":419},"https:\u002F\u002Fdocs-origin.unrealengine.com\u002Flatest\u002FINT\u002FProgramming\u002FRendering\u002FShaderInPlugin\u002FQuickStart\u002Findex.html",[420],"nofollow","官方文档","]，不过官方文档的操作没有进行充分的解释，只是教你怎么把引擎插件的LensDistortion插件给拷贝并修改成自己的插件，不过刚好可以方便对Shader绑定进行理解。",[331,424,425],{"id":425},"基础重载",[10,427,428],{},"ShouldCache用于定义是否在指定的平台要编译这个材质。",[10,430,431],{},"ModifyCompilationEnvironment用于在特定的平台上添加自己的定义，但是示例中直接就加进了两个自己的定义",[235,433,436],{"className":434,"code":435,"language":240},[238],"OutEnvironment.SetDefine(TEXT(\"GRID_SUBDIVISION_X\"), kGridSubdivisionX);\nOutEnvironment.SetDefine(TEXT(\"GRID_SUBDIVISION_Y\"), kGridSubdivisionY);\n",[242,437,435],{"__ignoreMap":244},[331,439,440],{"id":440},"参数绑定",[10,442,443],{},"可以看到FLensDistortionUVGenerationShader继承自FGlobalShader，然后添加了",[235,445,448],{"className":446,"code":447,"language":240},[238],"FShaderParameter PixelUVSize;\nFShaderParameter RadialDistortionCoefs;\nFShaderParameter TangentialDistortionCoefs;\nFShaderParameter DistortedCameraMatrix;\nFShaderParameter UndistortedCameraMatrix;\nFShaderParameter OutputMultiplyAndAdd;\n",[242,449,447],{"__ignoreMap":244},[10,451,452],{},"几个成员，然后在构造中绑定",[235,454,457],{"className":455,"code":456,"language":240},[238],"PixelUVSize.Bind(Initializer.ParameterMap, TEXT(\"PixelUVSize\"));\nRadialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT(\"RadialDistortionCoefs\"));\n",[242,458,456],{"__ignoreMap":244},[10,460,461],{},"第二个参数是参数在Shader中的名称。",[10,463,464],{},"之后就可以通过",[235,466,469],{"className":467,"code":468,"language":240},[238],"SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue);\nSetShaderValue(RHICmdList, ShaderRHI, DistortedCameraMatrix, CompiledCameraModel.DistortedCameraMatrix);\n",[242,470,468],{"__ignoreMap":244},[10,472,473],{},"来进行修改了。",{"title":244,"searchDepth":475,"depth":476,"links":477},2,3,[478,482,486,492],{"id":25,"depth":475,"text":26,"children":479},[480,481],{"id":33,"depth":476,"text":33},{"id":62,"depth":476,"text":62},{"id":90,"depth":475,"text":90,"children":483},[484,485],{"id":96,"depth":476,"text":97},{"id":135,"depth":476,"text":136},{"id":222,"depth":475,"text":223,"children":487},[488,489,490,491],{"id":226,"depth":476,"text":227},{"id":265,"depth":476,"text":266},{"id":293,"depth":476,"text":294},{"id":309,"depth":476,"text":310},{"id":319,"depth":475,"text":320,"children":493},[494,499],{"id":326,"depth":476,"text":326,"children":495},[496,498],{"id":333,"depth":497,"text":334},4,{"id":379,"depth":497,"text":380},{"id":407,"depth":476,"text":408,"children":500},[501,502],{"id":425,"depth":497,"text":425},{"id":440,"depth":497,"text":440},"2018-01-01","md",{"layout":506,"status":507,"published":508,"author":509,"author_login":511,"author_email":512,"wordpress_id":513,"wordpress_url":514,"date_gmt":515,"excerpt":516},"post","publish",true,{"display_name":510,"login":511,"email":512,"url":244},"风铃","flinkor","flinkor@foxmail.com",2198,"\u002F\u002F?p=2198","2017-12-31 16:01:54 +0000",{"type":7,"value":517},[518],[10,519,12],{},"\u002F2018-01-01-ue4-rendering-code-view-01",{"title":5,"description":12},"_legacy\u002F2018\u002F2018-01-01-ue4-rendering-code-view-01",[524,525],"shader","UE4","rDtA9FsRgIkV52wCHpGUtcAfohNBZfb4k6UKJoiKUyo",1788763179440]