UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - mesh
1）URP Shader FrameBuffer Fetch Mali Crash ​2）Unity模型Lightmap UV相关的疑问 3）动画上下半身融合问题 4）AnimatorControllerPlayable.PrepareFrame函数在什么情况下调用 这是第338篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：465082844 Shader Q：使用FrameBuffer Fetch华为Mali设备编译Shader直接闪退，但是如果把纹理采样去掉就正常了，有遇到过这种问题的嘛? Unity 2021.3.23 Custom SRP Device : HUAWEI P40 --- Mali-G76 设备本身支持GL_EXT_shader_framebuffer_fetch。 Shader "unlit/test" { Properties { _Color ("Main Color", Color) = (1,1,1,1) [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Pass { Name "ForwardLit" Tags {"LightMode" = "UniversalForward"} HLSLPROGRAM #define ENABLE_FRAMEBUFFER_FETCH 1 #pragma vertex Vertex #pragma fragment Fragment #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; float4 texcoord : TEXCOORD0; half4 color : TEXCOORD2; UNITY_VERTEX_OUTPUT_STEREO }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); CBUFFER_START(UnityPerMaterial) half4 _Color; CBUFFER_END Varyings Vertex(Attributes input) { Varyings output; UNITY_SETUP_INSTANCE_ID(input); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); output.positionCS = TransformObjectToHClip(input.positionOS.xyz); output.texcoord = input.texcoord.xyxy; output.color = input.color; return output; } half4 FragmentInner(Varyings input) { half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_LinearClamp, input.texcoord.xy).rgba; return color * input.color * _Color; } void Fragment(Varyings input, inout half4 output : CoLoR0) { half4 col = FragmentInner(input); output.rgb = lerp(output.rgb, col.rgb, col.a); output.a = col.a; } ENDHLSL } } } 2023/04/20 19:54:29.555 13797 13846 Debug Unity GL_EXT_debug_marker GL_ARM_rgba8 GL_ARM_mali_shader_binary GL_OES_depth24 GL_OES_depth_texture GL_OES_depth_texture_cube_map GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_EXT_read_format_bgra GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture GL_OES_standard_derivatives GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_EGL_image_external_essl3 GL_OES_EGL_sync GL_OES_texture_npot GL_OES_vertex_half_float GL_OES_required_internalformat GL_OES_vertex_array_object GL_OES_mapbuffer GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg GL_EXT_texture_type_2_10_10_10_REV GL_OES_fbo_render_mipmap GL_OES_element_index_uint GL_EXT_shadow_samplers GL_OES_texture_compression_astc GL_KHR_texture_compression_astc_ldr GL_KHR_texture_compression_astc_hdr GL_KHR_texture_compression_astc_sliced_3d GL_EXT_texture_compression_astc_decode_mode GL_EXT_texture_compression_astc_decode_mode_rgb9e5 GL_KHR_debug GL_EXT_occlusion_query_boolean GL_EXT_disjoint_timer_query GL_EXT_blend_minmax GL_EXT_discard_framebuffer 2023/04/20 19:54:29.555 13797 13846 Debug Unity GL_OES_get_program_binary GL_OES_texture_3D GL_EXT_texture_storage GL_EXT_multisampled_render_to_texture GL_EXT_multisampled_render_to_texture2 GL_OES_surfaceless_context GL_OES_texture_stencil8 GL_EXT_shader_pixel_local_storage GL_ARM_shader_framebuffer_fetch GL_ARM_shader_framebuffer_fetch_depth_stencil GL_ARM_mali_program_binary GL_EXT_sRGB GL_EXT_sRGB_write_control GL_EXT_texture_sRGB_decode GL_EXT_texture_sRGB_R8 GL_EXT_texture_sRGB_RG8 GL_KHR_blend_equation_advanced GL_KHR_blend_equation_advanced_coherent GL_OES_texture_storage_multisample_2d_array GL_OES_shader_image_atomic GL_EXT_robustness GL_EXT_draw_buffers_indexed GL_OES_draw_buffers_indexed GL_EXT_texture_border_clamp GL_OES_texture_border_clamp GL_EXT_texture_cube_map_array GL_OES_texture_cube_map_array GL_OES_sample_variables GL_OES_sample_shading GL_OES_shader_multisample_interpolation GL_EXT_shader_io_blocks GL_OES_shader_io_blocks GL_EXT_tessellation_shader GL_OES_tessellation_shader GL_EXT_primitive_bounding_box GL_OES_primitive_bounding_ 2023/04/20 19:54:29.555 13797 13846 Debug Unity box GL_EXT_geometry_shader GL_OES_geometry_shader GL_ANDROID_extension_pack_es31a GL_EXT_gpu_shader5 GL_OES_gpu_shader5 GL_EXT_texture_buffer GL_OES_texture_buffer GL_EXT_copy_image GL_OES_copy_image GL_EXT_shader_non_constant_global_initializers GL_EXT_color_buffer_half_float GL_EXT_unpack_subimage GL_EXT_color_buffer_float GL_EXT_YUV_target GL_OVR_multiview GL_OVR_multiview2 GL_OVR_multiview_multisampled_render_to_texture GL_KHR_robustness GL_KHR_robust_buffer_access_behavior GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_EXT_protected_textures GL_EXT_buffer_storage GL_EXT_external_buffer GL_EXT_EGL_image_array GL_EXT_texture_filter_anisotropic GL_ARM_texture_unnormalized_coordinates GL_HUAWEI_smart_cache GL_EXT_shader_framebuffer_fetch GL_HUAWEI_framebuffer_partial_update A1：Mali-G76就是不支持Fetch的，可以看一下下面这个博客：https://www.cnblogs.com/minggoddess/p/11471369.html 感谢QyoJWUqeW5Yj@UWA问答社区提供了回答 A2：ARM Mali的芯片不支持GL_EXT_shader_framebuffer_fetch。其实它支持的是ARM_framebuffer_fetch。所以你得为ARM设备单独写代码。 感谢陆旭佳@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6441fea4c165ab39387f39cb Mesh Q：当前在测试不同画质条件下，切换渲染模型和材质的功能。发现在模型切换后，会出现光照贴图UV对不齐的问题。Unity中的模型Lightmap UV具体是怎么保存在模型数据中的？ 现在发现在开启Generate Lightmap UVs时，模型会额外增加一些顶点。暂不确定是否仅有多UV的模型（UV1被占用）会出现这种情况。看其他数据的保存方式和官方文档不匹配，不知道是为什么？ 针对以上问题，有经验的朋友欢迎转至社区交流分享： https://answer.uwa4d.com/question/647056170cb71c66f8a213ac Animation Q：动画上下半身的融合如果在接缝处有旋转或者位移，动画会表现怪异的抖动，表现不正确。 在网上找的这两个动作试的，用的Humanoid，也试过其他的，也有问题。 第一张图的上半身带有旋转，第二张图的下半身不带有旋转，上半身遮罩的两只腿，脚下圆盘和两只脚的IK。 以前在项目中的处理是混合动画让动作修改在腰间不带旋转和位移（位移带不带忘记了），原先项目动画用的Generic。 有以下几点疑问，期待大家的解答。 1. 是不是Unity的多层混合本身就最好避免在混合的接缝处（就像上下半身这种的腰间）“打架”，要不就上下都不动，或者上下半身本就在旋转或位移是一致的。 2. 对动画的制作不太懂，如果上一条成立的话，在动画制作是否可以把一些关键的位移旋转之类的从接缝处抽离到更下一层，保证接缝处的一致，不知道这样可行不可行。 3. Generic是不是更好一点，在遮罩上可以具体选择遮罩的骨骼。 4. Generic是不是没法把动画里的XZ位移去掉（Humanoid可以通过不勾选Bake In Pose去除）。 PS：如果勾上Loop Pose，一些循环动画反而可以去掉，这是没问题的吗？ 5. Humanoid遮罩下面的IK和脚下圆盘是干什么用的。 针对以上问题，有经验的朋友欢迎转至社区交流分享： https://answer.uwa4d.com/question/646e3551279f4105e1cb5433 Animation Q：在进行项目测试的时候，出现Animator的Update次数都还比较少基本稳定在2个左右， 但出现AnimatorControllerPlayable.PrepareFrame最高有出现达80多次，请问AnimatorControllerPlayable.PrepareFrame这个函数是在什么情况下调用？ A：AnimatorControllerPlayable.PrepareFrame函数是在AnimatorControllerPlayable对象即将开始播放新的一帧时调用的。具体来说，它是在Unity的Animator组件中的Update过程中被调用的，而Animator组件在每一帧都会被更新。 在每一帧更新Animator组件时，Animator会检查当前播放的动画是否有变化，如果有，则会更新AnimatorControllerPlayable对象，以便该对象能够播放新的动画。在更新AnimatorControllerPlayable对象之前，Unity会调用AnimatorControllerPlayable.PrepareFrame函数，以便在播放新的动画时为该对象做好准备工作，例如重置其内部状态等。 感谢NG週@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/63886cc80a02a86d7466f9a9 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：465082844
1）Addressable卸载AssetBundle失效的疑惑 ​2）模型Meta中的hasExtraRoot参数的作用及其历史原因 3）TMP为什么有多次Delegate.Combine()的GC 4）准备在海外发行游戏，比较常用的身份认证类SDK 这是第331篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：465082844 Addressable Q：项目开发的版本是UnityWebGL，我有一个初包场景A，一个过渡场景Loading，两个用Addressables打的Bundle场景B与C，都是Remote的。 我在A场景点击按钮，使用SceneManager.LoadScene到Loading，然后用Addressables.LoadSceneAsync加载Bundle场景B，并保存了B场景的句柄Handle。 进入到B场景后，点击按钮跳转到Loading场景，然后拿到保存的B场景句柄Handle，使用Addressables.UnloadSceneAsync与Resources.UnloadUnusedAssets，然后Addressables.LoadSceneAsync加载Bundle场景C，并把原B场景的句柄Handle赋值C场景的句柄。 进入到C场景后，又切换B场景，在Google浏览器F12模式下，第二次加载B场景，网络模块下没显示场景B的Bundle。 给我的感觉是B场景的Bundle在跳转C场景的时候没有被卸载，希望能帮助解答一下这是什么原因。 我查看了源码，Addressables.UnloadSceneAsync的底层确实是调用了AssetBundle.Unload(true);的。 A：Addressable加载过一次的AssetBundle，会下载到本地硬盘缓存，Addressable.UnloadSceneAsync和Resources.UnloadUnusedAssets这两个操作只是会让相关资源从Runtime内存中卸载，不会删除本地硬盘的内容。 第二次加载是不用从远程服务器加载的，而是通过本地硬盘加载，所以不存在网络下载了。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/642406386778221ae3095c5e Mesh Q：模型Meta中的hasExtraRoot参数的作用是什么？有什么历史原因么？ 模型Meta中有个奇怪的参数hasExtraRoot，看起来是humanDescription旗下的，但实际会对任何类型的模型都造成影响。 这个参数无论是在API还是编辑器界面，都找不到调整的地方。当前项目中一部分是0，一部分是1。观察得出，这个在为1的情况下，会保留外层无Mesh关联的Transform；为0的情况下会移除顶层Child不超过1，并且无Mesh的Transform。 不知道这个是怎么生成的，是不是为了适配某些历史版本呢？ 针对以上问题，有经验的朋友欢迎转至社区交流分享： https://answer.uwa4d.com/question/64252d9f6778221ae30a0fd9 TextMeshPro Q：TMP为什么有多次Delegate.Combine()的GC？ 只有一个界面对一个TMP的OnPreRenderText进行了一次+=的操作，为什么在其他界面也发现有Delegate.Combine()的GC，且每次大小不同？ 下图是唯一操作过的界面： 下图是其他界面，有2MB的，还有些几十KB的： 针对以上问题，有经验的朋友欢迎转至社区交流分享： https://answer.uwa4d.com/question/6423ff146778221ae30955ca Platform Q：准备在海外发行游戏，请问身份认证类SDK有哪些比较常用？最好是能直接支持Unity接入的，谢谢。 A：Google、Facebook或Game Center应该都可以。 感谢WTF-082699@UWA问答社区提供了回答，有经验的朋友欢迎转至社区交流分享： https://answer.uwa4d.com/question/6423accf6778221ae3091670 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：465082844
1）Vulkan API的性能及兼容性 ​2）FrameTiming.gpuFrameTime获取GPU耗时有什么条件 3）MMO里面的寻路网格如何制作 4）万国这种联盟边界的实现思路 这是第314篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：请问一下目前Android设备用Vulkan API的项目多吗，性能、兼容性及稳定性如何？ A：Vulkan用的比较好的应该是《天涯明月刀》手游，《王者荣耀》在三星平台上也开发过一段时间的Vulkan的版本，但是之后好像就更新打回ES版本了。 感谢萧小俊@UWA问答社区提供了回答 有没有更多的开发者能提供一些Vulkan稳定性相关的经验分享呢？欢迎大家转至社区交流： https://answer.uwa4d.com/question/6114de488f8c834241698b95 Rendering Q：手机为什么上获取不到FrameTiming.gpuFrameTime的时间？ 版本Unity 2020.3，手机华为 Mate 30P，CPU时间可以显示但GPU时间获取不到，测试代码如下： using System; using UnityEngine; using Unity.Profiling; public class FrameTimingsHUDDisplay : MonoBehaviour { GUIStyle m_Style; // Profiling state. private int frameCount = 0; private float accumulatedFrameTimeCPU = 0.0f; private float accumulatedFrameTimeGPU = 0.0f; private float frameSampleRateMS = 0.0f; private FrameTiming[] m_FrameTimings = new FrameTiming[1]; void Awake() { m_Style = new GUIStyle(); m_Style.fontSize = 15; m_Style.normal.textColor = Color.white; } void OnGUI() { CaptureTimings(); var reportMsg = $"\nCPU: {m_FrameTimings[0].cpuFrameTime:00.00}" + $"\nMain Thread: {m_FrameTimings[0].cpuFrameTime:00.00}" + $"\ncpuTimeFrameComplete: {m_FrameTimings[0].cpuTimeFrameComplete:00.00}" + $"\ncpuTimePresentCalled: {m_FrameTimings[0].cpuTimePresentCalled:00.00}" + $"\nGPU: {m_FrameTimings[0].gpuFrameTime:00.00}" + $"\nCPUTotal: {accumulatedFrameTimeCPU:00.00}" + $"\nCPUAverage: {(accumulatedFrameTimeCPU / frameCount):00.00}" + $"\nGPUTotal: {accumulatedFrameTimeGPU:00.00}" + $"\nGPUAverage: {(accumulatedFrameTimeGPU / frameCount):00.00}"; var oldColor = GUI.color; GUI.color = new Color(1, 1, 1, 1); float w = 300, h = 210; GUILayout.BeginArea(new Rect(32, 100, w, h), "Frame Stats", GUI.skin.window); GUILayout.Label(reportMsg, m_Style); GUILayout.EndArea(); GUI.color = oldColor; } private void CaptureTimings() { FrameTimingManager.CaptureFrameTimings(); uint frameTimingsCount = FrameTimingManager.GetLatestTimings(1, m_FrameTimings); if (frameTimingsCount != 0) { accumulatedFrameTimeCPU += (float)m_FrameTimings[0].cpuFrameTime; accumulatedFrameTimeGPU += (float)m_FrameTimings[0].gpuFrameTime; } else { accumulatedFrameTimeCPU += Time.unscaledDeltaTime * 1000.0f; // No GPU time to query. } ++frameCount; } } 以上问题有哪位大拿也曾经做过类似的测试，可以来分享一下，欢迎转至社区交流： https://answer.uwa4d.com/question/63440ef8dec0b5016d28fcf1 Mesh Q：MMO里面的寻路网格如何制作呢？ 自动生成的不精准。美术制作是拼起来的，某些点忘记连或者重叠会有问题。一般是怎么解决的呢？现在就是美术单独做的Mesh，但是他们的Mesh如果由两个面点没连着，或者某个三角形的2个点，重叠了，就会有问题。 A：简单说下思路，把地形当做一个2D平面，代码创建一个x*y大小的Mesh，格子大小保持Unity的单位1就行，这个Mesh了你控制好覆盖这个2D平面，然后从Mesh的每个顶点坐标向下发射射线，检测碰撞，计算当前顶点的高度。算好后，就修改Mesh的各个顶点高度。这样，Mesh成型后就是个大概的地面建筑模型了。 然后编个简单Shader，只显示顶点色和边沿。这方面网上有很多Shader。Mesh就用这个Shader，即可显示网格样子。实在不行，你Scene里面的网格模式。 接着就是写代码，根据鼠标点击滑动坐标，把对应的Mesh网格涂色即可，比如涂绿色为路面。红色为障碍。最后计算这些格子的x，y坐标。用A*算法寻路即可。 感谢牛头人不服@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/62b061119b935a01f583e514 Rendering Q：请教一下万国这种联盟边界的实现思路。 是多个Quad交叉重叠后，想办法把他们交叉的区域不绘制？还是直接自己Draw一个这样的多边形的Mesh？ A1：万国这个应该用的地图16位状态拼接的方法。可以借鉴暴雪的地图地表的生成方式。 感谢1 9 7 3-311135@UWA问答社区提供了回答 A2：万国是Draw Mesh。在不同的LOD下，精细度不同。我之前抓过Mesh看过。 感谢承影@UWA问答社区提供了回答 是否还有更多的实现方法呢？欢迎大家转至社区交流： https://answer.uwa4d.com/question/6264eb5836a2fd066de67469 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）Timeline技能编辑器如何提取关键帧信息 ​2）Mesh资源开启R/W内存的翻倍问题 3）UI的Animator动画对Canvas.SendWillRenderCanvas()耗时影响 4）对Shader变体的理解 这是第281篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Script Q：最近在用Unity做一个技能编辑器，让策划可以在Timeline编辑器里编辑技能动画、施法特效、受击动画、受击特效和播放声音等等。编辑这些都没什么问题，我想根据编辑好的Timeline导出一系列行为节点，在Editor的环境下导出成配置（包含所有行为节点）。现在我不清楚怎么获取Timeline里第m帧开始某动画x，第n帧结束这些行为。同时怎么能给动画打上对应Tag？ A：Timeline的组成如下图： 每个Track对应一个TrackAsset其实就是一个PlayableAsset，整个Timeline也是一个TimelineAsset，里面存了所有Track的数据： 对于每个Clip的开始和结束时间在TimelineClip中可以获取： 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c00a17d8413e18eb546b3b Mesh Q：为什么Profiler中显示开启网格的Read/Write没有使其内存翻倍，而仅仅是上升了一点？ A：如果是FBX中的网格资源，则该网格资源很有可能除了自身网格的顶点数据外，还带有动画和骨骼信息。而当开启FBX的Read/Write时，仅会使网格资源自身的内存翻倍，而动画和骨骼信息不会，且Profiler并不能将他们分开统计，从而产生了整体没有翻倍的现象，如图。 开启Read/Write： 没开Read/Write： 这一点可以通过将FBX设置中Animation Type设置为None、取消Import Animation勾选、取消Import Materials勾选证明。此时Profiler中该网格资源内存显著下降，并基本符合开启Read/Write前后内存翻倍了。 值得一提的是，在较新版本的Unity中（图截自2020.3），选中网格资源是可以看到其内存的理论大小和具体组成部分的。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c05c15d8413e18eb54ec09 UGUI Q：对UGUI添加Animator动画会造成UI更新耗时吗？如果Animator组件处于Active状态但没有进行Loop会有持续耗时吗？ A：在Unity 2018.4版本中对于这个问题实验如下： 步骤1，场景中为一个UI添加修改Scale的动画，并复制1000个； 步骤2，添加了其Position和Color属性的动画； 步骤3，修改了所在Canvas的Render Mode为Overlay模式和Camera模式； 步骤4，修改了所在Canvas的Pixel Perfect为打开。 只进行1的情况下无论动画是否Loop，Canvas.SendWillRenderCanvas()都只会在刚开始时有一段高耗时。 而修改2之后的所有情况下，开启Loop时Canvas.SendWillRenderCanvas()始终具有高耗时。但关闭Loop后，播放完一次动画后Canvas.SendWillRenderCanvas()的耗时就始终接近于0了，如图： 而此时动画系统本身的Animator.Update()耗时仍然很高，如图： 总的来说，从2018.4上的这次实验来看，现版本的UGUI不会被不在播放状态的Animator影响而造成Canvas.SendWillRenderCanvas()耗时。 感谢Faust@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c05b07d8413e18eb54ea3e Shader Q：除了不建议使用Standard Shader外，还有其他的Unity默认的Shader不推荐使用吗？比如官方的Universal Render Pipeline/Particles/Unlit有6个变体，其中也用到了雾效的计算。但是我们项目中没有用到雾效，而且这个Shader用得非常多，这类情况是否有必要自己弄一个简洁版？ A：关于Shader变体数量主要影响的是Shader的内存（虽然变体数量和内存并不是完全的线性关系），如下PPS中的Uber变体数为3.74K很高： 主要是因为有很多multi_compile的Keywords，建议手动删除一些不用的（或使用shader_feature代替），可以大大降低变体数量： 以上示例就是简单粗暴的优化对内存影响较大的Shader，当然最好是通过SVC对项目中所有Shader的所有变体进行管理，但工作量相对会大很多。 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/61c672c1d8413e18eb5db606 封面图来源于 Animation Rigging: Advanced Setups 展示了Unity动画索具包的高级设置和用例。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）Lua代码内存泄漏的疑惑 ​2）一个Lua重复加载后造成的泄露问题 3）关于Addressable中计数的疑惑 4）合并Mesh的插件推荐 这是第265篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Script Q：Lua Profiler提示调用红框中函数，每次都会增加大概0.23KB的内存，且随时间一直增加，请教一下，这个是因为闭包函数中用了局部变量i，局部table a导致的吗？ A：是局部变量table a的内存在增加。至于是否泄露：因为局部变量返回的时候被引用了，如果卸载合理，每次GC是会清除的，不会造成泄露。对代码的建议是：如果数据是相同的，建议做一个池子复用，不要每次生成。这样会造成堆内存升高导致触发GC，造成大量耗时。另外，闭包的创建也会增加内存。 感谢小ben@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/612355068f8c8342417da8a0 Script Q：现在的被动技是Lua写的，所以每场战斗会Reload，但是又没有调用package.loaded[‘xxx’] = nil来强制释放，目前用的Tolua，不知道会不会自动释放？ A：package.loaded其实是Lua内部维护的一个Table。Require的时候都会优先从package.loaded中查询一下，如果查询成功，会直接返回package.loaded[“Name”]， 如果需要释放，必须手动package.loaded[‘xxx’] = nil。 感谢萧小俊@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6128c02c8f8c8342418590d6 Addressable Q：试用一下Addressables，看了看调试窗口，发现Instantiation Counts这里的值只要调用过一次Addressables.InstantiateAsync后就一直为1，根据文档的描述是这里是每帧Addressables.InstantiateAsync的调用次数，难道真的是每帧都在调用？还是说是Unity的显示有错？ Addressables版本1.16.19，Unity版本2020.3.14f1，测试代码如下： public AssetReference Prefab; List<GameObject> ActiveObjs; private void Update() { if (Input.GetMouseButtonDown(0)) { if (Camera.main.GetMouseInputCastPoint(Input.mousePosition, out var point)) {//这里是自己扩展的 Prefab.InstantiateAsync(point,Quaternion.identity).Completed += (op)=> { ActiveObjs.Add(op.Result); }; } } } void ReleaseInstance() { foreach (var item in ActiveObjs){ Prefab.AssetReference.ReleaseInstance(item); } ActiveObjs.Clear(); } A：这个并不是每帧调用的数据，这个面板里面统计的是“驻留”的Operation以及被引用的次数，可以帮助查看哪些该被卸载的Bundle但是却还在内存中。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/612363588f8c8342417dc28c Mesh Q：在没有美术的前提下，Unity程序想要合并Mesh，求推荐好用的插件，我知道的有MeshBaker，有没有其它的推荐？ A1：推荐使用SimpleLod，不过这个我们没用只是测试过，主要还是MeshBaker。 感谢萧小俊@UWA问答社区提供了回答 A2：首先，StaticBatchingUtility.Combine这个方法归根结底是一个手动静态批处理的操作，它的直观作用是通过静态批处理降低DrawCall。虽然它可以合成Mesh，但是这个Mesh我确实拿不到（如果谁可以拿到请不吝赐教）。 然后回归该问题，其实需求只是一个功能性而并非优化性的问题，只要把几个Mesh拼接进一个物体就可以了，那么CombineInstance就和这个需求很对口。 代码如下： 该方法的好处就是可以把需要合并的物体的材质信息也包含进来，效果如下： 合并完了之后可以通过AssetDatabase.CreateAsset保存该Mesh： 感谢翟孟飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6125ba858f8c834241813013 20210830 更多精彩问题等你回答～ 1.Vulkan API的性能及兼容性 2.Unity TMP字体方案如何选择 3.如何实现AAB包的增量更新 封面图来源于：Miku-LuaProfiler xLua的性能分析器组件，同时支持XLua、SLua、ToLua。直观地显示Lua的GC和耗时、调用次数等。 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
这是第249篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） 本期目录： Unity内置资源如何打包避免冗余 SpriteAtlas的“冗余”问题 关于Mesh占用内存的问题 UGUI.Rendering.UpdateBatches耗时较高 Plugins的DLL是如何影响Package的 AssetBundle Q：现在打包AssetBundle，Unity中内置资源能指定AssetBundle名称吗？内置资源在AssetBundle中的冗余一般是怎么解决的？ 查了现有资料，针对内置资源打包AssetBundle冗余的处理都是将内置资源提取或者下载到本地，然后修改资源的引用关系，这样打包就可以指定内置资源的AssetBundle名称。想了解下Unity现在支持脚本打包AssetBunle时指定内置资源的Bundle名称吗？从而防止多个资源依赖同一个内置资源，导致冗余吗？ A：可以使用Scriptable Build Pipeline来实现题主想要的功能，具体的方法可以参考Addressable中打包内置Shader的思路。 public static IList<IBuildTask> AssetBundleBuiltInResourcesExtraction() { var buildTasks = new List<IBuildTask>(); // Setup buildTasks.Add(new SwitchToBuildPlatform()); buildTasks.Add(new RebuildSpriteAtlasCache()); // Player Scripts buildTasks.Add(new BuildPlayerScripts()); buildTasks.Add(new PostScriptsCallback()); // Dependency buildTasks.Add(new CalculateSceneDependencyData()); #if UNITY_2019_3_OR_NEWER buildTasks.Add(new CalculateCustomDependencyData()); #endif buildTasks.Add(new CalculateAssetDependencyData()); buildTasks.Add(new StripUnusedSpriteSources()); buildTasks.Add(new CreateBuiltInResourcesBundle("UnityBuiltInResources")); //将CreateBuiltInShadersBundle改成自己创建的类 buildTasks.Add(new PostDependencyCallback()); // Packing buildTasks.Add(new GenerateBundlePacking()); buildTasks.Add(new UpdateBundleObjectLayout()); buildTasks.Add(new GenerateBundleCommands()); buildTasks.Add(new GenerateSubAssetPathMaps()); buildTasks.Add(new GenerateBundleMaps()); buildTasks.Add(new PostPackingCallback()); // Writing buildTasks.Add(new WriteSerializedFiles()); buildTasks.Add(new ArchiveAndCompressBundles()); buildTasks.Add(new AppendBundleHash()); buildTasks.Add(new PostWritingCallback()); // Generate manifest files // TODO: IMPL manifest generation return buildTasks; } [MenuItem("AssetBundles/GenerateAB")] public static void GenerateAB() { var outputPath = "Assets/AssetBundles"; if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath); BuildTarget targetPlatform = BuildTarget.StandaloneWindows; var group = BuildPipeline.GetBuildTargetGroup(targetPlatform); var parameters = new BundleBuildParameters(targetPlatform, group, outputPath); var buildInput = ContentBuildInterface.GenerateAssetBundleBuilds(); IBundleBuildContent content = new BundleBuildContent(buildInput); var taskList = AssetBundleBuiltInResourcesExtraction(); //创建自己的task ReturnCode exitCode = ContentPipeline.BuildAssetBundles(parameters, content, out result, taskList); if (exitCode < ReturnCode.Success) return; var manifest = ScriptableObject.CreateInstance<CompatibilityAssetBundleManifest>(); manifest.SetResults(result.BundleInfos); File.WriteAllText(parameters.GetOutputFilePathForIdentifier(Path.GetFileName(outputPath) + ".manifest"), manifest.ToString()); } 下面的代码是CreateBuiltInResourcesBundle.cs里面的，从CreateBuiltInShadersBundle.cs里面复制一个类，并修改一点点代码： public ReturnCode Run() { HashSet<ObjectIdentifier> buildInObjects = new HashSet<ObjectIdentifier>(); foreach (AssetLoadInfo dependencyInfo in m_DependencyData.AssetInfo.Values) buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid)); foreach (SceneDependencyInfo dependencyInfo in m_DependencyData.SceneInfo.Values) buildInObjects.UnionWith(dependencyInfo.referencedObjects.Where(x => x.guid == k_BuiltInGuid)); ObjectIdentifier[] usedSet = buildInObjects.ToArray(); Type[] usedTypes = ContentBuildInterface.GetTypeForObjects(usedSet); if (m_Layout == null) m_Layout = new BundleExplictObjectLayout(); //Type shader = typeof(Shader); //for (int i = 0; i < usedTypes.Length; i++) //{ // if (usedTypes[i] != shader) // continue; // m_Layout.ExplicitObjectLocation.Add(usedSet[i], ShaderBundleName); //} //上面是打包内置Shader的操作，改成全部资源就可以了 foreach (ObjectIdentifier identifier in usedSet) { m_Layout.ExplicitObjectLocation.Add(identifier, ShaderBundleName); } if (m_Layout.ExplicitObjectLocation.Count == 0) m_Layout = null; return ReturnCode.Success; } 测试如下： 将Unity默认的Effect做成prefab并打包成AssetBundle（包名为ps），可以看到特效用到的内置资源都在这个AssetBundle中： 使用SBP打包后，如下： 可以看到ps的AssetBundle中已经没有资源，生成的内置的AssetBundle中有ps用到的3个内置的资源，并且ps依赖UnityBuiltInResources这个AssetBundle： 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/604b26cacfa35d5b53669a6c AssetBundle Q：我之前对SpriteAtlas的理解是，UI在渲染的时候会根据关联的Sprite信息找到对应的SpriteAtlas，这样每个UI渲染都用了同样的Atlas，这样可以减少Draw Call。所以理论来看，应该只需要Atlas这一张图就好了。可为什么我看到打Atlas的AssetBundle包里除了Atlas图还有引用的原图。在UI的AssetBundle包里，也有引用的原图。 请问为什么有这样的“冗余”，以及到底Image引用的是图集还是图片，原理是什么？ 如果Atlas包里放进原图则Atlas包里有原图但是UI包里没有，如果原图放到AssetBundle Package外面，则Atlas包和UI包里都有原图，这是什么原理？ A1：建议你先看一下这篇文章： 【Unity游戏开发】SpriteAtlas与AssetBundle最佳食用方案 在合理使用SpriteAtlas的情况下，当我们把AssetBundle包解开以后，会发现里面会包含一张Texture和若干个Sprite这两种资产。Texture是纹理，显示的文件大小较大；而Sprite可以理解为一个描述了精灵在整张纹理上的偏移量位置信息的数据文件，显示的文件大小较小。 因此这个不是冗余，是正常现象。 感谢马三小伙儿@UWA问答社区提供了回答 A2：不过确实存在一个冗余的问题：如果Prefab1和Prefab2引用了同一个Atlas的Sprite，那么这个Atlas至少要主动包含在一个AssetBundle中，否则会被动打入两个包中，造成冗余。 Atlas没有设置AssetBundle包： Atlas打到其中一个AssetBundle包中： 感谢Prin@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/608949fa6bb31032f9791494 Mesh Q：关于Mesh占用内存的问题，在Unity 2020中能看见Mesh中包含哪些信息： 导入骨骼 这里如果不导入骨骼，顶点信息占用空间会减少一倍，请问导入骨骼后顶点信息增加了什么？ 未导入骨骼 第二个问题是：在导入骨骼的情况下内存中这个Mesh占用了0.6MB刚好差不多是上面Inspector中显示的两倍，做过其它模型的测试也是两倍： 本来以为是开启Read/Write Enabled的问题，但是发现开不开启这个选项占用内存都不变。这个内存占用是怎么来的，开启Read/Write Enabled是否具体会造成两倍的内存开销？ 经过测试发现，在不导入骨骼的情况下，开启Read/Write Enabled是不开启占用内存的两倍，不开启Read/Write Enabled占用内存和Inspector相同。在导入骨骼的情况下开不开启Read/Write Enabled都是Inspector界面显示内存的两倍。推测是导入了骨骼之后，默认会修改模型顶点，相当于默认开启了Read/Write Enabled。 A：第一个问题： Inspector面板的Vertices一栏指的是Mesh的顶点属性（或通道），如果该Mesh包含某个通道的数据，就意味着每个顶点都有一份该顶点属性。 而Unity定义的顶点通道一共有14个： 如果导入了骨骼，多出来的顶点属性是顶点的骨骼权重和骨骼索引。 可通过Mesh.boneWeights()和Mesh.GetBonesPerVertex()访问。 然而，一个BoneWeight属性存了4个float32和4个int32，一共8x4=32Bytes。一个Bones index是一个Byte。 (8x4+1)Byte/Vert x 5512Vert = 177.63KB 第二个问题：导入骨骼动画，要在CPU端做蒙皮计算，就是要在CPU获取顶点属性。 感谢Prin@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6088d9dc6bb31032f979147e UGUI Q：我的测评报告中UGUI.Rendering.UpdateBatches占用较高，想问问是什么原因导致的呢？ A1：场景中Transform发生改变的UI元素太多了。看起来，场景中发生改变的Canvas有3个，而这3个Canvas下Transform发生改变的元素有312个。 感谢Prin@UWA问答社区提供了回答 A2：当Canvas中的UI元素触发CanvasRenderer.SyncTransform次数较多（几百次的量级）的时候，父节点UGUI.Rendering.UpdateBatches的耗时也会比较高。 测试后发现，在Unity 2018、2019和2020的版本中，调用SetActive(true)将UI元素从Deactive的状态变成Active状态，会导致UI元素所在的Canvas中的所有UI元素都触发CanvasRenderer.SyncTransform。在Unity 2017的版本中这样的操作只会影响这个SetActive(true)的元素本身，不知道是Unity的Bug还是本身就是这么设计的。不过在Unity 2018、2019和2020的版本中，可以使用设置Scale为0或者1的方法来隐藏显示UI，这样就只有Scanle变化的那个UI元素本身触发CanvasRenderer.SyncTransform了。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6092b91d6bb31032f97914e8 Script Q：如下图所示，工程一打开就会报Timeline的异常，后面发现是跟Plugins下某个DLL有关，删除该DLL Timeline就能正常。那么为什么DLL会影响Timeline。Timeline这几个重载函数肯定都在UnityEditor.CoreModule里面，怎么会找不到？ A：Unity的脚本有个严格的编译顺序： precompiled DLL -> asmdefs -> StandardAsset -> Plugins -> Plugins/Editor -> Assets -> Editor。 你的预编译DLL里面很有可能写了一个同名的PlayableBehaviour类，然后里面实现了同样的方法。 这个DLL优先于Packages下的Timeline被Unity加载编译，然后等到Timeline编译的时候，会发现有两个PlayableBehaviour，因此它就会找不到合适的方法进行重写。 按照上面这个思路，我在本地也复现了你的这种错误。 把这个DLL（ConsoleApp1.dll）扔到Plugins目录下就能看到同样的报错（DLL可戳原问答获取）。 感谢马三小伙儿@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/60962dfb6bb31032f979156a 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）下载AssetBundle的Mono内存问题 ​2）Unity 2019运行时获取Hierarchy上预制体资源路径 3）多个Submeshes模型合并后的显示问题 4）ToLua中访问Time.deltaTime为0 5）CacheServer莫名的断开连接 这是第242篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Memory Q：使用协程+UnityWebRequest下载Bundle时分配的内存释放不掉。 下载步骤如下： 1. 使用协程+UnityWebRequest下载Bundle； 2. 使用BinaryWriter将UnityWebRequest.downloadHandler.data的数据写入机器； 3. 调用UnityWebRequest的Dispose函数； 4. 调用Resources.UnloadUnusedAssets()，UnityWebRequest.ClearCookieCache()和System.GC.Collect()。 下载每一个Bundle都会执行如上流程，但是Profiler调试真机发现，分配的内存无法释放，会把Mono内存越撑越大，请问大家遇到过这种情况吗？ A：之前遇到过类似的问题，题主可以参考一下，我们使用的Unity版本是2018.4.31。 不管是之前的WWW还是现在的WebRequest，都是使用的其成员DownloadHandler进行下载。当你访问“.data”属性时，其实访问的是一个“GetData()”函数的包装，这个函数返回的是一段native-memory data buffer的拷贝，这也是问题的根结所在。 第一个可以优化的点就是减少“.data”的使用，用临时变量缓存下来。 第二个就是替换DownloadHandler，Unity提供了多种DownloadHandler，我们当时是下载图片，所以用的是DownloadHandlerTexture，看你的问题，可以试试DownloadHandlerAssetBundle。 参考：https://docs.unity3d.com/ScriptReference/Networking.DownloadHandlerAssetBundle-ctor.html 感谢Joke@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5d443ae927d7e5590f6cd84b Editor Q：美术希望在编辑器运行的状态下获取场景GameObject实例对应预制体的路径，以前Unity 2017是可以通过var pRoot = PrefabUtility.GetPrefabParent(go); return AssetDataBase.GetAssetPath(pRoot)，获取运行时预制路径的。 最近升级到2019发现接口已经更新，PrefabUtility.GetPrefabInstanceHandle(targetGameObject)、PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefab)等接口都不能在Unity运行的时候去获取Hierarchy里面的GameObject路径。 我问题里面的关键是运行状态，也就是点了播放，然后获取Hierarchy上预制的存储路径。PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefab)不点播放是可以获取，播放了只能获取Project上预制的存储路径。 A：PrefabUtility.GetNearestPrefabInstanceRoot：获取最近的预制体实例Root。 PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot：获取预制体路径。 if (PrefabUtility.IsPartOfPrefabInstance(seletedGo)) { string prefabAssetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(seletedGo); } GetPrefabAssetPathOfNearestInstanceRoot可以获取路径，“PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefab)” 这个接口传的不是Prefab部分，而是实例的。 如果不行，在Editor时，在它身上挂脚本或者别的方式建立映射，Runtime下再拿。 感谢静风霁@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/600fe5ea10a17c6c2b09dce1 Mesh Q：请教下，我测试一个Skinmesh合并功能，发现当Mesh包含多个Submeshes时，合并成功后，无法渲染显示出来。 当测试的Mesh不包含Submeshes时，可以合并显示。难道CombineMeshes只能合并不带Submeshes的Mesh？不知道问题出在哪？有什么解决思路吗？ 注：不管是使用“r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false); ”带上所有材质，还是“r.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false) ；”合并一张贴图材质，带多个Submeshes的模型只能合并无法渲染显示出来。不带多个Submeshes的模型能够正常合并显示。 A：测试发现，需要对每个CombineInstance重新指定三角面，才能正常显示带多个Submeshes的模型。 如这段代码： foreach (SkinnedMeshRenderer smr in allSkineMeshList) { for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++) { CombineInstance ci = new CombineInstance(); ci.mesh = smr.sharedMesh; ci.mesh.triangles = smr.sharedMesh.triangles; //这里重新指定 ci.subMeshIndex = sub; ci.transform = matrix * smr.transform.localToWorldMatrix; combineInstances.Add(ci); } } 感谢题主牛头人不服@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5fe55b1a10a17c6c2b09d778 Lua Q：在ToLua中访问Unity的Time.deltaTime始终为nil，赋值给Lua变量也是0。 PS：在ToLua访问Unity API之前已经进行过静态绑定，其他API都可以被Lua正常访问。 为什么ToLua中访问Time.deltaTime为0？ A：Lua的绑定必须写在Lua虚拟机启动之后： 感谢题主yxy@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/604ad2b3cfa35d5b53669a6a Editor Q：客户端打开Cache Server面板测试Connection会发现连接不上，然后点击一下服务器的控制台，Enter一下会提示： 然后客户端才能重新连接，怎么感觉像是失去焦点的意思，这是什么原因呢？ A：Windows在服务器的控制台右击设置属性/默认值 “取消勾选快速编辑模式”。 感谢aladdin@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/6049be0bcfa35d5b53669a23 封面图来源：VaSplineSnag VaSplineSnag是提供给UE4关卡设计师的样条曲线工具。 https://lab.uwa4d.com/lab/5b8627da02004fb65977efaf 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
1）关于Camera.activeTexture和Camera.targetTexture的疑问 ​2）如何降低动画文件的浮点数精度 3）使用EasyMovieTexture在Android播放视频失败 4）模型顶点色（Color）如何去除 5）多层3D场景寻路方案探讨 这是第227篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） Rendering Q：一直有个疑惑，Camera.activeTexture和Camera.targetTexture在Get的时候，Get到的是一个RenderTexture吗？ 我知道这两个的区别是一个只能获得当前所正在渲染的RenderTexture，一个是既可以获得也可以设置渲染目标，但这样activeTexture有什么必要性呢？ 是我对这两个的理解有错误吗？还有大家平时说的Backbuffer，在我渲染目标一直是屏幕的时候，这两个方法获得的都是Backbuffer吗？ A：第一个问题：Camera.targetTexture必须在相机指定Render Target才会非null，Camera.activeTexture获取到的值和调用的时机有关系。官方文档中写着可以在OnPostRender中获取到，比如使用了后处理，在OnPostRender中可以获取到后处理申请的RenderTexture，在Update中获取就是null。如果给相机设置了RenderTexture，那么targetTexture就是设置的RenderTexture。如果没有开启HDR，在OnPostRender中activeTexture获取到的也是这个RenderTexture。如果开启了HDR，则是一个另外申请的RenderTexture。 第二个问题：推荐使用RenderDoc查看渲染状态，分为两种情况，在安卓平台上开启了Always Blit和没开启会不一样。开启了Always Blit，会在最后多一个Present的过程，在Present之前，会将所有的渲染渲染到一个叫tex的RenderTexture上（这里不考虑后处理以及HDR导致生成了额外的RenderTexture），在Present的时候将tex Copy到Backbuffer里面。如果没有开启AlwaysBlit，比如Blit Type是Never，那么所有的渲染都是直接渲染到Backbuffer的（这里同样不考虑RenderTexture的情况），Blit Type为Auto的时候没有遇到出现Present的情况，在线性空间也没出现Present 。Backbuffer和那个用来Present的tex这两个对象都可以理解为系统提供的，从C#脚本中是没法获取到的，而且从Profiler里面也是看不到这两个对象的大小的。 总结起来就是：大部分情况下Camera.targetTexture都是为null的，除非相机设置了RenderTarget。 对于Camera.activeTexture，这个需要看调用时机，在OnPostRender中调用，如果有使用后处理或者HDR，则可以获取到申请到的RenderTexture，在Update里面获取是null的，其他的函数阶段没有详细测试过。 对于Backbuffer，这个系统是提供的，C#脚本是获取不到的。而且不一定是直接绘制到Backbuffer，就算没有使用后处理或者HDR，如果开启了Always Blit，也是会多一个中间层，在最后阶段中间层会Copy到Backbuffer。 感谢Xuan@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5faa4c5fdc477370c2c1f104 Animation Q：动画文件后处理可以做两件事，精度压缩，Scale曲线剔除。比起用工具修改原始FBX文件，这样比较灵活。 实际测试，在开启Optimal压缩的情况下，加上这个后处理，能再节省40%左右。 示例代码和测试结果见知乎专栏： https://zhuanlan.zhihu.com/p/27378492 void OnPostprocessModel(GameObject g) { // for skeleton animations. List<AnimationClip> animationClipList = new List<AnimationClip>(AnimationUtility.GetAnimationClips(g)); if (animationClipList.Count == 0) { AnimationClip[] objectList = UnityEngine.Object.FindObjectsOfType (typeof(AnimationClip)) as AnimationClip[]; animationClipList.AddRange(objectList); } foreach (AnimationClip theAnimation in animationClipList) { try { //去除scale曲线 foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(theAnimation)) { string name = theCurveBinding.propertyName.ToLower(); if (name.Contains("scale")) { AnimationUtility.SetEditorCurve(theAnimation, theCurveBinding, null); } } //浮点数精度压缩到f3 AnimationClipCurveData[] curves = null; curves = AnimationUtility.GetAllCurves(theAnimation); Keyframe key; Keyframe[] keyFrames; for (int ii = 0; ii < curves.Length; ++ii) { AnimationClipCurveData curveDate = curves[ii]; if (curveDate.curve == null || curveDate.curve.keys == null) { //Debug.LogWarning(string.Format("AnimationClipCurveData {0} don't have curve; Animation name {1} ", curveDate, animationPath)); continue; } keyFrames = curveDate.curve.keys; for (int i = 0; i < keyFrames.Length; i++) { key = keyFrames[i]; key.value = float.Parse(key.value.ToString("f3")); key.inTangent = float.Parse(key.inTangent.ToString("f3")); key.outTangent = float.Parse(key.outTangent.ToString("f3")); keyFrames[i] = key; } curveDate.curve.keys = keyFrames; theAnimation.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve); } } catch (System.Exception e) { Debug.LogError(string.Format("CompressAnimationClip Failed !!! animationPath : {0} error: {1}", assetPath, e)); } } } A1：压的是文本文件的大小，只是编辑器下更快，不论怎么处理，内存还是一个Float。 感谢海的味道@UWA问答社区提供了回答 A2：Mecanim的动画系统的压缩确实不是靠改变Float类型来达到的，而是通过降低数值位数后，可以产生更多的Const曲线，从而让引擎达到更高效存储的效果，进而达到所谓的“压缩”结果。 该问答由UWA提供 A3：裁剪精度产生更多Const Curve，达到缩小体积的目的，这个可以理解。但除非动画曲线是动捕出来的，有大量抖动的细节，否则这样降低的内存很有限。而且统一暴力降精度在很多时候并不可取。 Unity官方的减帧算法是一套参数针对所有骨骼的，比较低效。推荐细化处理：针对不同关节，设置不同的误差阈值，手动做Linear Key Reduction。越靠近根骨骼，所需精度越高，允许的误差阈值越小。越靠近末端骨骼，误差阈值越大。对Position和Rotation Curve，使用不同的误差度量方式，面部、手指等关键骨骼再加额外配置。 这样可以有效大幅减小Clip内存和包体大小，尤其对于动捕产生的动画。脚本处理方法仅适用于.anim文件，且由于会产生额外的EditorCurve，在处理完后资产大小可能会不减反增。但最终EditorCurve会合并入Curve，AssetBundle和运行时内存是会减小的。 CompressAnimationClips.cs 感谢zhangji@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/593955b6c42dc04f4d8f7341 Video Q：使用EasyMovieTexture在Android播放视频失败，报错信息如下： AndroidJavaException: java.lang.IllegalStateException: Unable to update texture contents (see logcat for details) java.lang.IllegalStateException: Unable to update texture contents (see logcat for details) android.graphics.SurfaceTexture.nativeUpdateTexImage(Native Method) android.graphics.SurfaceTexture.updateTexImage(SurfaceTexture.java:248) com.EasyMovieTexture.EasyMovieTexture.UpdateVideoTexture(EasyMovieTexture.java:331) com.unity3d.player.UnityPlayer.nativeRender(Native Method) com.unity3d.player.UnityPlayer.access600(Unknown Source:0) com.unity3d.player.UnityPlayer600(UnknownSource:0)com.unity3d.player.UnityPlayerf1.handleMessage(Unknown Source:150) android.os.Handler.dispatchMessage(Handler.java:106) android.os.Looper.loop(Looper.java:219) com.unity3d.player.UnityPlayer1.handleMessage(UnknownSource:150)android.os.Handler.dispatchMessage(Handler.java:106)android.os.Looper.loop(Looper.java:219)com.unity3d.player.UnityPlayerf.run(Unknown Source:20) UnityEngine.AndroidJNISafe.CheckException () (at <3c22a197ab60454cb70124c69f2248be>:0) UnityEngine.AndroidJNISafe.CallVoidMethod (System.IntPtr obj, System.IntPtr methodID, UnityEngine.jvalue[] args) (at <3c22a197ab60454cb70124c69f2248be>:0) UnityEngine.AndroidJavaObject._Call (System.String methodName, System.Object[] args) (at <3c22a197ab60454cb70124c69f2248be>:0) UnityEngine.AndroidJavaObject.Call (System.String methodName, System.Object[] args) (at <3c22a197ab60454cb70124c69f2248be>:0) MediaPlayerCtrl.Call_UpdateVideoTexture () (at :0) MediaPlayerCtrl.Update () (at :0) A1：视频播放建议使用AssetStore中的AVProVideo，功能更强且兼容性更好一些。 感谢范君@UWA问答社区提供了回答 A2：建议使用自带的视频播放，兼容性更好。 感谢Robot.Huang@UWA问答社区提供了回答 A3：我之前遇到过一个EasyMovieTexture的问题，而且只出现在某个小米手机上，我们最后把视频格式改为mp4，码率我记得也改的跟EasyMovieTexture示例的视频一样然后就好了。 ps：你可以先用示例的视频测试一下，如果同样播放不了，那就是其他原因了。 感谢lanyt@UWA问答社区提供了回答 A4：直接用官方自己的VideoPlayer，如下图： 感谢小辉辉-177865@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5fa2cc80dc477370c2c1f04b Mesh Q：在UWA中的模型分析页面中有关于模型顶点色的信息，得知如果不需要此数据，最好在Max中不要导出此属性，那么如果清除此属性呢？ 我尝试过新建一个新的模型，这时此模型是不带顶点色属性的，一旦修改顶点色后就无法再清除此属性了，求解。 A1：感谢kent的回答，但是他提供的Demo内的exe我这里打开会报错，自己尝试从FBXSDK导出了一个exe。 FbxSDK官网下载，根据VS的使用版本来下载就可以，不影响下面的教程。 FbxSDK与VisualStudio环境搭建 这个教程会教你打开一个Samples，随便打开一个Sample，然后把Kent提供的main.cxx内容替换到同名文件内，编译出exe就好。 这里提供一个自己编译的，需要配合Kent提供的Demo使用： ClearUVClr.exe 感谢高松@UWA问答社区提供了回答 A2：我在使用的时候发现有的Fbx文件替换无效，经查是main文件中 只获取了第一个节点，所以我改成遍历获取 这里提供main文件和exe文件： main.cxx ClearUVClr.exe 感谢Lim@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5a8f77970b827e2c0bfdcfaf Script Q：目前面临一个寻路方案的需求，期望有相关经验的朋友探讨一下各种实现方案的优劣和可行性，开阔下思路。 需求抽象之后大致如下： 1. 3D多层场景，但是场景不会很大，几百米乘以几百米以内吧； 2. 场景分层，高度上可能有多层重叠，层之间有地面阻挡，挖空的地方有各种穿梭方式； 3. 场景内有箱子、小巷之类的阻挡与道路； 4. 玩家可能会有类似轻功的飞行移动方式。 3D方面可选的方案不多，比如Navmesh、体素化、纯物理，目前比较倾向于体素化的方案，纯物理的目前测试下来感觉性能消耗有点不能接受。当然需求方面也有一些可以取舍的，想看看各位有经验的朋友有什么好的想法和建议。 A：最近看到一些3D寻路的解决方案想到这个问题，挂靠一下： SVON：基于八叉树场景划分+A星寻路的算法实现，有开源库（Native源码+Unity工程）见链接。 Mercuna 3D Navigation：看官方介绍类似SVON，对A星算法进行了优化，有UE和Unity的插件，但没找到相关插件下载，需要联系官方。 感谢羽飞@UWA问答社区提供了回答，欢迎大家转至社区交流： https://answer.uwa4d.com/question/5ee2100e849af62b5798da2e 封面图来源于网络 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859（原群已满员）
在游戏开发中，我们有时需要对美术同学给出的模型进行简化。目的是在减少面数的同时，尽可能的维持模型的外观。这种技术可以用在LOD（Levels of Details）上，当模型与摄像机的距离大于一定值之后，使用面数较少的模型来代替高模，这样可以减少GPU带宽消耗和渲染压力。 简化分为三种： 静态的； 动态的； 视角依赖的。 通常情况下，我们会让美术提供面数不同的几个模型，或者我们使用工具对高模进行减面并存成多个Mesh，然后在游戏运行的时候，根据模型与摄像机的位置关系，动态的替换Mesh。这是一种静态的方法。 本文将讨论一种动态的网格简化算法。这种算法的好处在于，美术只需要提供一个高模，程序便可以自动的生成量级不同的低模。 原文链接： http://dev.gameres.com/Program/Visual/3D/PolygonReduction.pdf（感谢UWA答主马古斯提供的文章和思路） 三角边坍缩 这里使用了三角边坍缩的方法来进行网格简化，将两个顶点合并成一个顶点，如图所示： 对于要坍缩的边uv，删除这条边两侧的面A和面B，用v来替换u，连接v和u的其他邻居点，并删除u。其中v称为u的坍缩目标。 对于一个实体模型（具有封闭的边界，根据边界可以将空间分为模型内部和模型外部两部分），一次坍缩，可以移除两个三角面，三条边和一个顶点。通过反复的迭代，最终就会使模型简化到预期的面数。 但是如何选择要移除的点，才能尽可能小的影响模型的外观呢？这里就需要用到坍缩代价计算公式： 其中Tu是包含顶点u的三角形的集合，Tuv是同时包含顶点u和顶点v的三角形的集合。 上面公式表示将u坍缩到v（移除u）所需要的代价。第一部分是边的长度，直观上讲，在模型简化过程中，小的细节应该优先被移除。第二部分是u点周围的曲率变化，理论上曲率变化越小的顶点，所处的区域越平坦，应该优先被移除。需要注意的是，将u坍缩到v和将v坍缩到u的代价可能不一样。 通过这个公式，我们就可以对每个点计算坍缩到其相邻点的代价，然后选取坍缩代价最小的相邻点作为其坍缩目标。 实现细节 根据以上的描述，可以将实现分为以下步骤： 1.搜集顶点、三角面和三角边的关系； 2.计算坍缩代价和坍缩目标，并排序； 3.替换坍缩代价最小的点，并重新计算相邻点的坍缩代价和坍缩目标，更新有序列表； 4.判断当前顶点数量是否大于目标数量，是则重复第3步。 显然，在游戏中实时的进行以上步骤是不现实的，尤其是第2步，相当于对整个模型的所有顶点遍历了多次。所以，要将它拆分成离线烘焙和运行时两个部分。 离线烘焙 离线烘焙会输出两个int数组：permutation和vertex_map。 步骤： 1）收集顶点信息：主要是顶点位置和index，另外需要初始化两个列表：包含顶点的三角面列表和顶点的邻居列表； 2）收集三角面信息：获取每个三角面所包含的三个顶点，并计算法线（用于计算曲率）；同时，将该三角面加入顶点的三角面列表，并将每个顶点加入另外两个顶点的邻居列表中去； 3）计算顶点与其邻居点的坍缩代价，选择坍缩代价最小的邻居点作为坍缩目标； 4）依据坍缩代价对所有的顶点进行排序； 5）替换坍缩代价最小的顶点：获取坍缩代价最小的顶点u及其坍缩目标顶点v，遍历u的三角面列表，如果包含v就删除该三角面，否则将u替换为v。重新计算u的邻居点的坍缩代价和坍缩目标，并更新列表； 6）令permutation[u.index] = 当前顶点数量，令vertex_map[u.index] = v.index (如果没有坍缩目标，则赋值为-1)； 7）判断当前顶点总数是否大于0，是则重复第5步。 permutation保存了每个顶点被移除的倒数次序（1是最后被移除的，最大的是第一个被移除的），vertex_map保存了每个顶点的坍缩目标的位置。 运行时 输入需要绘制的最大顶点数量n。 步骤： 1.遍历三角面 1.1 获得当前三角面的三个顶点的index，即idx0、idx1和idx2。 1.2 如果permutation[idx0] >= n，则idx0=vertex_map[idx0]，否则执行1.5。 1.3 如果idx0==-1 or idx1 == idx0 or idx2 == idx0，该三角面不参与绘制。同理映射并判断idx1和idx2。 1.4 返回1.1。 1.5 当前三角面加入绘制列表。 2. 根据三角面的数据，整理顶点属性。 此外，网格的原始信息会被保留，在游戏运行期间，就可以在任意LOD上自由切换。 细节优化 为了得到更好的性能和较好的效果，本文还对上面的步骤进行了一下优化： 1）最小堆排序：离线烘焙第4步中，被移除点的邻居点需要重新计算坍缩代价，也就意味着坍缩列表也会动态变化。所以这里使用最小优先队列（最小堆）来保存顶点和坍缩代价，并且动态调整顶点顺序。（详参:算法导论第3版第6章）； 2）删除不用顶点：为了减少带宽消耗，并提高GPU的Cache命中率，在运行时第2步中，要根据三角面的数据对Mesh的顶点数组（还有uv、uv2、colors、normals、tangents、boneWeights等）和三角面数组进行重新排列。（详参后文的完整实现）； 3）边界点处理：实际操作中，会有两种比较麻烦的情况：一种是不闭合的三角面，例如飘带、披风等。还有一种是两个点拥有相同的位置，但是不同的uv或normal，例如Unity3D的Sphere是有一条接缝的。如果不考虑这两种情况，坍缩的时候，因为没有找到正确的坍缩目标来代替原顶点，就会出现镂空、破损的现象。针对这种处理，在计算坍缩消耗的时候，会将这些边缘点的曲率设为2（可以在编辑器里调整）； 4）内存优化禁术：为了避免每次新建Mesh的vertices等数组，目前使用unsafe的手段来修改数组的大小（感谢UWA答主lujian提供的禁术）； 5）JobSystem：离线烘焙中使用了JobSystem来加速烘焙。 完整实现 https://lab.uwa4d.com/lab/5b55ed36d7f10a201fd75b4e 效果展示 异特龙 面数分别为3890（原）、1960、962、459、263。 巨魔 面数分别为13432（原）、6723、3415、1634、788。 参考文献 A Simple, Fast, and Effective Polygon Reduction Algorithm BunnyLod MeshSimplify UWA问答：List的ToArray有什么办法能避免内存分配吗？ Real-Time Rendering, 3rd Edition 算法导论 这是侑虎科技第464篇文章，感谢作者凯奥斯供稿。欢迎转发分享，未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。QQ群：793972859（原群已满员）。 作者主页：https://zhuanlan.zhihu.com/commentsofchaos 作者也是U Sparkle活动参与者，UWA欢迎更多开发朋友加入U Sparkle开发者计划，这个舞台有你更精彩！
本期聚集话题： UGUI Android齐刘海适配、UWA GOT测试Mono卡顿、如何清除Unity中创建的Mesh的顶点色、MeshBaker的适用性... 这是第114篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题，建议阅读时间10分钟，认真读完必有收获。 UWA 问答社区：answer.uwa4d.com UWA QQ群2：793972859（原群已满员） 内存 Q：我用的是Unity 2017.2.0这个版本，在 PC Editor 上，Profiler 显示 GfxDriver 不断增加，经排查是由 Canvas 里一个游戏对象引起，该游戏对象成分如下： 只要将该游戏对象关闭，GfxDriver 立刻停止增加，因此我怀疑该版本的 Unity 存在问题，导致这种制作动画的方式都会引发内存泄漏，于是我把该游戏对象拉到一个空场景中，然而并没有发现 GfxDriver 增加。到此，不知道该怎么进一步排查问题。 这是Unity 2017上UGUI的Bug，在Unity 2017.1版本中无论是Editor还是Release版本都会出现该泄露问题。 Release版本在2017.2以后被修复，但Editor版本要在2017.4.1版本以后才被修复。 对于真机版本，题主在当前版本中可不用担心，如果担心Editor版总是因为内存过大而崩溃，可以尝试升级到2017.4.1以后。 该回答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b0fa6a754646026e9ddd6d7 UI制作 Q：有人做过 UGUI Android齐刘海（如荣耀10）的适配吗？大家有什么经验可以分享的吗？ 给个简单的方案吧，以iPhone X为例。 1）准备launch image，以关闭iPhone X默认的两侧黑边。（Unity 2017之后可以直接提供x的闪屏图片，可略过此步骤） cp -R "../yourPorject/Assets/ArtResources/SplashScreen/iphonex_h.png" "./Unity-iPhone/Images.xcassets/LaunchImage.launchimage/iphonex_h.png" cp -R "../yourPorject/Assets/ArtResources/SplashScreen/iphonex_v.png" "./Unity-iPhone/Images.xcassets/LaunchImage.launchimage/iphonex_v.png" cp -R "../yourPorject/Assets/ArtResources/SplashScreen/Contents.json" "./Unity-iPhone/Images.xcassets/LaunchImage.launchimage/Contents.json" 2）我们在UGUI Root下，有个desktop的概念，作为所有UI的祖先层，设为水平垂直方向均为stretch, Top设为88, Bottom设为-88。（88是最小间隔，132是官方推荐的安全间隔）。 其中第2步对其他ui使用者都是透明的，两侧填充无意义的UI图片，对UI制作上也几乎没有影响。整个改造过程很短，推荐尝试。 PS：未考虑iPhone X的横条。 感谢史云柯@UWA问答社区提供了回答 有一个简单的方案，就是给最上层界面配置一个横纵Stretch自动拉伸的RectTransform，检测当发现是iPhone X时，打开界面代码中自动设置 Left Top Right Bottom 为44.这个方案我在雨松那里看到的，是个很简单粗暴的方案，当然有些地方可能还要微调一下。参考网址：http://www.xuanyusong.com/archives/4464 感谢小小克劳德@UWA问答社区提供了回答 此问答来自问答社区，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b0cd5fa09749726e4188d42 资源 Q：Unity中创建的Mesh为什么带有顶点色？如何才能创建不带顶点色的Mesh？或者怎么在unity中把顶点色给抹掉？ 我已经做过以下尝试： 1）mesh.color = null，mesh.colors32 = null 没用。 2）重新new一个Mesh，不赋值顶点色，没用。 3）转成obj再放到Unity中看，居然还有顶点色。但是把obj放到Max中看，是没有的，转成FBX再放到项目中，还是有顶点色。 经过一番探索，发现问题：关闭Use Direct3D 11选项，重启Unity即可。关了Use Direct3D 11之后，重新创建Mesh，此时已经不带顶点色了。Obj文件以及不带顶点色的FBX文件，直接预览，顶点色也没了。开关位置：Edit>Project Settings>Player>Other Settings>Use Direct3D 11（PC选项卡下） 感谢题主@CrazyCoder分享经验，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b10004109749726e4188d96 堆内存 Q：使用MeshBaker动态合并模型，在连续动态添加合并模型是产生了大量的GC，随着添加的越多，GC产生的越多，可能有4MB大小，这个是什么情况？MeshBaker不适合用来做动态模型合并？ 是的，Mesh Baker插件是在上层对Mesh数据进行大量合批，期间需要生成大量的Vertex Array，从而会造成大量的堆内存分配。如果是复杂的MMO、ARPG等需要大量Mesh合并的项目，一般不建议使用Mesh Baker这个插件。 静态物体的话，其实可以直接通过Static Batching来做即可。一般Mesh Baker会有团队在游戏运行时动态Batch Skinned Mesh，这个当数量较多时是不建议使用的。 此问答由UWA提供，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5b11089b54646026e9ddd6ed GOT Q：今天刚开始用GOT，发现调试Mono的时候总会在战斗刚开始的时候卡死，Overview和Assets都没不会这样。请问这是什么原因造成的？卡死的时候应该正在加载一些资源，是和这个有关么？ 这说明此时的堆内存在超大量分配，UWA GOT需要大量计算来记录这些分配的堆内存信息。另外，如果在资源加载时是否也伴随着Log的输出，则会加剧卡顿的程度。建议题主先关闭Log输出试试。 此问答来自问答社区，欢迎大家转至社区进行进一步交流： https://answer.uwa4d.com/question/5a0c2ab3784c7a0f682d3719 今天的分享就到这里。当然，生有涯而知无涯。在漫漫的开发周期中，您看到的这些问题也许都只是冰山一角，我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入，也许你的方法恰能解别人的燃眉之急；而他山之“石”，也能攻你之“玉”。 官网：www.uwa4d.com 官方技术博客：blog.uwa4d.com 官方问答社区：answer.uwa4d.com 官方技术QQ群：793972859（原群已满员）