Merge pull request #1410 from Santarh/basisu

Add the capability of importing texture extension.
This commit is contained in:
ousttrue
2021-12-08 21:07:33 +09:00
committed by GitHub
17 changed files with 231 additions and 92 deletions

View File

@@ -0,0 +1,63 @@
using System;
using UniJSON;
namespace UniGLTF
{
/// <summary>
/// `texture` に対する extension.
///
/// https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_basisu
/// </summary>
[Serializable]
public sealed class glTF_KHR_texture_basisu
{
public const string ExtensionName = "KHR_texture_basisu";
private static readonly Utf8String ExtensionNameUt8 = Utf8String.From(ExtensionName);
/// <summary>
/// glTF Id
/// </summary>
[JsonSchema(Minimum = 0)]
public int source = -1;
public static bool TryGet(glTFTexture texture, out glTF_KHR_texture_basisu basisuExtension)
{
if (texture?.extensions is glTFExtensionImport importedExtensions)
{
foreach (var kv in importedExtensions.ObjectItems())
{
if (kv.Key.GetUtf8String() == ExtensionNameUt8)
{
basisuExtension = Deserialize(kv.Value);
return basisuExtension != null;
}
}
}
basisuExtension = default;
return false;
}
private static glTF_KHR_texture_basisu Deserialize(JsonNode json)
{
if (json.Value.ValueType != ValueNodeType.Object) return null;
foreach (var kv in json.ObjectItems())
{
var key = kv.Key.GetString();
switch (key)
{
case nameof(source):
var value = kv.Value.GetInt32();
return new glTF_KHR_texture_basisu
{
source = value,
};
}
}
return null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ed158370be0b4340b9d571c199878591
timeCreated: 1638944458

View File

@@ -5,6 +5,11 @@ using UniJSON;
namespace UniGLTF
{
/// <summary>
/// material が参照する `textureInfo` に対する extension.
///
/// https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform
/// </summary>
[Serializable]
public class glTF_KHR_texture_transform
{

View File

@@ -47,32 +47,6 @@ namespace UniGLTF
[JsonSchema(EnumValues = new object[] { "image/jpeg", "image/png" }, EnumSerializationType = EnumSerializationType.AsString)]
public string mimeType;
public string GetExt()
{
switch (mimeType)
{
case "image/png":
return ".png";
case "image/jpeg":
return ".jpg";
default:
if (uri.FastStartsWith("data:image/jpeg;"))
{
return ".jpg";
}
else if (uri.FastStartsWith("data:image/png;"))
{
return ".png";
}
else
{
return Path.GetExtension(uri)?.ToLowerInvariant() ?? string.Empty;
}
}
}
// empty schemas
public glTFExtension extensions;
public glTFExtension extras;

View File

@@ -314,23 +314,19 @@ namespace UniGLTF
return result;
}
public ArraySegment<Byte> GetBytesFromImage(int imageIndex)
public (ArraySegment<byte> binary, string mimeType)? GetBytesFromImage(int imageIndex)
{
if (imageIndex < 0 || imageIndex >= GLTF.images.Count) return default;
var image = GLTF.images[imageIndex];
if (string.IsNullOrEmpty(image.uri))
{
return GetBytesFromBufferView(image.bufferView);
return (GetBytesFromBufferView(image.bufferView), image.mimeType);
}
else
{
return GetBytesFromUri(image.uri);
return (GetBytesFromUri(image.uri), image.mimeType);
}
}
public ArraySegment<Byte> GetImageBytesFromTextureIndex(int textureIndex)
{
var imageIndex = GLTF.textures[textureIndex].source;
return GetBytesFromImage(imageIndex);
}
}
}

View File

@@ -44,7 +44,7 @@ namespace UniGLTF
{
var (offset, scale) =
GltfTextureImporter.GetTextureOffsetAndScale(src.pbrMetallicRoughness.baseColorTexture);
var (key, textureParam) = GltfTextureImporter.CreateSRGB(data,
var (key, textureParam) = GltfTextureImporter.CreateSrgb(data,
src.pbrMetallicRoughness.baseColorTexture.index, offset, scale);
textureSlots.Add("_MainTex", textureParam);
}

View File

@@ -54,7 +54,7 @@ namespace UniGLTF
public static (SubAssetKey, TextureDescriptor) BaseColorTexture(GltfData data, glTFMaterial src)
{
var (offset, scale) = GltfTextureImporter.GetTextureOffsetAndScale(src.pbrMetallicRoughness.baseColorTexture);
return GltfTextureImporter.CreateSRGB(data, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale);
return GltfTextureImporter.CreateSrgb(data, src.pbrMetallicRoughness.baseColorTexture.index, offset, scale);
}
public static (SubAssetKey, TextureDescriptor) StandardTexture(GltfData data, glTFMaterial src)
@@ -84,7 +84,7 @@ namespace UniGLTF
public static (SubAssetKey, TextureDescriptor) EmissiveTexture(GltfData data, glTFMaterial src)
{
var (offset, scale) = GltfTextureImporter.GetTextureOffsetAndScale(src.emissiveTexture);
return GltfTextureImporter.CreateSRGB(data, src.emissiveTexture.index, offset, scale);
return GltfTextureImporter.CreateSrgb(data, src.emissiveTexture.index, offset, scale);
}
}

View File

@@ -11,32 +11,44 @@ namespace UniGLTF
/// </summary>
public static class GltfTextureImporter
{
public static Byte[] ToArray(ArraySegment<byte> bytes)
/// <summary>
/// glTF の Texture が存在せず Image のみのものを、Texture として扱いたい場合の関数.
/// </summary>
public static (SubAssetKey, TextureDescriptor) CreateSrgbFromOnlyImage(GltfData data, int imageIndex, string uniqueName, string uri)
{
if (bytes.Array == null)
{
return new byte[] { };
}
else if (bytes.Offset == 0 && bytes.Count == bytes.Array.Length)
{
return bytes.Array;
}
else
{
Byte[] result = new byte[bytes.Count];
Buffer.BlockCopy(bytes.Array, bytes.Offset, result, 0, result.Length);
return result;
}
var name = TextureImportName.GetUnityObjectName(TextureImportTypes.sRGB, uniqueName, uri);
var texDesc = new TextureDescriptor(
name,
Vector2.zero,
Vector2.one,
default,
TextureImportTypes.sRGB,
default,
default,
() =>
{
var imageBytes = data.GetBytesFromImage(imageIndex);
return Task.FromResult<(byte[], string)?>((ToArray(imageBytes?.binary ?? default), null));
},
default, default, default, default, default);
return (texDesc.SubAssetKey, texDesc);
}
public static (SubAssetKey, TextureDescriptor) CreateSRGB(GltfData data, int textureIndex, Vector2 offset, Vector2 scale)
public static (SubAssetKey, TextureDescriptor) CreateSrgb(GltfData data, int textureIndex, Vector2 offset, Vector2 scale)
{
var gltfTexture = data.GLTF.textures[textureIndex];
var gltfImage = data.GLTF.images[gltfTexture.source];
var name = TextureImportName.GetUnityObjectName(TextureImportTypes.sRGB, gltfTexture.name, gltfImage.uri);
var sampler = TextureSamplerUtil.CreateSampler(data.GLTF, textureIndex);
GetTextureBytesAsync getTextureBytesAsync = () => Task.FromResult(ToArray(data.GetImageBytesFromTextureIndex(textureIndex)));
var param = new TextureDescriptor(name, gltfImage.GetExt(), gltfImage.uri, offset, scale, sampler, TextureImportTypes.sRGB, default, default, getTextureBytesAsync, default, default, default, default, default);
var param = new TextureDescriptor(
name,
offset, scale,
sampler,
TextureImportTypes.sRGB,
default,
default,
() => Task.FromResult(GetImageBytesFromTextureIndex(data, textureIndex)),
default, default, default, default, default);
return (param.SubAssetKey, param);
}
@@ -46,8 +58,16 @@ namespace UniGLTF
var gltfImage = data.GLTF.images[gltfTexture.source];
var name = TextureImportName.GetUnityObjectName(TextureImportTypes.Linear, gltfTexture.name, gltfImage.uri);
var sampler = TextureSamplerUtil.CreateSampler(data.GLTF, textureIndex);
GetTextureBytesAsync getTextureBytesAsync = () => Task.FromResult(ToArray(data.GetImageBytesFromTextureIndex(textureIndex)));
var param = new TextureDescriptor(name, gltfImage.GetExt(), gltfImage.uri, offset, scale, sampler, TextureImportTypes.Linear, default, default, getTextureBytesAsync, default, default, default, default, default);
var param = new TextureDescriptor(
name,
offset,
scale,
sampler,
TextureImportTypes.Linear,
default,
default,
() => Task.FromResult(GetImageBytesFromTextureIndex(data, textureIndex)),
default, default, default, default, default);
return (param.SubAssetKey, param);
}
@@ -57,8 +77,16 @@ namespace UniGLTF
var gltfImage = data.GLTF.images[gltfTexture.source];
var name = TextureImportName.GetUnityObjectName(TextureImportTypes.NormalMap, gltfTexture.name, gltfImage.uri);
var sampler = TextureSamplerUtil.CreateSampler(data.GLTF, textureIndex);
GetTextureBytesAsync getTextureBytesAsync = () => Task.FromResult(ToArray(data.GetImageBytesFromTextureIndex(textureIndex)));
var param = new TextureDescriptor(name, gltfImage.GetExt(), gltfImage.uri, offset, scale, sampler, TextureImportTypes.NormalMap, default, default, getTextureBytesAsync, default, default, default, default, default);
var param = new TextureDescriptor(
name,
offset,
scale,
sampler,
TextureImportTypes.NormalMap,
default,
default,
() => Task.FromResult(GetImageBytesFromTextureIndex(data, textureIndex)),
default, default, default, default, default);
return (param.SubAssetKey, param);
}
@@ -73,7 +101,7 @@ namespace UniGLTF
var gltfTexture = data.GLTF.textures[metallicRoughnessTextureIndex.Value];
name = TextureImportName.GetUnityObjectName(TextureImportTypes.StandardMap, gltfTexture.name, data.GLTF.images[gltfTexture.source].uri);
sampler = TextureSamplerUtil.CreateSampler(data.GLTF, metallicRoughnessTextureIndex.Value);
getMetallicRoughnessAsync = () => Task.FromResult(ToArray(data.GetImageBytesFromTextureIndex(metallicRoughnessTextureIndex.Value)));
getMetallicRoughnessAsync = () => Task.FromResult(GetImageBytesFromTextureIndex(data, metallicRoughnessTextureIndex.Value));
}
GetTextureBytesAsync getOcclusionAsync = default;
@@ -85,10 +113,20 @@ namespace UniGLTF
name = TextureImportName.GetUnityObjectName(TextureImportTypes.StandardMap, gltfTexture.name, data.GLTF.images[gltfTexture.source].uri);
}
sampler = TextureSamplerUtil.CreateSampler(data.GLTF, occlusionTextureIndex.Value);
getOcclusionAsync = () => Task.FromResult(ToArray(data.GetImageBytesFromTextureIndex(occlusionTextureIndex.Value)));
getOcclusionAsync = () => Task.FromResult(GetImageBytesFromTextureIndex(data, occlusionTextureIndex.Value));
}
var texDesc = new TextureDescriptor(name, ".png", null, offset, scale, sampler, TextureImportTypes.StandardMap, metallicFactor, roughnessFactor, getMetallicRoughnessAsync, getOcclusionAsync, default, default, default, default);
var texDesc = new TextureDescriptor(
name,
offset,
scale,
sampler,
TextureImportTypes.StandardMap,
metallicFactor,
roughnessFactor,
getMetallicRoughnessAsync,
getOcclusionAsync,
default, default, default, default);
return (texDesc.SubAssetKey, texDesc);
}
@@ -123,5 +161,62 @@ namespace UniGLTF
return (offset, scale);
}
public static (byte[] binary, string mimeType)? GetImageBytesFromTextureIndex(GltfData data, int textureIndex)
{
if (Application.isPlaying)
{
// NOTE: Runtime の場合は、拡張を考える.
var imageIndex = GetImageIndexFromTexture(data, textureIndex);
if (textureIndex >= 0 && textureIndex < data.GLTF.textures.Count)
{
var texture = data.GLTF.textures[textureIndex];
if (glTF_KHR_texture_basisu.TryGet(texture, out var basisuExtension))
{
imageIndex = basisuExtension.source;
}
}
if (!imageIndex.HasValue) return default;
var imageBytes = data.GetBytesFromImage(imageIndex.Value);
return (ToArray(imageBytes?.binary ?? default), imageBytes?.mimeType);
}
else
{
// NOTE: Editor の場合は通常通り読み込む.
var imageIndex = GetImageIndexFromTexture(data, textureIndex);
if (!imageIndex.HasValue) return default;
var imageBytes = data.GetBytesFromImage(imageIndex.Value);
return (ToArray(imageBytes?.binary ?? default), imageBytes?.mimeType);
}
}
private static int? GetImageIndexFromTexture(GltfData data, int textureIndex)
{
if (textureIndex < 0 || textureIndex >= data.GLTF.textures.Count) return default;
return data.GLTF.textures[textureIndex].source;
}
private static byte[] ToArray(ArraySegment<byte> bytes)
{
if (bytes.Array == null)
{
return new byte[] { };
}
else if (bytes.Offset == 0 && bytes.Count == bytes.Array.Length)
{
return bytes.Array;
}
else
{
var result = new byte[bytes.Count];
Buffer.BlockCopy(bytes.Array, bytes.Offset, result, 0, result.Length);
return result;
}
}
}
}

View File

@@ -292,7 +292,7 @@ namespace VRM
meta.Title = gltfMeta.title;
if (gltfMeta.texture >= 0)
{
var (key, param) = GltfTextureImporter.CreateSRGB(Data, gltfMeta.texture, Vector2.zero, Vector2.one);
var (key, param) = GltfTextureImporter.CreateSrgb(Data, gltfMeta.texture, Vector2.zero, Vector2.one);
meta.Thumbnail = await TextureFactory.GetTextureAsync(param, awaitCaller) as Texture2D;
}
meta.AllowedUser = gltfMeta.allowedUser;

View File

@@ -36,7 +36,7 @@ namespace VRM
texture = GltfTextureImporter.CreateNormal(data, textureIdx, offset, scale);
break;
default:
texture = GltfTextureImporter.CreateSRGB(data, textureIdx, offset, scale);
texture = GltfTextureImporter.CreateSrgb(data, textureIdx, offset, scale);
break;
}
return true;

View File

@@ -69,7 +69,7 @@ namespace VRM
{
if (vrm.meta.texture > -1)
{
texture = GltfTextureImporter.CreateSRGB(data, vrm.meta.texture, Vector2.zero, Vector2.one);
texture = GltfTextureImporter.CreateSrgb(data, vrm.meta.texture, Vector2.zero, Vector2.one);
return true;
}

View File

@@ -151,7 +151,7 @@ namespace UniVRM10
try
{
var (offset, scale) = GetTextureOffsetAndScale(info);
pair = GltfTextureImporter.CreateSRGB(data, info.index, offset, scale);
pair = GltfTextureImporter.CreateSrgb(data, info.index, offset, scale);
return true;
}
catch (NullReferenceException)

View File

@@ -106,18 +106,7 @@ namespace UniVRM10
}
var uniqueName = GlbLowLevelParser.FixNameUnique(used, imageName);
var objectName = TextureImportName.GetUnityObjectName(TextureImportTypes.sRGB, uniqueName, gltfImage.uri);
GetTextureBytesAsync getThumbnailImageBytesAsync = () =>
{
var bytes = data.GetBytesFromImage(imageIndex);
return Task.FromResult(GltfTextureImporter.ToArray(bytes));
};
var texDesc = new TextureDescriptor(objectName, gltfImage.GetExt(), gltfImage.uri, Vector2.zero, Vector2.one, default, TextureImportTypes.sRGB, default, default,
getThumbnailImageBytesAsync, default, default,
default, default, default
);
value = (texDesc.SubAssetKey, texDesc);
value = GltfTextureImporter.CreateSrgbFromOnlyImage(data, imageIndex, uniqueName, gltfImage.uri);
return true;
}
}

View File

@@ -12,6 +12,11 @@ namespace VRMShaders
/// </summary>
public byte[] ImageData { get; }
/// <summary>
/// Texture の mimeType
/// </summary>
public string DataMimeType { get; }
/// <summary>
/// Texture に求められる色空間
/// </summary>
@@ -37,9 +42,10 @@ namespace VRMShaders
/// </summary>
public TextureWrapMode WrapModeV { get; }
internal DeserializingTextureInfo(byte[] imageData, ColorSpace colorSpace, bool useMipmap, FilterMode filterMode, TextureWrapMode wrapModeU, TextureWrapMode wrapModeV)
internal DeserializingTextureInfo(byte[] imageData, string dataMimeType, ColorSpace colorSpace, bool useMipmap, FilterMode filterMode, TextureWrapMode wrapModeU, TextureWrapMode wrapModeV)
{
ImageData = imageData;
DataMimeType = dataMimeType;
ColorSpace = colorSpace;
UseMipmap = useMipmap;
FilterMode = filterMode;
@@ -47,9 +53,10 @@ namespace VRMShaders
WrapModeV = wrapModeV;
}
internal DeserializingTextureInfo(byte[] imageData, ColorSpace colorSpace, SamplerParam samplerParam)
internal DeserializingTextureInfo(byte[] imageData, string dataMimeType, ColorSpace colorSpace, SamplerParam samplerParam)
{
ImageData = imageData;
DataMimeType = dataMimeType;
ColorSpace = colorSpace;
UseMipmap = samplerParam.EnableMipMap;
FilterMode = samplerParam.FilterMode;

View File

@@ -13,7 +13,7 @@ namespace VRMShaders
/// File.WriteAllBytes
/// </summary>
/// <returns></returns>
public delegate Task<byte[]> GetTextureBytesAsync();
public delegate Task<(byte[] binary, string mimeType)?> GetTextureBytesAsync();
/// <summary>
/// 入力 glTF ファイルを Import した結果生成される、UnityEngine.Texture のアセット 1 つを確定させる Import 情報。
@@ -21,8 +21,6 @@ namespace VRMShaders
public readonly struct TextureDescriptor
{
public readonly string UnityObjectName;
public readonly string Ext;
public readonly string Uri;
public readonly Vector2 Offset;
public readonly Vector2 Scale;
@@ -46,7 +44,7 @@ namespace VRMShaders
/// </summary>
public SubAssetKey SubAssetKey => new SubAssetKey(SubAssetKey.TextureType, UnityObjectName);
public TextureDescriptor(string name, string ext, string uri, Vector2 offset, Vector2 scale, SamplerParam sampler, TextureImportTypes textureType, float metallicFactor, float roughnessFactor,
public TextureDescriptor(string name, Vector2 offset, Vector2 scale, SamplerParam sampler, TextureImportTypes textureType, float metallicFactor, float roughnessFactor,
GetTextureBytesAsync i0,
GetTextureBytesAsync i1,
GetTextureBytesAsync i2,
@@ -55,8 +53,6 @@ namespace VRMShaders
GetTextureBytesAsync i5)
{
UnityObjectName = name;
Ext = ext;
Uri = uri;
Offset = offset;
Scale = scale;
Sampler = sampler;

View File

@@ -80,7 +80,7 @@ namespace VRMShaders
// https://docs.unity3d.com/2018.4/Documentation/Manual/StandardShaderMaterialParameterNormalMap.html
var data0 = await texDesc.Index0();
var rawTexture = await TextureDeserializer.LoadTextureAsync(
new DeserializingTextureInfo(data0, ColorSpace.Linear, texDesc.Sampler),
new DeserializingTextureInfo(data0?.binary, data0?.mimeType, ColorSpace.Linear, texDesc.Sampler),
awaitCaller);
rawTexture.name = subAssetKey.Name;
_textureCache.Add(subAssetKey, rawTexture);
@@ -96,14 +96,14 @@ namespace VRMShaders
{
var data0 = await texDesc.Index0();
metallicRoughnessTexture = await TextureDeserializer.LoadTextureAsync(
new DeserializingTextureInfo(data0, ColorSpace.Linear, texDesc.Sampler),
new DeserializingTextureInfo(data0?.binary, data0?.mimeType, ColorSpace.Linear, texDesc.Sampler),
awaitCaller);
}
if (texDesc.Index1 != null)
{
var data1 = await texDesc.Index1();
occlusionTexture = await TextureDeserializer.LoadTextureAsync(
new DeserializingTextureInfo(data1, ColorSpace.Linear, texDesc.Sampler),
new DeserializingTextureInfo(data1?.binary, data1?.mimeType, ColorSpace.Linear, texDesc.Sampler),
awaitCaller);
}
@@ -123,7 +123,7 @@ namespace VRMShaders
{
var data0 = await texDesc.Index0();
var rawTexture = await TextureDeserializer.LoadTextureAsync(
new DeserializingTextureInfo(data0, ColorSpace.sRGB, texDesc.Sampler),
new DeserializingTextureInfo(data0?.binary, data0?.mimeType, ColorSpace.sRGB, texDesc.Sampler),
awaitCaller);
rawTexture.name = subAssetKey.Name;
_textureCache.Add(subAssetKey, rawTexture);
@@ -133,7 +133,7 @@ namespace VRMShaders
{
var data0 = await texDesc.Index0();
var rawTexture = await TextureDeserializer.LoadTextureAsync(
new DeserializingTextureInfo(data0, ColorSpace.Linear, texDesc.Sampler),
new DeserializingTextureInfo(data0?.binary, data0?.mimeType, ColorSpace.Linear, texDesc.Sampler),
awaitCaller);
rawTexture.name = subAssetKey.Name;
_textureCache.Add(subAssetKey, rawTexture);

View File

@@ -11,6 +11,17 @@ namespace VRMShaders
{
public async Task<Texture2D> LoadTextureAsync(DeserializingTextureInfo textureInfo, IAwaitCaller awaitCaller)
{
switch (textureInfo.DataMimeType)
{
case "image/png":
break;
case "image/jpeg":
break;
default:
Debug.LogWarning($"Texture image MIME type `{textureInfo.DataMimeType}` is not supported.");
break;
}
var texture = new Texture2D(2, 2, TextureFormat.ARGB32, textureInfo.UseMipmap, textureInfo.ColorSpace == ColorSpace.Linear);
if (textureInfo.ImageData != null)
{