Added GenerateMask to AssetsHelpers;

This commit is contained in:
Misaki
2024-12-26 00:44:11 +09:00
parent 509357011c
commit 2a455513bc
47 changed files with 974 additions and 297 deletions

View File

@@ -0,0 +1,33 @@
using UnityEditor;
using UnityEngine;
namespace Misaki.ArtToolEditor
{
internal class GenerateMaskMenu
{
[MenuItem("Assets/Art Tools/Texture Helpers/Generate Mask", true)]
public static bool GenerateMaskValidator()
{
foreach (var selectedObject in Selection.objects)
{
if (selectedObject is not Texture2D)
{
return false;
}
}
return true;
}
[MenuItem("Assets/Art Tools/Texture Helpers/Generate Mask")]
public static void GenerateMask()
{
var window = EditorWindow.GetWindow<OutputOptionsWindow>(true, "Generate Mask Output Options");
window.WithItemSource(Selection.objects);
window.RegisterVisualProvider<GenerateMaskVisualProvider>();
window.RegisterProcessor<GenerateMaskProcessor>();
window.InitializeAndShow();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 353f02bb002aafd419d5472f331069a9

View File

@@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Unity.Collections;
using UnityEditor;
using UnityEngine;
namespace Misaki.ArtToolEditor
{
internal class GenerateMaskProcessor : IAssetsProcessor
{
private struct PackingConfig : IDisposable
{
public string outputDirectory;
public NativeList<FixedString64Bytes> texturePaths;
public void Dispose()
{
texturePaths.Dispose();
}
}
private class Constants
{
internal const string Generate_Mask_Shader_Path = "Hidden/GenerateMask";
internal const string Texture_R_Property = "_TexR";
internal const string Texture_G_Property = "_TexG";
internal const string Texture_B_Property = "_TexB";
internal const string Texture_A_Property = "_TexA";
internal const string Fallback_Property = "_Fallback";
internal const string Has_Texture_R_Keyword = "_HAS_TEX_R";
internal const string Has_Texture_G_Keyword = "_HAS_TEX_G";
internal const string Has_Texture_B_Keyword = "_HAS_TEX_B";
internal const string Has_Texture_A_Keyword = "_HAS_TEX_A";
internal const string Png_Extension = ".png";
}
public string metallicTextureRegex = ".*_metallic";
public string aoTextureRegex = ".*_ao|.*_ambientocclusion";
public string detailMaskTextureRegex = ".*_detailmask";
public string smoothnessTextureRegex = ".*_smoothness";
public TextureFallbackType metallicFallbackType = TextureFallbackType.Black;
public TextureFallbackType aoFallbackType = TextureFallbackType.White;
public TextureFallbackType detailMaskFallbackType = TextureFallbackType.White;
public TextureFallbackType smoothnessFallbackType = TextureFallbackType.LinearGray;
public string textureGroupingRegex = "^([^_]+)_";
public string namingRegex;
public TextureNamingSourceType namingSource;
public string replaceBy = "Mask";
public void OnPreProcess(AssetsProcessContext context)
{
context.userData = new Dictionary<string, PackingConfig>();
}
public void OnProcess(UnityEngine.Object source, string outputDirectory, AssetsProcessContext context)
{
if (source is not Texture2D texture)
{
return;
}
var groupConfigs = (Dictionary<string, PackingConfig>)context.userData;
var match = Regex.Match(texture.name, textureGroupingRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (!match.Success)
{
return;
}
var groupName = match.Groups[1].Value;
if (!groupConfigs.ContainsKey(groupName))
{
groupConfigs[groupName] = new PackingConfig
{
outputDirectory = outputDirectory,
texturePaths = new NativeList<FixedString64Bytes>(4, Allocator.TempJob)
};
}
var config = groupConfigs[groupName];
config.texturePaths.AddNoResize(AssetDatabase.GetAssetPath(texture));
}
public void OnPostProcess(AssetsProcessContext context)
{
var groupConfigs = (Dictionary<string, PackingConfig>)context.userData;
try
{
foreach (var group in groupConfigs)
{
var metallicTexture = FindTexture(group.Value.texturePaths, metallicTextureRegex);
var aoTexture = FindTexture(group.Value.texturePaths, aoTextureRegex);
var detailMaskTexture = FindTexture(group.Value.texturePaths, detailMaskTextureRegex);
var smoothnessTexture = FindTexture(group.Value.texturePaths, smoothnessTextureRegex);
GetTextureSizeAndName(metallicTexture, aoTexture, detailMaskTexture, out var textureSize, out var textureName);
SetupAndBlitTexture(metallicTexture, aoTexture, detailMaskTexture, smoothnessTexture, textureSize, out var tempRT, out var material);
ExportTextureToPNG(group, textureName, tempRT);
UnityEngine.Object.DestroyImmediate(material);
tempRT.Release();
}
}
catch (Exception e)
{
Debug.LogError(e.Message);
}
finally
{
foreach (var group in groupConfigs)
{
group.Value.Dispose();
}
}
}
private static Texture2D FindTexture(NativeList<FixedString64Bytes> texturePaths, string metallicTextureRegex)
{
foreach (var texturePath in texturePaths)
{
if (Regex.IsMatch(texturePath.ToString(), metallicTextureRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled))
{
return AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath.ToString());
}
}
return null;
}
private void GetTextureSizeAndName(Texture2D metallicTexture, Texture2D aoTexture, Texture2D detailMaskTexture, out Vector2Int textureSize, out string textureName)
{
textureSize = new Vector2Int(2048, 2048);
textureName = string.Empty;
switch (namingSource)
{
case TextureNamingSourceType.R:
textureSize = new Vector2Int(metallicTexture.width, metallicTexture.height);
textureName = Regex.Replace(metallicTexture.name, namingRegex, replaceBy, RegexOptions.IgnoreCase | RegexOptions.Compiled);
break;
case TextureNamingSourceType.G:
textureSize = new Vector2Int(aoTexture.width, aoTexture.height);
textureName = Regex.Replace(aoTexture.name, namingRegex, replaceBy, RegexOptions.IgnoreCase | RegexOptions.Compiled);
break;
case TextureNamingSourceType.B:
textureSize = new Vector2Int(detailMaskTexture.width, detailMaskTexture.height);
textureName = Regex.Replace(detailMaskTexture.name, namingRegex, replaceBy, RegexOptions.IgnoreCase | RegexOptions.Compiled);
break;
case TextureNamingSourceType.A:
textureSize = new Vector2Int(detailMaskTexture.width, detailMaskTexture.height);
textureName = Regex.Replace(detailMaskTexture.name, namingRegex, replaceBy, RegexOptions.IgnoreCase | RegexOptions.Compiled);
break;
}
}
private void SetupAndBlitTexture(Texture2D textureR, Texture2D textureG, Texture2D textureB, Texture2D textureA, Vector2Int textureSize, out RenderTexture tempRT, out Material material)
{
tempRT = RenderTexture.GetTemporary(textureSize.x, textureSize.y, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
tempRT.Create();
material = new Material(Shader.Find(Constants.Generate_Mask_Shader_Path));
if (textureR != null)
{
material.SetTexture(Constants.Texture_R_Property, textureR);
material.EnableKeyword(Constants.Has_Texture_R_Keyword);
}
if (textureG != null)
{
material.SetTexture(Constants.Texture_G_Property, textureG);
material.EnableKeyword(Constants.Has_Texture_G_Keyword);
}
if (textureB != null)
{
material.SetTexture(Constants.Texture_B_Property, textureB);
material.EnableKeyword(Constants.Has_Texture_B_Keyword);
}
if (textureA != null)
{
material.SetTexture(Constants.Texture_A_Property, textureA);
material.EnableKeyword(Constants.Has_Texture_A_Keyword);
}
var fallback = new Vector4(
GetFallbackValue(metallicFallbackType),
GetFallbackValue(aoFallbackType),
GetFallbackValue(detailMaskFallbackType),
GetFallbackValue(smoothnessFallbackType)
);
material.SetVector("_Fallback", fallback);
Graphics.Blit(null, tempRT, material);
}
private static void ExportTextureToPNG(KeyValuePair<string, PackingConfig> group, string textureName, RenderTexture tempRT)
{
var exportTexture = new Texture2D(tempRT.width, tempRT.height, TextureFormat.RGBA32, false);
exportTexture.ReadPixels(new Rect(0, 0, tempRT.width, tempRT.height), 0, 0);
exportTexture.Apply();
var bytes = exportTexture.EncodeToPNG();
var path = Path.Combine(group.Value.outputDirectory, textureName + Constants.Png_Extension);
File.WriteAllBytes(path, bytes);
UnityEngine.Object.DestroyImmediate(exportTexture);
AssetDatabase.ImportAsset(path);
}
private float GetFallbackValue(TextureFallbackType type)
{
return type switch
{
TextureFallbackType.White => 1.0f,
TextureFallbackType.LinearGray => 0.73f,
TextureFallbackType.Gray => 0.5f,
TextureFallbackType.Black => 0.0f,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 94ec75a6994a6db41b574c5bebf56670

View File

@@ -0,0 +1,86 @@
using System;
using Unity.Properties;
using UnityEngine.UIElements;
namespace Misaki.ArtToolEditor
{
internal class GenerateMaskVisualProvider : OptionsVisualProvider
{
internal override VisualElement ContentAfterList()
{
var root = new VisualElement();
root.Add(new HelpBox("All regex are case insensitive", HelpBoxMessageType.Info));
root.Add(CreateTextInputWithEnumField("Metallic Regex",
nameof(GenerateMaskProcessor.metallicTextureRegex),
nameof(GenerateMaskProcessor.metallicFallbackType),
TextureFallbackType.Black));
root.Add(CreateTextInputWithEnumField("AO Regex",
nameof(GenerateMaskProcessor.aoTextureRegex),
nameof(GenerateMaskProcessor.aoFallbackType),
TextureFallbackType.White));
root.Add(CreateTextInputWithEnumField("Detail Mask Regex",
nameof(GenerateMaskProcessor.detailMaskTextureRegex),
nameof(GenerateMaskProcessor.detailMaskFallbackType),
TextureFallbackType.White));
root.Add(CreateTextInputWithEnumField("Smoothness Regex",
nameof(GenerateMaskProcessor.smoothnessTextureRegex),
nameof(GenerateMaskProcessor.smoothnessFallbackType),
TextureFallbackType.LinearGray));
root.Add(CreateTextInputField("Grouping Regex",
nameof(GenerateMaskProcessor.textureGroupingRegex)));
root.Add(CreateTextInputWithEnumField("Naming Regex",
nameof(GenerateMaskProcessor.namingRegex),
nameof(GenerateMaskProcessor.namingSource),
TextureNamingSourceType.R));
root.Add(CreateTextInputField("Replace By",
nameof(GenerateMaskProcessor.replaceBy)));
return root;
}
private VisualElement CreateTextInputField(string label, string binding)
{
var textField = new TextField(label);
textField.SetBinding(nameof(TextField.value), new DataBinding() { dataSourcePath = PropertyPath.FromName(binding) });
return textField;
}
private VisualElement CreateTextInputWithEnumField(string label, string textInputBinding, string enumBinding, Enum defaultEnumValue)
{
var root = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row,
}
};
var textField = new TextField(label)
{
style =
{
flexGrow = 1,
}
};
textField.SetBinding(nameof(TextField.value), new DataBinding() { dataSourcePath = PropertyPath.FromName(textInputBinding) });
var enumField = new EnumField(defaultEnumValue)
{
style =
{
width = 100,
}
};
enumField.SetBinding(nameof(EnumField.value), new DataBinding() { dataSourcePath = PropertyPath.FromName(enumBinding) });
root.Add(textField);
root.Add(enumField);
return root;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 591e3f52dbd05794395f0918f2b19557