< Summary

Class:DCL.FlattenedMaterialsData
Assembly:AvatarMeshCombiner
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/AvatarMeshCombiner.cs
Covered lines:6
Uncovered lines:0
Coverable lines:6
Total lines:182
Line coverage:100% (6 of 6)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
FlattenedMaterialsData(...)0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarMeshCombiner/AvatarMeshCombiner.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Linq;
 3using Unity.Collections;
 4using UnityEngine;
 5using UnityEngine.Rendering;
 6
 7namespace DCL
 8{
 9    /// <summary>
 10    /// This class is used by the AvatarMeshCombiner to combine meshes. Each layer represents a new generated sub-mesh.
 11    /// </summary>
 12    public class CombineLayer
 13    {
 14        public List<SkinnedMeshRenderer> renderers = new List<SkinnedMeshRenderer>();
 15        public Dictionary<Texture2D, int> textureToId = new Dictionary<Texture2D, int>();
 16        public CullMode cullMode;
 17        public bool isOpaque;
 18
 19        public override string ToString()
 20        {
 21            string rendererString = $"renderer count: {renderers?.Count ?? 0}";
 22            string textureIdString = "texture ids: {";
 23
 24            foreach ( var kvp in textureToId )
 25            {
 26                textureIdString += $" tx hash: {kvp.Key.GetHashCode()} id: {kvp.Value} ";
 27            }
 28
 29            textureIdString += "}";
 30
 31            return $"cullMode: {cullMode} - isOpaque: {isOpaque} - {rendererString} - {textureIdString}";
 32        }
 33    }
 34
 35    /// <summary>
 36    /// This class is used to determine the original materials uniform properties
 37    /// will be passed on to UV values. texturePointers and emissionColors are bound to UV channels.
 38    /// Colors are bound to the color channel.
 39    /// </summary>
 40    public class FlattenedMaterialsData
 41    {
 2342        public List<Material> materials = new List<Material>();
 43        public NativeArray<Vector3> texturePointers;
 44        public NativeArray<Vector4> colors;
 45        public NativeArray<Vector4> emissionColors;
 2346        public FlattenedMaterialsData(int vertexCount)
 47        {
 2348            texturePointers = new NativeArray<Vector3>(vertexCount, Allocator.Temp, NativeArrayOptions.UninitializedMemo
 2349            colors = new NativeArray<Vector4>(vertexCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
 2350            emissionColors = new NativeArray<Vector4>(vertexCount, Allocator.Temp, NativeArrayOptions.UninitializedMemor
 2351        }
 52    }
 53
 54    /// <summary>
 55    /// This utility class can combine avatar meshes into a single optimal mesh.
 56    /// </summary>
 57    public static class AvatarMeshCombiner
 58    {
 59        public struct Output
 60        {
 61            public bool isValid;
 62            public Mesh mesh;
 63            public Material[] materials;
 64        }
 65
 66        // These must match the channels defined in the material shader.
 67        // Channels must be defined with the TEXCOORD semantic.
 68        private const int TEXTURE_POINTERS_UV_CHANNEL_INDEX = 2;
 69        private const int EMISSION_COLORS_UV_CHANNEL_INDEX = 3;
 70
 71        /// <summary>
 72        /// CombineSkinnedMeshes combines a list of skinned mesh renderers that share the same bone structure into a
 73        /// single mesh with the fewest numbers of sub-meshes as possible. It will return a list of materials that match
 74        /// the number of sub-meshes to be used with a Renderer component.
 75        /// <br/>
 76        /// The sub-meshes are divided according to the following constraints:
 77        /// <ul>
 78        /// <li>Culling mode</li>
 79        /// <li>Transparency or opacity of each renderer to be combined</li>
 80        /// <li>Texture count of the accumulated renderers, only including emission and albedo textures.</li>
 81        /// </ul>
 82        /// </summary>
 83        /// <param name="bindPoses">Bindposes that will be used by all the renderers.</param>
 84        /// <param name="bones">Bones that will be used by all the renderers.</param>
 85        /// <param name="renderers">Renderers to be combined.</param>
 86        /// <param name="materialAsset">Material asset to be used in the resulting Output object. This Material will be 
 87        /// <returns>An Output object with the mesh and materials. Output.isValid will return true if the combining is s
 88        public static Output CombineSkinnedMeshes(Matrix4x4[] bindPoses, Transform[] bones, SkinnedMeshRenderer[] render
 89        {
 90            Output result = new Output();
 91            (Vector3 pos, Quaternion rot, Vector3 scale)[] bonesTransforms = null;
 92
 93            if(keepPose)
 94                bonesTransforms = bones.Select(x => (x.position, x.rotation, x.localScale)).ToArray();
 95
 96            //
 97            // Reset bones to put character in T pose. Renderers are going to be baked later.
 98            // This is a workaround, it had to be done because renderers original matrices don't match the T pose.
 99            // We need wearables in T pose to properly combine the avatar mesh.
 100            //
 101            AvatarMeshCombinerUtils.ResetBones(bindPoses, bones);
 102
 103            //
 104            // Get combined layers. Layers are groups of renderers that have a id -> tex mapping.
 105            //
 106            // This id is going to get written to uv channels so the material can use up to 12 textures
 107            // in a single draw call.
 108            //
 109            // Layers are divided accounting for the 12 textures limit and transparency/opaque limit.
 110            //
 111            List<CombineLayer> layers = CombineLayerUtils.Slice( renderers );
 112
 113            if ( layers == null )
 114            {
 115                result.isValid = false;
 116                return result;
 117            }
 118
 119            // Here, the final combined mesh is generated. This mesh still doesn't have the UV encoded
 120            // samplers and some needed attributes. Those will be added below.
 121            var combineInstancesData = AvatarMeshCombinerUtils.ComputeCombineInstancesData( layers );
 122            Mesh finalMesh = AvatarMeshCombinerUtils.CombineMeshesWithLayers(combineInstancesData, layers);
 123
 124            // Note about bindPoses and boneWeights reassignment:
 125            //
 126            // This has to be done because CombineMeshes doesn't identify different meshes
 127            // with boneWeights that correspond to the same bones. Also, bindposes are
 128            // stacked and repeated for each mesh when they shouldn't.
 129            //
 130            // This is OK when combining multiple SkinnedMeshRenderers that animate independently,
 131            // but not in our use case.
 132
 133            finalMesh.bindposes = bindPoses;
 134
 135            var bonesPerVertex = AvatarMeshCombinerUtils.CombineBonesPerVertex(layers);
 136            var boneWeights = AvatarMeshCombinerUtils.CombineBonesWeights(layers);
 137            finalMesh.SetBoneWeights(bonesPerVertex, boneWeights);
 138
 139            bonesPerVertex.Dispose();
 140            boneWeights.Dispose();
 141
 142            var flattenedMaterialsData = AvatarMeshCombinerUtils.FlattenMaterials( layers, materialAsset );
 143            finalMesh.SetUVs(EMISSION_COLORS_UV_CHANNEL_INDEX, flattenedMaterialsData.emissionColors);
 144            finalMesh.SetUVs(TEXTURE_POINTERS_UV_CHANNEL_INDEX, flattenedMaterialsData.texturePointers);
 145            finalMesh.SetColors(flattenedMaterialsData.colors);
 146
 147            flattenedMaterialsData.emissionColors.Dispose();
 148            flattenedMaterialsData.texturePointers.Dispose();
 149            flattenedMaterialsData.colors.Dispose();
 150
 151            // Each layer corresponds with a subMesh. This is to take advantage of the sharedMaterials array.
 152            //
 153            // When a renderer has many sub-meshes, each materials array element correspond to the sub-mesh of
 154            // the same index. Each layer needs to be renderer with its own material, so it becomes very useful.
 155            //
 156            var subMeshDescriptors = AvatarMeshCombinerUtils.ComputeSubMeshes( layers );
 157            if ( subMeshDescriptors.Count > 1 )
 158            {
 159                finalMesh.subMeshCount = subMeshDescriptors.Count;
 160                finalMesh.SetSubMeshes(subMeshDescriptors);
 161            }
 162
 163            finalMesh.Optimize();
 164
 165            result.mesh = finalMesh;
 166            result.materials = flattenedMaterialsData.materials.ToArray();
 167            result.isValid = true;
 168
 169            if (keepPose)
 170            {
 171                for (int i = 0; i < bones.Length; i++)
 172                {
 173                    bones[i].position = bonesTransforms[i].pos;
 174                    bones[i].rotation = bonesTransforms[i].rot;
 175                    bones[i].localScale = bonesTransforms[i].scale;
 176                }
 177            }
 178
 179            return result;
 180        }
 181    }
 182}