Skip to content

Commit

Permalink
Merge pull request #967 from Santarh/exportTextureCorrect
Browse files Browse the repository at this point in the history
Restrict conditions about using raw texture file bytes while exporting glTF in UnityEditor.
  • Loading branch information
ousttrue authored May 21, 2021
2 parents 895c488 + b11da32 commit c5ba480
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ static void Export_OcclusionMetallicRoughness(Material m, ITextureExporter textu
}
}

int index = textureExporter.ExportAsGltfMetallicSmoothnessOcclusionCombined(metallicSmoothTexture, smoothness, occlusionTexture);
int index = textureExporter.ExportAsCombinedGltfPbrParameterTextureFromUnityStandardTextures(metallicSmoothTexture, smoothness, occlusionTexture);

if (index != -1 && metallicSmoothTexture != null)
{
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
116 changes: 116 additions & 0 deletions Assets/UniGLTF/Tests/UniGLTF/4x4_gray_import_as_normal_map.png.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@

namespace UniGLTF
{
public sealed class TextureColorSpaceSpecificationTests
public sealed class EditorTextureSerializerTests
{
private static readonly string AssetPath = "Assets/UniGLTF/Tests/UniGLTF";
private static readonly string SrgbGrayImageName = "4x4_gray_import_as_srgb";
private static readonly string LinearGrayImageName = "4x4_gray_import_as_linear";
private static readonly string NormalMapGrayImageName = "4x4_gray_import_as_normal_map";
private static readonly Texture2D SrgbGrayTex = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/{SrgbGrayImageName}.png");
private static readonly Texture2D LinearGrayTex = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/{LinearGrayImageName}.png");
private static readonly Texture2D NormalMapGrayTex = AssetDatabase.LoadAssetAtPath<Texture2D>($"{AssetPath}/{NormalMapGrayImageName}.png");
private static readonly Color32 JustGray = new Color32(127, 127, 127, 255);
private static readonly Color32 SrgbGrayInSrgb = JustGray;
private static readonly Color32 SrgbGrayInLinear = ((Color) SrgbGrayInSrgb).linear;
private static readonly Color32 LinearGrayInLinear = JustGray;
private static readonly Color32 LinearGrayInSrgb = ((Color) LinearGrayInLinear).gamma;
private static readonly Color32 NormalizedLinearGrayInLinear = new Color32(127, 127, 255, 255);

[Test]
public void InputAssetsRawImage()
Expand Down Expand Up @@ -74,6 +77,42 @@ public void AssignLinearImageToSrgbTextureProperty()
UnityEngine.Object.DestroyImmediate(exportedTex);
}

[Test]
public void AssignSrgbImageToLinearTextureProperty()
{
var exportedTex = AssignTextureToMaterialPropertyAndExportAndExtract(SrgbGrayTex, SrgbGrayImageName, "_OcclusionMap");
// R channel is occlusion in glTF spec.
Assert.AreEqual(SrgbGrayInLinear.r, GetFirstPixelInTexture2D(exportedTex).r);
UnityEngine.Object.DestroyImmediate(exportedTex);
}

[Test]
public void AssignLinearImageToLinearTextureProperty()
{
var exportedTex = AssignTextureToMaterialPropertyAndExportAndExtract(LinearGrayTex, LinearGrayImageName, "_OcclusionMap");
// R channel is occlusion in glTF spec.
Assert.AreEqual(LinearGrayInLinear.r, GetFirstPixelInTexture2D(exportedTex).r);
UnityEngine.Object.DestroyImmediate(exportedTex);
}

[Test]
public void AssignLinearImageToNormalTextureProperty()
{
var exportedTex = AssignTextureToMaterialPropertyAndExportAndExtract(LinearGrayTex, LinearGrayImageName, "_BumpMap");
Assert.AreEqual(NormalizedLinearGrayInLinear, GetFirstPixelInTexture2D(exportedTex));
// B channel is different from 127. Because it will be normalized as normal vector.
UnityEngine.Object.DestroyImmediate(exportedTex);
}

[Test]
public void AssignLinearImageAsNormalMapSettingsToNormalTextureProperty()
{
var exportedTex = AssignTextureToMaterialPropertyAndExportAndExtract(NormalMapGrayTex, NormalMapGrayImageName, "_BumpMap");
Assert.AreEqual(NormalizedLinearGrayInLinear, GetFirstPixelInTexture2D(exportedTex));
// B channel is different from 127. Because it will be normalized as normal vector.
UnityEngine.Object.DestroyImmediate(exportedTex);
}

private static Texture2D AssignTextureToMaterialPropertyAndExportAndExtract(Texture2D srcTex, string srcImageName, string propertyName)
{
// Prepare
Expand Down
108 changes: 80 additions & 28 deletions Assets/VRMShaders/GLTF/IO/Editor/EditorTextureSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.IO;
using System.Reflection;
using UniGLTF;
Expand All @@ -12,44 +13,62 @@ public sealed class EditorTextureSerializer : ITextureSerializer
private readonly RuntimeTextureSerializer m_runtimeSerializer = new RuntimeTextureSerializer();

/// <summary>
/// Export するときに オリジナルのテクスチャーアセット(png/jpg)を使用するか否か
/// 条件は、
/// Texture をオリジナルのテクスチャアセット(png/jpg)ファイルのバイト列そのまま出力してよいかどうか判断する
/// 具体的な条件は以下
///
/// * TextureAsset が存在する
/// * TextureImporter の maxSize
/// * TextureImporter の maxSize が画像の縦横サイズ以上
/// * TextureImporter の色空間設定が exportColorSpace と一致する
/// * 各 Texture Type ごとの判定
///
/// Unity の Texture2D のデータは、その参照元であるテクスチャアセットファイルのデータと一致することはむしろ稀。
/// </summary>
public bool CanExportAsEditorAssetFile(Texture texture)
public bool CanExportAsEditorAssetFile(Texture texture, ColorSpace exportColorSpace)
{
if (texture is Texture2D texture2D && !string.IsNullOrEmpty(UnityEditor.AssetDatabase.GetAssetPath(texture2D)))
// Exists as UnityEditor Texture2D Assets ?
if (!TryGetAsEditorTexture2DAsset(texture, out var texture2D, out var textureImporter)) return false;

// Maintain original width/height ?
if (!IsTextureSizeMaintained(texture2D, textureImporter)) return false;

// Equals color space ?
if (!IsFileColorSpaceSameWithExportColorSpace(texture2D, textureImporter, exportColorSpace)) return false;

// Each Texture Importer Type Validation
switch (textureImporter.textureType)
{
// exists Texture2D asset
if (IsMaxTextureSizeSmallerThanOriginalTextureSize(texture2D))
{
// Texture Inspector の MaxSize 設定で、テクスチャをオリジナルサイズよりも小さいサイズで Texture 化する指示を行っているため
// glTF Exporter もそれにしたがって、解釈をする
//
// 4096x4096 のような巨大なテクスチャーがそのまま出力されることを、Unityの TextureImporter.maxSize により防止する
//
case TextureImporterType.Default:
break;
case TextureImporterType.NormalMap:
// A texture has "Normal map" TextureType is ALWAYS converted into normalized normal pixel by Unity.
// So we must copy it.
return false;
}

// use Texture2D asset. EncodeToPng
return true;
case TextureImporterType.GUI:
case TextureImporterType.Sprite:
case TextureImporterType.Cursor:
case TextureImporterType.Cubemap:
case TextureImporterType.Cookie:
case TextureImporterType.Lightmap:
case TextureImporterType.HDRI:
case TextureImporterType.Advanced:
case TextureImporterType.SingleChannel:
// Not Supported TextureImporterType
return false;
default:
throw new ArgumentOutOfRangeException();
}

// not Texture2D or not exists Texture2D asset. EncodeToPng
return false;
return true;
}

public (byte[] bytes, string mime) ExportBytesWithMime(Texture2D texture, ColorSpace textureColorSpace)
public (byte[] bytes, string mime) ExportBytesWithMime(Texture2D texture, ColorSpace exportColorSpace)
{
if (CanExportAsEditorAssetFile(texture) && TryGetBytesWithMime(texture, out byte[] bytes, out string mime))
if (CanExportAsEditorAssetFile(texture, exportColorSpace) && TryGetBytesWithMime(texture, out byte[] bytes, out string mime))
{
return (bytes, mime);
}

return m_runtimeSerializer.ExportBytesWithMime(texture, textureColorSpace);
return m_runtimeSerializer.ExportBytesWithMime(texture, exportColorSpace);
}

/// <summary>
Expand Down Expand Up @@ -87,14 +106,34 @@ private bool TryGetBytesWithMime(Texture2D texture, out byte[] bytes, out string
return false;
}

private bool TryGetAsEditorTexture2DAsset(Texture texture, out Texture2D texture2D, out TextureImporter assetImporter)
{
texture2D = texture as Texture2D;
if (texture2D != null)
{
var path = AssetDatabase.GetAssetPath(texture2D);
if (!string.IsNullOrEmpty(path))
{
assetImporter = AssetImporter.GetAtPath(path) as TextureImporter;
if (assetImporter != null)
{
return true;
}
}
}

texture2D = null;
assetImporter = null;
return false;
}

/// <summary>
/// TextureImporter.maxTextureSize が オリジナルの画像Sizeより小さいか
/// Texture2D の画像サイズが、オリジナルの画像サイズを維持しているかどうか
///
/// TextureImporter の MaxTextureSize 設定によっては、Texture2D の画像サイズはオリジナルも小さくなりうる。
/// </summary>
private bool IsMaxTextureSizeSmallerThanOriginalTextureSize(Texture2D src)
private bool IsTextureSizeMaintained(Texture2D texture, TextureImporter textureImporter)
{
var path = AssetDatabase.GetAssetPath(src);
var textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;

// private メソッド TextureImporter.GetWidthAndHeight を無理やり呼ぶ
var getSizeMethod = typeof(TextureImporter).GetMethod("GetWidthAndHeight", BindingFlags.NonPublic | BindingFlags.Instance);
if (textureImporter != null && getSizeMethod != null)
Expand All @@ -104,13 +143,26 @@ private bool IsMaxTextureSizeSmallerThanOriginalTextureSize(Texture2D src)
var originalWidth = (int)args[0];
var originalHeight = (int)args[1];
var originalSize = Mathf.Max(originalWidth, originalHeight);
if (textureImporter.maxTextureSize < originalSize)
if (textureImporter.maxTextureSize >= originalSize)
{
return true;
}
}

return false;
}

private bool IsFileColorSpaceSameWithExportColorSpace(Texture2D texture, TextureImporter textureImporter, ColorSpace colorSpace)
{
switch (colorSpace)
{
case ColorSpace.sRGB:
return textureImporter.sRGBTexture == true;
case ColorSpace.Linear:
return textureImporter.sRGBTexture == false;
default:
throw new ArgumentOutOfRangeException(nameof(colorSpace), colorSpace, null);
}
}
}
}
4 changes: 2 additions & 2 deletions Assets/VRMShaders/GLTF/IO/Runtime/ITextureExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public interface ITextureExporter
int ExportAsLinear(Texture src);

/// <summary>
/// 指定の Metallic, Roughness, Occlusion 情報を、 glTF 仕様に準拠した 1 枚の合成テクスチャとして出力するように指示する。
/// Unity Standard Shader の Metallic, Roughness, Occlusion 情報を、 glTF 仕様に準拠した 1 枚の合成テクスチャとして出力するように指示する。
/// </summary>
int ExportAsGltfMetallicSmoothnessOcclusionCombined(Texture metallicSmoothTexture, float smoothness, Texture occlusionTexture);
int ExportAsCombinedGltfPbrParameterTextureFromUnityStandardTextures(Texture metallicSmoothTexture, float smoothness, Texture occlusionTexture);

/// <summary>
/// 指定の Texture を、glTF 仕様に準拠した Normal Texture に出力するように指示する。
Expand Down
7 changes: 4 additions & 3 deletions Assets/VRMShaders/GLTF/IO/Runtime/ITextureSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ public interface ITextureSerializer
/// <summary>
/// Texture をファイルのバイト列そのまま出力してよいかどうか判断する。
///
/// exportColorSpace はその Texture2D がアサインされる glTF プロパティの仕様が定める色空間を指定する。
/// Runtime 出力では常に false が望ましい。
/// </summary>
bool CanExportAsEditorAssetFile(Texture texture);
bool CanExportAsEditorAssetFile(Texture texture, ColorSpace exportColorSpace);

/// <summary>
/// Texture2D から実際のバイト列を取得する。
///
/// textureColorSpace はその Texture2D がアサインされる glTF プロパティの仕様が定める色空間を指定する。
/// exportColorSpace はその Texture2D がアサインされる glTF プロパティの仕様が定める色空間を指定する。
/// 具体的には Texture2D をコピーする際に、コピー先の Texture2D の色空間を決定するために使用する。
/// </summary>
(byte[] bytes, string mime) ExportBytesWithMime(Texture2D texture, ColorSpace textureColorSpace);
(byte[] bytes, string mime) ExportBytesWithMime(Texture2D texture, ColorSpace exportColorSpace);
}
}
Loading

0 comments on commit c5ba480

Please sign in to comment.