Skip to content

Instantly share code, notes, and snippets.

@esperecyan
Last active May 6, 2022 08:41
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esperecyan/28026fc96550bcf822b5401f2e078dba to your computer and use it in GitHub Desktop.
Save esperecyan/28026fc96550bcf822b5401f2e078dba to your computer and use it in GitHub Desktop.
『FBXMeshFileSizeReducer.cs』 Unity 2018.3以降でFBXのメッシュが肥大化する問題を抑制するエディタ拡張。 unitypackage: https://pokemori.booth.pm/items/1961154
using System.Linq;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEditor;
namespace Esperecyan.Unity.FBXMeshFileSizeReducer
{
/// <summary>
/// Unity 2018.3以降でFBXのメッシュが肥大化する問題を抑制します。
/// </summary>
/// <remarks>
/// FBX、およびメッシュがインポートされたときにシェイプキーの法線・接線データを削除 (FBXの場合は「ブレンドシェイプの法線」を「なし」に) します。
/// また、Assetsのメニューから、インポート済みのFBX、メッシュも処理できます。
///
/// VRM正規化後など、処理されたあとにプロジェクトの保存 (Ctrl+S) が必要です。
///
/// 動作確認バージョン: Unity 2018.4.20f1
/// SPDX-License-Identifier: MPL-2.0
/// (Mozilla Public License 2.0) <https://spdx.org/licenses/MPL-2.0.html>
/// Author: 100の人
/// 配布元: <https://gist.github.com/esperecyan/28026fc96550bcf822b5401f2e078dba>
///
/// 更新履歴:
/// 2020-05-19 v2.0.0
/// Unity 2018.4.20f1 において、FBXインポート設定の「ブレンドシェイプの法線」が機能しない不具合が直っていたため、
/// 「Legacy Blend Shape Normals」へチェックを入れる代わりに、同設定を「なし」に
/// 2020-04-09 v1.0.0
/// 表記等を修正
/// 2019-07-24
/// FBXインポート時にも処理するように (メッシュ同様に手動で処理する機能を追加)
/// 2019-06-03
/// FBXインポート時の代わりに、メッシュが取り出されたとき (インポートされたとき) に処理するように
///   すでに取り出されたメッシュを全処理する機能を追加
/// 2019-04-19
/// 公開
/// </remarks>
internal class Postprocessor : AssetPostprocessor
{
private struct BlendShapeFrame
{
internal string shapeName;
internal float frameWeight;
internal Vector3[] deltaVertices;
}
[MenuItem("Assets/シェイプキーの法線・接線データを削除", true)]
private static bool IsMesh()
{
if (!Selection.activeObject)
{
return false;
}
string path = AssetDatabase.GetAssetPath(Selection.activeObject);
if (string.IsNullOrEmpty(path))
{
return false;
}
switch (Path.GetExtension(path).ToLower())
{
case ".fbx":
return true;
case ".asset":
return Selection.activeObject is Mesh && AssetDatabase.LoadAllAssetsAtPath(path).Length == 1;
default:
return false;
}
}
[MenuItem("Assets/シェイプキーの法線・接線データを削除")]
private static void Reduce()
{
string path = AssetDatabase.GetAssetPath(Selection.activeObject);
string fileName = Path.GetFileName(path);
string message = "「" + fileName + "」に法線・接線データをもつシェイプキーはありません。";
if (Path.GetExtension(path).ToLower() == ".fbx")
{
if (Postprocessor.tagFBXWithUnusingBlendShapesNormalsAndTangents(path: path))
{
message = "「" + fileName + "」のシェイプキーの法線・接線データが利用されないようマークしました。";
}
}
else
{
if (Postprocessor.deleteBlendShapesNormalsAndTangents(Selection.activeObject as Mesh))
{
message = "「" + fileName + "」のシェイプキーの法線・接線データを削除しました。Ctrl+S などでプロジェクトを保存してください。";
}
}
EditorUtility.DisplayDialog("FBXMeshFileSizeReducer.cs", message, "OK");
}
[MenuItem("Assets/プロジェクト内に存在するすべてのメッシュからシェイプキーの法線・接線データを削除")]
private static void ReduceAllMeshes()
{
if (!EditorUtility.DisplayDialog(
"FBXMeshFileSizeReducer.cs",
"プロジェクト内に単独で存在するすべてのメッシュのシェイプキーの、法線・接線データを削除します。"
+ "また、FBXについてはシェイプキーの法線・接線データを参照しないように「ブレンドシェイプの法線」を「なし」にします。よろしいですか。",
"OK",
"キャンセル"
))
{
return;
}
var reducedMeshCount = 0;
foreach (string guid in AssetDatabase.FindAssets("t:Mesh"))
{
if (Postprocessor.makeBlendShapesNormalsAndTangentsUnuse(path: AssetDatabase.GUIDToAssetPath(guid)))
{
reducedMeshCount++;
}
}
EditorUtility.DisplayDialog("FBXMeshFileSizeReducer.cs", reducedMeshCount > 0
? reducedMeshCount + "個のメッシュのシェイプキーの、法線・接線データを削除 (FBXの場合は「ブレンドシェイプの法線」を「なし」に) しました。"
+ "Ctrl+S などでプロジェクトを保存してください。\n\n"
: "法線・接線データをもつシェイプキーがあるメッシュは、プロジェクト内には存在しません。", "OK");
}
private static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths
)
{
foreach (string path in importedAssets)
{
Postprocessor.makeBlendShapesNormalsAndTangentsUnuse(path: path);
}
if (importedAssets.Length == 1 && importedAssets[0].EndsWith("/BlendShape.asset"))
{
AssetDatabase.SaveAssets();
}
}
/// <summary>
/// 拡張子に応じて <see cref="Postprocessor.tagFBXWithUnusingBlendShapesNormalsAndTangents"/>、
/// または <see cref="Postprocessor.deleteBlendShapesNormalsAndTangents"/> を実行します。
/// </summary>
/// <remarks>
/// 対象外の拡張子の場合は何もしません。
/// </remarks>
/// <param name="path"></param>
/// <returns></returns>
private static bool makeBlendShapesNormalsAndTangentsUnuse(string path)
{
switch (Path.GetExtension(path).ToLower())
{
case ".fbx":
return Postprocessor.tagFBXWithUnusingBlendShapesNormalsAndTangents(path);
case ".asset":
var assets = AssetDatabase.LoadAllAssetsAtPath(path);
if (assets.Length != 1)
{
return false;
}
var mesh = assets[0] as Mesh;
if (!mesh)
{
return false;
}
return Postprocessor.deleteBlendShapesNormalsAndTangents(AssetDatabase.LoadAssetAtPath<Mesh>(path));
default:
return false;
}
}
/// <summary>
/// FBXでシェイプキーの法線・接線データを使用しないようマーク (「ブレンドシェイプの法線」を「なし」に) します。
/// </summary>
/// <param name="path"></param>
/// <returns>マークを行った場合、すでに行われていた場合は <c>true</c>。</returns>
private static bool tagFBXWithUnusingBlendShapesNormalsAndTangents(string path)
{
if (AssetDatabase.LoadAllAssetsAtPath(path).All(obj => {
var mesh = obj as Mesh;
return !mesh || mesh.blendShapeCount == 0;
}))
{
return false;
}
var importer = AssetImporter.GetAtPath(path) as ModelImporter;
if (importer.importBlendShapeNormals == ModelImporterNormals.None)
{
return true;
}
importer.importBlendShapeNormals = ModelImporterNormals.None;
AssetDatabase.ImportAsset(path);
return true;
}
/// <summary>
/// シェイプキーの法線・接線データを削除します。
/// </summary>
/// <param name="mesh"></param>
/// <returns>削除を行った場合 <c>true</c>。</returns>
private static bool deleteBlendShapesNormalsAndTangents(Mesh mesh)
{
var blendShapeFrames = new List<BlendShapeFrame>();
if (mesh.blendShapeCount == 0)
{
return false;
}
var normalsAndTangentsExisted = false;
for (var shapeIndex = 0; shapeIndex < mesh.blendShapeCount; shapeIndex++)
{
string shapeName = mesh.GetBlendShapeName(shapeIndex);
int frameCount = mesh.GetBlendShapeFrameCount(shapeIndex);
for (var frameIndex = 0; frameIndex < frameCount; frameIndex++)
{
var deltaVertices = new Vector3[mesh.vertexCount];
var deltaNormals = new Vector3[mesh.vertexCount];
var deltaTangents = new Vector3[mesh.vertexCount];
mesh.GetBlendShapeFrameVertices(
shapeIndex,
frameIndex,
deltaVertices,
deltaNormals,
deltaTangents
);
if (!normalsAndTangentsExisted)
{
normalsAndTangentsExisted = deltaNormals.Any(normal => normal != Vector3.zero)
|| deltaTangents.Any(normal => normal != Vector3.zero);
}
blendShapeFrames.Add(new BlendShapeFrame
{
shapeName = shapeName,
frameWeight = mesh.GetBlendShapeFrameWeight(shapeIndex, frameIndex),
deltaVertices = deltaVertices,
});
}
}
if (!normalsAndTangentsExisted)
{
return false;
}
mesh.ClearBlendShapes();
foreach (BlendShapeFrame frame in blendShapeFrames)
{
mesh.AddBlendShapeFrame(frame.shapeName, frame.frameWeight, frame.deltaVertices, null, null);
}
EditorUtility.SetDirty(mesh);
return true;
}
}
}
fileFormatVersion: 2
guid: eaabe383859f4c64fb002aa53ec51565
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment