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