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.比如有上千个物体,每一个的渲染都需要去调用一次底层接口 ...
随机推荐
- 了解cron以及使用cron定时备份MySQL
cron是一个linux下的定时执行工具,可以在无需人工干预的情况下运行作业.由于Cron 是Linux的内置服务,但它不自动起来,可以用以下的方法启动.关闭这个服务: /sbin/service c ...
- 鼠标交互插件threex.domevents介绍
threex.domevents是一个three.js的扩展库,支持3D场景的交互.和我们操作DOM树的事件相似,名称都是一样的.所以使用起来非常方便.另外他也提供了连接操作.单击网格可实现跳转功能. ...
- 【中间件安全】IIS6安全加固规范
1. 适用情况 适用于使用IIS6进行部署的Web网站. 2. 技能要求 熟悉IIS配置操作,能够利用IIS进行建站,并能针对站点使用IIS进行安全加固. 3. 前置条件 1. 根据站点开放端口.进程 ...
- zeppelin 一直报这个警告 也是醉了
用./zeppelin-daemon.sh start 启动zeppelin 一直报这个警告.. WARN [2017-03-23 19:11:34,461] ({qtp483422889-45} N ...
- PowerBI与Visio
前言 如何在Power BI中使用Visio, 刚好最近微软推出了适用于Power BI 的 Visio自定义可视化对象预览,分享给大家. 我们先看一下效果: 通过自定义可视化对象,将Visio ...
- E - Stones 优先队列
来源1896 Because of the wrong status of the bicycle, Sempr begin to walk east to west every morning an ...
- Struts2与spingmvc区别
1.Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上Spr ...
- Realm 简介
是一个跨平台的本地数据库,比sqlite 数据库更轻量级,执行效率更高. 官网地址:https://realm.io/docs/java/latest/
- CF3A Shortest path of the king
The king is left alone on the chessboard. In spite of this loneliness, he doesn't lose heart, becaus ...
- SpringBoot:四种读取properties文件的方式
前言 在项目开发中经常会用到配置文件,配置文件的存在解决了很大一份重复的工作.今天就分享四种在Springboot中获取配置文件的方式. 注:前三种测试配置文件为springboot默认的applic ...