Last active
June 6, 2024 02:21
-
-
Save aras-p/0f0b02aa193346be18e5f130f2704782 to your computer and use it in GitHub Desktop.
Unity DDS file exporter for compressed textures
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Adds context menu to TextureImporter objects, saves .dds next to input texture, including mipmaps. | |
// Tested with Unity 2021.3.4 | |
using System; | |
using UnityEngine; | |
using UnityEditor; | |
using System.IO; | |
using Unity.Collections.LowLevel.Unsafe; | |
struct DDSHeader | |
{ | |
public static uint FourCC(char a, char b, char c, char d) | |
{ | |
return (uint)a | ((uint)b<<8) | ((uint)c<<16) | ((uint)d<<24); | |
} | |
public const uint DDSD_CAPS = 0x00000001; | |
public const uint DDSD_PIXELFORMAT = 0x00001000; | |
public const uint DDSD_WIDTH = 0x00000004; | |
public const uint DDSD_HEIGHT = 0x00000002; | |
public const uint DDSD_MIPMAPCOUNT = 0x00020000; | |
public const uint DDSD_LINEARSIZE = 0x00080000; | |
public const uint DDPF_FOURCC = 0x00000004; | |
public const uint DDSCAPS_TEXTURE = 0x00001000; | |
public const uint DDSCAPS_COMPLEX = 0x8; | |
public const uint DDSCAPS_MIPMAP = 0x400000; | |
public uint fourcc; | |
public uint size; | |
public uint flags; | |
public uint height; | |
public uint width; | |
public uint linearSize; | |
public uint depth; | |
public uint mipmapcount; | |
public uint unused0, unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10; | |
public uint pf_size; | |
public uint pf_flags; | |
public uint pf_fourcc; | |
public uint pf_bitcount; | |
public uint pf_rmask; | |
public uint pf_gmask; | |
public uint pf_bmask; | |
public uint pf_amask; | |
public uint caps1; | |
public uint caps2; | |
public uint caps3; | |
public uint caps4; | |
public uint unused11; | |
public static DDSHeader Create(int width, int height, int mip0DataSize, int mipCount, TextureFormat fmt) | |
{ | |
uint fourcc = 0; | |
switch (fmt) | |
{ | |
case TextureFormat.DXT1: fourcc = DDSHeader.FourCC('D', 'X', 'T', '1'); break; | |
case TextureFormat.DXT5: fourcc = DDSHeader.FourCC('D', 'X', 'T', '5'); break; | |
default: fourcc = DDSHeader.FourCC('D', 'X', '1', '0'); break; | |
} | |
var dds = new DDSHeader | |
{ | |
fourcc = DDSHeader.FourCC('D', 'D', 'S', ' '), | |
size = 124, | |
flags = DDSHeader.DDSD_CAPS | DDSHeader.DDSD_PIXELFORMAT | DDSHeader.DDSD_WIDTH | | |
DDSHeader.DDSD_HEIGHT | DDSHeader.DDSD_LINEARSIZE | (mipCount > 1 ? DDSHeader.DDSD_MIPMAPCOUNT : 0), | |
width = (uint)width, | |
height = (uint)height, | |
linearSize = (uint)mip0DataSize, | |
mipmapcount = (uint)mipCount, | |
pf_size = 32, | |
pf_flags = DDSHeader.DDPF_FOURCC, | |
pf_fourcc = fourcc, | |
caps1 = DDSHeader.DDSCAPS_TEXTURE | (mipCount > 0 ? DDSHeader.DDSCAPS_MIPMAP | DDSCAPS_COMPLEX : 0) | |
}; | |
return dds; | |
} | |
} | |
public class ExportDDS : MonoBehaviour | |
{ | |
[MenuItem("CONTEXT/TextureImporter/Export DDS")] | |
static unsafe void ExportTextureDDS(MenuCommand command) | |
{ | |
var imp = (TextureImporter)command.context; | |
if (imp == null) return; | |
var inPath = imp.assetPath; | |
if (!imp.isReadable) | |
{ | |
imp.isReadable = true; | |
AssetDatabase.ImportAsset(inPath); | |
} | |
var outPath = Path.ChangeExtension(inPath, "dds"); | |
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(inPath); | |
if (tex == null) return; | |
var width = tex.width; | |
var height = tex.height; | |
var fmt = tex.format; | |
var mipCount = tex.mipmapCount; | |
var data = tex.GetPixelData<byte>(0); | |
var header = DDSHeader.Create(width, height, data.Length, mipCount, fmt); | |
var headerPtr = UnsafeUtility.AddressOf(ref header); | |
var headerSize = UnsafeUtility.SizeOf<DDSHeader>(); | |
if (headerSize != 128) | |
return; | |
uint dxgiFormat = fmt switch | |
{ | |
TextureFormat.BC4 => 80, | |
TextureFormat.BC5 => 83, | |
TextureFormat.BC6H => 95, | |
TextureFormat.BC7 => 98, | |
_ => 0 | |
}; | |
using (var stream = File.Open(outPath, FileMode.Create)) | |
{ | |
using (var writer = new BinaryWriter(stream)) | |
{ | |
writer.Write(new Span<byte>(headerPtr, headerSize)); | |
if (dxgiFormat != 0) | |
{ | |
writer.Write(dxgiFormat); | |
writer.Write(3); // resource dimension: 2D | |
writer.Write(0); // misc flag | |
writer.Write(1); // array size | |
writer.Write(0); // misc flags 2 | |
} | |
writer.Write(new Span<byte>(data.GetUnsafeReadOnlyPtr(), data.Length)); | |
for (int mip = 1; mip < mipCount; ++mip) | |
{ | |
var dataMip = tex.GetPixelData<byte>(mip); | |
writer.Write(new Span<byte>(dataMip.GetUnsafeReadOnlyPtr(), dataMip.Length)); | |
} | |
} | |
} | |
AssetDatabase.ImportAsset(outPath); | |
Debug.Log($"Texture {inPath} exported to {outPath}, {width}x{height}, {fmt}"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment