Unity场景物体动态合批
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:安卓延迟比较大是比较正常的,特别是在一些低端机上。
可以先试试Unity内置的延迟解决方案:ProjectSettings的Audio中的DSP Buffer Size设置为Best latency,然后短音效的AudioType改为Decomppress On Load。
如果无法解决 可以尝试一下官方提供的插件:NativeAudio SDK
感谢萧小俊@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(原群已满员)