Features of working with Mesh in Unity

    Computer graphics, as is known, is the basis of the gaming industry. In the process of creating graphic content, we inevitably encounter difficulties associated with the difference in its presentation in the creation environment and in the application. The risks of simple human inattention are added to these difficulties. Given the scale of game development, such problems occur either frequently or in large quantities.

    The struggle against such difficulties brought us to the thought of automation and writing articles on this topic. Most of the material will relate to working with Unity 3D , since it is the main development tool in Plarium Krasnodar. Hereinafter, 3D models and textures will be considered as graphic content.

    In this article we will talk about the features of access to data representation of 3D-objects in Unity . The material will be useful primarily for beginners, as well as for those developers who rarely interact with the internal presentation of such models.



    About 3D models in Unity - for the youngest




    In the standard approach, Unity uses the MeshFilter and MeshRenderer components to render the model . MeshFilter refers to the Mesh -asset that represents the model. For most shaders, geometry information is a mandatory minimum component for drawing a model on the screen. Data on texture scanning and animation bones may be missing if they are not involved. How this class is implemented inside and how everything is stored there is a secret for a certain amount of money with seven seals.

    From the outside, the mesh as an object provides access to the following data sets:

    • vertices - a set of positions of the vertices of the geometry in three-dimensional space with its own origin;
    • normals, tangents - sets of normal vectors and tangents to vertices, which are usually used to calculate lighting;
    • uv, uv2, uv3, uv4, uv5, uv6, uv7, uv8 - sets of coordinates for the texture scan;
    • colors, colors32 - sets of vertex color values, a textbook example of which use is mask blending;
    • bindposes - sets of matrices for positioning the vertices relative to the bones;
    • boneWeights - coefficients of the influence of bones on the vertices;
    • triangles - a set of indexes of vertices processed 3 at a time; each such triple represents a polygon (in this case a triangle) of the model.

    Access to information about vertices and polygons is implemented through the corresponding properties ( properties ), each of which returns an array of structures. A person who does not read the documentation rarely works with meshes in Unity , it may not be obvious that whenever you access vertex data in memory, a copy of the corresponding set is created as an array with a length equal to the number of vertices. This nuance is discussed in a small block of documentation . Also warn about this comments to the properties of the class Mesh , as discussed above. The reason for this behavior is the architectural feature of Unity in the context of the Mono runtime.. It can be schematically depicted as follows:



    The engine core (UnityEngine (native)) is isolated from the developer scripts, and its functionality is accessed via the UnityEngine library (C #). In fact, it is an adapter, since most methods serve as a layer for receiving data from the kernel. At the same time, the kernel and the rest of it, including your scripts, rotate under different processes and the script part only knows the list of commands. Thus, direct access to the memory used by the kernel from the script is missing.

    About access to internal data, or How bad things can be


    To demonstrate how bad things can be, let's analyze the amount of Garbage Collector memory being cleaned using the example from the documentation. For ease of profiling, wrap the same code in the Update method.

    publicclassMemoryTest : MonoBehaviour
    {
        public Mesh Mesh;
        privatevoidUpdate()
        {
            for (int i = 0; i < Mesh.vertexCount; i++)
            {
                float x = Mesh.vertices[i].x;
                float y = Mesh.vertices[i].y;
                float z = Mesh.vertices[i].z;
                DoSomething(x, y, z);
            }
        }
        privatevoidDoSomething(float x, float y, float z)
        {
    	//nothing to do
        }
    }
    

    We drove this script with a standard primitive - a sphere (515 vertices). Using the Profiler tool , in the Memory tab, you can see how much memory was marked for cleaning by the garbage collector in each of the frames. On our working machine, this value was ~ 9.2 MB.



    This is quite a lot even for a loaded application, and we here started a scene with one object, on which the simplest script was hung.

    It is important to mention the features of the .Net compiler and code optimization. Walking through the call chain, you may find that calling Mesh.vertices entails calling the extern method of the engine. This prevents the compiler from optimizing the code inside ourUpdate () method, despite the fact that DoSomething () is empty and the variables x, y, z for this reason are unused.

    Now we cache the array of positions at the start.

    publicclassMemoryTest : MonoBehaviour
    {
        public Mesh Mesh;
        private Vector3[] _vertices;
        privatevoidStart()
        {
            _vertices = Mesh.vertices;
        }
        privatevoidUpdate()
        {
            for (int i = 0; i < _vertices.Length; i++)
            {
                float x = _vertices[i].x;
                float y = _vertices[i].y;
                float z = _vertices[i].z;
                DoSomething(x, y, z);
            }
        }
        privatevoidDoSomething(float x, float y, float z)
        {
            //nothing to do
        }
    }



    An average of 6 KB. Another thing!

    This feature was one of the reasons why we had to implement our own structure for storing and processing mesh data.

    How we do it


    While working on large projects, an idea arose to make a tool for analyzing and editing imported graphic content. We will talk about the methods of analysis and transformation in the following articles. Now let's consider the data structure that we decided to write for the convenience of implementing algorithms, taking into account the peculiarities of access to information about the mesh.

    Initially, this structure looked like this:



    Here, the CustomMesh class represents, in fact, a mesh. Separately, in the form of Utility, we implemented the conversion from UntiyEngine.Meshand back. A mesh is defined by its array of triangles. Each triangle contains exactly three edges, which in turn are defined by two vertices. We decided to add to the vertices only the information we need for analysis, namely, position, normal, two channels of the texture scan ( uv0 for the main texture, uv2 for lighting) and color.

    After some time, it became necessary to turn up the hierarchy. For example, to find out from the triangle which mesh it belongs to. In addition, turning Down from CustomMesh to Vertex looked pretentious, and the unreasonable and significant amount of duplicate values ​​got on my nerves. For these reasons, the structure had to be reworked.



    ATCustomMeshPool implemented methods for easy management and access to all processed CustomMesh . At the expense of the MeshId field in each of the entities there is access to the information of the entire mesh. This data structure meets the requirements for the initial tasks. It is easy to expand by adding the appropriate data set to CustomMesh and the necessary methods to Vertex .

    It should be noted that this approach is not optimal in terms of performance. At the same time, most of the algorithms we implement are focused on content analysis in the Unity editor., because of what you do not often have to think about the amount of memory used. For this reason, we literally cache everything we can. We first test the implemented algorithm and then refactor its methods and in some cases simplify data structures to optimize the execution time.

    That's all for now. In the next article, we will discuss how to edit the 3D models already included in the project, and use the considered data structure.

    Also popular now: