/// <summary> /// Texture 合并管线第 3 步,创建 Atlas 并保存资源 /// </summary> /// <returns></returns> internal static void Step3_BuildAndSaveAtlasesAndStoreResults( TextureCombinePipelineData data, TextureCombineHandler combiner, ITextureCombineAtlasPacker packer, AtlasPackingResult atlasPackingResult, EditorMethodsInterface textureEditorMethods, AtlasesAndRects resultAtlasesAndRects) { //run the garbage collector to free up as much memory as possible before bake to reduce MissingReferenceException problems GC.Collect(); Texture2D[] atlases = new Texture2D[data.numAtlases]; //StringBuilder report = GenerateReport(data); //创建图集 packer.CreateAtlases(data, combiner, atlasPackingResult, atlases, textureEditorMethods); data.nonTexturePropertyBlender.AdjustNonTextureProperties(data.ResultMaterial, data.texPropertyNames, data.distinctMaterialTextures, textureEditorMethods); if (data.distinctMaterialTextures.Count > 0) { data.distinctMaterialTextures[0].AdjustResultMaterialNonTextureProperties(data.ResultMaterial, data.texPropertyNames); } List <MaterialAndUVRect> mat2rect_map = new List <MaterialAndUVRect>(); for (int i = 0; i < data.distinctMaterialTextures.Count; i++) { MaterialPropTexturesSet texSet = data.distinctMaterialTextures[i]; List <MatAndTransformToMerged> mats = texSet.matsAndGOs.mats; Rect allPropsUseSameTiling_encapsulatingSamplingRect; Rect propsUseDifferntTiling_obUVRect; texSet.GetRectsForTextureBakeResults(out allPropsUseSameTiling_encapsulatingSamplingRect, out propsUseDifferntTiling_obUVRect); for (int j = 0; j < mats.Count; j++) { Rect allPropsUseSameTiling_sourceMaterialTiling = texSet.GetMaterialTilingRectForTextureBakerResults(j); MaterialAndUVRect key = new MaterialAndUVRect( mats[j].mat, atlasPackingResult.rects[i], texSet.allTexturesUseSameMatTiling, allPropsUseSameTiling_sourceMaterialTiling, allPropsUseSameTiling_encapsulatingSamplingRect, propsUseDifferntTiling_obUVRect, texSet.tilingTreatment, mats[j].objName); if (!mat2rect_map.Contains(key)) { mat2rect_map.Add(key); } } } resultAtlasesAndRects.atlases = atlases; // one per texture on result shader resultAtlasesAndRects.texPropertyNames = ShaderTextureProperty.GetNames(data.texPropertyNames); // one per texture on source shader resultAtlasesAndRects.originMatToRect_map = mat2rect_map; combiner._destroyAllTemporaryTextures(); }
internal static void CreateTemporaryTexturesForAtlas(List <MaterialPropTexturesSet> distinctMaterialTextures, TextureCombineHandler combiner, int propIdx, TextureCombinePipelineData data) { for (int texSetIdx = 0; texSetIdx < data.distinctMaterialTextures.Count; texSetIdx++) { MaterialPropTexturesSet txs = data.distinctMaterialTextures[texSetIdx]; MaterialPropTexture matTex = txs.ts[propIdx]; if (matTex.isNull) { //create a small 16 x 16 texture to use in the atlas Color col = data.nonTexturePropertyBlender.GetColorForTemporaryTexture(txs.matsAndGOs.mats[0].mat, data.texPropertyNames[propIdx]); txs.CreateColoredTexToReplaceNull(data.texPropertyNames[propIdx].name, propIdx, data.fixOutOfBoundsUVs, combiner, col); } } }
public override void CreateAtlases( TextureCombinePipelineData data, TextureCombineHandler combiner, AtlasPackingResult packedAtlasRects, Texture2D[] atlases, EditorMethodsInterface textureEditorMethods) { Rect[] uvRects = packedAtlasRects.rects; int atlasSizeX = packedAtlasRects.atlasX; int atlasSizeY = packedAtlasRects.atlasY; Debug.Log("Generated atlas will be " + atlasSizeX + "x" + atlasSizeY); for (int propIdx = 0; propIdx < data.numAtlases; propIdx++) { Texture2D atlas = null; ShaderTextureProperty property = data.texPropertyNames[propIdx]; if (!TextureCombinePipeline.ShouldWeCreateAtlasForThisProperty(propIdx, data.considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) { atlas = null; Debug.Log("=== Not creating atlas for " + property.name + " because textures are null and default value parameters are the same."); } else { Debug.Log("=== Creating atlas for " + property.name); GC.Collect(); CreateTemporaryTexturesForAtlas(data.distinctMaterialTextures, combiner, propIdx, data); //use a jagged array because it is much more efficient in memory Color[][] atlasPixels = new Color[atlasSizeY][]; for (int j = 0; j < atlasPixels.Length; j++) { atlasPixels[j] = new Color[atlasSizeX]; } bool isNormalMap = false; if (property.isNormalMap) { isNormalMap = true; } for (int texSetIdx = 0; texSetIdx < data.distinctMaterialTextures.Count; texSetIdx++) { MaterialPropTexturesSet texSet = data.distinctMaterialTextures[texSetIdx]; MaterialPropTexture matTex = texSet.ts[propIdx]; string s = "Creating Atlas '" + property.name + "' texture " + matTex.GetTexName(); Debug.Log(string.Format("Adding texture {0} to atlas {1} for texSet {2} srcMat {3}", matTex.GetTexName(), property.name, texSetIdx, texSet.matsAndGOs.mats[0].GetMaterialName())); Rect r = uvRects[texSetIdx]; Texture2D t = texSet.ts[propIdx].GetTexture2D(); int x = Mathf.RoundToInt(r.x * atlasSizeX); int y = Mathf.RoundToInt(r.y * atlasSizeY); int ww = Mathf.RoundToInt(r.width * atlasSizeX); int hh = Mathf.RoundToInt(r.height * atlasSizeY); if (ww == 0 || hh == 0) { Debug.LogError("Image in atlas has no height or width " + r); } if (textureEditorMethods != null) { textureEditorMethods.SetReadWriteFlag(t, true, true); } DRect samplingRect = texSet.ts[propIdx].GetEncapsulatingSamplingRect(); CopyScaledAndTiledToAtlas(texSet.ts[propIdx], texSet, property, samplingRect, x, y, ww, hh, packedAtlasRects.padding[texSetIdx], atlasPixels, isNormalMap, data, combiner); } atlas = new Texture2D(atlasSizeX, atlasSizeY, TextureFormat.ARGB32, true); for (int j = 0; j < atlasPixels.Length; j++) { atlas.SetPixels(0, j, atlasSizeX, 1, atlasPixels[j]); } atlas.Apply(); Debug.Log("Saving atlas " + property.name + " w=" + atlas.width + " h=" + atlas.height); } atlases[propIdx] = atlas; if (data.saveAtlasesAsAssets && textureEditorMethods != null) { textureEditorMethods.SaveAtlasToAssetDatabase(atlases[propIdx], data.texPropertyNames[propIdx], propIdx, data.ResultMaterial); } else { data.ResultMaterial.SetTexture(data.texPropertyNames[propIdx].name, atlases[propIdx]); } data.ResultMaterial.SetTextureOffset(data.texPropertyNames[propIdx].name, Vector2.zero); data.ResultMaterial.SetTextureScale(data.texPropertyNames[propIdx].name, Vector2.one); combiner._destroyTemporaryTextures(data.texPropertyNames[propIdx].name); } }
internal static void CopyScaledAndTiledToAtlas(MaterialPropTexture source, MaterialPropTexturesSet sourceMaterial, ShaderTextureProperty shaderPropertyName, DRect srcSamplingRect, int targX, int targY, int targW, int targH, AtlasPadding padding, Color[][] atlasPixels, bool isNormalMap, TextureCombinePipelineData data, TextureCombineHandler combiner) { //HasFinished = false; Texture2D t = source.GetTexture2D(); Debug.Log(string.Format("CopyScaledAndTiledToAtlas: {0} inAtlasX={1} inAtlasY={2} inAtlasW={3} inAtlasH={4} paddX={5} paddY={6} srcSamplingRect={7}", t, targX, targY, targW, targH, padding.leftRight, padding.topBottom, srcSamplingRect)); float newWidth = targW; float newHeight = targH; float scx = (float)srcSamplingRect.width; float scy = (float)srcSamplingRect.height; float ox = (float)srcSamplingRect.x; float oy = (float)srcSamplingRect.y; int w = (int)newWidth; int h = (int)newHeight; if (data.considerNonTextureProperties) { t = combiner._createTextureCopy(shaderPropertyName.name, t); t = data.nonTexturePropertyBlender.TintTextureWithTextureCombiner(t, sourceMaterial, shaderPropertyName); } for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { float u = i / newWidth * scx + ox; float v = j / newHeight * scy + oy; atlasPixels[targY + j][targX + i] = t.GetPixelBilinear(u, v); } } //bleed the border colors into the padding for (int i = 0; i < w; i++) { for (int j = 1; j <= padding.topBottom; j++) { //top margin atlasPixels[(targY - j)][targX + i] = atlasPixels[(targY)][targX + i]; //bottom margin atlasPixels[(targY + h - 1 + j)][targX + i] = atlasPixels[(targY + h - 1)][targX + i]; } } for (int j = 0; j < h; j++) { for (int i = 1; i <= padding.leftRight; i++) { //left margin atlasPixels[(targY + j)][targX - i] = atlasPixels[(targY + j)][targX]; //right margin atlasPixels[(targY + j)][targX + w + i - 1] = atlasPixels[(targY + j)][targX + w - 1]; } } //corners for (int i = 1; i <= padding.leftRight; i++) { for (int j = 1; j <= padding.topBottom; j++) { atlasPixels[(targY - j)][targX - i] = atlasPixels[targY][targX]; atlasPixels[(targY + h - 1 + j)][targX - i] = atlasPixels[(targY + h - 1)][targX]; atlasPixels[(targY + h - 1 + j)][targX + w + i - 1] = atlasPixels[(targY + h - 1)][targX + w - 1]; atlasPixels[(targY - j)][targX + w + i - 1] = atlasPixels[targY][targX + w - 1]; } } }
//第二步:计算每个材质的属性对应的 Textures 统一尺寸 //每种材质(_mainTex,凹凸,Spec ect ...)中的纹理必须大小相同 //计算要使用的最佳尺寸。 考虑平铺 //如果图集中只有一种纹理会使用原始大小 internal static void Step2_CalculateIdealSizesForTexturesInAtlasAndPadding( TextureCombinePipelineData data, EditorMethodsInterface textureEditorMethods) { MaterialPropTexture.readyToBuildAtlases = true; data.allTexturesAreNullAndSameColor = CalculateAllTexturesAreNullAndSameColor(data); //计算 atlas 矩形尺寸 int padding = data.atlasPadding; if (data.distinctMaterialTextures.Count == 1 && data.fixOutOfBoundsUVs == false && data.considerNonTextureProperties == false) { Debug.Log("所有游戏物体使用相同的材质.将使用 Original textures ."); padding = 0; data.distinctMaterialTextures[0].SetThisIsOnlyTexSetInAtlasTrue(); data.distinctMaterialTextures[0].SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(TextureTilingTreatment.edgeToEdgeXY); } Debug.Assert(data.allTexturesAreNullAndSameColor.Length == data.texPropertyNames.Count, "allTexturesAreNullAndSameColor array must be the same length of texPropertyNames."); for (int i = 0; i < data.distinctMaterialTextures.Count; i++) { Debug.Log("为 TexSet " + i + " of " + data.distinctMaterialTextures.Count + "计算合适的尺寸"); MaterialPropTexturesSet txs = data.distinctMaterialTextures[i]; txs.idealWidth = 1; txs.idealHeight = 1; int tWidth = 1; int tHeight = 1; Debug.Assert(txs.ts.Length == data.texPropertyNames.Count, "length of arrays in each element of distinctMaterialTextures must be texPropertyNames.Count"); //在 MaterialPropTexturesSet 中所有 MaterialPropTextures 应为相同尺寸 for (int propIdx = 0; propIdx < data.texPropertyNames.Count; propIdx++) { if (ShouldWeCreateAtlasForThisProperty(propIdx, data.considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) { MaterialPropTexture matTex = txs.ts[propIdx]; Debug.Log(string.Format("为 texSet {0} ,property {1} 计算合适尺寸", i, data.texPropertyNames[propIdx].name)); if (!matTex.matTilingRect.size.Equals(Vector2.one) && data.distinctMaterialTextures.Count > 1) { Debug.LogWarning("Texture " + matTex.GetTexName() + "is tiled by " + matTex.matTilingRect.size + " tiling will be baked into a texture with maxSize:" + data.maxTilingBakeSize); } if (!txs.obUVscale.Equals(Vector2.one) && data.distinctMaterialTextures.Count > 1 && data.fixOutOfBoundsUVs) { Debug.LogWarning("Texture " + matTex.GetTexName() + " has out of bounds UVs that effectively tile by " + txs.obUVscale + " tiling will be baked into a texture with maxSize:" + data.maxTilingBakeSize); } if (matTex.isNull) { txs.SetEncapsulatingRect(propIdx, data.fixOutOfBoundsUVs); Debug.Log(string.Format("No source texture creating a 16x16 texture for {0} texSet {1} srcMat {2}", data.texPropertyNames[propIdx].name, i, txs.matsAndGOs.mats[0].GetMaterialName())); } if (!matTex.isNull) { Vector2 dim = GetAdjustedForScaleAndOffset2Dimensions(matTex, txs.obUVoffset, txs.obUVscale, data); if ((int)(dim.x * dim.y) > tWidth * tHeight) { Debug.Log(" 材质texture " + matTex.GetTexName() + " " + dim + " 需要比" + tWidth + " " + tHeight + "更大的尺寸"); tWidth = (int)dim.x; tHeight = (int)dim.y; } } } } if (data.resizePowerOfTwoTextures) { if (tWidth <= padding * 5) { Debug.LogWarning(String.Format("Some of the textures have widths close to the size of the padding. " + "It is not recommended to use _resizePowerOfTwoTextures with widths this small.", txs.ToString())); } if (tHeight <= padding * 5) { Debug.LogWarning(String.Format("Some of the textures have heights close to the size of the padding. " + "It is not recommended to use _resizePowerOfTwoTextures with heights this small.", txs.ToString())); } if (IsPowerOfTwo(tWidth)) { tWidth -= padding * 2; } if (IsPowerOfTwo(tHeight)) { tHeight -= padding * 2; } if (tWidth < 1) { tWidth = 1; } if (tHeight < 1) { tHeight = 1; } } Debug.Log("Ideal size is " + tWidth + " " + tHeight); txs.idealWidth = tWidth; txs.idealHeight = tHeight; } data.atlasPadding = padding; }
/// <summary> /// 第一步: /// 写入 TexturePipelineData 的 MaterialPropTexturesSet 列表,和 usedObjsToMesh 列表 /// 每个TexSet在 Atlas 中都是一个矩形。 /// 如果 allowedMaterialsFilter (过滤器)为空,则将收集 allObjsToMesh 上的所有材质,usedObjsToMesh 将与allObjsToMesh相同 /// 否则,将仅包括 allowedMaterialsFilter 中的材质,usedObjsToMesh将是使用这些材料的objs。 /// </summary> internal static void Step1_CollectDistinctMatTexturesAndUsedObjects(TextureCombinePipelineData data, EditorMethodsInterface textureEditorMethods, List <GameObject> usedObjsToMesh) { // 收集UsedObjects上不同的材质纹理 bool outOfBoundsUVs = false; Dictionary <int, MeshAnalysisResult[]> meshAnalysisResultsCache = new Dictionary <int, MeshAnalysisResult[]>(); //cache results for (int i = 0; i < data.allObjsToMesh.Count; i++) { GameObject obj = data.allObjsToMesh[i]; if (obj == null) { Debug.LogError("合并游戏物体列表中包含空物体"); return; } Mesh sharedMesh = MeshBakerUtility.GetMesh(obj); if (sharedMesh == null) { Debug.LogError("游戏物体 " + obj.name + " 网格为空"); return; } Material[] sharedMaterials = MeshBakerUtility.GetGOMaterials(obj); if (sharedMaterials.Length == 0) { Debug.LogError("游戏物体 " + obj.name + " 材质为空."); return; } //analyze mesh or grab cached result of previous analysis, stores one result for each submesh //处理网格数据 MeshAnalysisResult[] meshAnalysisResults; //每个游戏物体的主网格子网格数据数组 if (!meshAnalysisResultsCache.TryGetValue(sharedMesh.GetInstanceID(), out meshAnalysisResults)) { //获取参与合并物体的网格分析数据 meshAnalysisResults = new MeshAnalysisResult[sharedMesh.subMeshCount]; for (int j = 0; j < sharedMesh.subMeshCount; j++) { MeshBakerUtility.hasOutOfBoundsUVs(sharedMesh, ref meshAnalysisResults[j], j); if (data.normalizeTexelDensity) { meshAnalysisResults[j].submeshArea = GetSubmeshArea(sharedMesh, j); } if (data.fixOutOfBoundsUVs && !meshAnalysisResults[j].hasUVs) { meshAnalysisResults[j].uvRect = new Rect(0, 0, 1, 1); Debug.LogWarning("Mesh for object " + obj + " has no UV channel but 'consider UVs' is enabled." + " Assuming UVs will be generated filling 0,0,1,1 rectangle."); } } meshAnalysisResultsCache.Add(sharedMesh.GetInstanceID(), meshAnalysisResults); } //处理材质数据 for (int matIdx = 0; matIdx < sharedMaterials.Length; matIdx++) { Material mat = sharedMaterials[matIdx]; // 材质过滤器 if (data.allowedMaterialsFilter != null && !data.allowedMaterialsFilter.Contains(mat)) { continue; } outOfBoundsUVs = outOfBoundsUVs || meshAnalysisResults[matIdx].hasOutOfBoundsUVs; if (mat.name.Contains("(Instance)")) { Debug.LogError("The sharedMaterial on object " + obj.name + " has been 'Instanced'." + " This was probably caused by a script accessing the meshRender.material property in the editor. " + " The material to UV Rectangle mapping will be incorrect. " + "To fix this recreate the object from its prefab or re-assign its material from the correct asset."); return; } if (data.fixOutOfBoundsUVs) { if (!MeshBakerUtility.AreAllSharedMaterialsDistinct(sharedMaterials)) { Debug.LogWarning("游戏物体 " + obj.name + " 使用相同的材质在多个子网格. " + "可能生成奇怪的 resultAtlasesAndRects,尤其是与 _fixOutOfBoundsUVs 为 true 时"); } } //材质属性 Texutre 信息 MaterialPropTexture[] mts = new MaterialPropTexture[data.texPropertyNames.Count]; for (int propIdx = 0; propIdx < data.texPropertyNames.Count; propIdx++) { Texture tx = null; Vector2 scale = Vector2.one; Vector2 offset = Vector2.zero; float texelDensity = 0f; if (mat.HasProperty(data.texPropertyNames[propIdx].name)) { Texture txx = GetTextureConsideringStandardShaderKeywords(data.ResultMaterial.shader.name, mat, data.texPropertyNames[propIdx].name); if (txx != null) { if (txx is Texture2D) { //TextureFormat 验证 tx = txx; TextureFormat f = ((Texture2D)tx).format; bool isNormalMap = false; if (!Application.isPlaying && textureEditorMethods != null) { isNormalMap = textureEditorMethods.IsNormalMap((Texture2D)tx); } if ((f == TextureFormat.ARGB32 || f == TextureFormat.RGBA32 || f == TextureFormat.BGRA32 || f == TextureFormat.RGB24 || f == TextureFormat.Alpha8) && !isNormalMap) //DXT5 does not work { //可使用 } else { //TRIED to copy texture using tex2.SetPixels(tex1.GetPixels()) but bug in 3.5 means DTX1 and 5 compressed textures come out skewe //尝试使用tex2.SetPixels(tex1.GetPixels())复制纹理,但是3.5中的bug意味着DTX1和5压缩纹理出现扭曲 if (Application.isPlaying && data.packingAlgorithm != PackingAlgorithmEnum.MeshBakerTexturePacker_Fast) { Debug.LogWarning("合并列表中,游戏物体 " + obj.name + " 所使用的 Texture " + tx.name + " 使用的格式 " + f + "不是: ARGB32, RGBA32, BGRA32, RGB24, Alpha8 或 DXT. " + "无法在运行时重新设置尺寸" + "If format says 'compressed' try changing it to 'truecolor'"); return; } else { tx = (Texture2D)mat.GetTexture(data.texPropertyNames[propIdx].name); } } } else { Debug.LogError("合并列表中,游戏物体 " + obj.name + " 渲染网格使用的 Texture 不是 Texture2D. "); return; } } //像素密度 if (tx != null && data.normalizeTexelDensity) { //不考虑平铺和UV采样超出范围 if (meshAnalysisResults[propIdx].submeshArea == 0) { texelDensity = 0f; } else { texelDensity = (tx.width * tx.height) / (meshAnalysisResults[propIdx].submeshArea); } } //规格,偏移 GetMaterialScaleAndOffset(mat, data.texPropertyNames[propIdx].name, out offset, out scale); } mts[propIdx] = new MaterialPropTexture(tx, offset, scale, texelDensity); } // 收集材质参数值的平均值 data.nonTexturePropertyBlender.CollectAverageValuesOfNonTextureProperties(data.ResultMaterial, mat); Vector2 obUVscale = new Vector2(meshAnalysisResults[matIdx].uvRect.width, meshAnalysisResults[matIdx].uvRect.height); Vector2 obUVoffset = new Vector2(meshAnalysisResults[matIdx].uvRect.x, meshAnalysisResults[matIdx].uvRect.y); //Add to distinct set of textures if not already there TextureTilingTreatment tilingTreatment = TextureTilingTreatment.none; if (data.fixOutOfBoundsUVs) { tilingTreatment = TextureTilingTreatment.considerUVs; } //合并信息 distinctMaterialTextures 数据设置 //材质各参数 Texture,及 UV 偏移数据映射 MaterialPropTexturesSet setOfTexs = new MaterialPropTexturesSet(mts, obUVoffset, obUVscale, tilingTreatment); //one of these per submesh //材质及各变化参数Rect 数据 MatAndTransformToMerged matt = new MatAndTransformToMerged(new DRect(obUVoffset, obUVscale), data.fixOutOfBoundsUVs, mat); setOfTexs.matsAndGOs.mats.Add(matt); MaterialPropTexturesSet setOfTexs2 = data.distinctMaterialTextures.Find(x => x.IsEqual(setOfTexs, data.fixOutOfBoundsUVs, data.nonTexturePropertyBlender)); if (setOfTexs2 != null) { setOfTexs = setOfTexs2; } else { data.distinctMaterialTextures.Add(setOfTexs); } if (!setOfTexs.matsAndGOs.mats.Contains(matt)) { setOfTexs.matsAndGOs.mats.Add(matt); } if (!setOfTexs.matsAndGOs.gos.Contains(obj)) { setOfTexs.matsAndGOs.gos.Add(obj); //已使用 游戏物体 if (!usedObjsToMesh.Contains(obj)) { usedObjsToMesh.Add(obj); } } } } Debug.Log(string.Format("第一阶段完成;" + "参与合并的游戏物体的不同材质,各自包含与shader属性对应的不同的纹理,收集到 {0} 组 textures,即 {0} 个不同的材质," + "fixOutOfBoundsUV:{1} " + "considerNonTextureProperties:{2}", data.distinctMaterialTextures.Count, data.fixOutOfBoundsUVs, data.considerNonTextureProperties)); if (data.distinctMaterialTextures.Count == 0) { Debug.LogError("None of the source object materials matched any of the allowed materials for submesh with result material: " + data.ResultMaterial); return; } TextureCombinerMerging merger = new TextureCombinerMerging(data.considerNonTextureProperties, data.nonTexturePropertyBlender, data.fixOutOfBoundsUVs); merger.MergeOverlappingDistinctMaterialTexturesAndCalcMaterialSubrects(data.distinctMaterialTextures); }