proper texture swizzling

This commit is contained in:
4sval 2023-02-26 04:35:53 +01:00
parent ec2e25153e
commit 412c5dd786
5 changed files with 136 additions and 123 deletions

View File

@ -32,6 +32,7 @@ public class Options
public readonly Dictionary<string, Texture> Icons;
private readonly ETexturePlatform _platform;
private readonly string _game;
public Options()
{
@ -60,6 +61,7 @@ public class Options
};
_platform = UserSettings.Default.OverridedPlatform;
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.GameName.ToUpper();
SelectModel(Guid.Empty);
}
@ -181,9 +183,9 @@ public class Options
if (!Textures.TryGetValue(guid, out texture) && o.GetMipByMaxSize(UserSettings.Default.PreviewMaxTextureSize) is { } mip)
{
TextureDecoder.DecodeTexture(mip, o.Format, o.isNormalMap, _platform, out var data, out _);
if (fix) TextureHelper.FixChannels(o, mip, ref data);
texture = new Texture(data, mip.SizeX, mip.SizeY, o);
if (fix) TextureHelper.FixChannels(_game, texture);
Textures[guid] = texture;
}
return texture != null;

View File

@ -337,74 +337,7 @@ public class Material : IDisposable
return ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left);
}
private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f);
public void ImGuiTextureInspector(Texture fallback)
{
var texture = GetSelectedTexture() ?? fallback;
if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
{
SnimGui.NoFramePaddingOnY(() =>
{
SnimGui.Layout("Type");ImGui.Text($" : ({texture.Format}) {texture.Name}");
SnimGui.TooltipCopy("(?) Click to Copy Path", texture.Path);
SnimGui.Layout("Guid");ImGui.Text($" : {texture.Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
SnimGui.Layout("Import");ImGui.Text($" : {texture.ImportedWidth}x{texture.ImportedHeight}");
SnimGui.Layout("Export");ImGui.Text($" : {texture.Width}x{texture.Height}");
ImGui.EndTable();
});
}
var io = ImGui.GetIO();
var canvasP0 = ImGui.GetCursorScreenPos();
var canvasSize = ImGui.GetContentRegionAvail();
if (canvasSize.X < 50.0f) canvasSize.X = 50.0f;
if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f;
var canvasP1 = canvasP0 + canvasSize;
var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
var absoluteMiddle = canvasSize / 2.0f;
ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft);
if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
_scrolling.X += io.MouseDelta.X;
_scrolling.Y += io.MouseDelta.Y;
}
else if (ImGui.IsItemHovered() && io.MouseWheel != 0.0f)
{
var zoomFactor = 1.0f + io.MouseWheel * 0.1f;
var mousePosCanvas = io.MousePos - origin;
_scrolling.X -= (mousePosCanvas.X - absoluteMiddle.X) * (zoomFactor - 1);
_scrolling.Y -= (mousePosCanvas.Y - absoluteMiddle.Y) * (zoomFactor - 1);
_scrolling.Z *= zoomFactor;
origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
}
var drawList = ImGui.GetWindowDrawList();
drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424);
drawList.PushClipRect(canvasP0, canvasP1, true);
{
var sensitivity = _scrolling.Z * 25.0f;
for (float x = _scrolling.X % sensitivity; x < canvasSize.X; x += sensitivity)
drawList.AddLine(canvasP0 with { X = canvasP0.X + x }, canvasP1 with { X = canvasP0.X + x }, 0x28C8C8C8);
for (float y = _scrolling.Y % sensitivity; y < canvasSize.Y; y += sensitivity)
drawList.AddLine(canvasP0 with { Y = canvasP0.Y + y }, canvasP1 with { Y = canvasP0.Y + y }, 0x28C8C8C8);
}
drawList.PopClipRect();
drawList.PushClipRect(canvasP0, canvasP1, true);
{
var relativeMiddle = origin + absoluteMiddle;
var ratio = Math.Min(canvasSize.X / texture.Width, canvasSize.Y / texture.Height) * 0.95f * _scrolling.Z;
var size = new Vector2(texture.Width, texture.Height) * ratio / 2f;
drawList.AddImage(texture.GetPointer(), relativeMiddle - size, relativeMiddle + size);
drawList.AddRect(relativeMiddle - size, relativeMiddle + size, 0xFFFFFFFF);
}
drawList.PopClipRect();
}
private Texture GetSelectedTexture()
public Texture GetSelectedTexture()
{
return SelectedTexture switch
{

View File

@ -1,8 +1,10 @@
using System;
using System.Numerics;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Core.Misc;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@ -25,6 +27,17 @@ public class Texture : IDisposable
public int Width;
public int Height;
private const int DisabledChannel = (int)BlendingFactor.Zero;
private readonly bool[] _values = { true, true, true, true };
private readonly string[] _labels = { "R", "G", "B", "A" };
public int[] SwizzleMask =
{
(int) PixelFormat.Red,
(int) PixelFormat.Green,
(int) PixelFormat.Blue,
(int) PixelFormat.Alpha
};
public Texture(TextureType type)
{
_handle = GL.GenTexture();
@ -58,7 +71,7 @@ public class Texture : IDisposable
GL.TexImage2D(_target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
@ -80,7 +93,7 @@ public class Texture : IDisposable
GL.TexImage2D(_target, 0, texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, data);
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
@ -97,7 +110,7 @@ public class Texture : IDisposable
GL.TexImage2D(_target, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, ref color);
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
@ -114,7 +127,7 @@ public class Texture : IDisposable
}
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
@ -129,7 +142,7 @@ public class Texture : IDisposable
ProcessPixels(texture, _target);
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
@ -167,6 +180,12 @@ public class Texture : IDisposable
GL.BindTexture(_target, _handle);
}
public void Swizzle()
{
Bind();
GL.TexParameter(_target, TextureParameterName.TextureSwizzleRgba, SwizzleMask);
}
public IntPtr GetPointer() => (IntPtr) _handle;
public void WindowResized(int width, int height)
@ -194,6 +213,84 @@ public class Texture : IDisposable
{
GL.DeleteTexture(_handle);
}
private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f);
public void ImGuiTextureInspector()
{
if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
{
SnimGui.NoFramePaddingOnY(() =>
{
SnimGui.Layout("Type");ImGui.Text($" : ({Format}) {Name}");
SnimGui.TooltipCopy("(?) Click to Copy Path", Path);
SnimGui.Layout("Guid");ImGui.Text($" : {Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
SnimGui.Layout("Import");ImGui.Text($" : {ImportedWidth}x{ImportedHeight}");
SnimGui.Layout("Export");ImGui.Text($" : {Width}x{Height}");
SnimGui.Layout("Swizzle");
for (int c = 0; c < SwizzleMask.Length; c++)
{
if (ImGui.Checkbox(_labels[c], ref _values[c]))
{
Bind();
GL.TexParameter(_target, TextureParameterName.TextureSwizzleR + c, _values[c] ? SwizzleMask[c] : DisabledChannel);
}
ImGui.SameLine();
}
ImGui.EndTable();
});
}
var io = ImGui.GetIO();
var canvasP0 = ImGui.GetCursorScreenPos();
var canvasSize = ImGui.GetContentRegionAvail();
if (canvasSize.X < 50.0f) canvasSize.X = 50.0f;
if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f;
var canvasP1 = canvasP0 + canvasSize;
var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
var absoluteMiddle = canvasSize / 2.0f;
ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft);
if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
_scrolling.X += io.MouseDelta.X;
_scrolling.Y += io.MouseDelta.Y;
}
else if (ImGui.IsItemHovered() && io.MouseWheel != 0.0f)
{
var zoomFactor = 1.0f + io.MouseWheel * 0.1f;
var mousePosCanvas = io.MousePos - origin;
_scrolling.X -= (mousePosCanvas.X - absoluteMiddle.X) * (zoomFactor - 1);
_scrolling.Y -= (mousePosCanvas.Y - absoluteMiddle.Y) * (zoomFactor - 1);
_scrolling.Z *= zoomFactor;
origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
}
var drawList = ImGui.GetWindowDrawList();
drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424);
drawList.PushClipRect(canvasP0, canvasP1, true);
{
var sensitivity = _scrolling.Z * 25.0f;
for (float x = _scrolling.X % sensitivity; x < canvasSize.X; x += sensitivity)
drawList.AddLine(canvasP0 with { X = canvasP0.X + x }, canvasP1 with { X = canvasP0.X + x }, 0x28C8C8C8);
for (float y = _scrolling.Y % sensitivity; y < canvasSize.Y; y += sensitivity)
drawList.AddLine(canvasP0 with { Y = canvasP0.Y + y }, canvasP1 with { Y = canvasP0.Y + y }, 0x28C8C8C8);
}
drawList.PopClipRect();
drawList.PushClipRect(canvasP0, canvasP1, true);
{
var relativeMiddle = origin + absoluteMiddle;
var ratio = Math.Min(canvasSize.X / Width, canvasSize.Y / Height) * 0.95f * _scrolling.Z;
var size = new Vector2(Width, Height) * ratio / 2f;
drawList.AddImage(GetPointer(), relativeMiddle - size, relativeMiddle + size);
drawList.AddRect(relativeMiddle - size, relativeMiddle + size, 0xFFFFFFFF);
}
drawList.PopClipRect();
}
}
public enum TextureType

View File

@ -1,84 +1,65 @@
using CUE4Parse.UE4.Assets.Exports.Texture;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Shading;
public static class TextureHelper
{
private static readonly string _game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.GameName;
/// <summary>
/// Red : Specular (not used anymore)
/// Green : Metallic
/// Blue : Roughness
/// </summary>
public static void FixChannels(UTexture2D o, FTexture2DMipMap mip, ref byte[] data)
public static void FixChannels(string game, Texture texture)
{
// only if it makes a big difference pls
switch (_game)
switch (game)
{
// R: Whatever (AO / S / E / ...)
// G: Roughness
// B: Metallic
case "hk_project":
case "cosmicshake":
case "phoenix":
case "HK_PROJECT":
case "COSMICSHAKE":
case "PHOENIX":
{
unsafe
texture.SwizzleMask = new []
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // RBG
offset += 4;
}
}
}
(int) PixelFormat.Red,
(int) PixelFormat.Blue,
(int) PixelFormat.Green,
(int) PixelFormat.Alpha
};
break;
}
// R: Metallic
// G: Roughness
// B: Whatever (AO / S / E / ...)
case "shootergame":
case "divineknockout":
case "moonman":
case "SHOOTERGAME":
case "DIVINEKNOCKOUT":
case "MOONMAN":
{
unsafe
texture.SwizzleMask = new []
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // GRB
(d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // RBG
offset += 4;
}
}
}
(int) PixelFormat.Blue,
(int) PixelFormat.Red,
(int) PixelFormat.Green,
(int) PixelFormat.Alpha
};
break;
}
// R: Roughness
// G: Metallic
// B: Whatever (AO / S / E / ...)
case "ccff7r":
case "CCFF7R":
{
unsafe
texture.SwizzleMask = new []
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // RBG
(d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // BRG
offset += 4;
}
}
}
(int) PixelFormat.Blue,
(int) PixelFormat.Green,
(int) PixelFormat.Red,
(int) PixelFormat.Alpha
};
break;
}
}
texture.Swizzle();
}
}

View File

@ -701,7 +701,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
s.Renderer.Options.TryGetModel(out var model) &&
s.Renderer.Options.TryGetSection(model, out var section))
{
model.Materials[section.MaterialIndex].ImGuiTextureInspector(s.Renderer.Options.Icons["noimage"]);
(model.Materials[section.MaterialIndex].GetSelectedTexture() ?? s.Renderer.Options.Icons["noimage"]).ImGuiTextureInspector();
}
ImGui.End(); // if window is collapsed
}