特效合并,意思是说将粒子所用的零碎图片,以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性能优化之特效合并的更多相关文章

  1. Unity 性能优化(力荐)

    开始之前先分享几款性能优化的插件: 1.SimpleLOD : 除了同样拥有Mesh Baker所具有的Mesh合并.Atlas烘焙等功能,它还能提供Mesh的简化,并对动态蒙皮网格进行了很好的支持. ...

  2. Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译

    本文是Unity官方教程,性能优化系列的第三篇<Optimizing garbage collection in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  3. Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译

    本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  4. Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译

    本文是Unity官方教程,性能优化系列的第二篇<Diagnosing performance problems using the Profiler window>的简单翻译. 相关文章: ...

  5. Unity性能优化(1)-官方教程The Profiler window翻译

    本文是Unity官方教程,性能优化系列的第一篇<The Profiler window>的简单翻译. 相关文章: Unity性能优化(1)-官方教程The Profiler window翻 ...

  6. Unity性能优化的N种武器

    贴图: l  控制贴图大小,尽量不要超过 1024 x1024: l  尽量使用2的n次幂大小的贴图,否则GfxDriver里会有2份贴图: l  尽量使用压缩格式减小贴图大小: l  若干种贴图合并 ...

  7. Unity性能优化专题---腾讯牛人分享经验

    这里从三个纬度来分享下内存的优化经验:代码层面.贴图层面.框架设计层面. 一.代码层面. 1.foreach. Mono下的foreach使用需谨慎.频繁调用容易触及堆上限,导致GC过早触发,出现卡顿 ...

  8. Unity性能优化-音频设置

    没想到Unity的音频会成为内存杀手,在实际的商业项目中,音频的优化必不可少. 1. Unity支持许多不同的音频格式,但最终它将它们全部转换为首选格式.音频压缩格式有PCM.ADPCM.Vorbis ...

  9. Unity性能优化-DrawCall

    1. DrawCall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西.所以,是谁去调用这些接口呢?CPU.比如有上千个物体,每一个的渲染都需要去调用一次底层接口 ...

随机推荐

  1. Zookeeper客户端介绍

    客户端是开发人员使用Zookeeper的主要的途径,以下内容将对Zookeeper的内部原理进行详细的学习和讲解.ZooKeeper的客户端主要有一下几个核心组件组成: Zookeeper:提供客户端 ...

  2. 火币网行情获取的websocket客户端

    从验证结果看应该是网络关闭了,不过程序写的不错,可以作为其它websocket客户端的测试程序 # !/usr/bin/env python # -*- coding: utf-8 -*- # aut ...

  3. php -- 类对象调用静态方法

    以前一直以为 静态方法的调用:类名::静态方法 非静态方法的调用:类对象->非静态方法 最近研究一个类,发现一个比较奇怪的问题,用“类对象->静态方法”这种方式居然成功的调用了静态方法.很 ...

  4. 简单的 FastDFS + Nginx 应用实例

    版权声明:本文为GitChat作者的原创文章,未经 GitChat 同意不得转载. https://blog.csdn.net/GitChat/article/details/79479148 wx_ ...

  5. Linux 文件umask默认权限_012

    一.       umask介绍 Linux 系统用户创建一个新的目录或文件时,系统会默认会分配相应的权限.目录或文件的权限是如何产生的呢? 1.这就是umask的功能,umask设置了用户创建文件或 ...

  6. 将RAC软件转换为单实例软件

    将RAC软件转换为单实例软件 http://blog.itpub.net/26736162/viewspace-2155632/ 1. Stop database and CRS on both no ...

  7. dedecms wap 上一篇 下一篇 链接出错

    打开 \include\arc.archives.class.php 文件 大约在839 行,查找  $mlink = 'view.php?aid='.$preRow['id'];        修改 ...

  8. 手写一个selenium浏览器池

    维护一组浏览器,实现每分钟1000次查询.DriverPool使用变幻版只初始化一次的单例模式.维护每个浏览器的当前是否使用的状态. 不需要等待请求来了,临时开浏览器,开一个浏览器会耽误6秒钟. 可以 ...

  9. vMware 按装 MacOs

    大概思路:1.vMware11 下载以管理员运行2.服务项按名称排序把四荐停止运行3.插件unlock 以管理员运行4.载入apple Mac os x 10.11文件5.打开虚拟机 wzfou如果报 ...

  10. Windows下JDK多版本切换

    根据需要,我们可以在一台电脑上安装多个不同的JDK版本,在使用的过程中,可能需要进行版本质检的切换.下面简单说明在切换过程中需要注意的问题.(个人本机是部署了1.8和1.7版本的,安装目录均在C:\P ...