Added GenerateMask to AssetsHelpers;
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 353f02bb002aafd419d5472f331069a9
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94ec75a6994a6db41b574c5bebf56670
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 591e3f52dbd05794395f0918f2b19557
|
||||
Reference in New Issue
Block a user