/// <summary> /// Creates a new mesh. /// </summary> /// <param name="vertices">The mesh vertices.</param> /// <param name="indices">The mesh sub-mesh indices.</param> /// <param name="normals">The mesh normals.</param> /// <param name="tangents">The mesh tangents.</param> /// <param name="colors">The mesh colors.</param> /// <param name="boneWeights">The mesh bone-weights.</param> /// <param name="uvs2D">The mesh 2D UV sets.</param> /// <param name="uvs3D">The mesh 3D UV sets.</param> /// <param name="uvs4D">The mesh 4D UV sets.</param> /// <param name="bindposes">The mesh bindposes.</param> /// <returns>The created mesh.</returns> public static Mesh CreateMesh(Vector3[] vertices, int[][] indices, Vector3[] normals, Vector4[] tangents, Color[] colors, BoneWeight[] boneWeights, List <Vector2>[] uvs2D, List <Vector3>[] uvs3D, List <Vector4>[] uvs4D, Matrix4x4[] bindposes, BlendShape[] blendShapes) { if (vertices == null) { throw new ArgumentNullException(nameof(vertices)); } else if (indices == null) { throw new ArgumentNullException(nameof(indices)); } var newMesh = new Mesh(); int subMeshCount = indices.Length; #if UNITY_MESH_INDEXFORMAT_SUPPORT IndexFormat indexFormat; var indexMinMax = MeshUtils.GetSubMeshIndexMinMax(indices, out indexFormat); newMesh.indexFormat = indexFormat; #endif if (bindposes != null && bindposes.Length > 0) { newMesh.bindposes = bindposes; } newMesh.subMeshCount = subMeshCount; newMesh.vertices = vertices; if (normals != null && normals.Length > 0) { newMesh.normals = normals; } if (tangents != null && tangents.Length > 0) { newMesh.tangents = tangents; } if (colors != null && colors.Length > 0) { newMesh.colors = colors; } if (boneWeights != null && boneWeights.Length > 0) { newMesh.boneWeights = boneWeights; } if (uvs2D != null) { for (int uvChannel = 0; uvChannel < uvs2D.Length; uvChannel++) { if (uvs2D[uvChannel] != null && uvs2D[uvChannel].Count > 0) { newMesh.SetUVs(uvChannel, uvs2D[uvChannel]); } } } if (uvs3D != null) { for (int uvChannel = 0; uvChannel < uvs3D.Length; uvChannel++) { if (uvs3D[uvChannel] != null && uvs3D[uvChannel].Count > 0) { newMesh.SetUVs(uvChannel, uvs3D[uvChannel]); } } } if (uvs4D != null) { for (int uvChannel = 0; uvChannel < uvs4D.Length; uvChannel++) { if (uvs4D[uvChannel] != null && uvs4D[uvChannel].Count > 0) { newMesh.SetUVs(uvChannel, uvs4D[uvChannel]); } } } if (blendShapes != null) { MeshUtils.ApplyMeshBlendShapes(newMesh, blendShapes); } for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++) { var subMeshTriangles = indices[subMeshIndex]; #if UNITY_MESH_INDEXFORMAT_SUPPORT var minMax = indexMinMax[subMeshIndex]; if (indexFormat == UnityEngine.Rendering.IndexFormat.UInt16 && minMax.y > ushort.MaxValue) { int baseVertex = minMax.x; for (int index = 0; index < subMeshTriangles.Length; index++) { subMeshTriangles[index] -= baseVertex; } newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false, baseVertex); } else { newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false, 0); } #else newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false); #endif } newMesh.RecalculateBounds(); return(newMesh); }
/// <summary> /// Creates a new mesh. /// </summary> /// <param name="vertices">The mesh vertices.</param> /// <param name="indices">The mesh sub-mesh indices.</param> /// <param name="normals">The mesh normals.</param> /// <param name="tangents">The mesh tangents.</param> /// <param name="colors">The mesh colors.</param> /// <param name="boneWeights">The mesh bone-weights.</param> /// <param name="uvs2D">The mesh 2D UV sets.</param> /// <param name="uvs3D">The mesh 3D UV sets.</param> /// <param name="uvs4D">The mesh 4D UV sets.</param> /// <param name="bindposes">The mesh bindposes.</param> /// <returns>The created mesh.</returns> public static Mesh CreateMesh(Vector3[] vertices, int[][] indices, Vector3[] normals, Vector4[] tangents, Color[] colors, BoneWeight[] boneWeights, List <Vector2>[] uvs2D, List <Vector3>[] uvs3D, List <Vector4>[] uvs4D, Matrix4x4[] bindposes, BlendShape[] blendShapes) { var newMesh = new Mesh(); int subMeshCount = indices.Length; #if UNITY_MESH_INDEXFORMAT_SUPPORT IndexFormat indexFormat; var indexMinMax = MeshUtils.GetSubMeshIndexMinMax(indices, out indexFormat); newMesh.indexFormat = indexFormat; #endif if (bindposes != null && bindposes.Length > 0) { newMesh.bindposes = bindposes; } newMesh.subMeshCount = subMeshCount; newMesh.vertices = vertices; // If after assigning normals blendshapes are assigned, then blendshapes do not work correctly // In URP and HDRP configurations, so we add blendshapes first and then assign normals if (blendShapes != null) { MeshUtils.ApplyMeshBlendShapes(newMesh, blendShapes); } if (normals != null && normals.Length > 0) { newMesh.normals = normals; } if (tangents != null && tangents.Length > 0) { newMesh.tangents = tangents; } if (colors != null && colors.Length > 0) { newMesh.colors = colors; } if (boneWeights != null && boneWeights.Length > 0) { newMesh.boneWeights = boneWeights; } if (uvs2D != null) { for (int uvChannel = 0; uvChannel < uvs2D.Length; uvChannel++) { if (uvs2D[uvChannel] != null && uvs2D[uvChannel].Count > 0) { newMesh.SetUVs(uvChannel, uvs2D[uvChannel]); } } } if (uvs3D != null) { for (int uvChannel = 0; uvChannel < uvs3D.Length; uvChannel++) { if (uvs3D[uvChannel] != null && uvs3D[uvChannel].Count > 0) { newMesh.SetUVs(uvChannel, uvs3D[uvChannel]); } } } if (uvs4D != null) { for (int uvChannel = 0; uvChannel < uvs4D.Length; uvChannel++) { if (uvs4D[uvChannel] != null && uvs4D[uvChannel].Count > 0) { newMesh.SetUVs(uvChannel, uvs4D[uvChannel]); } } } //if (blendShapes != null) //{ // MeshUtils.ApplyMeshBlendShapes(newMesh, blendShapes); //baw did //} for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++) { var subMeshTriangles = indices[subMeshIndex]; #if UNITY_MESH_INDEXFORMAT_SUPPORT var minMax = indexMinMax[subMeshIndex]; if (indexFormat == UnityEngine.Rendering.IndexFormat.UInt16 && minMax.y > ushort.MaxValue) { int baseVertex = minMax.x; for (int index = 0; index < subMeshTriangles.Length; index++) { subMeshTriangles[index] -= baseVertex; } newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false, baseVertex); } else { newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false, 0); } #else newMesh.SetTriangles(subMeshTriangles, subMeshIndex, false); #endif } newMesh.RecalculateBounds(); return(newMesh); }
/// <summary> /// Generates the LODs and sets up a LOD Group for the specified game object. /// </summary> /// <param name="gameObject">The game object to set up.</param> /// <param name="levels">The LOD levels to set up.</param> /// <param name="autoCollectRenderers">If the renderers under the game object and any children should be automatically collected. /// Enabling this will ignore any renderers defined under each LOD level.</param> /// <param name="simplificationOptions">The mesh simplification options.</param> /// <param name="saveAssetsPath">The path to where the generated assets should be saved. Can be null or empty to use the default path.</param> /// <returns>The generated LOD Group.</returns> public static LODGroup GenerateLODs(GameObject gameObject, LODLevel[] levels, bool autoCollectRenderers, SimplificationOptions simplificationOptions, string saveAssetsPath) { if (gameObject == null) { throw new System.ArgumentNullException(nameof(gameObject)); } else if (levels == null) { throw new System.ArgumentNullException(nameof(levels)); } var transform = gameObject.transform; var existingLodParent = transform.Find(LODParentGameObjectName); if (existingLodParent != null) { throw new System.InvalidOperationException("The game object already appears to have LODs. Please remove them first."); } var existingLodGroup = gameObject.GetComponent <LODGroup>(); if (existingLodGroup != null) { throw new System.InvalidOperationException("The game object already appears to have a LOD Group. Please remove it first."); } saveAssetsPath = ValidateSaveAssetsPath(saveAssetsPath); var lodParentGameObject = new GameObject(LODParentGameObjectName); var lodParent = lodParentGameObject.transform; ParentAndResetTransform(lodParent, transform); var lodGroup = gameObject.AddComponent <LODGroup>(); Renderer[] allRenderers = null; if (autoCollectRenderers) { // Collect all enabled renderers under the game object allRenderers = GetChildRenderersForLOD(gameObject); } var renderersToDisable = new List <Renderer>((allRenderers != null ? allRenderers.Length : 10)); var lods = new LOD[levels.Length]; // No need to get the Renderers again and again we just cache it for once var meshRenderers = (from renderer in allRenderers let meshFilter = renderer.transform.GetComponent <MeshFilter>() where renderer.enabled && renderer as MeshRenderer != null && meshFilter != null && meshFilter.sharedMesh != null select renderer as MeshRenderer).ToArray(); var skinnedMeshRenderers = (from renderer in allRenderers where renderer.enabled && renderer as SkinnedMeshRenderer != null && (renderer as SkinnedMeshRenderer).sharedMesh != null select renderer as SkinnedMeshRenderer).ToArray(); List <Mesh> meshesChangedToReadible = new List <Mesh>(); bool areAnyCombinedLevels = false; foreach (var lodLevel in levels) { if (lodLevel.CombineMeshes) { areAnyCombinedLevels = true; break; } } // If there is any Lod level with the Combine meshes option selected then // we enable Read/Write flag on the original meshes if (areAnyCombinedLevels) { foreach (var renderer in meshRenderers) { var meshFilter = renderer.GetComponent <MeshFilter>(); if (meshFilter == null || meshFilter.sharedMesh == null) { continue; } if (!meshFilter.sharedMesh.isReadable) { MeshUtils.ChangeMeshReadibility(meshFilter.sharedMesh, true, false); if (meshFilter.sharedMesh.isReadable) { meshesChangedToReadible.Add(meshFilter.sharedMesh); } } } foreach (var renderer in skinnedMeshRenderers) { if (renderer == null || renderer.sharedMesh == null) { continue; } if (!renderer.sharedMesh.isReadable) { MeshUtils.ChangeMeshReadibility(renderer.sharedMesh, true, false); if (renderer.sharedMesh.isReadable) { meshesChangedToReadible.Add(renderer.sharedMesh); } } } } System.Exception exception = null; try { for (int levelIndex = 0; levelIndex < levels.Length; levelIndex++) { var level = levels[levelIndex]; var levelGameObject = new GameObject(string.Format("Level{0:00}", levelIndex)); var levelTransform = levelGameObject.transform; ParentAndResetTransform(levelTransform, lodParent); Renderer[] originalLevelRenderers = allRenderers ?? level.Renderers; var levelRenderers = new List <Renderer>((originalLevelRenderers != null ? originalLevelRenderers.Length : 0)); if (originalLevelRenderers != null && originalLevelRenderers.Length > 0) { StaticRenderer[] staticRenderers; SkinnedRenderer[] skinnedRenderers; if (level.CombineMeshes) { staticRenderers = CombineStaticMeshes(transform, levelIndex, meshRenderers); skinnedRenderers = CombineSkinnedMeshes(transform, levelIndex, skinnedMeshRenderers); } else { staticRenderers = GetStaticRenderers(meshRenderers); skinnedRenderers = GetSkinnedRenderers(skinnedMeshRenderers); } if (staticRenderers != null) { for (int rendererIndex = 0; rendererIndex < staticRenderers.Length; rendererIndex++) { var renderer = staticRenderers[rendererIndex]; var mesh = renderer.mesh; // Simplify the mesh if necessary if (level.Quality < 1f) { mesh = SimplifyMesh(mesh, level.Quality, simplificationOptions); SaveLODMeshAsset(mesh, gameObject.name, renderer.name, levelIndex, renderer.mesh.name, saveAssetsPath); if (renderer.isNewMesh) { DestroyObject(renderer.mesh); renderer.mesh = null; } } string rendererName = string.Format("{0:000}_static_{1}", rendererIndex, renderer.name); var levelRenderer = CreateLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, ref level); levelRenderers.Add(levelRenderer); } } if (skinnedRenderers != null) { for (int rendererIndex = 0; rendererIndex < skinnedRenderers.Length; rendererIndex++) { var renderer = skinnedRenderers[rendererIndex]; var mesh = renderer.mesh; // Simplify the mesh if necessary if (level.Quality < 1f) { mesh = SimplifyMesh(mesh, level.Quality, simplificationOptions); SaveLODMeshAsset(mesh, gameObject.name, renderer.name, levelIndex, renderer.mesh.name, saveAssetsPath); if (renderer.isNewMesh) { DestroyObject(renderer.mesh); renderer.mesh = null; } } string rendererName = string.Format("{0:000}_skinned_{1}", rendererIndex, renderer.name); var levelRenderer = CreateSkinnedLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, renderer.rootBone, renderer.bones, ref level); levelRenderers.Add(levelRenderer); } } } foreach (var renderer in originalLevelRenderers) { if (!renderersToDisable.Contains(renderer)) { renderersToDisable.Add(renderer); } } lods[levelIndex] = new LOD(level.ScreenRelativeTransitionHeight, levelRenderers.ToArray()); } } catch (System.Exception ex) { exception = ex; } //Restore meshes Read/Write flag foreach (var mesh in meshesChangedToReadible) { Debug.LogWarning($"Mesh \"{mesh.name}\" was not readible so we marked it readible for the mesh combining process to complete and changed it back to non-readible after completion. This process can slow down LOD generation. You may want to mark this mesh Read/Write enabled in the model import settings, so that next time LOD generation on this model can be faster."); MeshUtils.ChangeMeshReadibility(mesh, false, false); } if (exception != null) { throw exception; } CreateBackup(gameObject, renderersToDisable.ToArray()); foreach (var renderer in renderersToDisable) { renderer.enabled = false; } lodGroup.animateCrossFading = false; lodGroup.SetLODs(lods); return(lodGroup); }
/// <summary> /// Combines an array of meshes into a single mesh. /// </summary> /// <param name="meshes">The array of meshes to combine.</param> /// <param name="transforms">The array of transforms for the meshes.</param> /// <param name="materials">The array of materials for each mesh to combine.</param> /// <param name="bones">The array of bones for each mesh to combine.</param> /// <param name="resultMaterials">The resulting materials for the combined mesh.</param> /// <param name="resultBones">The resulting bones for the combined mesh.</param> /// <returns>The combined mesh.</returns> public static Mesh CombineMeshes(Mesh[] meshes, Matrix4x4[] transforms, Material[][] materials, Transform[][] bones, out Material[] resultMaterials, out Transform[] resultBones) { if (meshes == null) { throw new System.ArgumentNullException(nameof(meshes)); } else if (transforms == null) { throw new System.ArgumentNullException(nameof(transforms)); } else if (materials == null) { throw new System.ArgumentNullException(nameof(materials)); } else if (transforms.Length != meshes.Length) { throw new System.ArgumentException("The array of transforms doesn't have the same length as the array of meshes.", nameof(transforms)); } else if (materials.Length != meshes.Length) { throw new System.ArgumentException("The array of materials doesn't have the same length as the array of meshes.", nameof(materials)); } else if (bones != null && bones.Length != meshes.Length) { throw new System.ArgumentException("The array of bones doesn't have the same length as the array of meshes.", nameof(bones)); } int totalVertexCount = 0; int totalSubMeshCount = 0; for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; if (mesh == null) { throw new System.ArgumentException(string.Format("The mesh at index {0} is null.", meshIndex), nameof(meshes)); } else if (!mesh.isReadable) { throw new System.ArgumentException(string.Format("The mesh at index {0} is not readable.", meshIndex), nameof(meshes)); } totalVertexCount += mesh.vertexCount; totalSubMeshCount += mesh.subMeshCount; // Validate the mesh materials var meshMaterials = materials[meshIndex]; if (meshMaterials == null) { throw new System.ArgumentException(string.Format("The materials for mesh at index {0} is null.", meshIndex), nameof(materials)); } else if (meshMaterials.Length != mesh.subMeshCount) { throw new System.ArgumentException(string.Format("The materials for mesh at index {0} doesn't match the submesh count ({1} != {2}).", meshIndex, meshMaterials.Length, mesh.subMeshCount), nameof(materials)); } for (int materialIndex = 0; materialIndex < meshMaterials.Length; materialIndex++) { if (meshMaterials[materialIndex] == null) { throw new System.ArgumentException(string.Format("The material at index {0} for mesh at index {1} is null.", materialIndex, meshIndex), nameof(materials)); } } // Validate the mesh bones if (bones != null) { var meshBones = bones[meshIndex]; if (meshBones == null) { throw new System.ArgumentException(string.Format("The bones for mesh at index {0} is null.", meshIndex), nameof(meshBones)); } for (int boneIndex = 0; boneIndex < meshBones.Length; boneIndex++) { if (meshBones[boneIndex] == null) { throw new System.ArgumentException(string.Format("The bone at index {0} for mesh at index {1} is null.", boneIndex, meshIndex), nameof(meshBones)); } } } } var combinedVertices = new List <Vector3>(totalVertexCount); var combinedIndices = new List <int[]>(totalSubMeshCount); List <Vector3> combinedNormals = null; List <Vector4> combinedTangents = null; List <Color> combinedColors = null; List <BoneWeight> combinedBoneWeights = null; var combinedUVs = new List <Vector4> [MeshUtils.UVChannelCount]; List <Matrix4x4> usedBindposes = null; List <Transform> usedBones = null; var usedMaterials = new List <Material>(totalSubMeshCount); var materialMap = new Dictionary <Material, int>(totalSubMeshCount); int currentVertexCount = 0; for (int meshIndex = 0; meshIndex < meshes.Length; meshIndex++) { var mesh = meshes[meshIndex]; var meshTransform = transforms[meshIndex]; var meshMaterials = materials[meshIndex]; var meshBones = (bones != null ? bones[meshIndex] : null); int subMeshCount = mesh.subMeshCount; int meshVertexCount = mesh.vertexCount; var meshVertices = mesh.vertices; var meshNormals = mesh.normals; var meshTangents = mesh.tangents; var meshUVs = MeshUtils.GetMeshUVs(mesh); var meshColors = mesh.colors; var meshBoneWeights = mesh.boneWeights; var meshBindposes = mesh.bindposes; // Transform vertices with bones to keep only one bindpose if (meshBones != null && meshBoneWeights != null && meshBoneWeights.Length > 0 && meshBindposes != null && meshBindposes.Length > 0 && meshBones.Length == meshBindposes.Length) { if (usedBindposes == null) { usedBindposes = new List <Matrix4x4>(meshBindposes); usedBones = new List <Transform>(meshBones); } bool bindPoseMismatch = false; int[] boneIndices = new int[meshBones.Length]; for (int i = 0; i < meshBones.Length; i++) { int usedBoneIndex = usedBones.IndexOf(meshBones[i]); if (usedBoneIndex == -1) { usedBoneIndex = usedBones.Count; usedBones.Add(meshBones[i]); usedBindposes.Add(meshBindposes[i]); } else { if (meshBindposes[i] != usedBindposes[usedBoneIndex]) { bindPoseMismatch = true; } } boneIndices[i] = usedBoneIndex; } // If any bindpose is mismatching, we correct it first if (bindPoseMismatch) { var correctedBindposes = new Matrix4x4[meshBindposes.Length]; for (int i = 0; i < meshBindposes.Length; i++) { int usedBoneIndex = boneIndices[i]; correctedBindposes[i] = usedBindposes[usedBoneIndex]; } TransformVertices(meshVertices, meshBoneWeights, meshBindposes, correctedBindposes); } // Then we remap the bones RemapBones(meshBoneWeights, boneIndices); } // Transforms the vertices, normals and tangents using the mesh transform TransformVertices(meshVertices, ref meshTransform); TransformNormals(meshNormals, ref meshTransform); TransformTangents(meshTangents, ref meshTransform); // Copy vertex positions & attributes CopyVertexPositions(combinedVertices, meshVertices); CopyVertexAttributes(ref combinedNormals, meshNormals, currentVertexCount, meshVertexCount, totalVertexCount, new Vector3(1f, 0f, 0f)); CopyVertexAttributes(ref combinedTangents, meshTangents, currentVertexCount, meshVertexCount, totalVertexCount, new Vector4(0f, 0f, 1f, 1f)); CopyVertexAttributes(ref combinedColors, meshColors, currentVertexCount, meshVertexCount, totalVertexCount, new Color(1f, 1f, 1f, 1f)); CopyVertexAttributes(ref combinedBoneWeights, meshBoneWeights, currentVertexCount, meshVertexCount, totalVertexCount, new BoneWeight()); for (int channel = 0; channel < meshUVs.Length; channel++) { CopyVertexAttributes(ref combinedUVs[channel], meshUVs[channel], currentVertexCount, meshVertexCount, totalVertexCount, new Vector4(0f, 0f, 0f, 0f)); } for (int subMeshIndex = 0; subMeshIndex < subMeshCount; subMeshIndex++) { var subMeshMaterial = meshMaterials[subMeshIndex]; #if UNITY_MESH_INDEXFORMAT_SUPPORT var subMeshIndices = mesh.GetTriangles(subMeshIndex, true); #else var subMeshIndices = mesh.GetTriangles(subMeshIndex); #endif if (currentVertexCount > 0) { for (int index = 0; index < subMeshIndices.Length; index++) { subMeshIndices[index] += currentVertexCount; } } int existingSubMeshIndex; if (materialMap.TryGetValue(subMeshMaterial, out existingSubMeshIndex)) { combinedIndices[existingSubMeshIndex] = MergeArrays(combinedIndices[existingSubMeshIndex], subMeshIndices); } else { int materialIndex = combinedIndices.Count; materialMap.Add(subMeshMaterial, materialIndex); usedMaterials.Add(subMeshMaterial); combinedIndices.Add(subMeshIndices); } } currentVertexCount += meshVertexCount; } var resultVertices = combinedVertices.ToArray(); var resultIndices = combinedIndices.ToArray(); var resultNormals = (combinedNormals != null ? combinedNormals.ToArray() : null); var resultTangents = (combinedTangents != null ? combinedTangents.ToArray() : null); var resultColors = (combinedColors != null ? combinedColors.ToArray() : null); var resultBoneWeights = (combinedBoneWeights != null ? combinedBoneWeights.ToArray() : null); var resultUVs = combinedUVs.ToArray(); var resultBindposes = (usedBindposes != null ? usedBindposes.ToArray() : null); resultMaterials = usedMaterials.ToArray(); resultBones = (usedBones != null ? usedBones.ToArray() : null); return(MeshUtils.CreateMesh(resultVertices, resultIndices, resultNormals, resultTangents, resultColors, resultBoneWeights, resultUVs, resultBindposes, null)); }