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.比如有上千个物体,每一个的渲染都需要去调用一次底层接口 ...
随机推荐
- windows下IDEA的terminal配置bash命令
使用git-bash.exe会单独打开一个窗口,而我们希望是在终端内置的命令行.这里我使用bash.exe 在IDEA中,打开settings,设置相应的bash路径 settings–>Too ...
- linux下好玩或者好用的小工具
本篇文章用于记录自己认为很好玩的linux下的小工具,不断添加中..大家如果有什么好玩的小工具的话,欢迎留言告诉我. 1. cmatrix工具 功能介绍: 可以产生黑客帝国中字符满屏幕飞的效果,当你离 ...
- Linux 用户管理_015
一.用户基础了解 Linux是一个多任务.多用户的操作系统,每个用户和进程都需要对应一个用户和用户组,用户名相当于姓名, 用户UID相当于身份证号,用户组GID相当于公司的工号.用户与用户组的关系一对 ...
- PHP5.5+ APC 安装
因php 语言特性(短链接), 没法实现共享内存来提升性能. apc的出现给出了一个解决方案 .不过很可惜5.5+以后PHP官方已经废弃掉这个扩展. 幸好出现了 apcu扩展提供后续功能 api 也没 ...
- docker应用-3(搭建hadoop以及hbase集群)
要用docker搭建集群,首先需要构造集群所需的docker镜像.构建镜像的一种方式是,利用一个已有的镜像比如简单的linux系统,运行一个容器,在容器中手动的安装集群所需要的软件并进行配置,然后co ...
- WKWebView实现网页静态资源优先从本地加载
前言:最近微信的小游戏跳一跳特别的火,顺便也让h5小游戏更加的火热.另外微信小程序,以及支付宝的小程序都是用H5写的.无论是小游戏还是小程序,这些都需要加载更多的资源文件,处理更多的业务.这些都对网页 ...
- axios 中断请求
1 <button onclick="test()">click me</button> <script src="https://unpk ...
- vue里v-for下的key的作用
将v-for的元素赋予唯一的key属性,则会打破‘就地复用原则’: 这个就地复用原则是指 如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确 ...
- myBatis框架_关于怎么获得多表查询的总记录数
<!-- 查找总记录数 --> <select id="billCount" resultType="int"> select coun ...
- 巧用border效果
目的: 我们在做css的时候为了提高网站的效率减少服务器请求,我们可以通过css来实现一些简单的图片特效,比如说三角形,这篇文章讲解的是通过边框实现不同的效果. 上面样式部分代码: <style ...