UE4自学到什么程度怎么样才能自学英语找到工作

344被浏览13,593分享邀请回答386 条评论分享收藏感谢收起281 条评论分享收藏感谢收起UE4学习UE4学习学习UE4的博客记录关注专栏更多UE4{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\u002Fpay.zhihu.com\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&title&:&UE4编辑器分析 之 基础交互--DeltaTracker&,&author&:&yanjinzha&,&content&:&\u003Cp\u003EEditorAxis & MouseDeltaTracker&SnappingUtils\u003C\u002Fp\u003E\u003Cp\u003E负责物件坐标变换的交互和实现,实现物件的平移、旋转、缩放等\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E类图:\u003C\u002Fp\u003E\u003Cimg src=\&v2-6f34cc384c57e03064eab69.png\& data-rawwidth=\&939\& data-rawheight=\&443\&\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003EEditorAxis负责交互状态的保存和转换,同时提供拖动的交互入口。物件执行变换后,会调整EditorAxis的跟随状态;保持一直跟随物件。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EMouseDeltaTracker有三个作用:用来记录鼠标拖拽的时候积累的delta值,然后把delta值转换成坐标变换的delta translation,最后应用到物件上。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E拖动坐标轴移动物体 时序图:\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cimg src=\&v2-cab.png\& data-rawwidth=\&572\& data-rawheight=\&437\&\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003ESnappingUtil负责吸附功能的数据归一,过程发生在DeltaTracker进行计算累计Delta的阶段, 使拖拽的更精准的控制歩幅。时序图如下:\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cimg src=\&v2-e2b69b605f1eb44d2a2bb03.png\& data-rawwidth=\&587\& data-rawheight=\&300\&\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003E附UED中针对拖拽控制移动的delta转换算法,示意图如下:\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cimg src=\&v2-b8f8bfc93782cf12dff4c465a67f8d71.png\& data-rawwidth=\&933\& data-rawheight=\&778\&\u003E\u003Cbr\u003E\u003Cblockquote\u003E\u003Cp\u003E确定拖拽平面及其法线;\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E确定相机位置,方向作为EyeDirection;\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E在相机的ViewMatrix和ProjectionMatrix中做Deproject,获取鼠标点的位置,方向作为PixelDirection;\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E图P0点为开始拖拽前的物体位置,P1为摄像机的位置\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cblockquote\u003E\u003Cp\u003E如果PixelDirection与拖拽平面的法线点乘的绝对值小于特定数(可看作两向量垂直,此时pixel方向约等于正在拖动的坐标轴的方向),不满足 拖拽要求。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E满足要求的情况下下会先计算P2:向量P1P2方向与Pixel相同,大小与P1P0相同,得出P2点作为ProposedEyeEndPoint\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E计算P1P2与拖拽平面的交点记为P3,P3可看作新的物体位置,但计算时会先在刚刚StartTracking的时候求出P0P3,作为初始的相对位置InitialOffSet;\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E在拖动的过程中P3的位置不断更新,而InitialOffSet不会改变就可以求出DeltaDrag\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E&,&updated&:new Date(&T06:29:16.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:0,&likeCount&:3,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T14:29:16+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-6ff324c27e010bba42e785dacadf4e9e_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:0,&likesCount&:3},&&:{&title&:&UE4 学习编辑器之坐标轴绘制和事件处理&,&author&:&yanjinzha&,&content&:&\u003Cp\u003E首先这不是UE4初学者的教程;是我记录学习的过程的博文;不过有兴趣可以讨论的可以联系我。\u003C\u002Fp\u003E\u003Cp\u003E1、绘制编辑器的坐标轴。Mesh的两种渲染的方式;\u003C\u002Fp\u003E\u003Cp\u003EUMeshComponent,PrimitiveDrawInterface绘制的dynamicMesh\u003C\u002Fp\u003E\u003Cp\u003E二者在渲染线程中的处理不同\u003C\u002Fp\u003E\u003Cp\u003E在render的不同过程的过程会先后的处理两种Mesh 牵涉到SceneProxy 例如在basepass的过程\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Efor (int32 ViewIndex = 0; ViewIndex & Views.Num(); ViewIndex++)\n{\n\tSCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, EventView, Views.Num() & 1, TEXT(\&View%d\&), ViewIndex);\n\tFViewInfo& View = Views[ViewIndex];\n\tif (View.ShouldRenderView())\n\t{\n\t\tRenderBasePassViewParallel(View, RHICmdList);\n\t}\n\n\tRenderEditorPrimitives(RHICmdList, View, bDirty);\n\n}\u003C\u002Fcode\u003ERenderBasePassViewParallel会把MeshCompoent的Mesh使用StaticMeshDrawList然后使用TBasePassDrawingPolicy进行渲染详见..\\Engine\\Source\\Runtime\\Renderer\\Private\\BasePassRendering.h\u003Cbr\u003E\u003Cbr\u003E而UnrealWidget使用PDI绘制DynamicMesh的方式会现在主线程把Mesh转换成FMeshBatch保存在ViewMeshElementList中如下:\u003Cbr\u003E\u003Ccode lang=\&cpp\&\u003Einline int32 FViewElementPDI::DrawMesh(const FMeshBatch& Mesh)\n{\n\tif (ensure(MeshBatchHasPrimitives(Mesh)))\n\t{\n\t\t\u002F\u002F Keep track of view mesh elements whether that have translucency.\n\t\tViewInfo-&bHasTranslucentViewMeshElements |=\u002F\u002FMesh.IsTranslucent() ? 1 : 0;\n\n\t\tuint8 DPGIndex = Mesh.DepthPriorityG\n\t\t\u002F\u002F Get the correct element list based on dpg index\n\t\t\u002F\u002F Translucent view mesh elements in the foreground dpg are not supported yet\n\t\tTIndirectArray&FMeshBatch&& ViewMeshElementList = ( ( DPGIndex == SDPG_Foreground
) ? ViewInfo-&TopViewMeshElements : ViewInfo-&ViewMeshElements );\n\n\t\tFMeshBatch* NewMesh = new(ViewMeshElementList) FMeshBatch(Mesh);\n\t\tif( CurrentHitProxy != nullptr )\n\t\t{\n\t\t\tNewMesh-&BatchHitProxyId = CurrentHitProxy-&Id;\n\t\t}\n\n\t\treturn 1;\n\t}\n\treturn 0;\n}\u003C\u002Fcode\u003E\u003Cp\u003E之后再渲染线程再根据不同的pass执行不同的渲染策略 例如在basepass的时候\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Eif (!View.Family-&EngineShowFlags.CompositeEditorPrimitives)\n{\n\tconst auto ShaderPlatform = View.GetShaderPlatform();\n\tconst bool bNeedToSwitchVerticalAxis = RHINeedsToSwitchVerticalAxis(ShaderPlatform);\n\t\u002F\u002F Draw the base pass for the view's batched mesh elements.\n\tbDirty |= DrawViewElements&FBasePassOpaqueDrawingPolicyFactory&(RHICmdList, View, FBasePassOpaqueDrawingPolicyFactory::ContextType(false, ESceneRenderTargetsMode::DontSet), SDPG_World, true) || bD\n\n\t\u002F\u002F Draw the view's batched simple elements(lines, sprites, etc).\n\tbDirty |= View.BatchedViewElements.Draw(RHICmdList, FeatureLevel, bNeedToSwitchVerticalAxis, View, false) || bD\n\n\t\u002F\u002F Draw foreground objects last\n\tbDirty |= DrawViewElements&FBasePassOpaqueDrawingPolicyFactory&(RHICmdList, View, FBasePassOpaqueDrawingPolicyFactory::ContextType(false, ESceneRenderTargetsMode::DontSet), SDPG_Foreground, true) || bD\n\n\t\u002F\u002F Draw the view's batched simple elements(lines, sprites, etc).\n\tbDirty |= View.TopBatchedViewElements.Draw(RHICmdList, FeatureLevel, bNeedToSwitchVerticalAxis, View, false) || bD\n\n\n}\u003C\u002Fcode\u003E\u003Cbr\u003E然后使用FBasePassOpaqueDrawingPolicyFactory绘制DynamicMesh\u003Ccode lang=\&cpp\&\u003Ebool FBasePassOpaqueDrawingPolicyFactory::DrawDynamicMesh(\n\tFRHICommandList& RHICmdList, \n\tconst FViewInfo& View,\n\tContextType DrawingContext,\n\tconst FMeshBatch& Mesh,\n\tbool bBackFace,\n\tbool bPreFog,\n\tconst FPrimitiveSceneProxy* PrimitiveSceneProxy,\n\tFHitProxyId HitProxyId, \n\tconst bool bIsInstancedStereo\n\t)\n{\n\t\u002F\u002F Determine the mesh's material and blend mode.\n\tconst FMaterial* Material = Mesh.MaterialRenderProxy-&GetMaterial(View.GetFeatureLevel());\n\tconst EBlendMode BlendMode = Material-&GetBlendMode();\n\n\t\u002F\u002F Only draw opaque materials.\n\tif(!IsTranslucentBlendMode(BlendMode))\n\t{\n\t\tProcessBasePassMesh(\n\t\t\tRHICmdList, \n\t\t\tFProcessBasePassMeshParameters(\n\t\t\t\tMesh,\n\t\t\t\tMaterial,\n\t\t\t\tPrimitiveSceneProxy,\n\t\t\t\t!bPreFog,\n\t\t\t\tDrawingContext.bEditorCompositeDepthTest,\n\t\t\t\tDrawingContext.TextureMode,\n\t\t\t\tView.GetFeatureLevel(), \n\t\t\t\tbIsInstancedStereo\n\t\t\t\t),\n\t\t\tFDrawBasePassDynamicMeshAction(\n\t\t\t\tView,\n\t\t\t\tbBackFace,\n\t\t\t\tMesh.DitheredLODTransitionAlpha,\n\t\t\t\tHitProxyId\n\t\t\t\t)\n\t\t\t);\n\t\\n\t}\n\telse\n\t{\n\t\\n\t}\n}\n后使用BasePass中的FDrawBasePassDynamicMeshAction执行绘制 见..\\Engine\\Source\\Runtime\\Renderer\\Private\\BasePassRendering.cpp 通过basePass的Process最后使用FMeshDrawingPolicy::DrawMesh调用Commandlist的绘制命令\u003C\u002Fcode\u003E\u003Cp\u003E特例:CustomMeshComponent 和UProceduralMeshComponent与其他的MeshCompoent渲染层应该没有区别,只是在Component层多了设置VertexBuffer和IndexBuffer的借口以及设置collision\u003C\u002Fp\u003E\u003Cp\u003E2、DynamicMesh的事件处理WidgetHitProxy;\u003C\u002Fp\u003E\u003Cp\u003EDynamicMesh需要做专门的碰撞处理;跟PrimitiveMeshComponent使用的不是用一个流程\u003C\u002Fp\u003E\u003Cp\u003EDynamicMesh和普通的Mesh不仅在渲染流程上不同;在物理上的处理也是不一样的,但对坐标轴来说仅需处理鼠标事件即可。普通的Mesh会有一套physics的逻辑去计算,所有的鼠标事件都是基于collision的物理;但对于坐标轴则相对简单WidgetHitProxy就是用来专门处理鼠标事件的。\u003C\u002Fp\u003E\u003Cp\u003EHitProxy定义 unrealWidget.h\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003E\u002F**\n * Widget hit proxy.\n *\u002F\nstruct HWidgetAxis : public HHitProxy\n{\n\tDECLARE_HIT_PROXY( UNREALED_API );\n\n\tEAxisList::Type A\n\tuint32 bDisabled:1;\n\n\tHWidgetAxis(EAxisList::Type InAxis, bool InbDisabled = false):\n\t\tHHitProxy(HPP_UI),\n\t\tAxis(InAxis),\n\t\tbDisabled(InbDisabled) {}\n\n\tvirtual EMouseCursor::Type GetMouseCursor() override\n\t{\n\t\tif (bDisabled)\n\t\t{\n\t\t\treturn EMouseCursor::SlashedC\n\t\t}\n\t\treturn EMouseCursor::CardinalC\n\t}\n\n\t\u002F**\n\t * Method that specifies whether the hit proxy *always* allows translucent primitives to be associated with it or not,\n\t * regardless of any other engine\u002Feditor setting. For example, if translucent selection was disabled, any hit proxies\n\t *
returning true would still allow translucent selection. In this specific case, true is always returned because geometry\n\t * mode hit proxies always need to be selectable or geometry mode will not function correctly.\n\t *\n\t * @return\ttrue if translucent primitives are always allowed false otherwise\n\t *\u002F\n\tvirtual bool AlwaysAllowsTranslucentPrimitives() const override\n\t{\n\t\\n\t}\n};\u003C\u002Fcode\u003E\u003Cp\u003E实现unrealwidget.cpp\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003EIMPLEMENT_HIT_PROXY(HWidgetAxis,HHitProxy);\u003C\u002Fcode\u003E\u003Cp\u003E使用\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Econst bool bDisabled = EditorModeTools ? (EditorModeTools-&IsDefaultModeActive() && GEditor-&HasLockedActors() ) :\n\t\tPDI-&SetHitProxy( new HWidgetAxis( InAxis, bDisabled) );\u003C\u002Fcode\u003E\u003Cp\u003E鼠标over的监听:在EditroViewportClient的Tick中执行ConditionalCheckHoveredHitProxy\u003C\u002Fp\u003E\u003Cp\u003Evoid FEditorViewportClient::ConditionalCheckHoveredHitProxy()\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003E{\n\t\u002F\u002F If it has been decided that there is more important things to do than check hit proxies, then don't check them.\n\tif( !bShouldCheckHitProxy || bWidgetAxisControlledByDrag == true )\n\t{\n\t\\n\t}\n\n\tHHitProxy* HitProxy = Viewport-&GetHitProxy(CachedMouseX,CachedMouseY);\n\n\tCheckHoveredHitProxy( HitProxy );\n\n\t\u002F\u002F We need to set this to false here as if mouse is moved off viewport fast, it will keep doing CheckHoveredOverHitProxy for this viewport when it should not.\n\tbShouldCheckHitProxy =\n}\u003C\u002Fcode\u003E\u003Cp\u003E参考HHitProxy* FViewport::GetHitProxy(int32 X,int32 Y)在..\\Engine\\Source\\Runtime\\Engine\\Private\\UnrealClient.cpp中\u003C\u002Fp\u003E\u003Cp\u003E牵涉到FHitProxyArray的管理;关于FHitProxyArray的调研\u003C\u002Fp\u003E\u003Cp\u003EFHitProxyMap的管理 \u003Cbr\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003EPDI-&SetHitProxy( new HWidgetAxis( InAxis, bDisabled) );\u003C\u002Fcode\u003E\u003Ccode lang=\&cpp\&\u003E包含两个过程:\u003C\u002Fcode\u003E\u003Ccode lang=\&cpp\&\u003Ea、初始化hitproxy 把HitProxy放进FHitProxyArray返回ProxyID;\u003C\u002Fcode\u003E\u003Ccode lang=\&cpp\&\u003Eb、PDI把新生成的HitProxy放入viewport的FHitProxyMap管理\u003C\u002Fcode\u003EHitProxyMap根据渲染更新:当画面需要重新渲染的时候HitProxyMap上面记录的RenderTargetTexture会被清掉;重新获取;并把RenderTargetTexture的内容复制到HitProxyMap的HitProxyTexture和tHitProxyCPUTexture上
代码如下:\u003Ccode lang=\&cpp\&\u003E\u002F\u002F If the hit proxy map isn't up to date, render the viewport client's hit proxies to it.\n\telse if (!bHitProxiesCached)\n\t{\n\t\tEnqueueBeginRenderFrame();\n\n\t\tENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(\n\t\t\tBeginDrawingCommandHitProxy,\n\t\t\tFViewport*, Viewport, this,\n\t\t\t{\n\t\t\t\u002F\u002F Set the hit proxy map's render target.\n\t\t\t\u002F\u002F Clear the hit proxy map to white, which is overloaded to mean no hit proxy.\n\t\t\tSetRenderTarget(RHICmdList, Viewport-&HitProxyMap.GetRenderTargetTexture(), FTextureRHIRef(), ESimpleRenderTargetMode::EClearColorExistingDepth, FExclusiveDepthStencil::DepthWrite_StencilWrite, true);\n\t\t});\n\n\t\t\u002F\u002F Let the viewport client draw its hit proxies.\n\t\tauto World = ViewportClient-&GetWorld();\n\t\tFCanvas Canvas(&HitProxyMap, &HitProxyMap, World, World ? World-&FeatureLevel : GMaxRHIFeatureLevel);\n\t\t{\n\t\t\tViewportClient-&Draw(this, &Canvas);\n\t\t}\n\t\tCanvas.Flush_GameThread();\n\n\t\t\u002F\u002FResolve surface to texture.\n\t\tENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(\n\t\t\tUpdateHitProxyRTCommand,\n\t\t\tFHitProxyMap*, HitProxyMap, &HitProxyMap,\n\t\t\t{\n\t\t\t\u002F\u002F Copy (resolve) the rendered thumbnail from the render target to its texture\n\t\t\tRHICmdList.CopyToResolveTarget(HitProxyMap-&GetRenderTargetTexture(), HitProxyMap-&GetHitProxyTexture(), false, FResolveParams());\n\t\t\tRHICmdList.CopyToResolveTarget(HitProxyMap-&GetRenderTargetTexture(), HitProxyMap-&GetHitProxyCPUTexture(), false, FResolveParams());\n\t\t});\n\n\t\tENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(\n\t\t\tEndDrawingCommand,\n\t\t\tFViewport*, Viewport, this,\n\t\t\t{\n\t\t\t\tViewport-&EndRenderFrame(RHICmdList, false, false);\n\t\t\t});\n\n\t\t\u002F\u002F Cache the hit proxies for the next GetHitProxyMap call.\n\t\tbHitProxiesCached =\n\t}\u003C\u002Fcode\u003E\u003Cp\u003E此过程应该是对HitProxy的rendertarget做了特殊处理表示的可能并不是像素的值\u003C\u002Fp\u003E\u003Cp\u003E下一步通过与正常的Viewport上记录的CachedHitProxyData合并\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003EENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(\n\t\t\tReadSurfaceCommand,\n\t\t\tFReadSurfaceContext, Context, ReadSurfaceContext,\n\t\t\t{\n\t\t\tRHICmdList.ReadSurfaceData(\n\t\t\tContext.Viewport-&HitProxyMap.GetHitProxyCPUTexture(),\n\t\t\tContext.Rect,\n\t\t\t*Context.OutData,\n\t\t\tFReadSurfaceDataFlags()\n\t\t\t);\n\t\t});\n\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Cp\u003E然后返回给GetRawHitProxyData接口。此过程中HitProxy的像素FColor已被单独修改,在取像素点的HitProxy的时候通过FColor获取HitProxyId,表示的即为FHitProxyArray中保存的HitProxies数组中的Index。就可以知道鼠标下面的HitProxy是哪个。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EFHitProxyId::FHitProxyId(FColor Color)\n{\n\tIndex = ((int32)Color.R && 16) | ((int32)Color.G && 8) | ((int32)Color.B && 0);\n}return FHitProxyArray::Get().GetHitProxyById(Id.Index);\u003C\u002Fcode\u003E&,&updated&:new Date(&T00:21:35.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:1,&likeCount&:3,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T08:21:35+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-6ff324c27e010bba42e785dacadf4e9e_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:1,&likesCount&:3},&&:{&title&:&UE4 对象类型Class及内存管理(1)&,&author&:&yanjinzha&,&content&:&UE4中的对象在内存中的表示目前大概可分为三类:·\u003Cp\u003E A、普通的C++类型 F Class \u003C\u002Fp\u003E\u003Cp\u003EB、智能指针\u003C\u002Fp\u003E\u003Cp\u003EC、UObject类型的UClass\u003C\u002Fp\u003E\u003Cp\u003E我们逐一说明他们的使用方法和注意事项\u003C\u002Fp\u003E\u003Cp\u003E1、针对普通的C++的class照普通的使用方式即可;UED中或者UE4的底层很多使用这种Class的例子;但UE4会统一的在前加F 比如:上次我们说的负责坐标轴绘制和处理的FWidget类(在UnrealWidget.h中)\u003C\u002Fp\u003E\u003Cp\u003E声明:\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Eclass FWidget\n\t: public FGCObject\n{\nFWidget();\n};\u003C\u002Fcode\u003E\u003Cp\u003E使用:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EFEditorViewportClient::FEditorViewportClient(FEditorModeTools* InModeTools, FPreviewScene* InPreviewScene, const TWeakPtr&SEditorViewport&& InEditorViewportWidget)\n\t: bAllowCinematicPreview(false)\n...\n, Widget(new FWidget)\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E销毁:\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003EFEditorViewportClient::~FEditorViewportClient()\n{\n\t...\n\tdelete W\n}\u003C\u002Fcode\u003E\u003Cp\u003E目前大部分的F Class都在UED的框架中使用。在game的框架中相同功能的Class都改成了UClass\u003C\u002Fp\u003E\u003Cp\u003E2、UE4的智能指针SmartPointer体系在引擎初期项目中用处主要在UI上;SlateUI是主要使用智能指针的地方。但随着后期UMG的出现SlateUI的部分被完全封装在UMG内部,现在研发人员一般不会接触到这一块。但对于研究引擎的人来说熟悉智能指针相当重要。以SWidget为例说明\u003C\u002Fp\u003E\u003Cp\u003E声明方式():\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Eclass SLATECORE_API SWidget\n\t: public FSlateControlledConstruction,\n\t
public TSharedFromThis&SWidget&\t\t\u002F\u002F Enables 'this-&AsShared()'\n{\n\tfriend struct FCurveS\n\t\u002F\u002Fdubeibei XMod\n\tfriend class UXScriptW\npublic:\n};\u003C\u002Fcode\u003E\u003Cp\u003E重点是类模版TSharedFromThis&SWidget&;TSharedPtr&&;TSharedRef&&\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Etemplate& class ObjectType, ESPMode Mode &\nclass TSharedFromThis\n{};\n\ntemplate& class ObjectType, ESPMode Mode &\nclass TSharedPtr\n{};\n\u003C\u002Fcode\u003E\u003Cp\u003Etemplate& class ObjectType, ESPMode Mode &\u003Cbr\u003Eclass TSharedRef\u003Cbr\u003E{};\u003Cbr\u003E\u003Cbr\u003Etemplate& class ObjectType, ESPMode Mode &\u003Cbr\u003Eclass TWeakPtr\u003Cbr\u003E{\u003Cbr\u003Epublic:\u003Cbr\u003E};\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E详情见:SharedPointer.h 其中指针会有SharedReferenceCount保存计数;计数为0时自动清理。TWeakPtr中有WeakReferenceCount保存计数,但TWeakPtr的引用计数不作为是否清理的标记,即TweakPtr中的Object可能已经被清理;但TweakPtr还在;可使用TWeakPtr的Isvalid来校验Object是否已被清理。\u003Cbr\u003E\u003Cbr\u003E使用方式:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003ETSharedPtr&SWidget& RetWidget = SNew(SInvalidationPanel)\u003C\u002Fcode\u003E\u003Cp\u003E或者:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003ESAssignNew(MyTextBlock, STextBlock)\n\u003C\u002Fcode\u003E\u003Cp\u003E或者:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EMakeShareable(new FUIController()); 返回的也是一个TSharedPtr\u003C\u002Fcode\u003E\u003Cp\u003E关于TSharedPtr TSharedRef TWeakPtr之间相互转换的使用:\u003C\u002Fp\u003E\u003Cp\u003ETSharedPtr
\u003Cu\u003EToSharedRef()-& \u003C\u002Fu\u003E TSharedRef
\u003C\u002Fp\u003E\u003Cp\u003ETWeakPtr
\u003Cu\u003EPin()-&\u003C\u002Fu\u003E
TSharedPtr\u003C\u002Fp\u003E\u003Cp\u003ETSharedRef\u003C\u002Fp\u003E\u003Cp\u003E转换使用StaticCastSharedPtr:\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003ETSharedPtr&SChannelListPageWidget& MyWidget = StaticCastSharedPtr&SChannelListPageWidget&(GetWidget());\u003C\u002Fcode\u003E\u003Cp\u003E3、关于 UObject的Class使用\u003C\u002Fp\u003E\u003Cp\u003EUObject是上层或者Game框架里面最经常用到的Class。把游戏框架中出现的各种东西抽象出一个UObject十分必要;通用的属性和接口;统一的GC;方便数据统计;等等\u003C\u002Fp\u003E\u003Cp\u003E声明示例如下:\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003EUCLASS(config = Game, meta = (ChildCanTick))\nclass PROJECTZ_API AMaterialParamaterObject : public AActor\n{\n\tGENERATED_UCLASS_BODY()\n\n\t\u002F** The CapsuleComponent being used for movement collision (by CharacterMovement). Always treated as being vertically aligned in simple collision check functions. *\u002F\n\tUPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly)\n\tclass USceneComponent* RootSceneC\n\n\t\u002F** The main skeletal mesh associated with this Character (optional sub-object). *\u002F\n\tUPROPERTY(Category = MapEditor, VisibleAnywhere, BlueprintReadOnly)\n\tclass UStaticMeshComponent* M\n\n\tstatic FName MeshComponentN\n\tstatic FName RootComponentN\n\n\tUFUNCTION(BlueprintCallable, Category = \&MapEditor|Objects\&)\n\tvoid ChangeFloatParamater(int32 ParaIndex, int32 NameIndex, float InValue);\n\n\tUFUNCTION(BlueprintCallable, Category = \&MapEditor|Objects\&)\n\tvoid ChangeIntParamater(int32 ParaIndex, int32 NameIndex, int32 InValue);\n\n\tUFUNCTION(BlueprintCallable, Category = \&MapEditor|Objects\&)\n\tvoid ChangeVectorParamater(int32 ParaIndex, int32 NameIndex, FVector InValue);\n\n\tUFUNCTION(BlueprintCallable, Category = \&MapEditor|Objects\&)\n\tvoid ChangeTextureParamater(int32 ParaIndex, int32 NameIndex, UTexture2D* InValue);\n\n\tUPROPERTY(Category = \&MapEditor|Objects\&, EditAnywhere, BlueprintReadWrite)\n\tTArray&FEditorMaterialParamater& P\n\n};\u003C\u002Fcode\u003E\u003Cp\u003E关于UHT:UnrealHeaderTool,UHT会根据UClass的头文件.h生成.generated.h文件把其中拥有UPROPERTY
UFUNCTION的标签的重新定义例如ChangeFloatParamater接口:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EDECLARE_FUNCTION(execChangeFloatParamater) \\\n\t{ \\\n\t\tP_GET_PROPERTY(UIntProperty,Z_Param_ParaIndex); \\\n\t\tP_GET_PROPERTY(UIntProperty,Z_Param_NameIndex); \\\n\t\tP_GET_PROPERTY(UFloatProperty,Z_Param_InValue); \\\n\t\tP_FINISH; \\\n\t\tP_NATIVE_BEGIN; \\\n\t\tthis-&ChangeFloatParamater(Z_Param_ParaIndex,Z_Param_NameIndex,Z_Param_InValue); \\\n\t\tP_NATIVE_END; \\\n\n\t}\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003Cbr\u003E新生成的.genreated.h文件会加入编译所有的UPROPERTY和UFUNCTION都会是继承自UObject的Class使用 ;然后再由GC统一的管理UObject;\u003C\u002Fp\u003E\u003Cp\u003E不加UPROPETY标签的属性不在UObject 此过程GC的范围之内;需要另外的控制,不然很容易内存泄漏。见下章详细说明。\u003C\u002Fp\u003E\u003Cp\u003E使用:\u003C\u002Fp\u003E\u003Cp\u003EMyGCProtectedObj = NewObject&UMyObjectClass&(this);\u003Cbr\u003E如果不想让UObject强制回收可设置:\u003Cbr\u003EYourObjectInstance-&AddToRoot();\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E销毁:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eif(!MyObject)\nif(!MyObject-&IsValidLowLevel())\n \nMyObject-&ConditionalBeginDestroy(); \u002F\u002Finstantly clears UObject out of memory\nMyObject =\n\n销毁Actor:\nif(!TheCharacter)\nif(!TheCharacter-&IsValidLowLevel())\nTheCharacter-&Destroy();\nTheCharacter-&ConditionalBeginDestroy(); \u002F\u002Fessential extra step, in all my testing\n\n\n\u003C\u002Fcode\u003E\u003Cp\u003E参考:\u003C\u002Fp\u003E\u003Ch1\u003E\u003Ca href=\&http:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FINT\u002FProgramming\u002FUnrealArchitecture\u002FObjects\u002FOptimizations\u002F\& data-editable=\&true\& data-title=\&Unreal Object Handling\&\u003EUnreal Object Handling\u003C\u002Fa\u003E\u003C\u002Fh1\u003E\u003Ch1\u003E\u003Ca href=\&https:\u002F\u002Fwiki.unrealengine.com\u002FGarbage_Collection_%26_Dynamic_Memory_Allocation\& data-title=\&Garbage Collection & Dynamic Memory Allocation\& class=\&\& data-editable=\&true\&\u003EGarbage Collection & Dynamic Memory Allocation\u003C\u002Fa\u003E\u003C\u002Fh1\u003E\u003Ch1\u003E\u003Ca href=\&https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FINT\u002FProgramming\u002FUnrealArchitecture\u002FSmartPointerLibrary\u002Findex.html\&\u003EUnreal Smart Pointer Library\u003C\u002Fa\u003E\u003C\u002Fh1\u003E&,&updated&:new Date(&T10:29:54.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:0,&likeCount&:0,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T18:29:54+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-6ff324c27e010bba42e785dacadf4e9e_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:0,&likesCount&:0},&&:{&title&:&UE4 对象类型及内存管理(2)&,&author&:&yanjinzha&,&content&:&在所有的对象保存和内存的问题中最重点的两个问题就是:不同对象指针间的互相保存和UObject的GC机制。除去F Class的对象和智能指针的对象之外有自己的内存管理方式之外(上篇文章已经分析),UObject对象的内存管理完全交由GarbageCollection完成。\u003Cp\u003E1、实施GC的关键因素是由Object间的引用关系决定的UObject状态。我们先从上层分析UObject的引用关系和类型之间相互引用的注意事项:\u003C\u002Fp\u003E\u003Cp\u003EA、UObject间的引用关系需要用指针强引用加UPROPERTY标签完成例如:\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ccode lang=\&cpp\&\u003EUCLASS(abstract, notplaceable, NotBlueprintable, HideCategories=(Collision,Rendering,\&Utilities|Transformation\&)) \nclass ENGINE_API AController : public AActor, public INavAgentInterface\n{\n\tGENERATED_UCLASS_BODY()\n\nprivate:\n\t\u002F** Pawn currently being controlled by this controller.
Use Pawn.Possess() to take control of a pawn *\u002F\n\tUPROPERTY(replicatedUsing=OnRep_Pawn)\n\tAPawn* P\n...\n};\u003C\u002Fcode\u003E\u003Cp\u003EUPROPERTY标签生成UProperty对象,UProperty对象可以控制对属性的访问等。也通过UProperty对象保存引用关系\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E不加UPROPERTY的如果想持久的保存指针的话,需要重写UObject的AddReferencedObjects接口为UObject对象添加引用。例如AActor类中为非UPROPERTY的成员变量OwnedComponents添加reference:\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Evoid AActor::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)\n{\n\tAActor* This = CastChecked&AActor&(InThis);\n\tCollector.AddReferencedObjects(This-&OwnedComponents);\n#if WITH_EDITOR\n\tif (This-&CurrentTransactionAnnotation.IsValid())\n\t{\n\t\tThis-&CurrentTransactionAnnotation-&AddReferencedObjects(Collector);\n\t}\n#endif\n\tSuper::AddReferencedObjects(InThis, Collector);\n}\n\u003C\u002Fcode\u003E\u003Cp\u003EB、引擎中的代码UCLass类引用的非Object对象的变量比如UStruct
UEnum 或者FVector等可是数据类型而定,如果是BlurprintType的类型;则需要写成UPROPERTY才能使用UObject的GC机制进行管理;目前BlurprintType的结构体,枚举都会被引擎编译成UStruct UEnum的对象。\u003C\u002Fp\u003E\u003Cp\u003EC、F Class引用UObject的时候注意;为避免内存泄漏FClass应继承与FGCObject,并实现AddReferencedObjects接口,在接口中给UObject的指针添加reference例如FWidget中:\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003Eclass FWidget\n\t: public FGCObject\n{\npublic:\n...\nprivate:\n
UMaterialInterface* TransparentPlaneMaterialXY;\n\tUMaterialInterface* GridM\n\n\tUMaterialInstanceDynamic* AxisMaterialX;\n\tUMaterialInstanceDynamic* AxisMaterialY;\n\tUMaterialInstanceDynamic* AxisMaterialZ;\n\tUMaterialInstanceDynamic* CurrentAxisM\n\n};\u003C\u002Fcode\u003E\u003Ccode lang=\&text\&\u003Evoid FWidget::AddReferencedObjects( FReferenceCollector& Collector )\n{\n\tCollector.AddReferencedObject( AxisMaterialX );\n\tCollector.AddReferencedObject( AxisMaterialY );\n\tCollector.AddReferencedObject( AxisMaterialZ );\n\tCollector.AddReferencedObject( OpaquePlaneMaterialXY );\n\tCollector.AddReferencedObject( TransparentPlaneMaterialXY );\n\tCollector.AddReferencedObject( GridMaterial );\n\tCollector.AddReferencedObject( CurrentAxisMaterial );\n}\n\u003C\u002Fcode\u003E\u003Cp\u003ED、智能指针一般不持久保存UObject的指针对象如果有需求我的习惯是使用TWeakObjectPtr的形式保存;可以用Isvalid先判断指针是否有效,但是因为是weakreference不对GC产生阻止的作用。\u003C\u002Fp\u003E\u003Cp\u003E2、下面说下我对于UObject的GarbageCollection的理解,代码太多我不一定理解的全面。\u003C\u002Fp\u003E\u003Cp\u003EA、先说最基本的UObject对象的内存空间的分配和释放;目前FUObjectAllocator处理UObject对象的内存分配和回收 不论上层多复杂但归根结底都要到关于内存的问题都会归到这里。比如我们runtime的时候执行destroy的操作,这时其实对象所占的空间依然存在,如果有指针引用到该对象的话我们可以看到内容为none;指针并不是nullptr;只有等执行了GC之后才会真正执行释放。同样UObject实例的时候会分配内存\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003EGUObjectAllocator.FreeUObject(Object);\n\u003C\u002Fcode\u003E\u003Ccode lang=\&text\&\u003EObj = (UObject *)GUObjectAllocator.AllocateUObject(TotalSize,Alignment,GIsInitialLoad);\n\u003C\u002Fcode\u003E\u003Cp\u003EB、综上GC的最终目标其实就是对那些状态为Unreachable或者NoStrongReference等的Object对象执行销毁ConditionalFinishDestroy;另一个过程对已经执行销毁过程的Object释放内存FreeUObject。详见IncrementalPurgeGarbage在..\u002FPrivate\\UObject\\GarbageCollection.cpp\u003C\u002Fp\u003E\u003Cp\u003EC、关于UObject的状态标记Flag\u003C\u002Fp\u003E\u003Ccode lang=\&cpp\&\u003E\u002F** Objects flags for internal use (GC, low level UObject code) *\u002F\nenum class EInternalObjectFlags : int32\n{\n\tNone = 0,\n\t\u002F\u002F All the other bits are reserved, DO NOT ADD NEW FLAGS HERE!\n\tReachableInCluster = 1 && 23, \u002F\u002F\u002F External reference to object in cluster exists\n\tClusterRoot = 1 && 24, \u002F\u002F\u002F& Root of a cluster\n\tNative = 1 && 25, \u002F\u002F\u002F& Native (UClass only).\n\tAsync = 1 && 26, \u002F\u002F\u002F& Object exists only on a different thread than the game thread.\n\tAsyncLoading = 1 && 27, \u002F\u002F\u002F& Object is being asynchronously loaded.\n\tUnreachable = 1 && 28, \u002F\u002F\u002F& Object is not reachable on the object graph.\n\tPendingKill = 1 && 29, \u002F\u002F\u002F& Objects that are pending destruction (invalid for gameplay but valid objects)\n\tRootSet = 1 && 30, \u002F\u002F\u002F& Object will not be garbage collected, even if unreferenced.\n\tNoStrongReference = 1 && 31, \u002F\u002F\u002F& The object is not referenced by any strong reference. The flag is used by GC.\n\n\tGarbageCollectionKeepFlags = Native | Async | AsyncLoading,\n\t\u002F\u002F Make sure this is up to date!\n\tAllFlags = ReachableInCluster | ClusterRoot | Native | Async | AsyncLoading | Unreachable | PendingKill | RootSet | NoStrongReference\n};\n\u003C\u002Fcode\u003E\u003Cp\u003Eflag作为GC的状态保存在FUObjectItem中\u003C\u002Fp\u003E\u003Cp\u003ED、FUObjectItem和FUObjectArray以及UGCObjectReferencer\u003C\u002Fp\u003E\u003Cp\u003EFUObjectItem封装了单个的UObject指针在FUObjectArray中;FUObjectArray包含所有的live的Object对象\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003EFUObjectItem:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F**\n* Single item in the UObject array.\n*\u002F\nstruct FUObjectItem\n{\n\u002F\u002F Pointer to the allocated object\nclass UObjectBase* O\n\u002F\u002F Internal flags\nint32 F\n...\n};\u003C\u002Fcode\u003EFFixedUObjectArray:\u003Cbr\u003E\u003Ccode lang=\&cpp\&\u003Eclass FFixedUObjectArray\n{\n\t\u002F** Static master table to chunks of pointers **\u002F\n\tFUObjectItem* O\n...\n};\u003C\u002Fcode\u003E\u003Cp\u003EFUObjectArray:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eclass COREUOBJECT_API FUObjectArray\n{\npublic:\n...\n\u002F\u002Ftypedef TStaticIndirectArrayThreadSafeRead&UObjectBase, 8 * 1024 * 1024 \u002F* Max 8M UObjects *\u002F, 16384 \u002F* allocated in 64K\u002F128K chunks *\u002F & TUObjectA\n\ttypedef FFixedUObjectArray TUObjectA\n...\nTUObjectArray ObjO\n...\n};\u003C\u002Fcode\u003E\u003Cp\u003EUGCObjectReferencer为非UClass的对象引用UObject对象指针提供了宿主;从而构成引用关系;GCObjectReference不依赖与任何对象因为设置了RootSet\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Evoid Init()\n\t{\n\t\t\u002F\u002F Some objects can get created after the engine started shutting down (lazy init of singletons etc).\n\t\tif (!GIsRequestingExit)\n\t\t{\n\t\t\tStaticInit();\n\t\t\tcheck(GGCObjectReferencer);\n\t\t\t\u002F\u002F Add this instance to the referencer's list\n\t\t\tGGCObjectReferencer-&AddObject(this);\n\t\t}\n\t}\n\u003C\u002Fcode\u003E\u003Cp\u003E在执行GC CollectReference的过程中会把非UClass类的Uobject指针的引用关系指到GGCObjectReferencer上从而保证Uobject不会被清理。\u003C\u002Fp\u003E\u003Cp\u003EE、分析GC的过程\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E入口CollectGarbageInternal
GarbageCollection.cpp\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F** \n * Deletes all unreferenced objects, keeping objects that have any of the passed in KeepFlags set\n *\n * @param\tKeepFlags\t\t\tobjects with those flags will be kept regardless of being referenced or not\n * @param\tbPerformFullPurge\tif true, perform a full purge after the mark pass\n *\u002F\nvoid CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge)\n{\n...\n};\u003C\u002Fcode\u003E\u003Cp\u003E关键的部分在ReachabilityAnalysis和执行IncrementalPurgeGarbage\u003C\u002Fp\u003E\u003Cp\u003EReachabilityAnalysis:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E执行reachability分析的入口在CollectGarbageInternal
中\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Econst double StartTime = FPlatformTime::Seconds();\n\t\t\tFRealtimeGC TagUsedRealtimeGC;\n\t\t\tTagUsedRealtimeGC.PerformReachabilityAnalysis(KeepFlags, bForceSingleThreadedGC);\n\t\t\tUE_LOG(LogGarbage, Log, TEXT(\&%f ms for GC\&), (FPlatformTime::Seconds() - StartTime) * 1000);\u003C\u002Fcode\u003E\u003Cp\u003E先执行MarkObjectsAsUnreachable把异常的ObjectItem状态修改:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EObjectItem-&SetFlags(EInternalObjectFlags::Unreachable | EInternalObjectFlags::NoStrongReference);\n\u003C\u002Fcode\u003E\u003Cp\u003E正常的放进ObjectsToSerialize\u003Cbr\u003E\u003Cbr\u003E然后执行CollectReferences ,遍历ObjectsToSerialize中的Object;从中取出ReferenceTokenStream\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F Get pointer to token stream and jump to the start.\nFGCReferenceTokenStream* RESTRICT TokenStream = &CurrentObject-&GetClass()-&ReferenceTokenS\n\u003C\u002Fcode\u003E\u003Cp\u003E然后遍历referenceTokenStream中的ReferenceInfo\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EFGCReferenceInfo ReferenceInfo = TokenStream-&AccessReferenceInfo(ReferenceTokenStreamIndex);\n\u003C\u002Fcode\u003E\u003Cp\u003E再根据不同的referencetype执行ReferenceProcessor的HandleTokenStreamObjectReference\u003C\u002Fp\u003E\u003Cp\u003E或者执行AddreferenceObject 构成ClusterReference\u003C\u002Fp\u003E\u003Cp\u003E这步处理的结果会把一些ReferencedClusterRootObjectItem的EInternalObjectFlags::NoStrongReference | EInternalObjectFlags::Unreachable flag清理掉\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EReferencedClusterRootObjectItem-&ClearFlags(EInternalObjectFlags::NoStrongReference | EInternalObjectFlags::Unreachable);\n\u003C\u002Fcode\u003E\u003Cp\u003EIncrementalPurgeGarbage:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E入口在CollectGarbageInternal
中:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eif (bPerformFullPurge || GIsEditor)\n\n{\n\nIncrementalPurgeGarbage(false);\n\n}\n\u003C\u002Fcode\u003E\u003Cp\u003E分为两个过程对那些状态为Unreachable或者NoStrongReference等的Object对象执行销毁ConditionalFinishDestroy;和另一个过程对已经执行销毁过程的Object释放内存FreeUObject。详见IncrementalPurgeGarbage\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eif (ObjectItem-&IsUnreachable())\n\t\t\t{\n\t\t\t\tUObject* Object = static_cast&UObject*&(ObjectItem-&Object);\n\t\t\t\t\u002F\u002F Object should always have had BeginDestroy called on it and never already be destroyed\n\t\t\t\tcheck( Object-&HasAnyFlags( RF_BeginDestroyed ) && !Object-&HasAnyFlags( RF_FinishDestroyed ) );\n\n\t\t\t\t\u002F\u002F Only proceed with destroying the object if the asynchronous cleanup started by BeginDestroy has finished.\n\t\t\t\tif(Object-&IsReadyForFinishDestroy())\n\t\t\t\t{\n#if PERF_DETAILED_PER_CLASS_GC_STATS\n\t\t\t\t\t\u002F\u002F Keep track of how many objects of a certain class we're purging.\n\t\t\t\t\tconst FName& ClassName = Object-&GetClass()-&GetFName();\n\t\t\t\t\tint32 InstanceCount = GClassToPurgeCountMap.FindRef( ClassName );\n\t\t\t\t\tGClassToPurgeCountMap.Add( ClassName, ++InstanceCount );\n#endif\n\t\t\t\t\t\u002F\u002F Send FinishDestroy message.\n\t\t\t\t\tObject-&ConditionalFinishDestroy();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\u002F\u002F The object isn't ready for FinishDestroy to be called yet.
This is common in the\n\t\t\t\t\t\u002F\u002F case of a graphics resource that is waiting for the render thread \&release fence\&\n\t\t\t\t\t\u002F\u002F to complete.
Just calling IsReadyForFinishDestroy may begin the process of releasing\n\t\t\t\t\t\u002F\u002F a resource, so we don't want to block iteration while waiting on the render thread.\n\n\t\t\t\t\t\u002F\u002F Add the object index to our list of objects to revisit after we process everything else\n\t\t\t\t\tGGCObjectsPendingDestruction.Add(Object);\n\t\t\t\t\tGGCObjectsPendingDestructionCount++;\n\t\t\t\t}\n\t\t\t}\u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Cbr\u003Efree的过程\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EFUObjectItem* ObjectItem = *GObjCurrentPurgeObjectI\n\t\t\tcheckSlow(ObjectItem);\n\t\t\tif (ObjectItem-&IsUnreachable())\n\t\t\t{\n\t\t\t\tUObject* Object = (UObject*)ObjectItem-&O\n\t\t\t\tcheck(Object-&HasAllFlags(RF_FinishDestroyed|RF_BeginDestroyed));\n\t\t\t\tGIsPurgingObject\t\t\t\t= \n\t\t\t\tObject-&~UObject();\n\t\t\t\tGUObjectAllocator.FreeUObject(Object);\n\t\t\t\tGIsPurgingObject\t\t\t\t=\n\t\t\t\t\u002F\u002F Keep track of purged stats.\n\t\t\t\tGPurgedObjectCountSinceLastMarkPhase++;\n\t\t\t}\n\n\u003C\u002Fcode\u003E&,&updated&:new Date(&T11:40:35.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:0,&likeCount&:6,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T19:40:35+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-6ff324c27e010bba42e785dacadf4e9e_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:0,&likesCount&:6},&&:{&title&:&分享一张自己绘制的GamePlay的简单类图&,&author&:&yanjinzha&,&content&:&\u003Cimg src=\&v2-d5ce7ffcb339a47b1471c.png\& data-rawwidth=\&751\& data-rawheight=\&469\&\u003E&,&updated&:new Date(&T03:01:51.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:2,&likeCount&:1,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T11:01:51+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-6ff324c27e010bba42e785dacadf4e9e_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:2,&likesCount&:1},&&:{&title&:&UE4中UI解决方案Slate&,&author&:&yanjinzha&,&content&:&\u003Cp\u003E直接把两年前整理的东西拿出来了;虽然有点过时,但UE4的UI解决方案一直都是Slate表示的,只是上层封了层UMG之后感觉UE4的团队就很少有精力扩展Slate的东西了。许多UI层的组件需求和效果需求反应都很迟钝。。。\u003C\u002Fp\u003E3Cp\u003E一、ShooterGame中UI Menu的分析 \u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EUI Menu 不是Display Object,而是控制page的Controller\u003C\u002Fli\u003E\u003Cli\u003E负责控制page widget加载显示移除\u003C\u002Fli\u003E\u003Cli\u003E负责传输数据(Data)、控制器(PlayerController)、代理(Delegate)到page(从gameplay)\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E负责部分界面交互调用的逻辑处理\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cimg src=\&v2-dd9c85da5c59.png\& data-rawwidth=\&478\& data-rawheight=\&325\&\u003E\u003Cul\u003E\u003Cli\u003EInitialized\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E初始化过程与GamePlay 相关,在PlayerController,PostInitializeComponents的时候(或GameMode beginPlay)的时候进行Menu初始化;该步在Engine的Loadmap中完成。Menu被初始化,同时也对Menu控制的page widget进行初始化\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003ELife\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E控制Page Widget显示、移除与Page widget进行正常的数据传递,等等\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EDestroyed\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E一般发生在切换关卡或退出客户端的时候,每次切换关卡由引擎自动进行垃圾回收和初始化\u003C\u002Fp\u003E\u003Cp\u003E二、Slate解决方案介绍\u003C\u002Fp\u003E\u003Cp\u003E1、Widgets概述\u003C\u002Fp\u003E\u003Cli\u003E作为DisplayObject显示在viewport中,负责显示内容\u003C\u002Fli\u003E\u003Cli\u003E以智能指针的形式使用;继承自TSharedFromThis\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EWidget分LeafWidget、CompoundWidget、Panel、RichTextBlock、WeakWidget\u003C\u002Fli\u003E\u003Cli\u003E使用的时候用SNEW操作符、或者SASSIGNNEW操作符,然后对options进行修改;SNew创建一个widget对象,要加入DisplayObjectList才能绘制,SAssignNew创建一个widget对象并保存该对象的智能指针\u003C\u002Fli\u003E\u003Cli\u003E构造Widget操作:接受外部参数(布局、Delegate、属性等),定义Style样式,添加ChildSlot(构造DisplayObject)等\u003C\u002Fli\u003E\u003Cli\u003E构造示例SBorder:\u003C\u002Fli\u003E\u003Cli\u003E\u003Cimg src=\&v2-6623dda654dd89d0dcb6f8a6c7327b71.png\& data-rawwidth=\&955\& data-rawheight=\&482\&\u003E2、组件LeafWidget\u003C\u002Fli\u003E\u003Cli\u003E不可再分的单元;组件层次的最下层次节点;直接继承自SWidget;没有child Slot\u003C\u002Fli\u003E\u003Cli\u003E负责最基础的单元信息显示;LeafWidgets are usually intended as building blocks for aggregate widgets\u003C\u002Fli\u003E\u003Cli\u003E目前的LeafWidget有STextBlock, SImage, SColorBlock, SCircularThrobber, SEditableText, SColorWheel, SSlider,SSpacer, SProgressBar...\u003C\u002Fli\u003E\u003Cbr\u003E3、组件CompoundWidget\u003Cul\u003E\u003Cli\u003Ewidgets with a fixed number of explicitly named child slots定义稍微复杂的组件\u003C\u002Fli\u003E\u003Cli\u003ESButton、SBorder、SCheckBox等\u003C\u002Fli\u003E\u003Cli\u003ECompoundWidget内容可根据自己的需求添加可以是很复杂的Page Widget也可是简单实用的控件,在Construct里面添加子节点\u003C\u002Fli\u003E\u003Cli\u003E如SButton中包含Sborder、STextBlock\u003C\u002Fli\u003E\u003Cli\u003E内部layout和width、height的控制\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E4、布局Layout(SPanel)\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E一些SCompoundWidget可以负责布局,当然由Slate层暴露给上层使用;例如SOverLay,SBox, SBorder等\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003ESOverlay:有Z-order属性可设置显示层级\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003ESBox :支持padding,widthoverride,heightoverride是目前发现唯一可是设置绝对长宽数据的组件(起效需要不跟其它的相对布局参数冲突)\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003ESVerticalBox纵向布局的box;设置width fill和auto方法;\u003C\u002Fli\u003E\u003Cli\u003ESHorizontalBox横向布局的box;设置height fill和auto方法、\u003C\u002Fli\u003E\u003Cli\u003E用Align的方式进行定位HAligh、VAlign\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003ESGridePanel:可分格进行widget 布局\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EZ-order控制控件的分层显示(SOverlay组件)\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E除此之外Spane的派生类l是一个可以控制Slot的布局组件;布局规则更加自由;以上的组件布局规则相对来说局限性较大,SPanel则使用更加自由的锚点+盒子模型\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E相对布局的布局模型 与css的盒子模型类似;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EPadding对子控件的slot进行相对定位\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E用Align的方式进行对齐定位HAligh、VAlign\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E对child slot的Width和Height的控制 Fill方式和auto方式\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EZ-order控制控件的分层显示(SOverlay组件)\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E布局是需要注意的是对应两种布局的size制定方法fill方法和auto方法是Slate的layout两种计算布局的机制\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EComputerDesiredSize:从自己的childSlot开始先计算child的size来决定自己的desiredsize\u003C\u002Fli\u003E\u003Cli\u003EArrangeChildren:父容器传进来一个AllocatedGeometry,严格按照这个Geometry的信息绘制自己\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E5、Widget 创建和绘制\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E关于DisplayObjectList,Swidget有Children列表,要加入到children列表中才能被绘制;\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003E布局类控件使用addSlot、removeSlot添加插槽的方式添加child\u003C\u002Fli\u003E\u003Cli\u003E非布局类控件使用ContentSlot的方式添加内容;只能添加一个组件;写成ChildSlot[]的形式\u003C\u002Fli\u003E\u003Cli\u003E绘制OnPaint;LeafWidget在Onpaint方法中用DrawElement绘制基础的组件\u003C\u002Fli\u003E\u003Cli\u003ESCompoundWdiget 既可以在Onpaint中绘制需要的基础图形也可以在Construct中添加childContent\u003C\u002Fli\u003E\u003Cli\u003E布局的控件找出arrangedchildren,每个子组件自己负责Paint\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E6、Event(事件)\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E事件定义 作为组件参数定义,\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003E\u003Ccode lang=\&text\&\u003E\u002F** Called when the button is clicked *\u002F\t\nSLATE_EVENT( FOnClicked, OnClicked )\u003C\u002Fcode\u003E\u003C\u002Fli\u003E\u003Cbr\u003E\u003Cli\u003E响应定义,定义代理处理事件响应(全局的)\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Ccode lang=\&text\&\u003E\u002F**\n * A delegate that is invoked when widgets want to notify a user that they have been clicked.\n * Intended for use by buttons and other button-like widgets.\n *\u002F\nDECLARE_DELEGATE_RetVal( FReply, FOnClicked )\u003C\u002Fcode\u003E\u003Cul\u003E\u003Cli\u003E基础事件监听 由SlateApplication调用\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E传入响应函数\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Ccode lang=\&text\&\u003E.OnClicked(UIHandler.Pin().ToSharedRef(),&FLoginPage::OnClickLoginBtn)\u003C\u002Fcode\u003E\u003Cp\u003EUI事件处理的过程(普通的鼠标事件和键盘事件)\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EFWindowsApplication在AppWndProc处接受win32传进的message;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EAppWndProc根据message的类型对message分类处理然后执行deferMessage\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EdeferMessage检查消息是否需要延迟处理;延迟处理则放入处理listDeferredMessages;否则立即执行ProcessDeferredMessage\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EProcessDeferredMessage根据msg类型调用MessageHandler的不同方法开始处理事件(关于FWindowsApplication中的MessageHandler;其实就是一个FSlateApplication)如鼠标左键mouseDown的处理,则执行\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Ccode lang=\&text\&\u003EMessageHandler-&OnMouseDown( CurrentNativeEventWindowPtr, EMouseButtons::Left );\u003C\u002Fcode\u003E\u003Cul\u003E\u003Cli\u003EFSlateApplication作为FWindowsApplication的MessageHandler开始处理具体的事件;转化msg为 FPointEvent 然后进入ProcessMessage的处理 如OnMouseDown 执行ProcessMouseButtonDownMessage\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EFSlateApplication 在ProcessMessage过程的处理根据不同的事件不同 如鼠标单击事件 会使用LocateWindowUnderMouse方法找出当前FPointEvent点上的所有widgetPath,然后对于每个widget path 里的widget生成FReply响应,并执行Widget里面对应的处理事件的方法;如鼠标左键单击的事件会执行
最后开始进行ProcessReply的过程\u003C\u002Fli\u003E\u003Cbr\u003E\u003Cli\u003EProcessReply的过程主要 Apply any requests form the Reply to the application\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cp\u003E7、数据更新\u003C\u002Fp\u003E\u003Cp\u003ESWidget在Construct的过程会对所有的dynamic的属性绑定delegate,delegate在Tick的时候执行计算返回数据;示例:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E.ColorAndOpacity(this,&SShooterMenuItem::GetButtonTextColor);\u003C\u002Fcode\u003E8、组件生命周期\u003Cul\u003E\u003Cli\u003E构造(SNew或SAssingNew的时候执行)\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003Eadded to parent\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E进入正常的使用阶段(Visible的使用Visible、Hidden、MouseHidden等参数)\u003C\u002Fli\u003E\u003Cli\u003Eremove from parent\u003C\u002Fli\u003E\u003Cli\u003Edestroy\u003C\u002Fli\u003E\u003Cli\u003E检查remove from DisplayObjectList后Tick和Onpaint方式的执行?(不再执行Visibility同样)\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E三、Slate的Animation机制\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EAnimation 机制 使用 construct调用的方法 tick调用 修改属性 ;另有 FCurveSequence 进行计算差值\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003EConstruct 中定义样式 \u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Ccode lang=\&text\&\u003E.Padding(TAttribute&FMargin&(this,&SShooterMenuWidget::GetLeftMenuOffset))
\u003C\u002Fcode\u003E\u003Cul\u003E\u003Cli\u003E定义FCurveSequence 来计算差\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Cul\u003E\u003Cli\u003E\u003Ccode lang=\&text\&\u003ELeftMenuScrollOutCurve = LeftMenuWidgetAnimation.AddCurve(0,MenuChangeDuration,ECurveEaseFunction::QuadInOut);\n\tLeftMenuWidgetAnimation.Play();\u003C\u002Fcode\u003E\u003C\u002Fli\u003E\u003Cli\u003ETick时修改属性值\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cbr\u003E\u003Ccode lang=\&text\&\u003EFMargin SShooterMenuWidget::GetLeftMenuOffset() const\n\t{\n\t\tconst float LeftBoxSizeX = LeftBox-\t&GetDesiredSize().X + OutlineWidth * 2;\n\t\treturn FMargin(0, 0,-LeftBoxSizeX + \tLeftMenuScrollOutCurve.GetLerp() * \tLeftBoxSizeX,0);\n\n\t}\u003C\u002Fcode\u003E\u003Cp\u003E对animation状态的检查:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E一般在Tick方法中访问animation的状态\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EIsAtEnd\u003C\u002Fli\u003E\u003Cli\u003EISAtStart\u003C\u002Fli\u003E\u003Cli\u003EIsForward\u003C\u002Fli\u003E\u003Cli\u003EIsInReverse\u003C\u002Fli\u003E\u003Cli\u003EIsPlaying\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E四、Style和资源、Font支持(Style)这一部分在改用了UMG之后基本不用了;\u003C\u002Fp\u003E&,&updated&:new Date(&T04:53:20.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:12,&likeCount&:4,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T12:53:20+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic4.zhimg.com\u002Fv2-6ff324c27e010bba42e785dacadf4e9e_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:12,&likesCount&:4},&&:{&title&:&CullDistance和平截头体裁剪&,&author&:&yanjinzha&,&content&:&废话不多说;今天特地研究了下CullDistanceVolume的实现\u003Cp\u003E不使用CullDistanceVolume的时候可以通过PrimitiveComponent的DesiredMaxDrawDistance参数控制Mesh的裁剪;\u003C\u002Fp\u003E\u003Cimg src=\&v2-dbcfb9b34f601ff82cbf85.jpg\& data-rawwidth=\&454\& data-rawheight=\&240\&\u003E\u003Cbr\u003E\u003Cp\u003E使用CullDistanceVolume的时候可针对一个区域内所有物件使用裁剪;CullDistance的裁剪每组参数有两个属性,Size:表示物体FBoxSphereBounds的直径,CullDistance:表示裁剪距离;参数组表示含义为物体尺寸在size附近的物件在与摄像机的距离大于CullDistance的时候会被裁剪;如果有好几组CullDistance数据会选Size最相近的Distance\u003C\u002Fp\u003E\u003Cimg src=\&v2-eb0608c2bab9c9e36af78e.jpg\& data-rawwidth=\&432\& data-rawheight=\&146\&\u003E\u003Cbr\u003E\u003Cp\u003E出现在CullDistanceVolume内部的物体不想被裁剪的话,可以设定AllowCullDistanceVolume为false\u003C\u002Fp\u003E\u003Cimg src=\&v2-62e1b4ce17e7cea718ee93cf162b5932.jpg\& data-rawwidth=\&362\& data-rawheight=\&29\&\u003E\u003Cbr\u003E\u003Cp\u003E经验:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E在实际运行中,以摄像机到物体包围盒中心的距离来同设置的裁剪距离做比较。\u003C\u002Fli\u003E\u003Cli\u003E使用多级划分的CullDistanceVolume对渲染可以起到很好的优化效果。\u003C\u002Fli\u003E\u003Cli\u003E对于超大物体的Cull Distance设置要考虑到物体自身最大半径的影响。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003ECullDistanceVolume会比较参数组中的size选与物体BoundingBox最接近的参数组的Distance(注意是相对最接近);对于CullDistanceVolume中的任何物体理论上都可以裁剪,如果设置的最大size的参数组size为500,那么size为500以上的物体都会使用这个参数组的CullDistance进行裁剪\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Evoid ACullDistanceVolume::GetPrimitiveMaxDrawDistances(TMap&UPrimitiveComponent*,float&& OutCullDistances)\n{\n\t\u002F\u002F Nothing to do if there is no brush component or no cull distances are set\n\tif (GetBrushComponent() && CullDistances.Num() & 0 && bEnabled)\n\t{\n\t\tfor (TPair&UPrimitiveComponent*, float&& PrimitiveMaxDistancePair : OutCullDistances)\n\t\t{\n\t\t\tUPrimitiveComponent* PrimitiveComponent = PrimitiveMaxDistancePair.K\n\n\t\t\t\u002F\u002F Check whether primitive supports cull distance volumes and its center point is being encompassed by this volume.\n\t\t\tif (EncompassesPoint(PrimitiveComponent-&GetComponentLocation()))\n\t\t\t{\t\t\n\t\t\t\t\u002F\u002F Find best match in CullDistances array.\n\t\t\t\tconst float PrimitiveSize = PrimitiveComponent-&Bounds.SphereRadius * 2;\n\t\t\t\tfloat CurrentError = FLT_MAX;\n\t\t\t\tfloat CurrentCullDistance = 0.f;\n\t\t\t\tfor (const FCullDistanceSizePair& CullDistancePair : CullDistances)\n\t\t\t\t{\n\t\t\t\t\tconst float Error = FMath::Abs( PrimitiveSize - CullDistancePair.Size );\n\t\t\t\t\tif (Error & CurrentError)\n\t\t\t\t\t{\n\t\t\t\t\t\tCurrentError = E\n\t\t\t\t\t\tCurrentCullDistance = CullDistancePair.CullD\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfloat& CullDistance = PrimitiveMaxDistancePair.V\n\n\t\t\t\t\u002F\u002F LD or other volume specified cull distance, use minimum of current and one used for this volume.\n\t\t\t\tif (CullDistance & 0.f)\n\t\t\t\t{\n\t\t\t\t\tCullDistance = FMath::Min(CullDistance, CurrentCullDistance);\n\t\t\t\t}\n\t\t\t\t\u002F\u002F LD didn't specify cull distance, use current setting directly.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCullDistance = CurrentCullD\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Cp\u003E最后执行裁剪的过程在平截头体的裁剪中实现:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eif (DistanceSquared & FMath::Square(MaxDrawDistance + FadeRadius) ||\n\t\t\t\t\t\t(DistanceSquared & Bounds.MinDrawDistanceSq) ||\n\t\t\t\t\t\t(UseCustomCulling && !View.CustomVisibilityQuery-&IsVisible(VisibilityId, FBoxSphereBounds(Bounds.Origin, Bounds.BoxExtent, Bounds.SphereRadius))) ||\n\t\t\t\t\t\t(bAlsoUseSphereTest && View.ViewFrustum.IntersectSphere(Bounds.Origin, Bounds.SphereRadius) == false) ||\n\t\t\t\t\t\tView.ViewFrustum.IntersectBox(Bounds.Origin, Bounds.BoxExtent) == false)\n\t\t\t\t\t{\n\t\t\t\t\t\tSTAT(NumCulledPrimitives.Increment());\n\t\t\t\t\t}\n\u003C\u002Fcode\u003E&,&updated&:new Date(&T09:18:39.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:0,&likeCount&:2,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T17:18:39+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic3.zhimg.com\u002Fv2-7a9bd67ccda3b25c58fb3_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:0,&likesCount&:2},&&:{&title&:&Tile-Based Deferred Rendering&,&author&:&yanjinzha&,&content&:&\u003Cp\u003E转:\u003C\u002Fp\u003E\u003Cp\u003E目前所有的移动设备都使用的是 \u003Ca href=\&http:\u002F\u002Fblog.imgtec.com\u002Fpowervr\u002Fa-look-at-the-powervr-graphics-architecture-tile-based-rendering\& data-editable=\&true\& data-title=\&Tile-Based Deferred Rendering(TBDR)\&\u003ETile-Based Deferred Rendering(TBDR)\u003C\u002Fa\u003E 的渲染架构。TBDR 的基本流程是这样的,当提交渲染命令的时候,GPU 不会立刻进行渲染,而是一帧内所有的渲染命令积攒起来,最后统一渲染。在渲染到 FrameBuffer 的时候,也不是依次执行所有的命令将 Fragment 结果填充到 FrameBuffer 中。而是在 GPU 内部有着叫做 Tile 的高速渲染器,这些 Tiles 虚拟的将 FrameBuffer 分割成小块(光栅化后得到很多 Fragment,很容易就能决定 Fragment 所在的 Tile),每次执行一小块中的所有渲染命令,完成后将结果写回 FrameBuffer。这些 Tile 一般会在 32x32 像素的大小,当然根据设计的不同而各部相同。Tile 的数量一般不足以完全平铺整个 FrameBuffer,一次只能覆盖一部分 FrameBuffer 的区域,所以每帧内同一个 Tile 会执行多次渲染操作。\u003C\u002Fp\u003E\u003Cp\u003ETBDR 的渲染架构带来了一个非常大的好处,就是 Hidden Surface Removal。当 vertex shader 执行完成后,通过插值得到很多 fragment,这个之后每个 fargment 的深度值就已经知道了,那么就可以利用这个深度值将最终不会渲染到屏幕(被其它 fragment 遮挡)的 fragment 剔除,减少了很多 fragment shader 计算量,提高了填充率。注意,这个只对非透明的物体有效,如果是 AlphaTest(shader 中表面为使用了 clip 或者 discard) 或者 Transparent(Alpha 不是 1),是没有 Hidden Surface Removal 效果的。因为很简单,透明的 fragment 无法遮挡住后面 fragment。也就是说并不是 AlphaTest 和 Blend 本身是大消耗操作,而是因为破坏了 Hidden Surface Removal。\u003C\u002Fp\u003E\u003Cp\u003E在 TBDR 的渲染架构下还有很重要的一点需要注意。立即渲染模式下,有一个技巧是,当每一帧都不去清屏的时候是可以提高效率的,因为 clear 操作需要将值写入 FramBuffer 中的每一个像素中,这是需要花费一定时间的。而这个技巧在 TBDR 中是行不通的,反而会起到反效果。这是因为,如果你没有调用 clear 操作,表示你认为上一帧的内容是不能丢弃的,所以在渲染 tile 的时候,硬件会将 FrameBuffer 中数据先写入 Tile,然后再执行渲染,这个写入操作无形中增加了很多的负担,有可能就会严重影响到程序的执行效率。\u003C\u002Fp\u003E\u003Cp\u003E下面是几个很好的参考资料:\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\u002Fwww.seas.upenn.edu\u002F~pcozzi\u002FOpenGLInsights\u002FOpenGLInsights-TileBasedArchitectures.pdf\& data-editable=\&true\& data-title=\&OpenGLInsights-TileBasedArchitectures.pdf\& class=\&\&\u003EOpenGLInsights-TileBasedArchitectures.pdf\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&https:\u002F\u002Fdeveloper.apple.com\u002Flibrary\u002Ftvos\u002Fdocumentation\u002F3DDrawing\u002FConceptual\u002FOpenGLES_ProgrammingGuide\u002FPerformance\u002FPerformance.html\& data-editable=\&true\& data-title=\&Tuning Your OpenGL ES App\&\u003ETuning Your OpenGL ES App\u003C\u002Fa\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T09:06:29.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:1,&likeCount&:6,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T17:06:29+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic3.zhimg.com\u002Fv2-7a9bd67ccda3b25c58fb3_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:1,&likesCount&:6},&&:{&title&:&UE4合并DrawCall的做法实现&,&author&:&yanjinzha&,&content&:&之前对UE4 UI的绘制中合并DC的部分不是太了解;不得不说源码中各种理解的坑;稍微不注意有些东西是理解不到的。除非有目的的去找某个功能是怎么实现的,但是这样又会忽略其他功能的实现。我之前也经常看底层的代码绘制渲染的逻辑,但是每次看的感觉都会有新收获。之前没感觉有合并DC的操作,直到看了某人UOD上的UI优化的讲稿,觉得有必要仔细的追查一下。以下是结果:\u003Cp\u003E大概介绍绘制UI的过程先。绘制 UI的过程分三个部分: \u003C\u002Fp\u003E\u003Cp\u003E第一部分主要在SlateApplication中PrivateDrawWindow\n主线程Tick驱动控件不断递归调用Paint和Onpaint搜集DrawElement,存入windowelementlist \u003C\u002Fp\u003E\u003Cp\u003E第二部分 SlateRHIrender中调用FSlateRHIRenderer::DrawWindows_Private。 处理windowelementlist中的DrawElement;使用FSlateElementBatcher把DrawElement转化成FSlateBatchData批次数据,具体调用在FSlateElementBatcher::AddElements中;针对layer优化的代码和合并drawcall的代码在这部分代码中 \u003C\u002Fp\u003E\u003Cp\u003E第三部分 全渲染线程 使用上一步生成的FSlateBatchData生成renderbatch数据然后送进渲染的pipeline;在FSlateRHIRenderer::DrawWindow_RenderThread接口中 \u003C\u002Fp\u003E\u003Cp\u003E合并的过程发生在mainthread ,FSlateApplication执行DrawWindows的接口调用SlateRHIRender做RHI的处理的时候 ;算是第二个过程发生的时候。详细过程如下: \u003C\u002Fp\u003E\u003Cp\u003E\nFSlateElementBatcher在执行DrawElement向FSlataElementBatch转换的过程中,挨个遍历DrawElement,然后挨个转换,把生成的FSlataElementBatch以及对应的Layer关系存储进DrawLayer的Map中。存储的层级关系是FSlateElementBatcher(ElementBatcher)--》FSlateDrawLayer(DrawLayer)-》FElementBatchMap(LayerToElementBatches)。\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E以下是FElementBatchMap的定义:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eclass FElementBatchMap \n{ \npublic: \nFElementBatchMap() \n{ \nReset(); \n} \n\nFORCEINLINE_DEBUGGABLE int32 Num() const { return Layers.Num() + OverflowLayers.Num(); } \n\nFORCEINLINE_DEBUGGABLE TUniqueObj&FElementBatchArray&* Find(uint32 Layer) \n{ \nif ( Layer & (uint32)Layers.Num() ) \n{ \nif ( ActiveLayers[Layer] ) \n{ \nreturn &Layers[Layer]; \n} \n\ \n} \nelse \n{ \nreturn OverflowLayers.Find(Layer); \n} \n} \n\nFORCEINLINE_DEBUGGABLE TUniqueObj&FElementBatchArray&& Add(uint32 Layer) \n{ \nif ( Layer & (uint32)Layers.Num() ) \n{ \nMinLayer = FMath::Min(Layer, MinLayer); \nMaxLayer = FMath::Max(Layer, MaxLayer); \nActiveLayers[Layer] = \nreturn Layers[Layer]; \n} \nelse \n{ \nreturn OverflowLayers.Add(Layer); \n} \n} \n\nFORCEINLINE_DEBUGGABLE void Sort() \n{ \nOverflowLayers.KeySort(TLess&uint32&()); \n} \n\ntemplate &typename TFunc& \nFORCEINLINE_DEBUGGABLE void ForEachLayer(const TFunc& Process) \n{ \nif ( MinLayer & (uint32)Layers.Num() ) \n{ \nfor ( TBitArray&&::FConstIterator BitIt(ActiveLayers, MinLayer); BitIt; ++BitIt ) \n{ \nif ( BitIt.GetValue() == 0 ) \n{ \ \n} \n\nconst int32 BitIndex = BitIt.GetIndex(); \nFElementBatchArray& ElementBatches = *Layers[BitIndex]; \n\nif ( ElementBatches.Num() & 0 ) \n{ \nProcess(BitIndex, ElementBatches); \n} \n\nif ( ((uint32)BitIndex) &= MaxLayer ) \n{ \ \n} \n} \n} \n\nfor ( TMap&uint32, TUniqueObj&FElementBatchArray&&::TIterator It(OverflowLayers); It; ++It ) \n{ \nuint32 Layer = It.Key(); \nFElementBatchArray& ElementBatches = *It.Value(); \n\nif ( ElementBatches.Num() & 0 ) \n{ \nProcess(Layer, ElementBatches); \n} \n} \n} \n\nFORCEINLINE_DEBUGGABLE void Reset() \n{ \nMinLayer = UINT_MAX; \nMaxLayer = 0; \nActiveLayers.Init(false, Layers.Num()); \nOverflowLayers.Reset(); \n} \n\nprivate: \nTBitArray&& ActiveL \nTStaticArray&TUniqueObj&FElementBatchArray&, 256& L \nTMap&uint32, TUniqueObj&FElementBatchArray&& OverflowL \nuint32 MinL \nuint32 MaxL \n}; \n\n\u003C\u002Fcode\u003E\u003Cp\u003E在每次开始转换之前会先向FElementBatchMap查找是否有可以和当前合并的DrawElement对应的FSlateElementBatch,即查找相似的FSlateElementBatch是否在map中已经存在;如果存在就会把之前的FSlateElementBatch返回,然后再FSlateElementBatch对应的BatchData中拼接Vertices和Indices;从而完成合并DC的过程。\u003C\u002Fp\u003E\u003Cp\u003E查找相似的FSlateElementBatch的过程如下以Simage的控件示例: \u003Cbr\u003E\u003Cbr\u003E\u002F\u002F Create a temp batch so we can use it as our\nkey to find if the same batch already exists \u003Cbr\u003EFSlateElementBatch TempBatch( InTexture,\nShaderParams, ShaderType, PrimitiveType, DrawEffects, DrawFlags, ScissorRect,\n0, 0, nullptr, SceneIndex ); \u003Cbr\u003E\u003Cbr\u003EFSlateElementBatch* ElementBatch =\n(*ElementBatches)-&FindByKey( TempBatch ); \u003Cbr\u003E\u003Cbr\u003E然后从BatchData中找出FSlateElementBatch对应的FSlateVertexArray和FSlateIndexArray进行拼接 如下 :\u003Cbr\u003E\u003Cbr\u003EFSlateElementBatch& ElementBatch =\nFindBatchForElement( Layer, FShaderParams(), Resource, ESlateDrawPrimitive::TriangleList,\nESlateShader::Default, InDrawEffects, DrawFlags, DrawElement.GetScissorRect(),\nDrawElement.GetSceneIndex()); \u003Cbr\u003EFSlateVertexArray& BatchVertices =\nBatchData-&GetBatchVertexList(ElementBatch); \u003Cbr\u003EFSlateIndexArray& BatchIndices = BatchData-&GetBatchIndexList(ElementBatch);\u003C\u002Fp\u003E\u003Cp\u003E总结查找合并DC的条件 首先Layer相同,其次根据DrawElement生成的FSlateElementBatch相同 \u003Cbr\u003E\u002F\u002F Create a temp batch so we can use it as our\nkey to find if the same batch already exists \u003Cbr\u003EFSlateElementBatch TempBatch( InTexture,\nShaderParams, ShaderType, PrimitiveType, DrawEffects, DrawFlags, ScissorRect,\n0, 0, nullptr, SceneIndex );\u003C\u002Fp\u003E\u003Cp\u003E总结最后的对应关系 :\u003Cbr\u003EDrawElement(Draw的前期执行Paint和OnPaint生成,存储在WindowsList上)对应RHI过程生成layer 多对一 \u003C\u002Fp\u003E\u003Cp\u003E\nRHI过程生成的Layer和FSlateElementBatch为一对多的关系 \u003C\u002Fp\u003E\u003Cp\u003E\nDrawElement与RHI生成的FSlateElementBatch为多对一的关系;这步会发生DC的合并 \u003C\u002Fp\u003E\u003Cp\u}

我要回帖

更多关于 怎样才能找到工作 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信