Unity性能优化之特效合并
特效合并,意思是说将粒子所用的零碎图片,以shader为单位合并成一张图集,好处就是可以降低draw call。试想,合并前每个粒子使用一个material,而每一个material就要占用一个drawcall,而合并后多个粒子可以用同一个material,这样就降低了drawcall,提升了性能。
转载请注明出处:http://www.cnblogs.com/jietian331/p/8625078.html
合并工具的代码如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine; namespace AssetBundle
{
public class ParticleSystemCombiner : ScriptableObject
{
public const string
AtlasFolder = "Assets/Cloth/Resources/ParticleSystemAtlas",
ParticleAtlasPath = "Assets/Cloth/Resources/ParticleSystemAtlas/particle_atlas.prefab",
SettingFilepath = "Assets/Editor/ParticleSystemCombinerSetting.csv"; static string[] EffectObjFolders = new string[]
{
"Assets/Cloth/Resources/Effect/Cloth",
"Assets/Cloth/Resources/Model/Equip",
}; static ParticleAtlases s_atlasesData;
static List<string> s_materials;
static Dictionary<string, int> s_texturesSize; static ParticleAtlases AtlasesData
{
get
{
if (s_atlasesData == null)
s_atlasesData = AssetDatabase.LoadAssetAtPath<ParticleAtlases>(ParticleAtlasPath);
return s_atlasesData;
}
} static Dictionary<string, int> TexturesSize
{
get
{
if (s_texturesSize == null)
{
s_texturesSize = new Dictionary<string, int>(); string[] lines = File.ReadAllLines(SettingFilepath);
bool decode = false;
foreach (var line in lines)
{
if (!decode)
{
if (line.StartsWith("# Texture Size"))
{
decode = true;
}
}
else
{
if (line.StartsWith("#"))
{
decode = false;
}
} if (decode)
{
string[] words = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (words.Length > )
{
string name = words[];
int size;
int.TryParse(words[], out size);
if (size != )
s_texturesSize[name] = size;
}
}
}
}
return s_texturesSize;
}
} static List<string> NotCombineTextures
{
get
{
List<string> list = new List<string>();
string[] lines = File.ReadAllLines(SettingFilepath);
bool decode = false;
foreach (var line in lines)
{
if (!decode)
{
if (line.StartsWith("# Not Combine Textures"))
{
decode = true;
}
}
else
{
if (line.StartsWith("#"))
{
decode = false;
}
} if (decode)
{
string[] words = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (words.Length > && !string.IsNullOrEmpty(words[]))
{
list.Add(words[]);
}
}
}
return list;
}
} #region for build public static void ClearCache()
{
s_atlasesData = null;
s_materials = null;
} public static List<ParticleAtlases.TextureItem> GetParticlesUsedAtlas(GameObject effectObj)
{
List<ParticleAtlases.TextureItem> texturesData = new List<ParticleAtlases.TextureItem>();
ParticleSystem[] particles = effectObj.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem ps in particles)
{
ParticleSystemRenderer render = ps.GetComponent<ParticleSystemRenderer>();
if (!render || !render.sharedMaterial)
{
Debug.LogWarning("Particle no material: " + ps.name);
continue;
} Texture texture = render.sharedMaterial.mainTexture;
if (ps.textureSheetAnimation.enabled || !texture)
continue; foreach (var atlasData in AtlasesData.Atlases)
{
foreach (var t in atlasData.Textures)
{
if (t.Name == texture.name)
{
texturesData.Add(t);
break;
}
}
}
} return texturesData;
} public static void ProcessEffectObj(GameObject obj)
{
ParticleSystem[] particles = obj.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem ps in particles)
{
ParticleSystemRenderer render = ps.GetComponent<ParticleSystemRenderer>();
if (!render || !render.sharedMaterial)
{
Debug.LogWarning("Particle no material: " + ps.name);
continue;
} Texture texture = render.sharedMaterial.mainTexture;
if (ps.textureSheetAnimation.enabled || !texture)
continue; ParticleAtlases.Atlas target = AtlasesData.Atlases.FirstOrDefault(a => a.Textures.Any(t => t.Name == texture.name && t.ShaderName == render.sharedMaterial.shader.name)); if (target != null)
{
ParticleLoader loader = ps.GetComponent<ParticleLoader>();
if (!loader)
loader = ps.gameObject.AddComponent<ParticleLoader>();
loader.TextureName = texture.name;
loader.ShaderName = render.sharedMaterial.shader.name;
render.sharedMaterial = null; if (!ps.trails.enabled)
render.trailMaterial = null;
}
} EditorUtility.SetDirty(obj);
AssetDatabase.SaveAssets();
} public static List<string> GetAllMaterials()
{
if (s_materials != null)
return s_materials; List<string> effects;
Dictionary<Shader, List<ParticleSystem>> dic = GetAllParticles(out effects);
s_materials = new List<string>(); foreach (var pair in dic)
{
foreach (ParticleSystem ps in pair.Value)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
string path = AssetDatabase.GetAssetPath(r.sharedMaterial);
if (!s_materials.Contains(path))
s_materials.Add(path);
}
} return s_materials;
} #endregion [MenuItem("BuildTool/AssetBundle/Combine Particle System")]
static void Init()
{
CombineAllEffectTextures(); EditorUtility.DisplayDialog("finished", "All work finished.", "ok");
} static Dictionary<Shader, List<ParticleSystem>> GetAllParticles(out List<string> effectObjs)
{
// 获取所有的粒子
List<ParticleSystem> particlesList = new List<ParticleSystem>();
List<string> objPaths = new List<string>();
foreach (var effectObjFolder in EffectObjFolders)
{
string[] paths = Directory.GetFiles(effectObjFolder, "*.prefab", SearchOption.AllDirectories);
objPaths.AddRange(paths);
}
effectObjs = new List<string>(); foreach (string path in objPaths)
{
string pathFixed = path.Replace("\\", "/");
effectObjs.Add(pathFixed);
GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(pathFixed);
ParticleSystem[] particles = obj.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem ps in particles)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
bool needCombine = !ps.textureSheetAnimation.enabled
&& r.sharedMaterial
&& r.sharedMaterial.shader.name != "Particles/Alpha Blended Premultiply"
&& r.sharedMaterial.mainTexture
&& r.sharedMaterial.mainTexture.width == r.sharedMaterial.mainTexture.height;
if (needCombine)
particlesList.Add(ps);
}
} // 分类
Dictionary<Shader, List<ParticleSystem>> dic = new Dictionary<Shader, List<ParticleSystem>>();
foreach (ParticleSystem ps in particlesList)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
var shader = r.sharedMaterial.shader;
if (!dic.ContainsKey(shader))
dic[shader] = new List<ParticleSystem>();
dic[shader].Add(ps);
} return dic;
} public static List<string> CombineAllEffectTextures()
{
List<string> atlases = new List<string>(); // 获取所有的粒子
List<string> effects;
Dictionary<Shader, List<ParticleSystem>> dic = GetAllParticles(out effects); // combine
Dictionary<Texture2D, Material> dictTextures = new Dictionary<Texture2D, Material>();
List<ParticleAtlases.Atlas> atlasesData = new List<ParticleAtlases.Atlas>();
List<string> notCombineTextures = NotCombineTextures; foreach (var pair in dic)
{
// get textures
dictTextures.Clear();
foreach (ParticleSystem ps in pair.Value)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
Texture2D texture = (Texture2D)r.sharedMaterial.mainTexture;
if (!notCombineTextures.Contains(texture.name) && !dictTextures.ContainsKey(texture))
dictTextures.Add(texture, r.sharedMaterial);
} if (dictTextures.Count < )
continue; Texture2D[] texturesArray = dictTextures.Keys.ToArray(); // combine texture
string atlasName = string.Format("ParticleAtlas_{0}", Path.GetFileNameWithoutExtension(pair.Key.name));
string atlasPath = string.Format("{0}/{1}.png", AtlasFolder, atlasName);
Uploader.CreateDirectory(atlasPath);
Rect[] rects;
Vector2[] textureSizes;
Texture2D atlas = CombineTextures(texturesArray, atlasPath, out rects, out textureSizes);
atlases.Add(atlasPath); // create material
string matPath = string.Format("{0}/{1}.mat", AtlasFolder, atlasName);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
if (mat == null)
{
mat = new Material(pair.Key);
AssetDatabase.CreateAsset(mat, matPath);
}
mat.mainTexture = atlas; // get config
ParticleAtlases.TextureItem[] texturesData = new ParticleAtlases.TextureItem[texturesArray.Length];
for (int i = ; i < texturesArray.Length; i++)
{
Rect rect = rects[i];
Texture2D texture2D = texturesArray[i];
Vector2 textureSize = textureSizes[i]; // will resize temp texture, so cann't use texture2D.width
int numTilesX = (int)(atlas.width / textureSize.x);
int numTilesY = (int)(atlas.height / textureSize.y);
int colIndex = (int)(rect.x * numTilesX);
int rowIndex = (int)(numTilesY - - rect.y * numTilesY);
int index = rowIndex * numTilesX + colIndex; // get color
Material oldMat = dictTextures[texture2D];
Color32 oldColor = oldMat.GetColor("_TintColor");
string strColor = string.Format("{0}_{1}_{2}_{3}", oldColor.r, oldColor.g, oldColor.b, oldColor.a); string shaderName = oldMat.shader.name; int depth = oldMat.renderQueue; texturesData[i] = new ParticleAtlases.TextureItem()
{
Color = oldColor,
Depth = depth,
Index = index,
Name = texture2D.name,
NumTilesX = numTilesX,
NumTilesY = numTilesY,
ShaderName = shaderName,
};
} ParticleAtlases.Atlas atlasData = new ParticleAtlases.Atlas()
{
Material = mat,
Textures = texturesData,
};
atlasesData.Add(atlasData);
} GameObject prefabObj = AssetDatabase.LoadAssetAtPath<GameObject>(ParticleAtlasPath);
if (!prefabObj)
prefabObj = PrefabUtility.CreatePrefab(ParticleAtlasPath, new GameObject());
ParticleAtlases atlasesCom = prefabObj.GetComponent<ParticleAtlases>();
if (!atlasesCom)
atlasesCom = prefabObj.AddComponent<ParticleAtlases>();
atlasesCom.Atlases = atlasesData.ToArray();
prefabObj.name = Path.GetFileNameWithoutExtension(ParticleAtlasPath); EditorUtility.SetDirty(prefabObj);
AssetDatabase.SaveAssets(); Debug.Log("All cloth effect textures combine finished!"); return atlases;
} static Texture2D CombineTextures(Texture2D[] textures, string path, out Rect[] rects, out Vector2[] textureSizes)
{
if (textures == null || textures.Length < )
{
Debug.LogError("None textures");
rects = null;
textureSizes = null;
return null;
} string tempFolderName = "_TempImages";
string tempFolder = "Assets/" + tempFolderName;
AssetDatabase.DeleteAsset(tempFolder);
AssetDatabase.CreateFolder("Assets", tempFolderName); List<string> newPaths = new List<string>();
var newTextures = new Texture2D[textures.Length];
textureSizes = new Vector2[textures.Length]; // 将原来的图片复制一份出来
for (int i = ; i < textures.Length; i++)
{
string texPath = AssetDatabase.GetAssetPath(textures[i]);
if (File.Exists(texPath))
{
string newPath = string.Format("{0}/{1}", tempFolder, Path.GetFileName(texPath));
AssetDatabase.CopyAsset(texPath, newPath);
newPaths.Add(newPath);
}
else
{
Debug.Log("File not exists: " + texPath);
}
} // make it readable
for (int i = ; i < newPaths.Count; i++)
{
string newPath = newPaths[i];
SetSourceTextureReadalbe(newPath);
Texture2D t = AssetDatabase.LoadAssetAtPath<Texture2D>(newPath);
textureSizes[i] = new Vector2(t.width, t.height); // 去掉边缘的一个像素
if (t.width > )
{
for (int j = ; j < t.width; j++)
{
t.SetPixel(j, , new Color(, , , ));
t.SetPixel(j, , new Color(, , , ));
t.SetPixel(j, t.height - , new Color(, , , ));
t.SetPixel(j, t.height - , new Color(, , , ));
}
} if (t.height > )
{
for (int z = ; z < t.height; z++)
{
t.SetPixel(, z, new Color(, , , ));
t.SetPixel(, z, new Color(, , , ));
t.SetPixel(t.width - , z, new Color(, , , ));
t.SetPixel(t.width - , z, new Color(, , , ));
}
} newTextures[i] = t;
} // pack
Texture2D atlas = new Texture2D(, , TextureFormat.ARGB32, false);
rects = atlas.PackTextures(newTextures, , , false); // save
byte[] bytes = atlas.EncodeToPNG();
File.WriteAllBytes(path, bytes);
AssetDatabase.Refresh();
AssetDatabase.SaveAssets(); // setting
TextureCompresser.CompressRGBA(path); // 删除临时目录
AssetDatabase.DeleteAsset(tempFolder); AssetDatabase.Refresh();
AssetDatabase.SaveAssets(); return AssetDatabase.LoadAssetAtPath<Texture2D>(path);
} static void SetSourceTextureReadalbe(string path)
{
string name = Path.GetFileNameWithoutExtension(path);
int maxSize;
TexturesSize.TryGetValue(name, out maxSize);
if (maxSize == )
maxSize = ; bool readable = true;
TextureImporterNPOTScale npotScale = TextureImporterNPOTScale.ToNearest;
TextureWrapMode wrapMode = TextureWrapMode.Clamp;
TextureImporterCompression compression = TextureImporterCompression.Uncompressed; bool changed = false; var importer = (TextureImporter)AssetImporter.GetAtPath(path);
TextureImporterSettings settings = new TextureImporterSettings();
importer.ReadTextureSettings(settings); settings.alphaIsTransparency = true;
settings.mipmapEnabled = false; if (settings.readable != readable)
{
settings.readable = readable;
changed = true;
} if (settings.npotScale != npotScale)
{
settings.npotScale = npotScale;
changed = true;
} if (settings.wrapMode != wrapMode)
{
settings.wrapMode = wrapMode;
changed = true;
} if (importer.maxTextureSize != maxSize)
{
importer.maxTextureSize = maxSize;
changed = true;
} if (importer.textureCompression != compression)
{
importer.textureCompression = compression;
changed = true;
} // set platform overriten as false
var androidSetting = importer.GetPlatformTextureSettings("Android");
var iosSetting = importer.GetPlatformTextureSettings("iPhone");
var pcSetting = importer.GetPlatformTextureSettings("Standalone");
if (androidSetting.overridden)
{
androidSetting.overridden = false;
changed = true;
}
if (iosSetting.overridden)
{
iosSetting.overridden = false;
changed = true;
}
if (pcSetting.overridden)
{
pcSetting.overridden = false;
changed = true;
}
importer.SetPlatformTextureSettings(androidSetting);
importer.SetPlatformTextureSettings(iosSetting);
importer.SetPlatformTextureSettings(pcSetting); if (changed)
{
importer.SetTextureSettings(settings);
AssetDatabase.ImportAsset(path);
}
} }
}
ParticleSystemCombiner
加载的代码:
using UnityEngine; public partial class ParticleLoader : MonoBehaviour
{
public string TextureName;
public string ShaderName;
}
ParticleLoader
using Common;
using UnityEngine; public partial class ParticleLoader : MonoBehaviour
{
static ParticleAtlases s_atlases; // 加载图集
public static void Initialize()
{
BundleLoader.Singleton.LoadAssetBundle("atlases/particlesystematlas.u", r =>
{
GameObject[] objs = r.Bundle.LoadAllAssets<GameObject>();
s_atlases = objs[].GetComponent<ParticleAtlases>();
});
} void OnEnable()
{
Load();
} void Load()
{
ParticleSystemRenderer renderer = GetComponent<ParticleSystemRenderer>();
if (!renderer)
return; ParticleAtlases.Atlas targetAtlas = null;
ParticleAtlases.TextureItem targetTextureConfig = null; // 找对应的配置
for (int i = ; i < s_atlases.Atlases.Length; i++)
{
ParticleAtlases.Atlas atlasData = s_atlases.Atlases[i];
for (int j = ; j < atlasData.Textures.Length; j++)
{
ParticleAtlases.TextureItem textureData = atlasData.Textures[j];
if (textureData.Name == this.TextureName && textureData.ShaderName == this.ShaderName)
{
targetAtlas = atlasData;
targetTextureConfig = textureData;
break;
}
} if (targetAtlas != null)
break;
} if (targetAtlas != null && targetTextureConfig != null)
{
ParticleSystem ps = GetComponent<ParticleSystem>(); renderer.sharedMaterial = targetAtlas.Material; // 渲染 SetTextureSheet(ps, targetTextureConfig); // 设置格子 SetStartColor(ps, renderer, targetTextureConfig.Color); // 设置颜色 SetDepth(renderer, targetTextureConfig); // 排序
}
} // 设置格子
void SetTextureSheet(ParticleSystem ps, ParticleAtlases.TextureItem targetTextureConfig)
{
var tsa = ps.textureSheetAnimation;
float curveConstant = (float)targetTextureConfig.Index / targetTextureConfig.NumTilesX / targetTextureConfig.NumTilesY;
tsa.enabled = true;
tsa.numTilesX = targetTextureConfig.NumTilesX;
tsa.numTilesY = targetTextureConfig.NumTilesY;
tsa.animation = ParticleSystemAnimationType.WholeSheet;
tsa.startFrame = new ParticleSystem.MinMaxCurve();
tsa.frameOverTime = new ParticleSystem.MinMaxCurve(curveConstant);
tsa.cycleCount = ;
} // 设置颜色
void SetStartColor(ParticleSystem ps, ParticleSystemRenderer renderer, Color matColor)
{
var main = ps.main;
switch (main.startColor.mode)
{
case ParticleSystemGradientMode.Color:
case ParticleSystemGradientMode.Gradient:
case ParticleSystemGradientMode.RandomColor:
case ParticleSystemGradientMode.TwoGradients:
var targetColor = main.startColor.color * matColor;
main.startColor = new UnityEngine.ParticleSystem.MinMaxGradient(targetColor);
break; case ParticleSystemGradientMode.TwoColors:
var colorMin = main.startColor.colorMin * matColor;
var colorMax = main.startColor.colorMax * matColor;
main.startColor = new UnityEngine.ParticleSystem.MinMaxGradient(colorMin, colorMax);
break; default:
Debug.LogError("Unknown mode: " + main.startColor.mode);
break;
} renderer.sharedMaterial.SetColor("_TintColor", Color.white);
} // 排序
void SetDepth(ParticleSystemRenderer renderer, ParticleAtlases.TextureItem targetTextureConfig)
{
int depth = targetTextureConfig.Depth;
if (depth > && renderer.sharedMaterial.renderQueue < depth)
renderer.sharedMaterial.renderQueue = depth;
} }
ParticleLoader
合并后的图集如下:

合并后的粒子如下:

效果如下:

Unity性能优化之特效合并的更多相关文章
- Unity 性能优化(力荐)
开始之前先分享几款性能优化的插件: 1.SimpleLOD : 除了同样拥有Mesh Baker所具有的Mesh合并.Atlas烘焙等功能,它还能提供Mesh的简化,并对动态蒙皮网格进行了很好的支持. ...
- Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译
本文是Unity官方教程,性能优化系列的第三篇<Optimizing garbage collection in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...
- Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译
本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...
- Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译
本文是Unity官方教程,性能优化系列的第二篇<Diagnosing performance problems using the Profiler window>的简单翻译. 相关文章: ...
- Unity性能优化(1)-官方教程The Profiler window翻译
本文是Unity官方教程,性能优化系列的第一篇<The Profiler window>的简单翻译. 相关文章: Unity性能优化(1)-官方教程The Profiler window翻 ...
- Unity性能优化的N种武器
贴图: l 控制贴图大小,尽量不要超过 1024 x1024: l 尽量使用2的n次幂大小的贴图,否则GfxDriver里会有2份贴图: l 尽量使用压缩格式减小贴图大小: l 若干种贴图合并 ...
- Unity性能优化专题---腾讯牛人分享经验
这里从三个纬度来分享下内存的优化经验:代码层面.贴图层面.框架设计层面. 一.代码层面. 1.foreach. Mono下的foreach使用需谨慎.频繁调用容易触及堆上限,导致GC过早触发,出现卡顿 ...
- Unity性能优化-音频设置
没想到Unity的音频会成为内存杀手,在实际的商业项目中,音频的优化必不可少. 1. Unity支持许多不同的音频格式,但最终它将它们全部转换为首选格式.音频压缩格式有PCM.ADPCM.Vorbis ...
- Unity性能优化-DrawCall
1. DrawCall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西.所以,是谁去调用这些接口呢?CPU.比如有上千个物体,每一个的渲染都需要去调用一次底层接口 ...
随机推荐
- python 遍历hadoop, 跟指定列表对比 包含列表中值的取出。
import sys import tstree fname = 'high_freq_site.list' tree = tstree.TernarySearchTrie() tree.loadDa ...
- 启动matlab时总是直接跳转到安装界面的解决方案
[关于2017双11过后MATLAB许可过期问题的解决方案] 在距离双11还有2个月之前,matlab会提示:Your MATLAB license will expire in 50 days -- ...
- NuGet Packages are missing,This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.
错误内容 This project references NuGet package(s) that are missing on this computer. Use NuGet Package R ...
- Rk3288 双屏异显单触摸
系统版本:RK3288 android 5.1 设备同时有两个lcd,主屏是mipi接口,带有触摸屏,触摸屏是usb接口,副屏是hdmi接口,没有触摸屏,正常情况下,两个lcd显示相同内容,触摸屏一切 ...
- Jquery实现轮播公告
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【Zookeeper系列】ZooKeeper伸缩性(转)
原文地址:https://www.cnblogs.com/sunddenly/p/4143306.html 一.ZooKeeper中Observer 1.1 ZooKeeper角色 经过前面的介绍,我 ...
- brew 源 & pip 源
brew源: https://www.zhihu.com/question/31360766 摘要 1. 替换formula 索引的镜像(即 brew update 时所更新内容) cd " ...
- XML文档中的xmlns、xmlns:xsi和xsi:schemaLocation
文章转载自:https://yq.aliyun.com/articles/40353 相信很多人和我一样,在编写Spring或者Maven或者其他需要用到XML文档的程序时,通常都是将这些XML文档头 ...
- pandas功能使用rename, reindex, set_index 详解
pandas rename 功能 在使用 pandas 的过程中经常会用到修改列名称的问题,会用到 rename 或者 reindex 等功能,每次都需要去查文档 当然经常也可以使用 df.colum ...
- J - Printer Queue 优先队列与队列
来源poj3125 The only printer in the computer science students' union is experiencing an extremely heav ...