From 88d942ec01aa14cded8650b6c5b1023d213d08ee Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:16:12 +1100 Subject: [PATCH] ColorTable impl --- Meddle/Meddle.Plugin/Models/ColorTable.cs | 157 ++++++++++++++++++ Meddle/Meddle.Plugin/Models/ColorTableRow.cs | 68 -------- Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs | 6 +- .../Meddle.Plugin/Utility/MaterialUtility.cs | 69 +------- .../ProcessCharacterNormalOperation.cs | 70 ++++++++ 5 files changed, 232 insertions(+), 138 deletions(-) create mode 100644 Meddle/Meddle.Plugin/Models/ColorTable.cs delete mode 100644 Meddle/Meddle.Plugin/Models/ColorTableRow.cs create mode 100644 Meddle/Meddle.Plugin/Utility/Materials/ProcessCharacterNormalOperation.cs diff --git a/Meddle/Meddle.Plugin/Models/ColorTable.cs b/Meddle/Meddle.Plugin/Models/ColorTable.cs new file mode 100644 index 0000000..6b46d75 --- /dev/null +++ b/Meddle/Meddle.Plugin/Models/ColorTable.cs @@ -0,0 +1,157 @@ +using System.Collections; +using System.Numerics; + +namespace Meddle.Plugin.Models; + +// https://github.com/Ottermandias/Penumbra.GameData/blob/45679aa32cc37b59f5eeb7cf6bf5a3ea36c626e0/Files/MtrlFile.ColorTable.cs +public unsafe struct ColorTable : IEnumerable +{ + public ColorTable(Half[] data) + { + fixed (byte* ptr = rowData) + { + for (var i = 0; i < NumRows; ++i) + { + var row = new Row(); + var span = row.AsHalves(); + for (var j = 0; j < 16; ++j) + span[j] = data[i * 16 + j]; + ((Row*)ptr)[i] = row; + } + } + } + + public ColorTable() + { + for (var i = 0; i < NumRows; ++i) + this[i] = Row.Default; + } + + public const int NumRows = 16; + private fixed byte rowData[NumRows * Row.Size]; + + public ref Row this[int i] + { + get + { + fixed (byte* ptr = rowData) + { + return ref ((Row*)ptr)[i]; + } + } + } + + public IEnumerator GetEnumerator() + { + for (var i = 0; i < NumRows; ++i) + yield return this[i]; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public unsafe struct Row + { + public const int Size = 32; + private fixed ushort data[16]; + + public static readonly Row Default = new() + { + Diffuse = Vector3.One, + Specular = Vector3.One, + SpecularStrength = 1.0f, + Emissive = Vector3.Zero, + GlossStrength = 20.0f, + TileSet = 0, + MaterialRepeat = new Vector2(16.0f), + MaterialSkew = Vector2.Zero, + }; + + public Vector3 Diffuse + { + readonly get => new(ToFloat(0), ToFloat(1), ToFloat(2)); + set + { + data[0] = FromFloat(value.X); + data[1] = FromFloat(value.Y); + data[2] = FromFloat(value.Z); + } + } + + public Vector3 Specular + { + readonly get => new(ToFloat(4), ToFloat(5), ToFloat(6)); + set + { + data[4] = FromFloat(value.X); + data[5] = FromFloat(value.Y); + data[6] = FromFloat(value.Z); + } + } + + public Vector3 Emissive + { + readonly get => new(ToFloat(8), ToFloat(9), ToFloat(10)); + set + { + data[8] = FromFloat(value.X); + data[9] = FromFloat(value.Y); + data[10] = FromFloat(value.Z); + } + } + + public Vector2 MaterialRepeat + { + readonly get => new(ToFloat(12), ToFloat(15)); + set + { + data[12] = FromFloat(value.X); + data[15] = FromFloat(value.Y); + } + } + + public Vector2 MaterialSkew + { + readonly get => new(ToFloat(13), ToFloat(14)); + set + { + data[13] = FromFloat(value.X); + data[14] = FromFloat(value.Y); + } + } + + public float SpecularStrength + { + readonly get => ToFloat(3); + set => data[3] = FromFloat(value); + } + + public float GlossStrength + { + readonly get => ToFloat(7); + set => data[7] = FromFloat(value); + } + + public ushort TileSet + { + readonly get => (ushort)(ToFloat(11) * 64f); + set => data[11] = FromFloat((value + 0.5f) / 64f); + } + + public readonly Span AsHalves() + { + fixed (ushort* ptr = data) + { + return new Span(ptr, 16); + } + } + + private readonly float ToFloat(int idx) + => (float)BitConverter.UInt16BitsToHalf(data[idx]); + + private static ushort FromFloat(float x) + => BitConverter.HalfToUInt16Bits((Half)x); + } +} diff --git a/Meddle/Meddle.Plugin/Models/ColorTableRow.cs b/Meddle/Meddle.Plugin/Models/ColorTableRow.cs deleted file mode 100644 index 40606c0..0000000 --- a/Meddle/Meddle.Plugin/Models/ColorTableRow.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Numerics; - -namespace Meddle.Plugin.Models; - -public class ColorTable -{ - public ColorTableRow[] Rows { get; } - - public ColorTable(Half[] table) - { - // Convert table to ColorTable rows - // table is 256, we want 16 rows - var colorTable = new ColorTableRow[16]; - for (var i = 0; i < colorTable.Length; i++) - { - var set = table.AsSpan(i * 16, 16); - // convert to floats - // values 0 to 1 - var floats = set.ToArray().Select(x => (float)x).ToArray(); - var diff = new Vector3(floats[0], floats[1], floats[2]); - var spec = new Vector3(floats[4], floats[5], floats[6]); - var emis = new Vector3(floats[8], floats[9], floats[10]); - var ss = floats[3]; - - // clamp values - diff = Vector3.Clamp(diff, Vector3.Zero, Vector3.One); - spec = Vector3.Clamp(spec, Vector3.Zero, Vector3.One); - emis = Vector3.Clamp(emis, Vector3.Zero, Vector3.One); - - colorTable[i] = new ColorTableRow(diff, spec, ss, emis); - } - - Rows = colorTable; - } - - public class ColorTableRow - { - public Vector3 Diffuse { get;} // 0,1,2 - public Vector3 Specular { get; } // 4,5,6 - public float SpecularStrength { get; } // 3 - public Vector3 Emissive { get; } // 8,9,10 - - public ColorTableRow(Vector3 diff, Vector3 spec, float ss, Vector3 emis) - { - Diffuse = diff; - Specular = spec; - SpecularStrength = ss; - Emissive = emis; - } - - public object Serialize() - { - // serialize vec3 not supported, so we'll just do it manually - - var diff = $"{Diffuse.X},{Diffuse.Y},{Diffuse.Z}"; - var spec = $"{Specular.X},{Specular.Y},{Specular.Z}"; - var emis = $"{Emissive.X},{Emissive.Y},{Emissive.Z}"; - - return new - { - Diffuse = diff, - Specular = spec, - SpecularStrength, - Emissive = emis, - }; - } - } -} diff --git a/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs b/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs index c96235f..e438648 100644 --- a/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs +++ b/Meddle/Meddle.Plugin/UI/CharacterTab.Utils.cs @@ -361,7 +361,7 @@ public static void DrawMaterial(Material material) if (material.ColorTable != null) { if (ImGui.CollapsingHeader($"Color Table##{material.GetHashCode()}")) - DrawColorTable(material.ColorTable); + DrawColorTable(material.ColorTable.Value); } } @@ -375,9 +375,9 @@ public static void DrawColorTable(ColorTable table) ImGui.TableSetupColumn("Emissive", ImGuiTableColumnFlags.WidthFixed, 50); ImGui.TableHeadersRow(); - for (var i = 0; i < table.Rows.Length; i++) + for (var i = 0; i < ColorTable.NumRows; i++) { - var row = table.Rows[i]; + var row = table[i]; ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.Text($"Row {i}"); diff --git a/Meddle/Meddle.Plugin/Utility/MaterialUtility.cs b/Meddle/Meddle.Plugin/Utility/MaterialUtility.cs index 7a53acf..b96a415 100644 --- a/Meddle/Meddle.Plugin/Utility/MaterialUtility.cs +++ b/Meddle/Meddle.Plugin/Utility/MaterialUtility.cs @@ -1,6 +1,7 @@ using System.Numerics; using Lumina.Data.Parsing; using Meddle.Plugin.Models; +using Meddle.Plugin.Utility.Materials; using SharpGLTF.Materials; using SkiaSharp; @@ -16,7 +17,7 @@ public static MaterialBuilder BuildCharacter(Material material, string name) { var normal = material.GetTexture(TextureUsage.SamplerNormal); - var operation = new ProcessCharacterNormalOperation(normal.Resource.ToTexture(), material.ColorTable!).Run(); + var operation = new ProcessCharacterNormalOperation(normal.Resource.ToTexture(), material.ColorTable!.Value).Run(); var baseColor = operation.BaseColor; if (material.TryGetTexture(TextureUsage.SamplerDiffuse, out var diffuseTexture)) @@ -291,70 +292,4 @@ public static SKTexture MultiplyBitmaps(SKTexture target, SKTexture multiplier, return result; } - - private class ProcessCharacterNormalOperation(SKTexture normal, ColorTable table) - { - public SKTexture Normal { get; } = normal.Copy(); - public SKTexture BaseColor { get; } = new(normal.Width, normal.Height); - public SKTexture Specular { get; } = new(normal.Width, normal.Height); - public SKTexture Emissive { get; } = new(normal.Width, normal.Height); - - private static TableRow GetTableRowIndices(float input) - { - // These calculations are ported from character.shpk. - var smoothed = (MathF.Floor(input * 7.5f % 1.0f * 2) - * (-input * 15 + MathF.Floor(input * 15 + 0.5f))) - + (input * 15); - - var stepped = MathF.Floor(smoothed + 0.5f); - - return new TableRow - { - Stepped = (int)stepped, - Previous = (int)MathF.Floor(smoothed), - Next = (int)MathF.Ceiling(smoothed), - Weight = smoothed % 1, - }; - } - - private ref struct TableRow - { - public int Stepped; - public int Previous; - public int Next; - public float Weight; - } - - public ProcessCharacterNormalOperation Run() - { - for (var y = 0; y < normal.Height; y++) - for (var x = 0; x < normal.Width; x++) - { - var normalPixel = Normal[x, y].ToVector4(); - - // Table row data (.a) - var tableRow = GetTableRowIndices(normalPixel.W); - var prevRow = table.Rows[tableRow.Previous]; - var nextRow = table.Rows[tableRow.Next]; - - // Base colour (table, .b) - var lerpedDiffuse = Vector3.Lerp(prevRow.Diffuse, nextRow.Diffuse, tableRow.Weight); - BaseColor[x, y] = new Vec4Ext(lerpedDiffuse, normalPixel.Z); - - // Specular (table) - var lerpedSpecularColor = Vector3.Lerp(prevRow.Specular, nextRow.Specular, tableRow.Weight); - var lerpedSpecularFactor = float.Lerp(prevRow.SpecularStrength, nextRow.SpecularStrength, tableRow.Weight); - Specular[x, y] = new Vec4Ext(lerpedSpecularColor, lerpedSpecularFactor); - - // Emissive (table) - var lerpedEmissive = Vector3.Lerp(prevRow.Emissive, nextRow.Emissive, tableRow.Weight); - Emissive[x, y] = lerpedEmissive.ToSkColor(); - - // Normal (.rg) - Normal[x, y] = new Vec4Ext(normalPixel.X, normalPixel.Y, 1, normalPixel.Z); - } - - return this; - } - } } diff --git a/Meddle/Meddle.Plugin/Utility/Materials/ProcessCharacterNormalOperation.cs b/Meddle/Meddle.Plugin/Utility/Materials/ProcessCharacterNormalOperation.cs new file mode 100644 index 0000000..3b0d9e7 --- /dev/null +++ b/Meddle/Meddle.Plugin/Utility/Materials/ProcessCharacterNormalOperation.cs @@ -0,0 +1,70 @@ +using System.Numerics; +using Meddle.Plugin.Models; + +namespace Meddle.Plugin.Utility.Materials; + +public class ProcessCharacterNormalOperation(SKTexture normal, ColorTable table) + { + public SKTexture Normal { get; } = normal.Copy(); + public SKTexture BaseColor { get; } = new(normal.Width, normal.Height); + public SKTexture Specular { get; } = new(normal.Width, normal.Height); + public SKTexture Emissive { get; } = new(normal.Width, normal.Height); + + private static TableRow GetTableRowIndices(float input) + { + // These calculations are ported from character.shpk. + var smoothed = (MathF.Floor(input * 7.5f % 1.0f * 2) + * (-input * 15 + MathF.Floor(input * 15 + 0.5f))) + + (input * 15); + + var stepped = MathF.Floor(smoothed + 0.5f); + + return new TableRow + { + Stepped = (int)stepped, + Previous = (int)MathF.Floor(smoothed), + Next = (int)MathF.Ceiling(smoothed), + Weight = smoothed % 1, + }; + } + + private ref struct TableRow + { + public int Stepped; + public int Previous; + public int Next; + public float Weight; + } + + public ProcessCharacterNormalOperation Run() + { + for (var y = 0; y < normal.Height; y++) + for (var x = 0; x < normal.Width; x++) + { + var normalPixel = Normal[x, y].ToVector4(); + + // Table row data (.a) + var tableRow = GetTableRowIndices(normalPixel.W); + var prevRow = table[tableRow.Previous]; + var nextRow = table[tableRow.Next]; + + // Base colour (table, .b) + var lerpedDiffuse = Vector3.Lerp(prevRow.Diffuse, nextRow.Diffuse, tableRow.Weight); + BaseColor[x, y] = new Vec4Ext(lerpedDiffuse, normalPixel.Z); + + // Specular (table) + var lerpedSpecularColor = Vector3.Lerp(prevRow.Specular, nextRow.Specular, tableRow.Weight); + var lerpedSpecularFactor = float.Lerp(prevRow.SpecularStrength, nextRow.SpecularStrength, tableRow.Weight); + Specular[x, y] = new Vec4Ext(lerpedSpecularColor, lerpedSpecularFactor); + + // Emissive (table) + var lerpedEmissive = Vector3.Lerp(prevRow.Emissive, nextRow.Emissive, tableRow.Weight); + Emissive[x, y] = lerpedEmissive.ToSkColor(); + + // Normal (.rg) + Normal[x, y] = new Vec4Ext(normalPixel.X, normalPixel.Y, 1, normalPixel.Z); + } + + return this; + } + }