Unity Shader: 一个简单的(规则化)序列帧动画(2)
接前文: Unity Shader: 一个简单的(规则化)序列帧动画(1)序列帧有时候会应用在数量特别庞大的场景,如下图所示:
image.png
创建了900个方阵,每个方阵内有25个对象,共22500个对象,每个对象使用统一的action,因为有自动合并批次,所以效率看起来似乎还可以,但实际应用中,我们不可能所有的方阵都整齐划一,不同的方阵在不同的时机有不同的action,所以通过交叉action的方式来模拟处理:
image.png
此时DC就已经升高到1万+,帧率也很低.
要解决此问题,需要用到GPUInstancing(官方文档为:https://docs.unity3d.com/Manual/GPUInstancing.html),使其满足同一Material具有不同表现的情况.
修改后的shader如下:
Shader "Test/SimpleMovieClip"{ Properties { _MainTex("Image Sequence", 2D) = "white" { }// 序列帧图片 _RowCount("总行数", Float) = 1 // 行数 _ColumnCount("总列数", Float) = 1 // 列数 _FrameRate("帧率", Range(1, 100)) = 30 // speed _ActionRowIndex("ActionRowIndex", Range(0, 100)) = 0 _ActionFrames("当前action帧数", Range(0, 100)) = 0 _Color("Color", Color) = (1,1,1,1) } SubShader { //一般序列帧动画的纹理会带有Alpha通道,因此要按透明效果渲染,需要设置标签,关闭深度写入,使用并设置混合 Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True"} ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag //#pragma target 3.0 // make fog work //#pragma multi_compile_fog #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float4 uv2 : TEXCOORD1; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float2 uv : TEXCOORD0; //UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in fragment Shader. }; sampler2D _MainTex; float4 _MainTex_ST; float _RowCount; float _ColumnCount; // float _FrameRate; // float _ActionRowIndex; // float _ActionFrames; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float, _FrameRate) UNITY_DEFINE_INSTANCED_PROP(float, _ActionRowIndex) UNITY_DEFINE_INSTANCED_PROP(float, _ActionFrames) UNITY_INSTANCING_BUFFER_END(Props) fixed4 _Color; v2f vert(appdata v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader. o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 是用原始uv,不进行平铺和偏移 // o.uv.xy = v.uv.xy;// * _MainTex_ST.xy + _MainTex_ST.zw; //UNITY_TRANSFER_FOG(o,o.vertex); return o; } fixed4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader. // 将时间取整(变成以秒为单位)相当于1秒1帧,放大到_FrameRate后,相当于得到帧index,通过index去计算行列索引. // 必须将纹理的wrap mode设置为Repeat(或类似的设定),因为当time>_ColumnCount*2时,row会大于_RowCount // uvoff中计算的y值会大于1,需要通过纹理的Repeat机制来重复显示. // 或者在外部维护一个index变量,并传进来,这样可以在外层将这个index进行重置为0 float index = floor(_Time.y * UNITY_ACCESS_INSTANCED_PROP(Props, _FrameRate)); // 取整得到行索引(播放顺序设计为从左到右,先行后列) float rowIndex = UNITY_ACCESS_INSTANCED_PROP(Props, _ActionRowIndex); // _ActionRowIndex;//floor(index / _ColumnCount); // 余数为列索引 //float columnIndex = fmod(index, _ActionFrames); // index - rowIndex * _ColumnCount; float columnIndex = fmod(index, UNITY_ACCESS_INSTANCED_PROP(Props, _ActionFrames)); // index - rowIndex * _ColumnCount; half2 iuv = i.uv.xy; // /_MainTex_ST.xy; // 使用中的行列值作为分割计算的元值(总比值). 相当于一个窗口,通过该窗口的上下左右定位得到每帧图片的uv half2 rawSplit = half2(_ColumnCount, _RowCount); // 当前uv通过rawSplit分割后,得到当前uv在总uv中的占比. 相当于(窗口的)固定大小 iuv /= rawSplit; // 通过当前计算出的行列值与总比值的比例,得到uv的起始偏移量. 相当于(窗口的)起始位置, row是从上到下,取反后转换为uv的从下到上 half2 uvoff = half2(columnIndex, -rowIndex) / rawSplit; iuv += uvoff; // iuv*=-1; fixed4 col = tex2D(_MainTex, iuv) * _Color; // apply fog //UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } FallBack "Transparent/VertexLit"}
将需要修改的(特性)变量由直接声明变更为了instanced模式:
UNITY_DEFINE_INSTANCED_PROP(float, _FrameRate)UNITY_DEFINE_INSTANCED_PROP(float, _ActionRowIndex)UNITY_DEFINE_INSTANCED_PROP(float, _ActionFrames)
同时勾选Enable GPU Instancing:
image.png
测试代码(C#)中,使用MaterialPropertyBlock进行属性修改:
// ...var propBlock = new MaterialPropertyBlock();propBlock.SetFloat("_ActionRowIndex", 0);propBlock.SetFloat("_ActionFrames", 5);propBlock.SetFloat("_FrameRate", 2);// ...// 进行设置:go.GetComponent<MeshRenderer>().SetPropertyBlock(propBlock);//...
(测试代码比较简单,请自行构建)
修改后效果图如下:
image.png
如上图所示DC大幅下降,帧率也有所提高. 此处额外查看下Instance的处理情况.
Instance的额外测试
通过Frame Debug观察这52个DC的具体情况:
图1.png
图2.png
默认1个,测试环境中的天空占6个,其它都是来自45个均来自Draw Mesh(instanced),图1处的红框部分是说明在instance机制下,每次合批中,有511个实例进行了合并(含有了2044个顶点). 预览图中看到的是2个三角形的片,是因为我的测试中是用生成的2个三角形来为显示对象的sharedMesh赋值的.
// ...var mesh = new Mesh();var size = 0.1f;var newVertices = new Vector3[]{ new Vector3(0, 0, 0), new Vector3(0, 0, size), new Vector3(size, 0, size), new Vector3(size, 0, 0),};var newUV = new Vector2[]{ new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1),new Vector2(1, 0),};var newTriangles = new int[]{ 0, 1, 2, 2, 3, 0,};mesh.vertices = newVertices;mesh.uv = newUV;mesh.triangles = newTriangles;// ...go.GetComponent<MeshFilter>().sharedMesh = mesh;// ...
图2中,最后一个Draw Mesh(instanced)是64个顶点. 相当于24*2044+64=90000个顶点, 刚好与原始设计(900个方阵,每个方阵内有25个对象,共22500个对象)的90000个顶点匹配.
在Unity Dynamic Batching中,一般要求单个模型的顶点信息数据不超过900个,但通过Instancing就可以超过此限制.
将2个三角形的模型替换为一个2000个顶点的模型来观察:
image.png
测试结果如下:
image.png
image.png
DC不变,合并的实例数量也不变(511个),但单次合并的顶点数变为了100万. 此举虽然能加大mesh合并但加重的是CPU的负担(要在CPU侧进行合并计算),要达到最优效率,需要找到平衡点.
具体原理参考: https://github.com/vanCopper/Unity-GPU-Instancing
回到主题
虽然在Instancing加持下,多个action性能消耗有所降低,但到此还没结束,实际情况中我们往往会有多个角色多个action的情况,哪怕一个角色的所有action合并为了一张贴图,但总会有多个角色存在于场景中的情况,现在测试2张Material交替显示的情况(第二张material设置了一个不同的颜色以示区分),模拟2个角色各自表现2个action,结果如下图所示:
image.png
性能又下降了,说明在一个节点之下放置不同Material时,也会导致Instancing不生效.
解决方式1: 在使用不同Material的对象上挂载一个SortingGroup,即将不同的Material进行分层处理:
// ...if (ismat2) go.AddComponent<UnityEngine.Rendering.SortingGroup>().sortingOrder = 1;else go.AddComponent<UnityEngine.Rendering.SortingGroup>().sortingOrder = 2;// ...
image.png
解决方式2: 按角色进行分层处理(类似canvas中的分层优化),将同类Material归类到一个节点下,然后在该节点添加SortingGroup,但当一个SortingGroup下节点数过多会收到一个错误:
image.png
将测试(C#)代码修改为同类Material归类为一个大组节点,其下再按4096为小组节点,小组节点下再挂载显示对象:
image.png
测试结果如下:
image.png
Frame Debug中也能看到全部都是instanced后的结果:
image.png
此方式是在每个显示对象的层级交错时有问题,但也基本满足需求.
除上述(动态合并)方式外,还可以使用Graphics.DrawMeshInstanced()进行直接绘制(没有显示节点对象,直接自行管理绘制数据)
具体可以参考: https://gist.github.com/Cyanilux/e7afdc5c65094bfd0827467f8e4c3c54
转载请注明出处: https://www.jianshu.com/p/e633db24ba31
页:
[1]