[{"data":1,"prerenderedAt":932},["ShallowReactive",2],{"blog-page-8":3,"blog-count":931},[4,113,231,379,703],{"id":5,"title":6,"body":7,"date":89,"description":13,"extension":90,"meta":91,"navigation":94,"path":106,"seo":107,"stem":108,"tags":109,"__hash__":112},"blogs\u002F_legacy\u002F2020\u002F2020-06-13-ue4-crash-log-on-direct12-rhi.md","UE4在DX12 RHI上崩溃两例记录",{"type":8,"value":9,"toc":83},"minimark",[10,14,17,20,23,27,30,41,44,50,54,57,63,74,80],[11,12,13],"p",{},"主要是发生在global shader的自定义上，如果你没有使用的话估计原因不太一样~",[11,15,16],{},"当前使用UE版本为UE4.25.1。",[11,18,19],{},"原本在理解上感觉UE4的RHI封装应当是一致的，但实际情况还是会有些不同。",[11,21,22],{},"结论上看，在Direct12的RHI上有一些更加严格的限制，如果不注意的话就会出现问题。",[24,25,26],"h2",{"id":26},"参数顺序",[11,28,29],{},"Dx12的RHI要求Vertex Shader和Pixel Shader的参数顺序是一致的，也就是说OutPut的次序就是Input的次序，否则就会出现以下错误",[31,32,37],"pre",{"className":33,"code":35,"language":36},[34],"language-text","Fatal error: [File:D:\u002FBuild\u002F++UE4+Licensee\u002FSync\u002FEngine\u002FSource\u002FRuntime\u002FD3D12RHI\u002FPrivate\u002FD3D12Util.cpp] [Line: 581] hr failed at D:\u002FBuild\u002F++UE4+Licensee\u002FSync\u002FEngine\u002FSource\u002FRuntime\u002FD3D12RHI\u002FPrivate\u002FWindows\u002FWindowsD3D12PipelineState.cpp:697 with error E_INVALIDARG\nUE4Editor.exe has triggered a breakpoint.\n","text",[38,39,35],"code",{"__ignoreMap":40},"",[11,42,43],{},"调整参数次序后不再出现。",[31,45,48],{"className":46,"code":47,"language":36},[34],"void MainVS(\n    in uint GlobalVertexId : SV_VertexID,\n-    out float4 OutPosition : SV_POSITION,\n-    out float2 OutUV : TEXCOORD0   \n+    out float2 OutUV : TEXCOORD0,\n+    out float4 OutPosition : SV_POSITION\n    )\n{\n    OutPosition = float4(kSpriteVertices[GlobalVertexId], 0, 1);\n    OutUV = kQuadUVs[GlobalVertexId];\n}\n\n\n\nvoid MainPS(\n    in noperspective float2 IntUV : TEXCOORD0,\n    in float4 SvPosition : SV_POSITION,\n    out float4 OutColor : SV_Target0\n    )\n{\n    OutColor = float4(1,IntUV.r,0,1);\n}\n",[38,49,47],{"__ignoreMap":40},[24,51,53],{"id":52},"setgraphicspipelinestate","SetGraphicsPipelineState",[11,55,56],{},"这个错误应当与初始化次序有关，主要是Vertex Shader等的参数必须在SetGraphicsPipelineState之后调用，否则就会出现一下错误",[31,58,61],{"className":59,"code":60,"language":36},[34],"Ensure condition failed: CachedShader == VertexShader [File:D:\u002FBuild\u002F++UE4+Licensee\u002FSync\u002FEngine\u002FSource\u002FRuntime\u002FD3D12RHI\u002FPrivate\u002FD3D12Commands.cpp] [Line: 38] Parameters are being set for a VertexShader which is not currently bound\nUE4Editor.exe has triggered a breakpoint.\n",[38,62,60],{"__ignoreMap":40},[11,64,65,66,73],{},"注意调整下次序就可以了，类似这样的[",[67,68,72],"a",{"href":69,"rel":70},"https:\u002F\u002Fgithub.com\u002FArisego\u002FUnrealLive2D\u002Fcommit\u002Fab8f25a34a2f9c491014ba33474eadd06c9a288a",[71],"nofollow","改动","]就可以了。",[31,75,78],{"className":76,"code":77,"language":36},[34],"        GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();\n        GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();\n\n+       SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);\n\n\n        VertexShader->SetParameters(RHICmdList, VertexShader.GetVertexShader(), ts_BaseColor, tsr_TextureRHI);\n        PixelShader->SetParameters(RHICmdList, PixelShader.GetPixelShader(), ts_BaseColor, tsr_TextureRHI);\n\n\n        \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n        \u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\u002F\n-       SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);\n\n\n\n        RHICmdList.SetStreamSource(0, ScratchVertexBufferRHI, 0);\n",[38,79,77],{"__ignoreMap":40},[11,81,82],{},"大的逻辑上并没有什么问题，应该也不会出现RHI封装语义不一致的问题。都是一些细节上的约定没搞明白造成的。",{"title":40,"searchDepth":84,"depth":85,"links":86},2,3,[87,88],{"id":26,"depth":84,"text":26},{"id":52,"depth":84,"text":53},"2020-06-13","md",{"layout":92,"status":93,"published":94,"author":95,"author_login":97,"author_email":98,"wordpress_id":99,"wordpress_url":100,"date_gmt":101,"excerpt":102},"post","publish",true,{"display_name":96,"login":97,"email":98,"url":40},"风铃","flinkor","flinkor@foxmail.com",2917,"\u002F?p=2917","2020-06-13 03:22:38 +0000",{"type":8,"value":103},[104],[11,105,13],{},"\u002F2020-06-13-ue4-crash-log-on-direct12-rhi",{"title":6,"description":13},"_legacy\u002F2020\u002F2020-06-13-ue4-crash-log-on-direct12-rhi",[110,111],"UE4","Direct12","3fhLcl2u7jyfS6j562ueYgKWvVKuAmOkN-Ws-IHxJxY",{"id":114,"title":115,"body":116,"date":216,"description":120,"extension":90,"meta":217,"navigation":94,"path":226,"seo":227,"stem":228,"tags":229,"__hash__":230},"blogs\u002F_legacy\u002F2020\u002F2020-06-01-ue4-editor-helper-dev-note.md","UE4编辑器帮助开发记录",{"type":8,"value":117,"toc":209},[118,121,124,127,130,136,139,145,148,154,158,161,166,169,172,175,178,181,184,187,190,194,197,203,206],[11,119,120],{},"UE4的编辑器帮助类对于开发一些小功能加快开发速度挺有用的。",[11,122,123],{},"当前使用的UE4版本为4.25。",[24,125,126],{"id":126},"命令行支持",[11,128,129],{},"要支持命令行，不仅仅需要继承UCommandlet。还要注意类的命名，否则可能会无法正确的执行：",[131,132,133],"blockquote",{},[11,134,135],{},"LogInit: Error: xxxxx looked like a commandlet, but we could not find the class.",[11,137,138],{},"主要是因为命名必须采用xxxxxxCommandlet的形式，因为在FEngineLoop::PreInitPreStartupScreen中作了特殊处理",[31,140,143],{"className":141,"code":142,"language":36},[34],"if (Token.StartsWith(TEXT(\"run=\")))\n{\n    Token.RightChopInline(4, false);\n    if (!Token.EndsWith(TEXT(\"Commandlet\")))\n    {\n        Token += TEXT(\"Commandlet\");\n    }\n}\n",[38,144,142],{"__ignoreMap":40},[11,146,147],{},"对于传入的参数可以使用帮助函数进行解析",[31,149,152],{"className":150,"code":151,"language":36},[34],"TArray\u003CFString> Tokens;\nTArray\u003CFString> Switches;\nTMap\u003CFString, FString> ParamVals;\n\nconst TCHAR* Parms = *Params;\nUCommandlet::ParseCommandLine(Parms, Tokens, Switches, ParamVals);\n",[38,153,151],{"__ignoreMap":40},[24,155,157],{"id":156},"console相关","Console相关",[11,159,160],{},"console对于编辑器内调试还是挺有帮助的，尤其是console var，简直调试利器，尤其是你的项目编译一次要十几分钟的时候……",[162,163,165],"h3",{"id":164},"console帮助","Console帮助",[11,167,168],{},"这个记得以前在社区有看到生成的方法，但是这次在网上搜了很久才找到方法。",[11,170,171],{},"其实很简单，在console里面输入help，就会自动在你项目目录里面生成几乎所有的console的说明文档了。还自带搜索过滤功能……",[11,173,174],{},"不过这个生成的其实不全，这次刚好用到的几个没提到的有：",[11,176,177],{},"net SOCKETS 输出所有打开的UChannel",[11,179,180],{},"net DUMPSERVERRPC 输出所有的ServerRpc",[11,182,183],{},"net NETFLOOD 发送 NMT_Netspeed 256次",[11,185,186],{},"net NETDEBUGTEXT 使用DebugText发送数据，参考NotifyControlMessage",[11,188,189],{},"net NETDISCONNECT 触发连接断开",[162,191,193],{"id":192},"添加console支持","添加Console支持",[11,195,196],{},"虽然现在编辑器有Python支持了，但是自定义的函数注册Console会比python轻快些，尤其只是一些不成体系的小帮助函数。",[31,198,201],{"className":199,"code":200,"language":36},[34],"static void _Import_Curve(const TArray\u003C FString >& InArgs);\n\nstatic FAutoConsoleCommand ImportXxxToCurve(\n    TEXT(\"aio.curvim\"),\n    TEXT(\"Import xxx to target, argument [curve asset ref] \"),\n    FConsoleCommandWithArgsDelegate::CreateStatic(&_Import_Curve),\n    ECVF_Cheat);\n",[38,202,200],{"__ignoreMap":40},[11,204,205],{},"传入的数组就是入参表。同类的还有FAutoConsoleCommandWithWorld和FAutoConsoleCommandWithOutputDevice的样子，不过没有使用过。",[11,207,208],{},"这边主要做一些自定义的资源导入导出指令，所以并不需要World呢。",{"title":40,"searchDepth":84,"depth":85,"links":210},[211,212],{"id":126,"depth":84,"text":126},{"id":156,"depth":84,"text":157,"children":213},[214,215],{"id":164,"depth":85,"text":165},{"id":192,"depth":85,"text":193},"2020-06-01",{"layout":92,"status":93,"published":94,"author":218,"author_login":97,"author_email":98,"wordpress_id":219,"wordpress_url":220,"date_gmt":221,"excerpt":222},{"display_name":96,"login":97,"email":98,"url":40},2911,"\u002F?p=2911","2020-06-01 08:09:00 +0000",{"type":8,"value":223},[224],[11,225,120],{},"\u002F2020-06-01-ue4-editor-helper-dev-note",{"title":115,"description":120},"_legacy\u002F2020\u002F2020-06-01-ue4-editor-helper-dev-note",[110],"AISPz3jz3z2Nb4LCj272Ak4Yv4QLvUuWVGfEzauJS7Y",{"id":232,"title":233,"body":234,"date":363,"description":238,"extension":90,"meta":364,"navigation":94,"path":373,"seo":374,"stem":375,"tags":376,"__hash__":378},"blogs\u002F_legacy\u002F2020\u002F2020-05-24-ue4-position-deviation-notes.md","UE4物理位置偏差小记两则",{"type":8,"value":235,"toc":354},[236,239,242,245,248,251,254,257,263,266,272,275,278,281,284,287,291,294,297,300,303,306,310,313,322,333,336,339,342,345,348,351],[11,237,238],{},"UE4的运动模拟在使用过程中有时会出现位置不在预期的情况。",[11,240,241],{},"虽然并不是会造成很大的问题，但是在需要使用精确位置的时候就会出现一些偏差。",[11,243,244],{},"当前UE4版本为4.25.0。",[24,246,247],{"id":247},"抛体运动",[11,249,250],{},"在不使用物理模拟移动物体时，包括SetActorLocation的sweep，都会使用到UPrimitiveComponent::MoveComponentImpl进行移动。sweep的好处是移动的结果会保证不会有穿插。",[11,252,253],{},"但是这种运动方式在快速移动时会有一点小问题，就是它在作碰撞回避之后的位置并不是完全贴紧目标碰撞体表面的。",[11,255,256],{},"主要原因是在发生碰撞后的PullBackHit中：",[31,258,261],{"className":259,"code":260,"language":36},[34],"static void PullBackHit(FHitResult& Hit, const FVector& Start, const FVector& End, const float Dist)\n{\n    const float DesiredTimeBack = FMath::Clamp(0.1f, 0.1f\u002FDist, 1.f\u002FDist) + 0.001f;\n    Hit.Time = FMath::Clamp( Hit.Time - DesiredTimeBack, 0.f, 1.f );\n}\n",[38,262,260],{"__ignoreMap":40},[11,264,265],{},"之后执行位置回退时，会使用这个计算的Time",[31,267,270],{"className":268,"code":269,"language":36},[34],"NewLocation = TraceStart + (BlockingHit.Time * (TraceEnd - TraceStart));\n",[38,271,269],{"__ignoreMap":40},[11,273,274],{},"也就是说，如果你是在进行子弹模拟的话，这个回避之后的位置有可能离碰撞表面会比较远。",[11,276,277],{},"这个时候使用返回的HitResult中的ImpactPoint或者Location反而更加接近一些。",[24,279,280],{"id":280},"骨骼位置",[11,282,283],{},"对于依赖于动画更新的骨骼而言，其物理体的位置与其渲染位置是会存在一个微小的偏差的。如果项目中使用了一些特定的优化的话，这个偏差可能会被放大。在使用的时候需要注意。",[11,285,286],{},"取到的位置不在预期点主要由以下两个函数引发：",[162,288,290],{"id":289},"updatekinematicbonestoanim","UpdateKinematicBonesToAnim",[11,292,293],{},"当我们调用USkinnedMeshComponent::GetBoneTransform的时候，其实并不是去取物理世界中取骨骼的位置。我们取到的结果决定于两个值，一个是当前的ComponentToWorld，另一个是缓存的GetComponentSpaceTransforms。",[11,295,296],{},"而GetComponentSpaceTransforms本身是一个维护的双缓冲系统，其更新依赖于USkinnedMeshComponent::FlipEditableSpaceBases。",[11,298,299],{},"也就是说，在实际更新过程中，这两者有一个过期就会导致出现偏差。",[11,301,302],{},"实际去更新物理位置的逻辑在USkeletalMeshComponent::UpdateKinematicBonesToAnim中能够找到。",[11,304,305],{},"作一个简单的测试，在USkeletalMeshComponent::UpdateKinematicBonesToAnim中直接return。然后在运行时使用pvd连接引擎，你会发现移动角色之后，骨骼在物理世界的位置并没有发生移动。此时渲染的位置却已经移动了。",[162,307,309],{"id":308},"setkinematictarget","setKinematicTarget",[11,311,312],{},"在UpdateKinematicBonesToAnim中还有另一个可能会影响到实际使用的更新，就是setKinematicTarget，如果在移动骨骼的时候没有设置Teleport的话，就会使用这个函数进行物理设置。",[11,314,315,316,321],{},"按照[",[67,317,320],{"href":318,"rel":319},"https:\u002F\u002Fdocs.nvidia.com\u002Fgameworks\u002Fcontent\u002Fgameworkslibrary\u002Fphysx\u002Fapireference\u002Ffiles\u002FclassPxRigidDynamic.html#4464d188e7a1e94582c9cf35da9bbc93",[71],"Physx的文档","]描述，这里面其实有个类似缓存的机制",[131,323,324,327,330],{},[11,325,326],{},"The move command will result in a velocity that will move the body into the desired pose. After the move is carried out during a single time step, the velocity is returned to zero. Thus, you must continuously call this in every time step for kinematic actors so that they move along a path.",[11,328,329],{},"This function simply stores the move destination until the next simulation step is processed, so consecutive calls will simply overwrite the stored target variable.",[11,331,332],{},"The motion is always fully carried out.",[11,334,335],{},"也就是说，“真实”的物理位置会在下一次模拟之后才会更新。",[162,337,338],{"id":338},"安全的获取方式",[11,340,341],{},"要“正确”的取到想要的位置，只要使用bodyinstance的GetUnrealWorldTransform就可以了。其中第二个参数bForceGlobalPose就是区分是否取setKineMaticTarget的位置。",[11,343,344],{},"如果是要渲染位置，还是用GetBoneTransform就可以。",[24,346,347],{"id":347},"总结",[11,349,350],{},"由于引擎其实是按帧模拟的，更多的时候，位置偏差其实发生在更新次序问题上。",[11,352,353],{},"所以除非在特殊情况下，还是不要在意比较好……",{"title":40,"searchDepth":84,"depth":85,"links":355},[356,357,362],{"id":247,"depth":84,"text":247},{"id":280,"depth":84,"text":280,"children":358},[359,360,361],{"id":289,"depth":85,"text":290},{"id":308,"depth":85,"text":309},{"id":338,"depth":85,"text":338},{"id":347,"depth":84,"text":347},"2020-05-24",{"layout":92,"status":93,"published":94,"author":365,"author_login":97,"author_email":98,"wordpress_id":366,"wordpress_url":367,"date_gmt":368,"excerpt":369},{"display_name":96,"login":97,"email":98,"url":40},2901,"\u002F?p=2901","2020-05-24 03:20:32 +0000",{"type":8,"value":370},[371],[11,372,238],{},"\u002F2020-05-24-ue4-position-deviation-notes",{"title":233,"description":238},"_legacy\u002F2020\u002F2020-05-24-ue4-position-deviation-notes",[110,377],"Physx","l-g_jPlFybyQICc4OAhNVRgImE-5AJNP0ExgK98W2OQ",{"id":380,"title":381,"body":382,"date":687,"description":386,"extension":90,"meta":688,"navigation":94,"path":697,"seo":698,"stem":699,"tags":700,"__hash__":702},"blogs\u002F_legacy\u002F2020\u002F2020-05-10-ue4-blender-hair-groom.md","UE4使用Blender的HairGroom笔记",{"type":8,"value":383,"toc":674},[384,387,390,405,409,418,421,424,431,434,438,441,444,450,453,466,469,472,478,481,486,489,495,498,504,508,511,514,520,523,529,532,535,538,541,544,550,553,556,559,565,571,574,580,583,586,589,592,598,601,607,610,616,619,624,627,632,635,638,643,646,651,654,657,660,663,668,671],[11,385,386],{},"UE4最近更新了对HairGroom的支持，于是对其进行了一些测试。",[11,388,389],{},"当前使用的UE4版本为4.25.0，Blender版本为2.82a。",[11,391,392,393,398,399,404],{},"本文主要参考Jayanam的教程[",[67,394,397],{"href":395,"rel":396},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Oq0q8AmTnDU",[71],"Blender 2.8 Hair Beginner Tutorial","]和Marvel Master的教程[",[67,400,403],{"href":401,"rel":402},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=vQIkyk0-TD4",[71],"Dynamic Hair Tutorial - Blender to Unreal Engine 4 Groom UE4.25","]。",[24,406,408],{"id":407},"ue4设置","UE4设置",[11,410,411,412,417],{},"这部分按照[",[67,413,416],{"href":414,"rel":415},"https:\u002F\u002Fdocs.unrealengine.com\u002Fzh-CN\u002FEngine\u002FHairRendering\u002FOverview\u002Findex.html",[71],"官方文档","]操作就可以了。",[11,419,420],{},"当然，跟着Marvel Master的教程做也是可以的。需要注意的是，当前Groom支持在实验阶段，所以有些部分可能会与实际操作不一致。",[11,422,423],{},"例如Niagra发射器的部分，按照官方文档操作之后",[11,425,426],{},[427,428],"img",{"alt":429,"src":430},"image","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb.png",[11,432,433],{},"Override Parameters并没有出现，由于并不影响实际效果，所以就先放置了。",[24,435,437],{"id":436},"blender内制作groom","Blender内制作Groom",[11,439,440],{},"教程里面是直接在导出的SkeletonMesh上做Groom的，默认情况下会从全体表面来发射头发，以功能验证而言倒是无所谓。",[11,442,443],{},"总之首先就是加个粒子系统，然后改成头发",[11,445,446],{},[427,447],{"alt":448,"src":449},"clip_image001","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image001_thumb.png",[11,451,452],{},"对于需要控制生长区域的情况，目前看主要有三种方法：",[454,455,456,460,463],"ol",{},[457,458,459],"li",{},"单独拉出Mesh用于头发发射",[457,461,462],{},"使用头发发射器的顶点组功能",[457,464,465],{},"使用贴图进行权重控制",[11,467,468],{},"似乎第三个方法主要用于微调的样子。",[11,470,471],{},"定点组的设置比较直观，使用贴图Mask的时候需要注意，必须调整贴图的影响方式",[11,473,474],{},[427,475],{"alt":476,"src":477},"clip_image0014","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0014_thumb.png",[11,479,480],{},"完成粒子编辑后，将粒子转换为Curv",[11,482,483],{},[427,484],{"alt":429,"src":485},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-2.png",[11,487,488],{},"然后作Curv转换",[11,490,491],{},[427,492],{"alt":493,"src":494},"clip_image0016","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0016_thumb.png",[11,496,497],{},"导出Abc，注意调整缩放",[11,499,500],{},[427,501],{"alt":502,"src":503},"clip_image0018","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0018_thumb.png",[24,505,507],{"id":506},"导入ue4","导入UE4",[162,509,510],{"id":510},"头发导入",[11,512,513],{},"导入的选项直接参照教程即可，流程上似乎有些奇怪，不过现在似乎就是这样工作的",[11,515,516],{},[427,517],{"alt":518,"src":519},"clip_image00110","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00110_thumb.png",[11,521,522],{},"导入之后，在Asset里面打开物理模拟",[11,524,525],{},[427,526],{"alt":527,"src":528},"clip_image00112","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00112_thumb.png",[11,530,531],{},"此时可以将资源拖到地图中预览。",[11,533,534],{},"这边遇到问题是点开之后拖到地图里面没有反应，此时，将Asset直接添加到角色蓝图内，会发现能动了，但是没有跟随骨骼限制运动。这时再拖到地图里面就会有物理反应了，可能是哪里有bug。",[162,536,537],{"id":537},"基础导入",[11,539,540],{},"无论如何，要让效果跟随物理基础，需要再次做一下导入工作。感觉之后的版本应该会改进，不然这样操作很奇怪",[11,542,543],{},"在blender中删除转换后的Mesh，选中角色模型",[11,545,546],{},[427,547],{"alt":548,"src":549},"clip_image00114","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00114_thumb.png",[11,551,552],{},"再导出一次abc，覆盖掉旧文件",[11,554,555],{},"在UE4的旧Asset上点击重新导入",[11,557,558],{},"需要重新设置物理模拟",[11,560,561],{},[427,562],{"alt":563,"src":564},"clip_image002","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image002_thumb.png",[11,566,567],{},[427,568],{"alt":569,"src":570},"clip_image003","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image003_thumb.png",[11,572,573],{},"这个时候就可以看到Groom的跟随骨骼动画的效果了",[11,575,576],{},[427,577],{"alt":578,"src":579},"clip_image004","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image004_thumb.png",[24,581,582],{"id":582},"头发试作",[11,584,585],{},"为了实际看看Groom的头发大概是什么风格，这边简单的进行了下测试",[162,587,588],{"id":588},"顶点组绘制控制",[11,590,591],{},"在模型上添加一个顶点组",[11,593,594],{},[427,595],{"alt":596,"src":597},"clip_image00116","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00116_thumb.png",[11,599,600],{},"然后将模式切换到权重绘制模式",[11,602,603],{},[427,604],{"alt":605,"src":606},"clip_image0024","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0024_thumb.png",[11,608,609],{},"这个时候就可以在模型上绘制顶点组的范围了",[11,611,612],{},[427,613],{"alt":614,"src":615},"clip_image0034","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0034_thumb.png",[11,617,618],{},"绘制完成后在粒子的顶点组里面将density指定到这个顶点组",[11,620,621],{},[427,622],{"alt":429,"src":623},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-3.png",[11,625,626],{},"就不会从整个头上发射头发了",[11,628,629],{},[427,630],{"alt":429,"src":631},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-4.png",[162,633,634],{"id":634},"粒子调整",[11,636,637],{},"切换到粒子编辑模式随便梳理一下头发",[11,639,640],{},[427,641],{"alt":429,"src":642},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-5.png",[11,644,645],{},"这里可以通过选项让梳理的时候能够看到子粒子的效果",[11,647,648],{},[427,649],{"alt":429,"src":650},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-7.png",[11,652,653],{},"这个梳理模式(粒子编辑器)还是挺强大的，有看到教程是直接从空白处开始刷出粒子的，不过由于没有什么美术细胞，就只能从简了。",[11,655,656],{},"按照教程里面的进行导出操作，就可以看到效果了",[162,658,659],{"id":659},"效果",[11,661,662],{},"努力进行了一番效果调整后，结果是这样的：",[11,664,665],{},[427,666],{"alt":429,"src":667},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-1.png",[11,669,670],{},"由于全局只用了一个发射器，尤其是没有做前发的部分，所以效果不是很好。另外，不知是没有处理好头发的效果，还是过于写实的原因，感觉Groom的和动画风格的模型有些不匹配。",[11,672,673],{},"本来是想拉个双马尾的，但是由于对Blender的操作还不是很熟悉，最后放弃了……感觉要达到ArtStation上看到的大神们做出来的效果对我而言暂时似乎是不太可能的。",{"title":40,"searchDepth":84,"depth":85,"links":675},[676,677,678,682],{"id":407,"depth":84,"text":408},{"id":436,"depth":84,"text":437},{"id":506,"depth":84,"text":507,"children":679},[680,681],{"id":510,"depth":85,"text":510},{"id":537,"depth":85,"text":537},{"id":582,"depth":84,"text":582,"children":683},[684,685,686],{"id":588,"depth":85,"text":588},{"id":634,"depth":85,"text":634},{"id":659,"depth":85,"text":659},"2020-05-10",{"layout":92,"status":93,"published":94,"author":689,"author_login":97,"author_email":98,"wordpress_id":690,"wordpress_url":691,"date_gmt":692,"excerpt":693},{"display_name":96,"login":97,"email":98,"url":40},2876,"\u002F?p=2876","2020-05-10 12:56:56 +0000",{"type":8,"value":694},[695],[11,696,386],{},"\u002F2020-05-10-ue4-blender-hair-groom",{"title":381,"description":386},"_legacy\u002F2020\u002F2020-05-10-ue4-blender-hair-groom",[110,701],"Blender","Yspt_F27sh5kZzkyHIEkNbC-a3oyN24-P5N4FvzBm1c",{"id":704,"title":705,"body":706,"date":916,"description":710,"extension":90,"meta":917,"navigation":94,"path":926,"seo":927,"stem":928,"tags":929,"__hash__":930},"blogs\u002F_legacy\u002F2020\u002F2020-04-11-blender-ue4-pipeline-note.md","Blender到UE4笔记",{"type":8,"value":707,"toc":901},[708,711,714,717,721,724,729,732,736,739,750,753,756,759,762,771,780,783,786,789,792,798,801,804,807,812,815,818,821,824,828,837,843,846,851,855,863,869,872,876,879,882,885,890,893,898],[11,709,710],{},"Blender导出FBX到UE4的路径有些常见的问题，每次碰到都重新去找原因造成时间浪费，于是这边记录下。",[11,712,713],{},"当前UE4版本为UE4.25.0；当前Blender版本为Blender2.82a。\nBlender最近升级到2.8了，有不少变化，不过一些基本的操作还是一样的。",[24,715,716],{"id":716},"导出导入",[162,718,720],{"id":719},"smooth-group","Smooth Group",[11,722,723],{},"导入的时候提示没有SmoothGroup，需要修改FBX的默认导出选项",[11,725,726],{},[427,727],{"alt":448,"src":728},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fclip_image001_thumb.png",[11,730,731],{},"这样就不会提示了",[162,733,735],{"id":734},"bind-pose","Bind pose",[11,737,738],{},"导入UE4时提示",[131,740,741,744,747],{},[11,742,743],{},"The following bones are missing from the bind pose:",[11,745,746],{},"This can happen for bones that are not vert weighted. If they are not in the correct orientation after importing,",[11,748,749],{},"please set the \"Use T0 as ref pose\" option or add them to the bind pose and reimport the skeletal mesh.",[11,751,752],{},"按照提示直接勾选Use T0 as ref pose，就不会提示了，结果没有明显变化。",[11,754,755],{},"是否有隐藏问题还不是很确定。",[162,757,758],{"id":758},"贴图丢了",[11,760,761],{},"这个是Blender2.8的新坑，主要是材质节点的组织方式变了。",[11,763,764,765,770],{},"从[",[67,766,769],{"href":767,"rel":768},"https:\u002F\u002Fdeveloper.blender.org\u002FT68399",[71],"官方社区的讨论","]来看，这个似乎不是BUG而是一个特性。",[11,772,773,774,779],{},"2.8的[",[67,775,778],{"href":776,"rel":777},"https:\u002F\u002Fwiki.blender.org\u002Fwiki\u002FReference\u002FRelease_Notes\u002F2.80\u002FPython_API\u002FModules",[71],"API变更记录","]里面可以看到，导出使用的帮助类为bpy_extras.node_shader_utils。对于导出失败的材质，实际使用这个helper去尝试获取材质，就会发现无法正确的获取到贴图信息。主要原因是一些复杂的材质使用逻辑导致的，对于只是将Blender当作文件转换工具的用户而言就有些困扰了呢。",[11,781,782],{},"暂时没有什么特别好的解决方案，对于没有特别必要升级2.8的情况可以回到2.79……",[162,784,785],{"id":785},"贴图扭曲",[11,787,788],{},"模型导入后，部分贴图效果有微妙的扭曲感。在一些部位能明显的看到贴图出现了偏移。在对Blender内的UV进行查看后，发现在整个路径中，UV的值过大了。虽然说在理论上这并不会造成问题，但是实际是在导入UE4后出现了较为明显的UV偏差。",[11,790,791],{},"详细的发生原因没有时间去细究，不确定问题是在Blender侧还是UE侧，通过使用脚本在Blender中直接将UV全部裁剪到0~1的范围内解决了问题。",[31,793,796],{"className":794,"code":795,"language":36},[34],"import bpy\n\nfor ts_mesh in bpy.data.meshes:\n    print(ts_mesh.name)\n    for ts_uvlayer in ts_mesh.uv_layers:\n        print(ts_uvlayer)\n        for ts_data in ts_uvlayer.data:\n            ts_uv = ts_data.uv\n            ts_uv.x = ts_uv.x % 1.0\n            ts_uv.y = ts_uv.y % 1.0\n            print(ts_data.uv)\n\n\n",[38,797,795],{"__ignoreMap":40},[162,799,800],{"id":800},"光照模式下出现断层",[11,802,803],{},"在UnLit模式下模型正常，光照模式下出现一个明显的断层。",[11,805,806],{},"这种时候可以对GBuffer进行可视化来排查问题",[11,808,809],{},[427,810],{"alt":429,"src":811},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-4.png",[11,813,814],{},"这次遇到的是法线贴图的处理方式有问题，能够明显看到在World Normal的可视化里面有一个断层，导致Detail Light那边同样出现了断层。",[11,816,817],{},"问题是法线贴图的混合，法线贴图不能通过简单的加法或者乘法来混合，UE4有提供混合节点BlendAngleCorrectedNormals，直接使用即可~",[24,819,820],{"id":820},"材质节点",[11,822,823],{},"Blender和UE4的材质节点有很多不同的地方，需要至少保证一些数学计算是一致的",[162,825,827],{"id":826},"rgb-to-bw","Rgb to bw",[11,829,830,831,836],{},"似乎是在做灰度转换的样子，[",[67,832,835],{"href":833,"rel":834},"https:\u002F\u002Fdocs.blender.org\u002Fmanual\u002Fen\u002Flatest\u002Fcompositing\u002Ftypes\u002Fconverter\u002Frgb_to_bw.html",[71],"官方Rgb_to_bw文档","]没有给出详细的计算方式。到源码里面的utils.py找到了",[31,838,841],{"className":839,"code":840,"language":36},[34],"def rgb_to_bw(r, g, b):\n    \"\"\"Method to convert rgb to a bw intensity value.\"\"\"\n    return 0.35 * r + 0.45 * g + 0.2 * b\n",[38,842,840],{"__ignoreMap":40},[11,844,845],{},"参照着实现",[11,847,848],{},[427,849],{"alt":429,"src":850},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-3.png",[162,852,854],{"id":853},"invert","Invert",[11,856,315,857,862],{},[67,858,861],{"href":859,"rel":860},"https:\u002F\u002Fdocs.blender.org\u002Fmanual\u002Fen\u002Flatest\u002Frender\u002Fshader_nodes\u002Fcolor\u002Finvert.html",[71],"Invert Node","]的描述，就是反转，但是有一个factor的参数，暂时采用svm_invert.h的实现",[31,864,867],{"className":865,"code":866,"language":36},[34],"ccl_device float invert(float color, float factor)\n{\n  return factor * (1.0f - color) + (1.0f - factor) * color;\n}\n",[38,868,866],{"__ignoreMap":40},[11,870,871],{},"没有配置factor的话就是OneMinus",[162,873,875],{"id":874},"mix","Mix",[11,877,878],{},"Mix等效于UE4的Interp节点。",[24,880,881],{"id":881},"功能差异",[11,883,884],{},"这次遇到一个Blender和UE4之间节点上的表现差异导致的误操作，在UE4里面，材质节点是这样的：",[11,886,887],{},[427,888],{"alt":429,"src":889},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-5.png",[11,891,892],{},"这里面RGB和下面的R\\G\\B是指向同样的值的，而在Blender里面，如果有这样的节点",[11,894,895],{},[427,896],{"alt":429,"src":897},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-6.png",[11,899,900],{},"这个时候Color和R\\G\\B其实是完全不同的东西，如果在进行材质功能映射的时候一定不要按照UE4这边的习惯去理解~",{"title":40,"searchDepth":84,"depth":85,"links":902},[903,910,915],{"id":716,"depth":84,"text":716,"children":904},[905,906,907,908,909],{"id":719,"depth":85,"text":720},{"id":734,"depth":85,"text":735},{"id":758,"depth":85,"text":758},{"id":785,"depth":85,"text":785},{"id":800,"depth":85,"text":800},{"id":820,"depth":84,"text":820,"children":911},[912,913,914],{"id":826,"depth":85,"text":827},{"id":853,"depth":85,"text":854},{"id":874,"depth":85,"text":875},{"id":881,"depth":84,"text":881},"2020-04-11",{"layout":92,"status":93,"published":94,"author":918,"author_login":97,"author_email":98,"wordpress_id":919,"wordpress_url":920,"date_gmt":921,"excerpt":922},{"display_name":96,"login":97,"email":98,"url":40},2831,"\u002F?p=2831","2020-04-11 11:51:28 +0000",{"type":8,"value":923},[924],[11,925,710],{},"\u002F2020-04-11-blender-ue4-pipeline-note",{"title":705,"description":710},"_legacy\u002F2020\u002F2020-04-11-blender-ue4-pipeline-note",[110,701],"rStqfgrMz4Rim14QEzNGZDIhyjXIBdOStHTnvMtMDcM",222,1788763181211]