Merge pull request #944 from Santarh/throwExportNonReadableTexture

Exporter can export compressed or non-readable texture.
This commit is contained in:
ousttrue
2021-05-14 12:47:04 +09:00
committed by GitHub
18 changed files with 212 additions and 61 deletions

View File

@@ -1,6 +1,8 @@
using System.IO;
using UnityEditor;
using UnityEngine;
using VRMShaders;
using ColorSpace = UniGLTF.ColorSpace;
namespace MeshUtility
{
@@ -34,7 +36,9 @@ namespace MeshUtility
return;
}
File.WriteAllBytes(path, texture.EncodeToPNG());
var (tex, mime) = TextureExporter.GetTextureBytesWithMime(texture, sRGB ? ColorSpace.sRGB : ColorSpace.Linear);
File.WriteAllBytes(path, tex);
Debug.Log($"save: {path}");
var assetsPath = AssetsPath.FromFullpath(path);

View File

@@ -3,12 +3,6 @@ using UnityEngine;
namespace UniGLTF
{
public enum ColorSpace
{
sRGB,
Linear,
}
public static class ColorConversionExtensions
{
public static float[] ToFloat4(this Color src, ColorSpace srcColorSpace, ColorSpace dstColorSpace)

View File

@@ -21,9 +21,9 @@ namespace UniGLTF
/// <param name="bufferIndex"></param>
/// <param name="texture"></param>
/// <returns>gltf texture index</returns>
public static int PushGltfTexture(this glTF gltf, int bufferIndex, Texture2D texture, Func<Texture2D, (byte[] bytes, string mime)> getTextureBytes)
public static int PushGltfTexture(this glTF gltf, int bufferIndex, Texture2D texture, ColorSpace textureColorSpace, gltfExporter.GetBytesWithMimeFromTexture2D getTextureBytes)
{
var bytesWithMime = getTextureBytes(texture);
var bytesWithMime = getTextureBytes(texture, textureColorSpace);
// add view
var view = gltf.buffers[bufferIndex].Append(bytesWithMime.bytes, glBufferTarget.NONE);

View File

@@ -214,12 +214,21 @@ namespace UniGLTF
return false;
}
public virtual void ExportExtensions(Func<Texture2D, (byte[], string)> getTextureBytes)
public virtual void ExportExtensions(GetBytesWithMimeFromTexture2D getTextureBytes)
{
// do nothing
}
public virtual void Export(MeshExportSettings meshExportSettings, Func<Texture, bool> useAsset, Func<Texture2D, (byte[], string)> getTextureBytes)
/// <summary>
/// Texture2D から実際のバイト列を取得するデリゲート。
///
/// textureColorSpace は Texture2D をコピーする際に用いる。
/// Texture2D 単体では、色空間を知ることができないため。
/// 一般には、その Texture2D がアサインされる glTF のプロパティの仕様が定める色空間と一致する。
/// </summary>
public delegate (byte[] bytes, string mime) GetBytesWithMimeFromTexture2D(Texture2D texture, ColorSpace textureColorSpace);
public virtual void Export(MeshExportSettings meshExportSettings, Func<Texture, bool> useAsset, GetBytesWithMimeFromTexture2D getTextureBytes)
{
var bytesBuffer = new ArrayByteBuffer(new byte[50 * 1024 * 1024]);
var bufferIndex = glTF.AddBuffer(bytesBuffer);
@@ -362,8 +371,8 @@ namespace UniGLTF
// Extension で Texture が増える場合があるので最後に呼ぶ
for (int i = 0; i < TextureManager.Exported.Count; ++i)
{
var unityTexture = TextureManager.Exported[i];
glTF.PushGltfTexture(bufferIndex, unityTexture, getTextureBytes);
var (unityTexture, colorSpace) = TextureManager.Exported[i];
glTF.PushGltfTexture(bufferIndex, unityTexture, colorSpace, getTextureBytes);
}
}
#endregion

View File

@@ -26,7 +26,7 @@ namespace UniGLTF
var materialExporter = new MaterialExporter();
materialExporter.ExportMaterial(material, textureManager);
var convTex0 = textureManager.Exported[0];
var (convTex0, colorSpace) = textureManager.Exported[0];
var sampler = TextureSamplerUtil.Export(convTex0);
Assert.AreEqual(glWrap.CLAMP_TO_EDGE, sampler.wrapS);

View File

@@ -3,6 +3,7 @@ using System.Linq;
using UniGLTF;
using UniJSON;
using UnityEngine;
using ColorSpace = UniGLTF.ColorSpace;
namespace VRM
@@ -14,7 +15,7 @@ namespace VRM
return new VRMMaterialExporter();
}
public static glTF Export(MeshExportSettings configuration, GameObject go, Func<Texture, bool> useAsset, Func<Texture2D, (byte[], string)> getTextureBytes)
public static glTF Export(MeshExportSettings configuration, GameObject go, Func<Texture, bool> useAsset, GetBytesWithMimeFromTexture2D getTextureBytes)
{
var gltf = new glTF();
using (var exporter = new VRMExporter(gltf))
@@ -32,7 +33,7 @@ namespace VRM
gltf.extensionsUsed.Add(glTF_VRM_extensions.ExtensionName);
}
public override void ExportExtensions(Func<Texture2D, (byte[], string)> getTextureBytes)
public override void ExportExtensions(GetBytesWithMimeFromTexture2D getTextureBytes)
{
// avatar
var animator = Copy.GetComponent<Animator>();
@@ -110,7 +111,7 @@ namespace VRM
VRM.meta.title = meta.Title;
if (meta.Thumbnail != null)
{
VRM.meta.texture = glTF.PushGltfTexture(glTF.buffers.Count - 1, meta.Thumbnail, getTextureBytes);
VRM.meta.texture = glTF.PushGltfTexture(glTF.buffers.Count - 1, meta.Thumbnail, ColorSpace.sRGB, getTextureBytes);
}
VRM.meta.licenseType = meta.LicenseType;

View File

@@ -127,7 +127,7 @@ namespace UniVRM10
return new float[] { -v.x, v.y, v.z };
}
public void Export(GameObject root, Model model, ModelExporter converter, ExportArgs option, Func<Texture2D, (byte[], string)> getTextureBytes, VRM10MetaObject metaObject = null)
public void Export(GameObject root, Model model, ModelExporter converter, ExportArgs option, gltfExporter.GetBytesWithMimeFromTexture2D getTextureBytes, VRM10MetaObject metaObject = null)
{
ExportAsset(model);
@@ -180,8 +180,8 @@ namespace UniVRM10
// Extension で Texture が増える場合があるので最後に呼ぶ
for (int i = 0; i < m_textureExporter.Exported.Count; ++i)
{
var unityTexture = m_textureExporter.Exported[i];
Storage.Gltf.PushGltfTexture(0, unityTexture, getTextureBytes);
var (unityTexture, texColorSpace) = m_textureExporter.Exported[i];
Storage.Gltf.PushGltfTexture(0, unityTexture, texColorSpace, getTextureBytes);
}
if (thumbnailTextureIndex.HasValue)
@@ -775,7 +775,7 @@ namespace UniVRM10
/// <param name="go"></param>
/// <param name="getTextureBytes"></param>
/// <returns></returns>
public static byte[] Export(GameObject go, Func<Texture2D, (byte[], string)> getTextureBytes = null)
public static byte[] Export(GameObject go, gltfExporter.GetBytesWithMimeFromTexture2D getTextureBytes = null)
{
if (getTextureBytes == null)
{

View File

@@ -2,6 +2,7 @@ using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using ColorSpace = UniGLTF.ColorSpace;
namespace VRMShaders
{
@@ -107,14 +108,14 @@ namespace VRMShaders
return false;
}
public static (byte[], string) GetTextureBytesWithMime(Texture2D texture)
public static (byte[], string) GetTextureBytesWithMime(Texture2D texture, ColorSpace colorSpace)
{
if (TryGetBytesWithMime(texture, out byte[] bytes, out string mime))
{
return (bytes, mime);
}
return TextureExporter.GetTextureBytesWithMime(texture);
return TextureExporter.GetTextureBytesWithMime(texture, colorSpace);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace UniGLTF
{
public enum ColorSpace
{
sRGB,
Linear,
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5e97eeb2080e43e18ba57f1a3820a7e6
timeCreated: 1620901197

View File

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using UnityEngine;
using ColorSpace = UniGLTF.ColorSpace;
namespace VRMShaders
@@ -22,10 +23,27 @@ namespace VRMShaders
}
public static Texture2D CopyTexture(Texture src, TextureImportTypes textureType, Material material)
{
return CopyTexture(src, textureType.GetColorSpace(), material);
}
public static Texture2D CopyTexture(Texture src, ColorSpace colorSpace, Material material)
{
Texture2D dst = null;
RenderTextureReadWrite colorSpace = textureType.GetColorSpace();
var renderTexture = new RenderTexture(src.width, src.height, 0, RenderTextureFormat.ARGB32, colorSpace);
RenderTextureReadWrite readWrite;
switch (colorSpace)
{
case ColorSpace.sRGB:
readWrite = RenderTextureReadWrite.sRGB;
break;
case ColorSpace.Linear:
readWrite = RenderTextureReadWrite.Linear;
break;
default:
throw new ArgumentOutOfRangeException(nameof(colorSpace), colorSpace, null);
}
var renderTexture = new RenderTexture(src.width, src.height, 0, RenderTextureFormat.ARGB32, readWrite);
if (material != null)
{
@@ -36,7 +54,7 @@ namespace VRMShaders
Graphics.Blit(src, renderTexture);
}
dst = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false, colorSpace == RenderTextureReadWrite.Linear);
dst = new Texture2D(src.width, src.height, TextureFormat.ARGB32, false, readWrite == RenderTextureReadWrite.Linear);
dst.ReadPixels(new Rect(0, 0, src.width, src.height), 0, 0);
dst.name = src.name;
dst.anisoLevel = src.anisoLevel;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using ColorSpace = UniGLTF.ColorSpace;
namespace VRMShaders
@@ -55,7 +56,7 @@ namespace VRMShaders
/// </summary>
/// <typeparam name="Texture2D"></typeparam>
/// <returns></returns>
public readonly List<Texture2D> Exported = new List<Texture2D>();
public readonly List<(Texture2D, ColorSpace)> Exported = new List<(Texture2D, ColorSpace)>();
/// <summary>
/// Texture の export index を得る
@@ -101,7 +102,7 @@ namespace VRMShaders
{
texture2D = TextureConverter.CopyTexture(src, TextureImportTypes.sRGB, null);
}
Exported.Add(texture2D);
Exported.Add((texture2D, ColorSpace.sRGB));
m_exportMap.Add(new ExportKey(src, ConvertTypes.None), index);
return index;
@@ -114,7 +115,33 @@ namespace VRMShaders
/// <returns></returns>
public int ExportLinear(Texture src)
{
throw new NotImplementedException();
if (src == null)
{
return -1;
}
var exportKey = new ExportKey(src, ConvertTypes.None);
// search cache
if (m_exportMap.TryGetValue(exportKey, out var index))
{
return index;
}
index = Exported.Count;
var texture2d = src as Texture2D;
if (m_useAsset(texture2d))
{
// do nothing
}
else
{
texture2d = TextureConverter.CopyTexture(src, TextureImportTypes.Linear, null);
}
Exported.Add((texture2d, ColorSpace.Linear));
m_exportMap.Add(exportKey, index);
return index;
}
/// <summary>
@@ -147,7 +174,7 @@ namespace VRMShaders
index = Exported.Count;
var texture2D = OcclusionMetallicRoughnessConverter.Export(metallicSmoothTexture, smoothness, occlusionTexture);
Exported.Add(texture2D);
Exported.Add((texture2D, ColorSpace.Linear));
if (metallicSmoothTexture != null)
{
m_exportMap.Add(new ExportKey(metallicSmoothTexture, ConvertTypes.OcclusionMetallicRoughness), index);
@@ -191,7 +218,7 @@ namespace VRMShaders
texture2D = NormalConverter.Export(src);
}
Exported.Add(texture2D);
Exported.Add((texture2D, ColorSpace.Linear));
m_exportMap.Add(new ExportKey(src, ConvertTypes.Normal), index);
return index;
@@ -200,10 +227,7 @@ namespace VRMShaders
/// <summary>
/// 画像のバイト列を得る
/// </summary>
/// <param name="bytes"></param>
/// <param name="texture"></param>
/// <returns></returns>
public static (byte[] bytes, string mime) GetTextureBytesWithMime(Texture2D texture)
public static (byte[] bytes, string mime) GetTextureBytesWithMime(Texture2D texture, ColorSpace colorSpace)
{
try
{
@@ -212,21 +236,39 @@ namespace VRMShaders
{
return (png, "image/png");
}
else
{
// Failed, because texture is compressed.
// ex. ".DDS" file, or Compression is enabled in Texture Import Settings.
return CopyTextureAndGetBytesWithMime(texture, colorSpace);
}
}
catch (Exception ex)
catch (ArgumentException ex)
{
// fail to EncodeToPng
// System.ArgumentException: not readable, the texture memory can not be accessed from scripts. You can make the texture readable in the Texture Import Settings.
// Failed, because texture is not readable.
Debug.LogWarning(ex);
// 単純に EncodeToPNG できないため、コピーしてから EncodeToPNG する。
return CopyTextureAndGetBytesWithMime(texture, colorSpace);
}
}
private static (byte[] bytes, string mime) CopyTextureAndGetBytesWithMime(Texture2D texture, ColorSpace colorSpace)
{
var copiedTex = TextureConverter.CopyTexture(texture, colorSpace, null);
var bytes = copiedTex.EncodeToPNG();
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(copiedTex);
}
else
{
UnityEngine.Object.DestroyImmediate(copiedTex);
}
{
// try copy and EncodeToPng
var copy = TextureConverter.CopyTexture(texture, TextureImportTypes.sRGB, null);
var png = copy.EncodeToPNG();
UnityEngine.Object.DestroyImmediate(copy);
return (png, "image/png");
}
return (bytes, "image/png");
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using UnityEngine;
using ColorSpace = UniGLTF.ColorSpace;
namespace VRMShaders
{
@@ -31,16 +32,16 @@ namespace VRMShaders
public static class TextureImportTypesExtensions
{
public static RenderTextureReadWrite GetColorSpace(this TextureImportTypes textureType)
public static ColorSpace GetColorSpace(this TextureImportTypes textureType)
{
switch (textureType)
{
case TextureImportTypes.sRGB:
return RenderTextureReadWrite.sRGB;
return ColorSpace.sRGB;
case TextureImportTypes.Linear:
case TextureImportTypes.StandardMap:
case TextureImportTypes.NormalMap:
return RenderTextureReadWrite.Linear;
return ColorSpace.Linear;
default:
throw new NotImplementedException();
}

View File

@@ -6,7 +6,7 @@ TextureImporter:
serializedVersion: 9
mipmaps:
mipMapMode: 0
enableMipMap: 1
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
@@ -31,12 +31,12 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
filterMode: 0
aniso: -1
mipBias: -100
wrapU: -1
wrapV: -1
wrapW: -1
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
@@ -48,7 +48,7 @@ TextureImporter:
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaUsage: 0
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
@@ -63,7 +63,7 @@ TextureImporter:
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0

View File

@@ -4,7 +4,7 @@ IHVImageFormatImporter:
externalObjects: {}
textureSettings:
serializedVersion: 2
filterMode: 1
filterMode: 0
aniso: 1
mipBias: 0
wrapU: 0

View File

@@ -0,0 +1,66 @@
using System.Linq;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using ColorSpace = UniGLTF.ColorSpace;
namespace VRMShaders
{
public sealed class CopyTextureTests
{
private static string AssetPath = "Assets/VRMShaders/GLTF/IO/Tests";
private static readonly Color32 Black = new Color32(0, 0, 0, 255);
private static readonly Color32 Gray = new Color32(127, 127, 127, 255);
private static readonly Color32 White = new Color32(255, 255, 255, 255);
private static readonly Color32[] PngTextureValues = new Color32[]
{
White, White, White, White,
Gray, Gray, Gray, Gray,
Gray, Gray, Gray, Gray,
Black, Black, Black, Black,
};
// DDS のテクスチャ圧縮によって別の色になっている
private static readonly Color32 CompressedBlack = new Color32(24, 24, 24, 255);
private static readonly Color32 CompressedGray = new Color32(101, 96, 101, 255);
private static readonly Color32 CompressedWhite = new Color32(255, 255, 255, 255);
private static readonly Color32[] DdsTextureValues = new Color32[]
{
CompressedBlack, CompressedBlack, CompressedBlack, CompressedBlack,
CompressedGray, CompressedGray, CompressedGray, CompressedGray,
CompressedGray, CompressedGray, CompressedGray, CompressedGray,
CompressedWhite, CompressedWhite, CompressedWhite, CompressedWhite,
};
[Test]
public void CopyFromNonReadableSRgbPng()
{
var nonReadableTex = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/4x4.png");
Assert.False(nonReadableTex.isReadable);
var copiedTex = TextureConverter.CopyTexture(nonReadableTex, ColorSpace.sRGB, null);
var pixels = copiedTex.GetPixels32(miplevel: 0);
Assert.AreEqual(pixels.Length, PngTextureValues.Length);
for (var idx = 0; idx < pixels.Length; ++idx)
{
Assert.AreEqual(PngTextureValues[idx], pixels[idx]);
}
}
[Test]
public void CopyFromNonReadableSRgbDds()
{
var compressedTex = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/4x4compressed.dds");
Assert.False(compressedTex.isReadable);
var copiedTex = TextureConverter.CopyTexture(compressedTex, ColorSpace.sRGB, null);
var pixels = copiedTex.GetPixels32(miplevel: 0);
Assert.AreEqual(pixels.Length, DdsTextureValues.Length);
for (var idx = 0; idx < pixels.Length; ++idx)
{
Assert.AreEqual(DdsTextureValues[idx], pixels[idx]);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3b001f23191141369d3482cfebfaae2f
timeCreated: 1620903873

View File

@@ -1,6 +1,7 @@
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using ColorSpace = UniGLTF.ColorSpace;
namespace VRMShaders
{
@@ -9,20 +10,20 @@ namespace VRMShaders
static string AssetPath = "Assets/VRMShaders/GLTF/IO/Tests";
[Test]
public void NotReadable()
public void NonReadablePng()
{
var readonlyTexture = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/4x4.png");
Assert.False(readonlyTexture.isReadable);
var (bytes, mime) = AssetTextureUtil.GetTextureBytesWithMime(readonlyTexture);
var nonReadableTex = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/4x4.png");
Assert.False(nonReadableTex.isReadable);
var (bytes, mime) = AssetTextureUtil.GetTextureBytesWithMime(nonReadableTex, ColorSpace.sRGB);
Assert.NotNull(bytes);
}
[Test]
public void Compressed()
public void NonReadableDds()
{
var readonlyTexture = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/4x4compressed.dds");
Assert.False(readonlyTexture.isReadable);
var (bytes, mime) = AssetTextureUtil.GetTextureBytesWithMime(readonlyTexture);
var (bytes, mime) = AssetTextureUtil.GetTextureBytesWithMime(readonlyTexture, ColorSpace.sRGB);
Assert.NotNull(bytes);
}
}