📜 ⬆️ ⬇️

Reducing the size of the Android build file in Unity

Build size is an important feature of a mobile application. If the application weighs a lot, it will be removed first when cleaning. Also smaller size can speed up launch, install, download.

Even an empty project in Unity weighs a lot. A blank Android project with default settings in Unity 2017.1 weighs 21637 KB. However, it can be very easily reduced to 11952 \ 12412 KB, specifying a platform for compilation (ARMv7 and x86, respectively).

By analogy with this, you can still try to reduce the weight a little more by choosing Graphic API. If you select OpenGLES2 instead of Auto Graphics API, you can save another 236 KB (11716 instead of 11952). The benefit is insignificant and a loss in performance is possible, so I do not recommend doing this.
')
Now let's talk about the contents of the project. Consider a 2D game with lots of sprites.
There are chances that many sprites will be symmetrical along one or more axes.

Let's check if there is an automatic compression for this case: let's compile the scene with the Sprite Renderer exposed with one texture, for example, this one.



ARMv7 build increased from 11952 KB 12046 KB, the increase from the empty build is 94 KB.

Now prepare half the texture:



We put two Sprite Renderer with the same position, at the right we set Flip X for mirroring, in the Sprite Import Settings settings we will specify Pivot Right for combining the mirror halves. It should turn out the same circle as it was before. Compile, look at the size: 12000 KB, that is, the increase is almost two times less (48 KB against 94). If there is any special compression, then by default it is ineffective.

In principle, it is already possible to try to mirror all the necessary textures, but in the framework of a larger project it will not be very convenient: you will have to make a half for each object. This problem can be solved by writing your own shader for horizontal mirroring of the image.

Find a standard Unity shader for Sprite Renderer.

Sprites / Default
Shader "Sprites/Default" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } Cull Off Lighting Off ZWrite Off Fog { Mode Off } Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile DUMMY PIXELSNAP_ON #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; half2 texcoord : TEXCOORD0; }; fixed4 _Color; v2f vert(appdata_t IN) { v2f OUT; OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; #ifdef PIXELSNAP_ON OUT.vertex = UnityPixelSnap (OUT.vertex); #endif return OUT; } sampler2D _MainTex; fixed4 frag(v2f IN) : SV_Target { fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color; c.rgb *= ca; return c; } ENDCG } } } 


Let's start by making sure that everything works. Create a shader, copy the code into it, change the name in the code to Sprites / HorizontalSymmetry. Now you need to create the material and select our shader. Let's try to assign our material to the Sprite Renderer. Should look like before.

Now we analyze the shader. All the magic happens here:

 fixed4 frag(v2f IN) : SV_Target { //tex2D      . //_MainTex -  , IN.texcoord -     0..1 fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color; // r,g,b    acrgb *= ca; return c; } 

This is a function that should return the color of the pixel at the specified point. We are given the position, texture, color for mixing. You should not be afraid of fixed4: it's just a data type with 4 float: r, g, b, a.

In the first line we get the color of the texture and then multiply it by a certain color IN.color. This color is a shader parameter, you can change it in Sprite Renderer / Color.

Next comes the color multiplication of alpha. This is due to the fact that transparency depends not only on alpha, but also on the value of rgb. For a better understanding of the color space, you can examine:

 fixed4 frag(v2f IN) : SV_Target { fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color; c.rgb=0.5; ca = 0.0; return c; } 

We get a transparent gray texture. With rgb = 1 and a = 0 it will be opaque white, with rgb = 0 and a = 0 fully transparent, rgb = 0 and a = 1 will be black opaque.

If everything worked out, then we will try now to make horizontal mirroring. We need to change this function so that in the first half of the image the whole texture is placed, in the second, the same texture but mirrored.

This problem can be solved in the forehead:

 fixed4 frag(v2f IN) : SV_Target { //      fixed2 nIn = IN.texcoord; //    0...2 nIn.x = nIn.x*2; //   ,   1..0 if (nIn.r>1) nIn.r = 2-nIn. //    fixed4 c = tex2D(_MainTex, nIN.texcoord) * IN.color; // r,g,b    acrgb *= ca; return c; } 

If you think a little, you can make the decision shorter, more beautiful, faster:

 fixed4 frag(v2f IN) : SV_Target { //   IN.texcoord.x = 1-abs(2*IN.texcoord.x-1); //     fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color; // r,g,b    acrgb *= ca; return c; } 

As an exercise, I suggest the reader solve this problem for vertical and double symmetry.

Sometimes there can be transparency artifacts due to the fact that the sprite is drawn by default on a specific contour (mesh). It is treated as follows: Sprite / Import Settings / Mesh type = Full Rect.

This method is theoretically capable of reducing the size of textures used by 4 times. Check how the build will behave with a quarter of the sprite (using a double symmetry shader). The build size is 11978 KB against 12000 (half sprite). Let me remind you that the empty project weighed 11952 KB. That is, again, the increase was almost doubled (in 3.6 from the original circle without optimization).

However, this is not the limit. In my game used a large number of washers with radial symmetry. This means that it is enough to have only one strip in order to set the whole circle! Moreover, a half strip (radius, not diameter).

Prepare the texture:


Now it's up to the shader. Our task is to make a circle from the line.

You can do this: find the distance of the current point from the center and use the texture coordinate with the position (1-distance * 2, 0). Multiplication by two occurs because the maximum distance from the center is 0.5, not 1. We subtract from the unit because the texture is prepared on the left (edge ​​of the circle) to the right (center of the circle).

Example of implementation:

 fixed4 frag(v2f IN) : SV_Target { fixed2 nIn = IN.texcoord; nIn.x = nIn.x-0.5; nIn.y = nIn.y-0.5; float dist = sqrt(nIn.x*nIn.x+nIn.y*nIn.y); IN.texcoord.x = 1-dist*2; fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color; c.rgb *= ca; return c; } 

Note: since the root is calculated here, the shader will work slower than the full circular image

Create a material, put it in the Sprite Renderer, set the Sprite = line, look. The image will be very narrow, so you need to stretch the sprite (set a large value Trasnform.Scale.y). It should make the original circle \ oval.

Check the size of the build with a new shader and a circle from the strip: it turned out 11957 KB. That is, the increment from an empty project is only 5 KB, and that includes the size of the shader.

As a result, we have a handy tool, with which in some cases you can significantly reduce the size of the build. It is suitable not only for Android, but also for any platform supporting shaders.

Source: https://habr.com/ru/post/335454/


All Articles