[{"data":1,"prerenderedAt":560},["ShallowReactive",2],{"blog-page-9":3,"blog-count":559},[4,134,226,320,432],{"id":5,"title":6,"body":7,"date":109,"description":13,"extension":110,"meta":111,"navigation":114,"path":126,"seo":127,"stem":128,"tags":129,"__hash__":133},"blogs\u002F_legacy\u002F2020\u002F2020-04-11-blender-ue4-texture-python-material-fix.md","Blender和UE4的贴图修复脚本",{"type":8,"value":9,"toc":102},"minimark",[10,14,17,20,23,26,29,34,37,44,47,58,61,65,68,78,84,87,93,96,99],[11,12,13],"p",{},"最近遇到了Blender导出的材质没有贴图的问题，刚好顺便研究下UE4和Blender的脚本结构。",[11,15,16],{},"当前UE4版本为UE4.25.0；当前Blender版本为Blender2.82a。",[11,18,19],{},"首先，目前Blender和UE4的脚本版本是不同的。Blender使用的是python3，而UE4停留在python2.7。",[11,21,22],{},"这边使用Blender其实主要是作为模型导出的中继，比如MMD、VRM、XPS以及一些其他的非主流模型，通过Blender这边导出为FBX，然后输入到UE4去。",[11,24,25],{},"由于Blender和UE4的材质结构是不同的，到了UE4那边之后肯定要重新调整节点。所以对于缺少贴图的情况，最致命的问题就是，我们很难把材质名称和它使用的贴图对应起来。",[11,27,28],{},"因此这边就是实现这个过程的自动化，首先，从Blender这边导出材质使用到的贴图，然后在UE4那边还原到材质中去。",[30,31,33],"h2",{"id":32},"blender贴图导出","Blender贴图导出",[11,35,36],{},"blender这边的script编辑器里面有个需要注意的地方，就是他的输出不是在左边的那个窗口输出的。虽然左边的console窗口可以用于测试指令，但是如果你在右侧的脚本上点击运行的话。输出其实是在外部的那个Console里面的。如果默认没有的话，需要在菜单中打开",[11,38,39],{},[40,41],"img",{"alt":42,"src":43},"image","\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-2.png",[11,45,46],{},"Blender的脚本文档还是比较丰富的，不过由于功能比较多，这边只是做一个简单的材质中的贴图导出，所以并不一定是一个好的实现",[48,49,54],"pre",{"className":50,"code":52,"language":53},[51],"language-text","# blender script\n# python3\n\nimport bpy\nimport json\n\nEXPORT_PATH = 'F:\u002Fmaterials.json'\n\nclass MatCollector:\n    MatDic = {}\n\n    # Collet material texture for single material\n    def _CollectMaterial(self, InMaterial):\n        MatTextures = []\n        for ts_node in InMaterial.node_tree.nodes:\n            if ts_node.bl_static_type == 'TEX_IMAGE' and ts_node.image:\n                MatTextures.append(ts_node.image.filepath)\n\n        tstr_MatName = InMaterial.name\n        if len(MatTextures) == 0:\n            print(\"[ExportMaterialTextures] \u003C\"+tstr_MatName+\"> has no texture\")\n            return\n\n        self.MatDic[tstr_MatName] = MatTextures\n        print(\"[ExportMaterialTextures] Material \" + tstr_MatName + \" with \" + str(len(MatTextures)) + \" texture\")\n        pass\n\n# List all materials and try pickout texture info\ndef ExportMaterialTextures(InPath):\n    print(\"[ExportMaterialTextures] Begin: \"+ InPath)\n    ts_MatCollector = MatCollector()\n    for Mat in bpy.data.materials:\n        ts_MatCollector._CollectMaterial(Mat)\n        \n    print(\"[ExportMaterialTextures] Material with texture count \" + str(len(ts_MatCollector.MatDic)))\n    with open(InPath, 'w') as f:\n        json.dump(ts_MatCollector.MatDic, f)\n\n    print(\"[ExportMaterialTextures] End\")\n\n\ndef main():\n    ExportMaterialTextures(EXPORT_PATH)\n\nif __name__ == \"__main__\":\n    main()\n","text",[55,56,52],"code",{"__ignoreMap":57},"",[11,59,60],{},"脚本的作用很简单，就是遍历场景内所有的材质，然后将每个材质中带有的所有贴图都输出出来。",[30,62,64],{"id":63},"ue4导入","UE4导入",[11,66,67],{},"当前UE4的编辑器Python插件是Beta状态，需要打开才能使用。",[11,69,70,71,74,75,77],{},"友情提醒：",[72,73],"br",{},"\n使用本文中的脚本前请注意备份项目文件!",[72,76],{},"\n脚本操作可能会导致不可预期的结果，请在了解这种危险的情况下使用脚本操作！\nUE4这边在完成FBX的导入之后，会看到所有的材质都是白的。所以我们这边用脚本，将对应的贴图添加到材质中去。不过，要在代码中操作材质是很麻烦的，连接的工作还是保留在了材质制作中。",[48,79,82],{"className":80,"code":81,"language":53},[51],"# unreal script\n# python2.7\n\nimport json\nimport unreal\n\nfrom sets import Set\n\nIMPORT_PATH = 'F:\u002Fmaterials.json'\nIMPORT_TARGET = '\u002FGame\u002FChara\u002F'\nTEXTURE_PATH = '\u002FGame\u002FChara\u002FTextures\u002F'\nTEXTURE_BASE = 'F:\u002Fz'\n\n\nclass MatTextureImporter:\n    IsStateGood = True\n\n    def ReadFromConfig(self):\n        unreal.log(\"[MaterialTextureImport] Begin\")\n\n        # Load from Json config\n        with open(IMPORT_PATH, 'r') as myfile:\n            data=myfile.read()\n        MatConf = json.loads(data)\n\n        # Try import texture\n        self._PreLoadTexture(MatConf)\n\n        if self.IsStateGood == False:\n            unreal.log_error(\"[MaterialTextureImport] Asset state not good, abort\")\n            return\n\n        # Try apply texture to material\n        unreal.log(\"[MaterialTextureImport] Total target material: \" + str(len(MatConf)))\n        with unreal.ScopedSlowTask(len(MatConf), \"Modifing materials\") as slow_task:\n            for ts_key in MatConf:\n                if slow_task.should_cancel():\n                    break\n                \n                slow_task.enter_progress_frame(1)\n                self._AddMaterialTexture(ts_key, MatConf[ts_key])\n\n        unreal.log(\"[MaterialTextureImport] End\")\n        return  \n\n    def _PreLoadTexture(self, InDic):\n        print(\"[MaterialTextureImport] Pre load textures - Begin\")\n\n        # Build texture list\n        tset_TexturePath = Set()\n        for ts_MatKey in InDic:\n            for ts_Texture in InDic[ts_MatKey]:\n                ts_TextureName = unreal.Paths.get_base_filename(ts_Texture)\n                ts_TextureUePath = TEXTURE_PATH + ts_TextureName\n\n                if unreal.EditorAssetLibrary.does_asset_exist(ts_TextureUePath):\n                    ts_TextureData = unreal.EditorAssetLibrary.find_asset_data(ts_TextureUePath)\n                    if ts_TextureData.is_valid():\n                        if ts_TextureData.asset_class == 'Texture2D':\n                            # We will never find a good name while operation takes more than once, so just ignore\n                            unreal.log_warning(\"[MaterialTextureImport] Texture \" + ts_TextureUePath + \" alread exist\")\n                            continue\n                        else:\n                            # While asset name conflicts, we will stop operation, as replacing or rename may break our project\n                            unreal.log_error(\"[MaterialTextureImport] Asset \" + ts_TextureUePath + \" alread exist and it is not a texture!\")\n                            self.IsStateGood = False\n                            return\n\n                tset_TexturePath.add(ts_Texture)\n\n        print(\"[MaterialTextureImport] Total texture need load: \" + str(len(tset_TexturePath)))\n\n        # Build import tasks\n        import_tasks = []\n        for ts_Texture in tset_TexturePath:\n            print(\"[MaterialTextureImport] - \" + ts_Texture)\n            AssetImportTask = unreal.AssetImportTask()\n            AssetImportTask.set_editor_property('filename', TEXTURE_BASE + ts_Texture)\n            AssetImportTask.set_editor_property('destination_path', TEXTURE_PATH)\n            AssetImportTask.set_editor_property('save', True)\n            import_tasks.append(AssetImportTask)\n\n        # Import textures\n        AssetTools = unreal.AssetToolsHelpers.get_asset_tools() \n        AssetTools.import_asset_tasks(import_tasks)\n\n        print(\"[MaterialTextureImport] Pre load textures - End\")\n        \n        return\n\n    def _AddMaterialTexture(self, InName, InList):\n        InName = InName.replace(' ', '_')\n        InName = InName.replace('.', '_')\n\n        print(\"[MaterialTextureImport] Material: \" + InName)\n\n        # Try load material\n        matpath = \"Material'\" + IMPORT_TARGET + InName + '.' + InName + \"'\"\n        ts_LoadedMat = unreal.load_asset(matpath)\n        if ts_LoadedMat is None:\n            unreal.log_warning(\"[MaterialTextureImport] Invalid material path: \" + matpath)\n            return\n\n        # Add texture to material\n        #unreal.MaterialEditingLibrary.delete_all_material_expressions(ts_LoadedMat)\n        td_TextureAdded = 0\n        for ts_TexturePath in InList:\n            ts_TextureName = unreal.Paths.get_base_filename(ts_TexturePath)\n            ts_TextureUePath = TEXTURE_PATH + ts_TextureName\n            ts_LoadedTexture = unreal.EditorAssetLibrary.load_asset(ts_TextureUePath)\n\n            if ts_LoadedTexture is None:\n                unreal.log_warning(\"[MaterialTextureImport] Could not load texture \"+ ts_TextureUePath)\n                continue\n\n            ts_TextureNodeBc = unreal.MaterialEditingLibrary.create_material_expression(ts_LoadedMat, unreal.MaterialExpressionTextureSample, -400, 250 * td_TextureAdded)\n            if ts_TextureNodeBc is None:\n                unreal.log_warning(\"[MaterialTextureImport] Could not create node\")\n                continue\n\n            ts_TextureNodeBc.set_editor_property(\"texture\", ts_LoadedTexture)\n            ts_TextureNodeBc.set_editor_property(\"desc\", ts_TextureName)\n            td_TextureAdded += 1\n\n        print(\"[MaterialTextureImport] Material get  \" + str(td_TextureAdded) + \" texture\")\n\n        return\n\ndef main():\n    ts_MatChanger = MatTextureImporter()\n    ts_MatChanger.ReadFromConfig()\n\nif __name__ == \"__main__\":\n    main()\n\n",[55,83,81],{"__ignoreMap":57},[11,85,86],{},"在制作中遇到的另一个问题是，由于模型那边Blender的材质使用到了复杂的Group Node，这边也对应的实现了Material Function，但是需要将材质改为使用Attribute输出。于是又写了个批量修改的脚本",[48,88,91],{"className":89,"code":90,"language":53},[51],"import unreal\n\ndef list_assets(InPath, InClass):\n    MatchedAssets = []\n\n    for ts_AssetPath in unreal.EditorAssetLibrary.list_assets(InPath):\n        ts_AssetData = unreal.EditorAssetLibrary.find_asset_data(ts_AssetPath)\n        if ts_AssetData.asset_class == InClass:\n            MatchedAssets.append(ts_AssetPath)\n\n    return MatchedAssets\n\n\n\ndef ModifyMaterials():\n    ts_Mats = list_assets(\"\u002FGame\u002FChara\u002F\", 'Material')\n    print(ts_Mats)\n    ts_MatModified = []\n    for ts_matPath in ts_Mats:\n        ts_mat = unreal.EditorAssetLibrary.load_asset(ts_matPath)\n        if ts_mat is None:\n            unreal.log_warning(\"[MatModify] No material in \" + ts_mat)\n            continue\n        #ts_mat.set_editor_property(\"use_material_attributes\", True)  \n\n        #unreal.MaterialEditingLibrary.recompile_material(ts_mat)\n        ts_MatModified.append(ts_mat)\n\n    unreal.EditorAssetLibrary.save_loaded_assets(ts_MatModified)\n\ndef main():\n    ModifyMaterials()\n\nif __name__ == \"__main__\":\n    main()\n\n",[55,92,90],{"__ignoreMap":57},[30,94,95],{"id":95},"总结",[11,97,98],{},"Blender和UE4两边的Python脚本都还是挺好用的。",[11,100,101],{},"不过UE4这边在批量修改完材质后会有一个卡顿的感觉，使用SlowTask包裹依然没有改善，不过由于是自用的脚本，就不纠结了。",{"title":57,"searchDepth":103,"depth":104,"links":105},2,3,[106,107,108],{"id":32,"depth":103,"text":33},{"id":63,"depth":103,"text":64},{"id":95,"depth":103,"text":95},"2020-04-11","md",{"layout":112,"status":113,"published":114,"author":115,"author_login":117,"author_email":118,"wordpress_id":119,"wordpress_url":120,"date_gmt":121,"excerpt":122},"post","publish",true,{"display_name":116,"login":117,"email":118,"url":57},"风铃","flinkor","flinkor@foxmail.com",2815,"\u002F?p=2815","2020-04-11 13:04:30 +0000",{"type":8,"value":123},[124],[11,125,13],{},"\u002F2020-04-11-blender-ue4-texture-python-material-fix",{"title":6,"description":13},"_legacy\u002F2020\u002F2020-04-11-blender-ue4-texture-python-material-fix",[130,131,132],"UE4","Blender","Python","YOQkIQJdqcC6WBD4OxarTU9B2stqZH2vyA88rL6ZNz8",{"id":135,"title":136,"body":137,"date":109,"description":141,"extension":110,"meta":211,"navigation":114,"path":220,"seo":221,"stem":222,"tags":223,"__hash__":225},"blogs\u002F_legacy\u002F2020\u002F2020-04-11-try-ue4-vrm.md","UE4的VRM插件试用",{"type":8,"value":138,"toc":207},[139,142,153,156,164,167,170,173,176,181,184,187,190,193,196,199,204],[11,140,141],{},"最近试用了下UE4的VRM插件。",[11,143,144,145,152],{},"感觉上VRoid这边的社区不是很活跃的样子，最近blender用的VRM插件也因为某些原因[",[146,147,151],"a",{"href":148,"rel":149},"https:\u002F\u002Fgithub.com\u002FiCyP\u002FVRM_IMPORTER_for_Blender2_8\u002Fissues\u002F7#issuecomment-606706478",[150],"nofollow","停止更新","]了。",[11,154,155],{},"不过VRoid其实是基于Gltf的，在使用上还是大致不会有很大的问题。",[11,157,158,159,163],{},"插件地址：",[146,160,161],{"href":161,"rel":162},"https:\u002F\u002Fgithub.com\u002Fruyo\u002FVRM4U",[150],"。由于官方有提供很多效果截图，这边就不贴图了~",[11,165,166],{},"VRM4U的功能主要包括三个方面：MToon支持、动画辅助和移动开发用的骨骼缩减。",[11,168,169],{},"主要感兴趣的还是动画风格渲染的MToon的实现，VRM4U这边的目标是使用非侵入式的方式来达到类似于VRoid官方MToon的效果。",[30,171,172],{"id":172},"光照",[11,174,175],{},"作为基础的MToon材质是基于UnLit的，为了防止ToneMap和Exposure的影响，在材质中进行了相应的抵消操作。",[11,177,178],{},[40,179],{"alt":42,"src":180},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb.png",[11,182,183],{},"对于UnLit的材质，为了模拟阴影效果，使用一个SceneCapture来对周围的阴影进行捕捉，并通过RenderTarget重新应用在了模型上。",[11,185,186],{},"光照效果方面，则直接使用自定义Shader节点来获取光照的信息并重新赋予到材质上。SkyLight有GetSkySHDiffuseSimple, GetSkySHDiffuse；DirectionalLight则使用DirectionalLightColor并通过光源于发现的关系来决定阴影的构造。",[11,188,189],{},"天空光源和AO也同样采用了类似的方式进行处理，基础的材质节点非常的复杂，如果之后有需要相关效果的，可以详细的看看具体的实现方式。",[30,191,192],{"id":192},"描边",[11,194,195],{},"描边特效方面，由于要兼容前向渲染，所以VRM没有采用PostProcess的方式。而是使用了模型复制的方式。",[11,197,198],{},"通过复制一个模型，并将其反转，同时在材质上使用Normal和WorldOffset来进行了某种程度的扩展。",[11,200,201],{},[40,202],{"alt":42,"src":203},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-1.png",[11,205,206],{},"反转的模型与原始模型的同步使用的是SetMasterPoseComponent，保证其能与动画同步。",{"title":57,"searchDepth":103,"depth":104,"links":208},[209,210],{"id":172,"depth":103,"text":172},{"id":192,"depth":103,"text":192},{"layout":112,"status":113,"published":114,"author":212,"author_login":117,"author_email":118,"wordpress_id":213,"wordpress_url":214,"date_gmt":215,"excerpt":216},{"display_name":116,"login":117,"email":118,"url":57},2809,"\u002F?p=2809","2020-04-11 09:45:49 +0000",{"type":8,"value":217},[218],[11,219,141],{},"\u002F2020-04-11-try-ue4-vrm",{"title":136,"description":141},"_legacy\u002F2020\u002F2020-04-11-try-ue4-vrm",[130,224],"VRM","IeCASCWTQq2bPnX_mEG99EGT1_a_iJ0kRx39UGj_NTI",{"id":227,"title":228,"body":229,"date":304,"description":233,"extension":110,"meta":305,"navigation":114,"path":314,"seo":315,"stem":316,"tags":317,"__hash__":319},"blogs\u002F_legacy\u002F2020\u002F2020-03-31-note-tweakobjectptr-compile.md","记录TWeakObjectPtr的一个比较坑的编译报错",{"type":8,"value":230,"toc":300},[231,234,237,240,243,246,249,252,258,261,264,267,270,276,279,285,288,294,297],[11,232,233],{},"最近在使用TWeakObjectPtr时遇到了一个比较奇怪的编译报错，记得之前解决过，后面又忘记了，所以这次记录下来。",[11,235,236],{},"为了防止UObject的生命周期混乱问题，官方的建议是加上UProperty。但是有的时候考虑到引用关系的维护，我们不希望使用UProperty来维护指针。",[11,238,239],{},"而且，如果一个指针并不会给蓝图使用却加上了UProperty的话，会无端的感觉很“重”。",[11,241,242],{},"如果不希望控制指针的生命周期，而又希望维护引用的话，可以使用TWeakObjectPtr。如果希望自己保护生命周期的话，可以使用TSharedPtr。TSharedPtr记得如果使用不当的话会有二次释放的问题，不过现在记不清楚触发方式了。",[11,244,245],{},"这边还是回答奇怪的编译错误上吧。",[30,247,248],{"id":248},"编译错误",[11,250,251],{},"在对裸指针进行保护时，使用TWeakObjectPtr后出现了这样的报错：",[48,253,256],{"className":254,"code":255,"language":53},[51],"2>xxx\\engine\\source\\runtime\\core\\public\\UObject\u002FWeakObjectPtrTemplates.h(55): error C2338: TWeakObjectPtr can only be constructed with UObject types\n2>xxx\\engine\\source\\runtime\\core\\public\\UObject\u002FWeakObjectPtrTemplates.h(50): note: while compiling class template member function 'TWeakObjectPtr\u003CAAwesomeActor,FWeakObjectPtr>::TWeakObjectPtr(const T *)'\n2>        with\n2>        [\n2>            T=AAwesomeActor\n2>        ]\n2>Project\\(62): note: see reference to function template instantiation 'TWeakObjectPtr\u003CAAwesomeActor,FWeakObjectPtr>::TWeakObjectPtr(const T *)' being compiled\n2>        with\n2>        [\n2>            T=AAwesomeActor\n2>        ]\n2>Project\\(62): note: see reference to class template instantiation 'TWeakObjectPtr\u003CAAwesomeActor,FWeakObjectPtr>' being compiled\n",[55,257,255],{"__ignoreMap":57},[11,259,260],{},"编译器似乎不认识我们的AAwesomeActor，使用各种forward declaration反而让问题越来越复杂。",[11,262,263],{},"但是我们又不想破坏include隔离，还是少许有些尴尬。",[30,265,266],{"id":266},"原因",[11,268,269],{},"其实这个错误是因为一个“坏”习惯造成的，通常为了避免忘记写初始化的情况，我们会给指针赋个初始值，改造之后就变成了这样：",[48,271,274],{"className":272,"code":273,"language":53},[51],"TWeakObjectPtr\u003CAAwesomeActor> MyAwesomeActor = nullptr;\n",[55,275,273],{"__ignoreMap":57},[11,277,278],{},"这样的话就导致模板编译的时候进入了“错误”的分支，在WeakObjectPtrTemplates.h中报错的行上面其实可以看到解释：",[48,280,283],{"className":281,"code":282,"language":53},[51],"\u002F\u002F This static assert is in here rather than in the body of the class because we want\n\u002F\u002F to be able to define TWeakObjectPtr\u003CUUndefinedClass>.\nstatic_assert(TPointerIsConvertibleFromTo\u003CT, const volatile UObject>::Value, \"TWeakObjectPtr can only be constructed with UObject types\");\n",[55,284,282],{"__ignoreMap":57},[11,286,287],{},"结论上来说，只要不加初始化就可以了，让模板编译走默认的构造函数就不会进到这里。",[48,289,292],{"className":290,"code":291,"language":53},[51],"TWeakObjectPtr\u003CAAwesomeActor> MyAwesomeActor;\n",[55,293,291],{"__ignoreMap":57},[11,295,296],{},"也就是说，这里初始化就会造成画蛇添足的效果。",[11,298,299],{},"有时候比较着急的时候一下子想不起来就真的比较郁闷。",{"title":57,"searchDepth":103,"depth":104,"links":301},[302,303],{"id":248,"depth":103,"text":248},{"id":266,"depth":103,"text":266},"2020-03-31",{"layout":112,"status":113,"published":114,"author":306,"author_login":117,"author_email":118,"wordpress_id":307,"wordpress_url":308,"date_gmt":309,"excerpt":310},{"display_name":116,"login":117,"email":118,"url":57},2781,"\u002F?p=2781","2020-03-31 14:17:22 +0000",{"type":8,"value":311},[312],[11,313,233],{},"\u002F2020-03-31-note-tweakobjectptr-compile",{"title":228,"description":233},"_legacy\u002F2020\u002F2020-03-31-note-tweakobjectptr-compile",[130,318],"Smart Pointer","66eOSSicIZqmQ-fcthAADAoN-Z7TkL2xVYC867SVlGg",{"id":321,"title":322,"body":323,"date":304,"description":327,"extension":110,"meta":418,"navigation":114,"path":427,"seo":428,"stem":429,"tags":430,"__hash__":431},"blogs\u002F_legacy\u002F2020\u002F2020-03-31-site-transform-note.md","网站迁移小记",{"type":8,"value":324,"toc":413},[325,328,331,334,337,341,344,347,350,356,359,365,368,371,374,377,383,386,389,392,395,404,410],[11,326,327],{},"由于Linode的线路实在过慢，后来迁移到Hostker上了。",[11,329,330],{},"Hostker的主机还算是快吧，主要是Linode那边没有CN2之类的线路，导致国内访问大部分时候都比较慢。",[11,332,333],{},"Hostker的香港主机试用了下，确实延迟很低。但是访问网站还是很慢，不知道是带宽问题还是别的什么问题。最后换到了日本的线路。",[11,335,336],{},"迁移同时被wordpress提示php版本不够，顺便一起升级了，主要遇到三个问题。",[30,338,340],{"id":339},"ipv6配置","IPv6配置",[11,342,343],{},"Hostker这边Ipv6地址不是自动配置的，需要自己设置。",[11,345,346],{},"由于我对服务器配置一直是一知半解，着实苦战了一番。",[11,348,349],{},"这边是ubuntu18，需要替换\u002Fetc\u002Fnetplan\u002F01-netcfg.yaml",[48,351,354],{"className":352,"code":353,"language":53},[51],"network:\n    ethernets:\n        ens3:\n            addresses:\n                - 106.28.186.237\u002F25\n                - xxxx:5040:8:4::36cb:caf0\u002F48\n            gateway4: 106.28.186.129\n            gateway6: xxxx:5040:8::1\n            nameservers:\n                addresses: [8.8.8.8, 1.1.1.1]\n            dhcp4: false\n            dhcp6: false\n\n    version: 2\n    renderer: networkd\n",[55,355,353],{"__ignoreMap":57},[11,357,358],{},"在尝试错误的过程中",[48,360,363],{"className":361,"code":362,"language":53},[51],"netplan try\nnetplan --debug generate\n",[55,364,362],{"__ignoreMap":57},[11,366,367],{},"两个指令比较有用",[30,369,370],{"id":370},"数据库创建",[11,372,373],{},"以前都是用web控制的单php文件数据库管理的。",[11,375,376],{},"这次怎么都连不上，似乎是安全策略变了，于是直接通过mysql语句来创建了数据库：",[48,378,381],{"className":379,"code":380,"language":53},[51],"CREATE DATABASE mywpdb;\nCREATE USER 'xxuser'@'localhost' IDENTIFIED BY 'greatpassword';\nGRANT ALL ON mywpdb.* TO 'xxuser'@'localhost';\n",[55,382,380],{"__ignoreMap":57},[30,384,385],{"id":385},"数据库替换",[11,387,388],{},"PHP升级过后，之前一直在使用的代码格式化展示插件不能用了，后面换成了EnlighterJS。",[11,390,391],{},"但是这样一来所有的旧文章就都无法正确的显示了。因此必须对标签进行替换。",[11,393,394],{},"之前替换https链接只要简单的执行REPLACE 就可以了，这次由于比较复杂，稍微搜索了一番才找到方法。",[11,396,397,398,403],{},"虽然有看到没有正则替换之类的功能的描述，好在这边用的是MariaDb的最新版本，似乎有[",[146,399,402],{"href":400,"rel":401},"https:\u002F\u002Fmariadb.com\u002Fkb\u002Fen\u002Fregexp_replace\u002F",[150],"正则替换功能","]：",[48,405,408],{"className":406,"code":407,"language":53},[51],"UPDATE copy_wp_posts\nSET post_content=REGEXP_REPLACE(post_content,'(\u003Cpre[^>]*>)','\u003Cpre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">')\nWHERE post_content REGEXP '\u003Cpre\\s*.*>'\n",[55,409,407],{"__ignoreMap":57},[11,411,412],{},"这样的话就成功完成替换了。",{"title":57,"searchDepth":103,"depth":104,"links":414},[415,416,417],{"id":339,"depth":103,"text":340},{"id":370,"depth":103,"text":370},{"id":385,"depth":103,"text":385},{"layout":112,"status":113,"published":114,"author":419,"author_login":117,"author_email":118,"wordpress_id":420,"wordpress_url":421,"date_gmt":422,"excerpt":423},{"display_name":116,"login":117,"email":118,"url":57},2786,"\u002F?p=2786","2020-03-31 15:10:36 +0000",{"type":8,"value":424},[425],[11,426,327],{},"\u002F2020-03-31-site-transform-note",{"title":322,"description":327},"_legacy\u002F2020\u002F2020-03-31-site-transform-note",[],"DHJtgsPSz0vNPlescqxRdQqzwXrfJAVHPsJy69F02NY",{"id":433,"title":434,"body":435,"date":543,"description":439,"extension":110,"meta":544,"navigation":114,"path":553,"seo":554,"stem":555,"tags":556,"__hash__":558},"blogs\u002F_legacy\u002F2019\u002F2019-12-30-ue4-live2d-plugin-rewrite.md","重构Live2D插件",{"type":8,"value":436,"toc":535},[437,440,443,446,449,452,455,458,461,464,467,476,479,482,485,489,492,495,498,501,504,507,510,513,516,520,523,526],[11,438,439],{},"这个插件一直没有更新过，有些重要的功能也没有完成，最近好容易找到些时间。",[11,441,442],{},"由于和上一次的更新已经有了很长的时间，Live2D的SDK已经变化很大。",[11,444,445],{},"再加上打算换成新的渲染方式，所以干脆对整个插件进行了重写。",[30,447,448],{"id":448},"放弃的部分",[11,450,451],{},"首先，从目标上，放弃了编辑器支持。因为实质上Live2D的大部分编辑功能都是在官方的编辑器里面的，外部几乎没有什么可以控制的。而如果要做一些UE4这边的编辑器内控制的话，由于没有具体的需求，实在是很难把握分寸，反而会无端的投入时间。所以这一块就放弃了。",[11,453,454],{},"另外，也放弃了将所有的控制功能都接入到蓝图层面。这部分的工作其实就只是函数转发，但是也会无端的浪费劳动力，因为如何对Live2D的模型进行控制其实每个人在用的时候也会不一样吧。",[11,456,457],{},"因此，这边的主要工作就关注在Live2D模型的读取和渲染上了。",[30,459,460],{"id":460},"模型渲染",[11,462,463],{},"由于前次定的目标比较大，所以并没有在渲染上花太多的精力，直接使用了RenderTarget的Canvas绘制功能。",[11,465,466],{},"这样的缺点是，渲染的效率非常的低下。很难想象能够在移动平台上使用。",[11,468,469,470,475],{},"因此，这次使用了UE4后面提供的[",[146,471,474],{"href":472,"rel":473},"https:\u002F\u002Fdocs.unrealengine.com\u002Fen-US\u002FProgramming\u002FRendering\u002FShaderInPlugin\u002FQuickStart\u002Findex.html",[150],"Global Shader Plugin","]系统。",[11,477,478],{},"这个系统在官方的说明里面非常的简略，但是其实实际的作用还是非常强大的。",[11,480,481],{},"本次就是通过自定义的VertexShader和PixelShader来进行Live2D的模型绘制。由于之前对渲染管线的知识有很多一知半解的部分，这次算是遇到了不少坑，也学到了很多东西。",[11,483,484],{},"其中遇到的最大的两个坑先记录在这里，其他的部分感觉就只是在探索API的用法了……",[486,487,488],"h3",{"id":488},"参数传递",[11,490,491],{},"参数传递上有个不知道能不能算作是坑的地方，UE4这边VertexShader和PixelShader在例子里面是有作分别实现的。",[11,493,494],{},"但是实际如果要传递参数的话，参数必须在公共的基类里面声明，否则传递过去的值就会出现错乱。",[11,496,497],{},"尤其是在PixelShader这边的表现特别明显，会出现奇怪的结果。",[486,499,500],{"id":500},"绘制混乱",[11,502,503],{},"另一个问题就是RenderThread和GameThread的关系。",[11,505,506],{},"由于之前没有怎么接触过需要双边控制的逻辑，所以遇到了奇怪的问题。",[11,508,509],{},"最主要的问题是，GameThread在将任务发到RenderThread之后，并不会马上就执行。",[11,511,512],{},"在偶然的情况下，会出现GameThread进入第二次Update的时候，RenderThread里面的任务还没有执行完成。",[11,514,515],{},"这次遇到的就是，由于渲染还在进行中，GameThread却又进去更新了Live2D模型，导致渲染那边取到了错误的VertexBuffer，最后出现了奇怪的渲染结果。",[30,517,519],{"id":518},"todo","TODO",[11,521,522],{},"其实有些渲染的细节选项还没有接入，但是由于手头上没有用到对应功能的模型，就算做了也不知道效果是不是正确的。",[11,524,525],{},"这块也只能等到用到的时候再搞了，不过既然主体已经完成了的话，也不会有什么太大的工作量。",[11,527,528,529,534],{},"最后，插件的地址更新了，现在在[",[146,530,533],{"href":531,"rel":532},"https:\u002F\u002Fgithub.com\u002FArisego\u002FUnrealLive2D",[150],"这里","]。",{"title":57,"searchDepth":103,"depth":104,"links":536},[537,538,542],{"id":448,"depth":103,"text":448},{"id":460,"depth":103,"text":460,"children":539},[540,541],{"id":488,"depth":104,"text":488},{"id":500,"depth":104,"text":500},{"id":518,"depth":103,"text":519},"2019-12-30",{"layout":112,"status":113,"published":114,"author":545,"author_login":117,"author_email":118,"wordpress_id":546,"wordpress_url":547,"date_gmt":548,"excerpt":549},{"display_name":116,"login":117,"email":118,"url":57},2769,"\u002F?p=2769","2019-12-30 15:08:00 +0000",{"type":8,"value":550},[551],[11,552,439],{},"\u002F2019-12-30-ue4-live2d-plugin-rewrite",{"title":434,"description":439},"_legacy\u002F2019\u002F2019-12-30-ue4-live2d-plugin-rewrite",[130,557],"Live2D","kYMvcs0bNv3W-P72SsfYWsfTvFrQ6K1R_p_8aZx_feY",222,1788763182270]