1)Unity场景物体动静合批
2)Unity内置音频Android平台播放提早问题
3)对Unity Package中的Shader打包防止冗余的计划
4)UnityEditor PropertyField并排显示谬误


这是第307篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。

UWA 问答社区:answer.uwa4d.com
UWA QQ群2:793972859(原群已满员)

Rendering

Q1:问题1:为了能使场景中物体合批(同材质球),场景在制作的时候雷同的预制体放在了一起,然而FrameDebug看到的却是乱序。请问在这个Frame里的程序是如何定义能力达到雷同的合批?也就是如何能起码化的Objects have different materials?

问题2:在FrameDebug高低排列且雷同物体,仍然没有合批。给出的起因是“Objects are lightmapped”。网上给出的解释是应用了不同的光照贴图(这里是一样的),或在雷同的光照贴图中由不同的光照贴图UV转换关系。这个转换关系是什么意思?是UV对应的Offset几个参数必须要一样么?然而物体在不同的地位,实践上肯定会不一样。请问这个要怎么解决?目前场景中物体占用了120个DrawCall,想尽可能优化。

A:第一个问题,针对不通明物体,Unity默认由近往远绘制(离相机的间隔),所以如果想要雷同的材质球是间断绘制的,能够通过调整RenderQueue来强行间断绘制,不过可能会导致渲染成果不合乎预期(遮挡关系错乱),另外的害处是会毁坏Early-Z的性能,尤其在没有HSR性能的低端机上,Overdraw会造成高复杂度的Shader带来的GPU高压力,所以须要衡量CPU提交Batch的耗时和GPU的压力。

第二个问题,同类型材质球(Shader一样,变体也一样),如果Lightmap index不一样(也就是Lightmap不一样),必定是不能合批的(SRP Batcher除外,SRP Batch忽视材质球,只有Shader和变体一样就能够)。在Lightmap雷同的状况下,如果对应的unity_LightmapST不一样,也是不能合批的。这里有两种办法解决,一种是用Static Batching,这样会让unity_LightmapST变成同一种,具体的UV会变成顶点数据(原本子Mesh的UV都是(0,1)的,合并Combined Mesh后会变成相似于(0.3,0.5)这样的区间),就是说顶点外面的UV原本存储的是Local空间的,变成Combined Mesh后,合体的Mesh数据外面存储的是World空间的,这样外在的unity_LightmapST对于不同的子Mesh就会变成共用的(1,1,0,0)这样的独特属性,也就能够合批了。另外一种是开启GPU Instancing,这样unity_LightmapST会变成CBuffer,这样也是会合批的。

如下图所示,Cube1和Cube2在FrameDebugger的惟一区别就是unity_LightmapST不一样,所以不开启GPU Instancing和Static Batching的状况下,是不能合批的,尽管它们是相邻的绘制程序,Lightmap也是雷同的。另外两个Cube(1)和Cube别离用的Lightmap2和Lightmap0。

开启动态合批后,两头的2个Cube合成了一个Batch,它们的unity_LightmapST变成了独特的(1,1,0,0),如下图:

材质球勾选GPU Instancing后,在FrameDebugger面板外面,曾经看不到unity_LightmapST这个属性了。这两个Cube也是合成了一个Instanced Batch。如下图:

感激Xuan@UWA问答社区提供了答复

Q2:对于第二个问题的解决办法。因为咱们是动态创建物体。所以无奈用动态合批。当我尝试应用GPU Instancing 后,批次简直没变,一种是咱们的不同的Mesh,应用同一个材质球,无奈合批;其次是同一个Mesh也无奈合批。如下提醒:

另外仍然还有“Objects are lightmaped”的提醒,且曾经没有Lightmap_ST,材质球都已勾选GPU:

还有Shader是有两个PASS的。是否另一个PASS也须要退出GPU Instancing 的代码?附Shader代码:

Shader "WF/SceneObj"{    Properties    {        [NoScaleOffset]_MainTex("主贴图 (RGBA)", 2D) = "white" {}        [Space(20)]        _OutlineWidth("描边宽度", Range(0, 0.5)) = 0.018        _OutlineColor("描边色彩", Color) = (0, 0, 0, 1)        [Space(20)]        _EmissionMask("自发光遮罩 (G)", Range(0, 5)) = 1        [HDR]_EmissionColor("自发光色彩", Color) = (1, 1, 1, 1)        _Color("Color", Color) = (1, 1, 1, 1)    }        SubShader        {            Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+1" }            // lightmap            Pass            {                Tags { "LightMode" = "ForwardBase" }                CGPROGRAM                #pragma multi_compile_fwdbase                #pragma multi_compile_instancing                #pragma vertex vert                #pragma fragment frag                #include "UnityCG.cginc"                #include "Lighting.cginc"                #include "AutoLight.cginc"                #pragma multi_compile_fog                #define USING_FOG (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))            struct a2v                {                    float4 vertex : POSITION;                    float3 normal : NORMAL;                    float2 texcoord : TEXCOORD0;                    float3 uv1 : TEXCOORD1; // lightmap                    UNITY_VERTEX_INPUT_INSTANCE_ID                };                struct v2f                {                    float4 pos : SV_POSITION;                    float2 uv : TEXCOORD0;                    float2 uv1 : TEXCOORD2;                    half3 worldlight : TEXCOORD1;#if USING_FOG                        fixed fog : TEXCOORD3;#endif                    UNITY_VERTEX_OUTPUT_STEREO                    UNITY_VERTEX_INPUT_INSTANCE_ID                };                sampler2D _MainTex;                float4 _MainTex_ST;                float _EmissionMask;                fixed4 _EmissionColor;                UNITY_INSTANCING_BUFFER_START(Props)                    UNITY_DEFINE_INSTANCED_PROP(float4, _Color)                    UNITY_INSTANCING_BUFFER_END(Props)                v2f vert(a2v v)                {                    v2f o;                    UNITY_SETUP_INSTANCE_ID(v);                    UNITY_TRANSFER_INSTANCE_ID(v, o);                    o.pos = UnityObjectToClipPos(v.vertex);                    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);                    //                     o.worldlight = UnityObjectToViewPos(v.normal);                    o.uv1 = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;                    // fog#if USING_FOG                    float3 eyePos = UnityObjectToViewPos(v.vertex);                    float fogCoord = length(eyePos.xyz);  // radial fog distance                    UNITY_CALC_FOG_FACTOR_RAW(fogCoord);                    o.fog = saturate(unityFogFactor);#endif                    return o;                }                fixed4 frag(v2f i) : SV_Target                {                    UNITY_SETUP_INSTANCE_ID(i);                    fixed4 originalCol = tex2D(_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);                    ////------------------------------漫反射------------------------------                    //-----------------------------------自发光                    //float emission = originalCol.a * _EmissionMask;                    //自发光                    //float emisstionValue = emission < 0.5 ? 0 : 1;                    fixed3 emisstionCol = originalCol.rgb * _EmissionColor.rgb * _EmissionMask;                    //                    //叠加                    fixed3 finnalCol = originalCol;                    fixed4 col = (fixed4)1;#if defined(LIGHTMAP_ON)                    //                    half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv1.xy);                    col.rgb = DecodeLightmap(bakedColorTex);#endif                    finnalCol = lerp(finnalCol, emisstionCol, originalCol.a) * col.rgb;                    // fog#if USING_FOG                    finnalCol.rgb = lerp(unity_FogColor.rgb, finnalCol.rgb, i.fog);#endif                    return fixed4(finnalCol, 1);                }                ENDCG            }            Pass            {                NAME "OUTLINE"                Cull Front                CGPROGRAM                #pragma multi_compile_fog                #pragma vertex vert                #pragma fragment frag                #include "UnityCG.cginc"                struct a2v                {                    float4 vertex : POSITION;                    float3 normal : NORMAL;                };                struct v2f                {                    float4 pos : SV_POSITION;                    UNITY_FOG_COORDS(0)                };                sampler2D _MainTex;                float4 _MainTex_ST;                fixed4 _OutlineColor;                float _OutlineWidth;                v2f vert(a2v v)                {                    v2f o;                    o.pos = UnityObjectToClipPos(v.vertex);                    float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);                    float2 ndcNormal = normalize(TransformViewToProjection(viewNormal.xy)) * pow(o.pos.w, 0.6) * 2;                    float4 nearUpperRight = mul(unity_CameraInvProjection, float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y));                    float aspect = abs(nearUpperRight.y / nearUpperRight.x);                    ndcNormal.x *= aspect;                    o.pos.xy += ndcNormal * _OutlineWidth * 0.1;                    UNITY_TRANSFER_FOG(o,o.pos);                    return o;                }                fixed4 frag(v2f i) : SV_Target                {                    fixed3 col = _OutlineColor.rgb ;                    return fixed4(col, 0);                }                ENDCG            }        }            Fallback "Diffuse"
A:动态创建物体的动态合批的办法能够参考StaticBatchingUtility.Combine的API,这是一项运行时动态合批的做法;其次如果应用了多个Pass的Shader,想要应用GPU Instancing进行合批,每个Pass都要设置成GPU Instancing能力失常合批。

感激宗卉轩@UWA问答社区提供了答复

Audio

Q:最近遇到Unity应用内置音频计划在Android平台音频提早问题,而且是有时候会提早。不晓得各位应用的是什么计划来解决这个问题?

A:安卓提早比拟大是比拟失常的,特地是在一些低端机上。<br/>

能够先试试Unity内置的提早解决方案:ProjectSettings的Audio中的DSP Buffer Size设置为Best latency,而后短音效的AudioType改为Decomppress On Load。<br/>

如果无奈解决 能够尝试一下官网提供的插件:NativeAudio SDK<br/>

如果不想用Unity内置的FMOD,能够尝试一下以下计划:Wwise、Criware

感激萧小俊@UWA问答社区提供了答复

AssetBundle

Q:收集完Shader Variants后,URP/UberPost变成双份了,怎么删掉?

A:这个问题我的判断是UberPost没有被指定AssetBundle,所以在打包SVC文件时,减少了一个额定的援用。而此时SVC文件须要指定AssetBundle,就将其依赖的未指定AssetBundle文件额定增加了进来。

这个问题的难点是:UberPost是在Packages下的,而Packages文件无奈指定AssetBundle。

解决方案1:将URP放到工程中,而非Packages下,这样就能够指定AssetBundle。
解决方案2:改用了能够批改AssetBundle关联性的ScriptableBuildPipeline,手动为UberPost指定打包到某个AssetBundle中,这样打包零碎检测到UberPost已有AssetBundle,就不会反复打包了。

感激小枫不会飞@UWA问答社区提供了答复

Editor

Q:我在制作一个工具,用来编辑很多内容,其中我有一个需要,就是在Editor窗口中去批改List的内容,比方增加新的项、删除和批改等。

我是用EditorGUILayout.PropertyField,成果如下:

然而我须要在若干列中展现多个PropertyField,此时成果成了这样子,能够显著看到第二个列表的文本框短了一大截:

如果是三个的话,基本就不显示:

我限度了前两个的长度,而后拉长界面,能够发现其实他能够显示,就是离得太远了:

求问这种状况怎么解决?以下是源码:

public class TestWindow : EditorWindow    {        [MenuItem("test2/open")]        private static void Test()        {            var s_CurrentWindow = GetWindow<TestWindow>("My Tool");            s_CurrentWindow.Show();        }        XmlNode node;        XmlNode node2;        XmlNode node3;        SerializedObject serObj;        SerializedProperty serPty;        SerializedObject serObj2;        SerializedProperty serPty2;        SerializedObject serObj3;        SerializedProperty serPty3;        private void OnGUI()        {            using (var scope2 = new EditorGUILayout.HorizontalScope())            {                serObj.Update();                serObj2.Update();                serObj3.Update();                EditorGUI.BeginChangeCheck();                using (var scope = new EditorGUILayout.VerticalScope(GUILayout.Width(500)))                {                    //temp = EditorGUILayout.TextField("What: ", temp);                    EditorGUILayout.PropertyField(serPty, true);                }                using (var scope = new EditorGUILayout.VerticalScope(GUILayout.Width(500)))                {                    //temp = EditorGUILayout.TextField("What: ", temp);                    EditorGUILayout.PropertyField(serPty2, true);                }                using (var scope = new EditorGUILayout.VerticalScope())                {                    EditorGUILayout.PropertyField(serPty3, true, GUILayout.ExpandWidth(true));                }                if (EditorGUI.EndChangeCheck())                {                    serObj.ApplyModifiedProperties();                    serObj2.ApplyModifiedProperties();                    serObj3.ApplyModifiedProperties();                }            }        }        private void OnEnable()        {            //var titleRow = NewRow(            //    new Label() { text = "Old" },            //    new Label() { text = "new" }            //    );            //titleRow.name = "title-compare-box";            //rootVisualElement.Add(titleRow);            //rootVisualElement            node = ScriptableObject.CreateInstance<XmlNode>();            node2 = ScriptableObject.CreateInstance<XmlNode>();            node3 = ScriptableObject.CreateInstance<XmlNode>();            node.InnerTextList.Add("QwQ");            node.CType = ContentType.List;            node2.InnerTextList.Add("heiheihie");            node2.CType = ContentType.List;            node3.InnerTextList.Add("WWW");            node3.CType = ContentType.List;            serObj = new SerializedObject(node);            serPty = serObj.FindProperty("InnerTextList");            serObj2 = new SerializedObject(node2);            serPty2 = serObj2.FindProperty("InnerTextList");            serObj3 = new SerializedObject(node3);            serPty3 = serObj3.FindProperty("InnerTextList");        }        private class XmlNode : ScriptableObject        {            public string Path { get; set; }            public string Name { get; set; }            public ContentType CType { get; set; }            public Dictionary<string, string> AttributeDict { get; set; } = new Dictionary<string, string>();            public List<string> InnerTextList = new List<string>();            public string InnerText { get; set; }            //public XmlNode(string path, string name, ContentType cType)            //{            //    Path = path;            //    Name = name;            //    CType = cType;            //    AttributeDict = new Dictionary<string, string>();            //    InnerTextList = new List<string>();            //}        }        private enum ContentType        {            String, // 单行文本 InnerText            List // 多行的内容,用List存        }    }
A1:倡议间接应用Odin插件。

感激张迪@UWA问答社区提供了答复

A2:这种List在Editor下能够应用ReorderableList,供EditorGUI应用,体现看起来跟你的需要差不多。

感激范世青@UWA问答社区提供了答复

封面图来源于网络


明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官网技术博客:blog.uwa4d.com
官网问答社区:answer.uwa4d.com
UWA学堂:edu.uwa4d.com
官网技术QQ群:793972859(原群已满员)