824 lines
26 KiB
C#
824 lines
26 KiB
C#
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using GLTF;
|
||
|
using GLTF.Schema;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Networking;
|
||
|
using UnityEngine.Rendering;
|
||
|
using UnityGLTF.Cache;
|
||
|
using UnityGLTF.Extensions;
|
||
|
|
||
|
namespace UnityGLTF
|
||
|
{
|
||
|
public class GLTFSceneImporter
|
||
|
{
|
||
|
public enum MaterialType
|
||
|
{
|
||
|
PbrMetallicRoughness,
|
||
|
KHR_materials_pbrSpecularGlossiness,
|
||
|
CommonConstant,
|
||
|
CommonPhong,
|
||
|
CommonBlinn,
|
||
|
CommonLambert
|
||
|
}
|
||
|
|
||
|
private enum LoadType
|
||
|
{
|
||
|
Uri,
|
||
|
Stream
|
||
|
}
|
||
|
|
||
|
protected GameObject _lastLoadedScene;
|
||
|
protected readonly Transform _sceneParent;
|
||
|
protected readonly Dictionary<MaterialType, Shader> _shaderCache = new Dictionary<MaterialType, Shader>();
|
||
|
public int MaximumLod = 300;
|
||
|
protected readonly GLTF.Schema.Material DefaultMaterial = new GLTF.Schema.Material();
|
||
|
protected string _gltfUrl;
|
||
|
protected string _gltfDirectoryPath;
|
||
|
protected Stream _gltfStream;
|
||
|
protected GLTFRoot _root;
|
||
|
protected AssetCache _assetCache;
|
||
|
protected AsyncAction _asyncAction;
|
||
|
protected bool _addColliders = false;
|
||
|
byte[] _gltfData;
|
||
|
LoadType _loadType;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a GLTFSceneBuilder object which will be able to construct a scene based off a url
|
||
|
/// </summary>
|
||
|
/// <param name="gltfUrl">URL to load</param>
|
||
|
/// <param name="parent"></param>
|
||
|
/// <param name="addColliders">Option to add mesh colliders to primitives</param>
|
||
|
public GLTFSceneImporter(string gltfUrl, Transform parent = null, bool addColliders = false)
|
||
|
{
|
||
|
_gltfUrl = gltfUrl;
|
||
|
_gltfDirectoryPath = AbsoluteUriPath(gltfUrl);
|
||
|
_sceneParent = parent;
|
||
|
_asyncAction = new AsyncAction();
|
||
|
_loadType = LoadType.Uri;
|
||
|
_addColliders = addColliders;
|
||
|
}
|
||
|
|
||
|
public GLTFSceneImporter(string rootPath, Stream stream, Transform parent = null, bool addColliders = false)
|
||
|
{
|
||
|
_gltfUrl = rootPath;
|
||
|
_gltfDirectoryPath = AbsoluteFilePath(rootPath);
|
||
|
_gltfStream = stream;
|
||
|
_sceneParent = parent;
|
||
|
_asyncAction = new AsyncAction();
|
||
|
_loadType = LoadType.Stream;
|
||
|
_addColliders = addColliders;
|
||
|
}
|
||
|
|
||
|
public GameObject LastLoadedScene
|
||
|
{
|
||
|
get { return _lastLoadedScene; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Configures shaders in the shader cache for a given material type
|
||
|
/// </summary>
|
||
|
/// <param name="type">Material type to setup shader for</param>
|
||
|
/// <param name="shader">Shader object to apply</param>
|
||
|
public virtual void SetShaderForMaterialType(MaterialType type, Shader shader)
|
||
|
{
|
||
|
_shaderCache.Add(type, shader);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Loads via a web call the gltf file and then constructs a scene
|
||
|
/// </summary>
|
||
|
/// <param name="sceneIndex">Index into scene to load. -1 means load default</param>
|
||
|
/// <param name="isMultithreaded">Whether to do loading operation on a thread</param>
|
||
|
/// <returns></returns>
|
||
|
public IEnumerator Load(int sceneIndex = -1, bool isMultithreaded = false)
|
||
|
{
|
||
|
if (_loadType == LoadType.Uri)
|
||
|
{
|
||
|
var www = UnityWebRequest.Get(_gltfUrl);
|
||
|
|
||
|
yield return www.Send();
|
||
|
|
||
|
if (www.responseCode >= 400 || www.responseCode == 0)
|
||
|
{
|
||
|
throw new WebRequestException(www);
|
||
|
}
|
||
|
|
||
|
_gltfData = www.downloadHandler.data;
|
||
|
}
|
||
|
else if (_loadType == LoadType.Stream)
|
||
|
{
|
||
|
// todo optimization: add stream support to parsing layer
|
||
|
int streamLength = (int)(_gltfStream.Length - _gltfStream.Position);
|
||
|
_gltfData = new byte[streamLength];
|
||
|
_gltfStream.Read(_gltfData, 0, streamLength);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
throw new Exception("Invalid load type specified: " + _loadType);
|
||
|
}
|
||
|
|
||
|
_root = GLTFParser.ParseJson(_gltfData);
|
||
|
yield return ImportScene(sceneIndex, isMultithreaded);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a scene based off loaded JSON. Includes loading in binary and image data to construct the meshes required.
|
||
|
/// </summary>
|
||
|
/// <param name="sceneIndex">The index of scene in gltf file to load</param>
|
||
|
/// <param name="isMultithreaded">Whether to use a thread to do loading</param>
|
||
|
/// <returns></returns>
|
||
|
protected IEnumerator ImportScene(int sceneIndex = -1, bool isMultithreaded = false)
|
||
|
{
|
||
|
Scene scene;
|
||
|
if (sceneIndex >= 0 && sceneIndex < _root.Scenes.Count)
|
||
|
{
|
||
|
scene = _root.Scenes[sceneIndex];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
scene = _root.GetDefaultScene();
|
||
|
}
|
||
|
|
||
|
if (scene == null)
|
||
|
{
|
||
|
throw new Exception("No default scene in gltf file.");
|
||
|
}
|
||
|
|
||
|
_assetCache = new AssetCache(
|
||
|
_root.Images != null ? _root.Images.Count : 0,
|
||
|
_root.Textures != null ? _root.Textures.Count : 0,
|
||
|
_root.Materials != null ? _root.Materials.Count : 0,
|
||
|
_root.Buffers != null ? _root.Buffers.Count : 0,
|
||
|
_root.Meshes != null ? _root.Meshes.Count : 0
|
||
|
);
|
||
|
|
||
|
if (_lastLoadedScene == null)
|
||
|
{
|
||
|
if (_root.Buffers != null)
|
||
|
{
|
||
|
// todo add fuzzing to verify that buffers are before uri
|
||
|
for (int i = 0; i < _root.Buffers.Count; ++i)
|
||
|
{
|
||
|
GLTF.Schema.Buffer buffer = _root.Buffers[i];
|
||
|
if (buffer.Uri != null)
|
||
|
{
|
||
|
yield return LoadBuffer(_gltfDirectoryPath, buffer, i);
|
||
|
}
|
||
|
else //null buffer uri indicates GLB buffer loading
|
||
|
{
|
||
|
byte[] glbBuffer;
|
||
|
GLTFParser.ExtractBinaryChunk(_gltfData, i, out glbBuffer);
|
||
|
_assetCache.BufferCache[i] = glbBuffer;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_root.Images != null)
|
||
|
{
|
||
|
for (int i = 0; i < _root.Images.Count; ++i)
|
||
|
{
|
||
|
Image image = _root.Images[i];
|
||
|
yield return LoadImage(_gltfDirectoryPath, image, i);
|
||
|
}
|
||
|
}
|
||
|
#if !WINDOWS_UWP
|
||
|
// generate these in advance instead of as-needed
|
||
|
if (isMultithreaded)
|
||
|
{
|
||
|
yield return _asyncAction.RunOnWorkerThread(() => BuildAttributesForMeshes());
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
var sceneObj = CreateScene(scene);
|
||
|
|
||
|
if (_sceneParent != null)
|
||
|
{
|
||
|
sceneObj.transform.SetParent(_sceneParent, false);
|
||
|
}
|
||
|
|
||
|
_lastLoadedScene = sceneObj;
|
||
|
}
|
||
|
|
||
|
protected virtual void BuildAttributesForMeshes()
|
||
|
{
|
||
|
for (int i = 0; i < _root.Meshes.Count; ++i)
|
||
|
{
|
||
|
GLTF.Schema.Mesh mesh = _root.Meshes[i];
|
||
|
if (_assetCache.MeshCache[i] == null)
|
||
|
{
|
||
|
_assetCache.MeshCache[i] = new MeshCacheData[mesh.Primitives.Count];
|
||
|
}
|
||
|
|
||
|
for(int j = 0; j < mesh.Primitives.Count; ++j)
|
||
|
{
|
||
|
_assetCache.MeshCache[i][j] = new MeshCacheData();
|
||
|
var primitive = mesh.Primitives[j];
|
||
|
BuildMeshAttributes(primitive, i, j);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void BuildMeshAttributes(MeshPrimitive primitive, int meshID, int primitiveIndex)
|
||
|
{
|
||
|
if (_assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes.Count == 0)
|
||
|
{
|
||
|
Dictionary<string, AttributeAccessor> attributeAccessors = new Dictionary<string, AttributeAccessor>(primitive.Attributes.Count + 1);
|
||
|
foreach (var attributePair in primitive.Attributes)
|
||
|
{
|
||
|
AttributeAccessor AttributeAccessor = new AttributeAccessor()
|
||
|
{
|
||
|
AccessorId = attributePair.Value,
|
||
|
Buffer = _assetCache.BufferCache[attributePair.Value.Value.BufferView.Value.Buffer.Id]
|
||
|
};
|
||
|
|
||
|
attributeAccessors[attributePair.Key] = AttributeAccessor;
|
||
|
}
|
||
|
|
||
|
if (primitive.Indices != null)
|
||
|
{
|
||
|
AttributeAccessor indexBuilder = new AttributeAccessor()
|
||
|
{
|
||
|
AccessorId = primitive.Indices,
|
||
|
Buffer = _assetCache.BufferCache[primitive.Indices.Value.BufferView.Value.Buffer.Id]
|
||
|
};
|
||
|
|
||
|
attributeAccessors[SemanticProperties.INDICES] = indexBuilder;
|
||
|
}
|
||
|
|
||
|
GLTFHelpers.BuildMeshAttributes(ref attributeAccessors);
|
||
|
_assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes = attributeAccessors;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual GameObject CreateScene(Scene scene)
|
||
|
{
|
||
|
var sceneObj = new GameObject(scene.Name ?? "GLTFScene");
|
||
|
|
||
|
foreach (var node in scene.Nodes)
|
||
|
{
|
||
|
var nodeObj = CreateNode(node.Value);
|
||
|
nodeObj.transform.SetParent(sceneObj.transform, false);
|
||
|
}
|
||
|
|
||
|
return sceneObj;
|
||
|
}
|
||
|
|
||
|
protected virtual GameObject CreateNode(Node node)
|
||
|
{
|
||
|
var nodeObj = new GameObject(node.Name ?? "GLTFNode");
|
||
|
|
||
|
Vector3 position;
|
||
|
Quaternion rotation;
|
||
|
Vector3 scale;
|
||
|
node.GetUnityTRSProperties(out position, out rotation, out scale);
|
||
|
nodeObj.transform.localPosition = position;
|
||
|
nodeObj.transform.localRotation = rotation;
|
||
|
nodeObj.transform.localScale = scale;
|
||
|
|
||
|
// TODO: Add support for skin/morph targets
|
||
|
if (node.Mesh != null)
|
||
|
{
|
||
|
CreateMeshObject(node.Mesh.Value, nodeObj.transform, node.Mesh.Id);
|
||
|
}
|
||
|
|
||
|
/* TODO: implement camera (probably a flag to disable for VR as well)
|
||
|
if (camera != null)
|
||
|
{
|
||
|
GameObject cameraObj = camera.Value.Create();
|
||
|
cameraObj.transform.parent = nodeObj.transform;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
if (node.Children != null)
|
||
|
{
|
||
|
foreach (var child in node.Children)
|
||
|
{
|
||
|
var childObj = CreateNode(child.Value);
|
||
|
childObj.transform.SetParent(nodeObj.transform, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nodeObj;
|
||
|
}
|
||
|
|
||
|
protected virtual void CreateMeshObject(GLTF.Schema.Mesh mesh, Transform parent, int meshId)
|
||
|
{
|
||
|
if(_assetCache.MeshCache[meshId] == null)
|
||
|
{
|
||
|
_assetCache.MeshCache[meshId] = new MeshCacheData[mesh.Primitives.Count];
|
||
|
}
|
||
|
|
||
|
for(int i = 0; i < mesh.Primitives.Count; ++i)
|
||
|
{
|
||
|
var primitive = mesh.Primitives[i];
|
||
|
var primitiveObj = CreateMeshPrimitive(primitive, meshId, i);
|
||
|
primitiveObj.transform.SetParent(parent, false);
|
||
|
primitiveObj.SetActive(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual GameObject CreateMeshPrimitive(MeshPrimitive primitive, int meshID, int primitiveIndex)
|
||
|
{
|
||
|
var primitiveObj = new GameObject("Primitive");
|
||
|
|
||
|
var meshFilter = primitiveObj.AddComponent<MeshFilter>();
|
||
|
|
||
|
if (_assetCache.MeshCache[meshID][primitiveIndex] == null)
|
||
|
{
|
||
|
_assetCache.MeshCache[meshID][primitiveIndex] = new MeshCacheData();
|
||
|
}
|
||
|
if (_assetCache.MeshCache[meshID][primitiveIndex].LoadedMesh == null)
|
||
|
{
|
||
|
if (_assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes.Count == 0)
|
||
|
{
|
||
|
BuildMeshAttributes(primitive, meshID, primitiveIndex);
|
||
|
}
|
||
|
var meshAttributes = _assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes;
|
||
|
var vertexCount = primitive.Attributes[SemanticProperties.POSITION].Value.Count;
|
||
|
|
||
|
// todo optimize: There are multiple copies being performed to turn the buffer data into mesh data. Look into reducing them
|
||
|
UnityEngine.Mesh mesh = new UnityEngine.Mesh
|
||
|
{
|
||
|
vertices = primitive.Attributes.ContainsKey(SemanticProperties.POSITION)
|
||
|
? meshAttributes[SemanticProperties.POSITION].AccessorContent.AsVertices.ToUnityVector3()
|
||
|
: null,
|
||
|
normals = primitive.Attributes.ContainsKey(SemanticProperties.NORMAL)
|
||
|
? meshAttributes[SemanticProperties.NORMAL].AccessorContent.AsNormals.ToUnityVector3()
|
||
|
: null,
|
||
|
|
||
|
uv = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(0))
|
||
|
? meshAttributes[SemanticProperties.TexCoord(0)].AccessorContent.AsTexcoords.ToUnityVector2()
|
||
|
: null,
|
||
|
|
||
|
uv2 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(1))
|
||
|
? meshAttributes[SemanticProperties.TexCoord(1)].AccessorContent.AsTexcoords.ToUnityVector2()
|
||
|
: null,
|
||
|
|
||
|
uv3 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(2))
|
||
|
? meshAttributes[SemanticProperties.TexCoord(2)].AccessorContent.AsTexcoords.ToUnityVector2()
|
||
|
: null,
|
||
|
|
||
|
uv4 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(3))
|
||
|
? meshAttributes[SemanticProperties.TexCoord(3)].AccessorContent.AsTexcoords.ToUnityVector2()
|
||
|
: null,
|
||
|
|
||
|
colors = primitive.Attributes.ContainsKey(SemanticProperties.Color(0))
|
||
|
? meshAttributes[SemanticProperties.Color(0)].AccessorContent.AsColors.ToUnityColor()
|
||
|
: null,
|
||
|
|
||
|
triangles = primitive.Indices != null
|
||
|
? meshAttributes[SemanticProperties.INDICES].AccessorContent.AsTriangles
|
||
|
: MeshPrimitive.GenerateTriangles(vertexCount),
|
||
|
|
||
|
tangents = primitive.Attributes.ContainsKey(SemanticProperties.TANGENT)
|
||
|
? meshAttributes[SemanticProperties.TANGENT].AccessorContent.AsTangents.ToUnityVector4()
|
||
|
: null
|
||
|
};
|
||
|
|
||
|
_assetCache.MeshCache[meshID][primitiveIndex].LoadedMesh = mesh;
|
||
|
}
|
||
|
|
||
|
meshFilter.sharedMesh = _assetCache.MeshCache[meshID][primitiveIndex].LoadedMesh;
|
||
|
|
||
|
var materialWrapper = CreateMaterial(
|
||
|
primitive.Material != null ? primitive.Material.Value : DefaultMaterial,
|
||
|
primitive.Material != null ? primitive.Material.Id : -1
|
||
|
);
|
||
|
|
||
|
var meshRenderer = primitiveObj.AddComponent<MeshRenderer>();
|
||
|
meshRenderer.material = materialWrapper.GetContents(primitive.Attributes.ContainsKey(SemanticProperties.Color(0)));
|
||
|
|
||
|
if (_addColliders)
|
||
|
{
|
||
|
var meshCollider = primitiveObj.AddComponent<MeshCollider>();
|
||
|
meshCollider.sharedMesh = meshFilter.mesh;
|
||
|
}
|
||
|
|
||
|
return primitiveObj;
|
||
|
}
|
||
|
|
||
|
protected virtual MaterialCacheData CreateMaterial(GLTF.Schema.Material def, int materialIndex)
|
||
|
{
|
||
|
MaterialCacheData materialWrapper = null;
|
||
|
if (materialIndex < 0 || _assetCache.MaterialCache[materialIndex] == null)
|
||
|
{
|
||
|
Shader shader;
|
||
|
|
||
|
// get the shader to use for this material
|
||
|
try
|
||
|
{
|
||
|
if (_root.ExtensionsUsed != null && _root.ExtensionsUsed.Contains("KHR_materials_pbrSpecularGlossiness"))
|
||
|
shader = _shaderCache[MaterialType.KHR_materials_pbrSpecularGlossiness];
|
||
|
else if (def.PbrMetallicRoughness != null)
|
||
|
shader = _shaderCache[MaterialType.PbrMetallicRoughness];
|
||
|
else if (_root.ExtensionsUsed != null && _root.ExtensionsUsed.Contains("KHR_materials_common")
|
||
|
&& def.CommonConstant != null)
|
||
|
shader = _shaderCache[MaterialType.CommonConstant];
|
||
|
else
|
||
|
shader = _shaderCache[MaterialType.PbrMetallicRoughness];
|
||
|
}
|
||
|
catch (KeyNotFoundException)
|
||
|
{
|
||
|
Debug.LogWarningFormat("No shader supplied for type of glTF material {0}, using Standard fallback", def.Name);
|
||
|
shader = Shader.Find("Standard");
|
||
|
}
|
||
|
|
||
|
shader.maximumLOD = MaximumLod;
|
||
|
|
||
|
var material = new UnityEngine.Material(shader);
|
||
|
|
||
|
if (def.AlphaMode == AlphaMode.MASK)
|
||
|
{
|
||
|
material.SetOverrideTag("RenderType", "TransparentCutout");
|
||
|
material.SetInt("_SrcBlend", (int) UnityEngine.Rendering.BlendMode.One);
|
||
|
material.SetInt("_DstBlend", (int) UnityEngine.Rendering.BlendMode.Zero);
|
||
|
material.SetInt("_ZWrite", 1);
|
||
|
material.EnableKeyword("_ALPHATEST_ON");
|
||
|
material.DisableKeyword("_ALPHABLEND_ON");
|
||
|
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
||
|
material.renderQueue = (int) UnityEngine.Rendering.RenderQueue.AlphaTest;
|
||
|
material.SetFloat("_Cutoff", (float) def.AlphaCutoff);
|
||
|
}
|
||
|
else if (def.AlphaMode == AlphaMode.BLEND)
|
||
|
{
|
||
|
material.SetOverrideTag("RenderType", "Transparent");
|
||
|
material.SetInt("_SrcBlend", (int) UnityEngine.Rendering.BlendMode.SrcAlpha);
|
||
|
material.SetInt("_DstBlend", (int) UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
||
|
material.SetInt("_ZWrite", 0);
|
||
|
material.DisableKeyword("_ALPHATEST_ON");
|
||
|
material.EnableKeyword("_ALPHABLEND_ON");
|
||
|
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
||
|
material.renderQueue = (int) UnityEngine.Rendering.RenderQueue.Transparent;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
material.SetOverrideTag("RenderType", "Opaque");
|
||
|
material.SetInt("_SrcBlend", (int) UnityEngine.Rendering.BlendMode.One);
|
||
|
material.SetInt("_DstBlend", (int) UnityEngine.Rendering.BlendMode.Zero);
|
||
|
material.SetInt("_ZWrite", 1);
|
||
|
material.DisableKeyword("_ALPHATEST_ON");
|
||
|
material.DisableKeyword("_ALPHABLEND_ON");
|
||
|
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
||
|
material.renderQueue = -1;
|
||
|
}
|
||
|
|
||
|
if (def.DoubleSided)
|
||
|
{
|
||
|
material.SetInt("_Cull", (int) CullMode.Off);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
material.SetInt("_Cull", (int) CullMode.Back);
|
||
|
}
|
||
|
|
||
|
if (def.PbrMetallicRoughness != null)
|
||
|
{
|
||
|
var pbr = def.PbrMetallicRoughness;
|
||
|
|
||
|
material.SetColor("_Color", pbr.BaseColorFactor.ToUnityColor());
|
||
|
|
||
|
if (pbr.BaseColorTexture != null)
|
||
|
{
|
||
|
var textureDef = pbr.BaseColorTexture.Index.Value;
|
||
|
material.SetTexture("_MainTex", CreateTexture(textureDef));
|
||
|
|
||
|
ApplyTextureTransform(pbr.BaseColorTexture, material, "_MainTex");
|
||
|
}
|
||
|
|
||
|
material.SetFloat("_Metallic", (float) pbr.MetallicFactor);
|
||
|
|
||
|
if (pbr.MetallicRoughnessTexture != null)
|
||
|
{
|
||
|
var texture = pbr.MetallicRoughnessTexture.Index.Value;
|
||
|
material.SetTexture("_MetallicRoughnessMap", CreateTexture(texture));
|
||
|
|
||
|
ApplyTextureTransform(pbr.MetallicRoughnessTexture, material, "_MetallicRoughnessMap");
|
||
|
}
|
||
|
|
||
|
material.SetFloat("_Roughness", (float) pbr.RoughnessFactor);
|
||
|
}
|
||
|
|
||
|
if (def.Extensions != null && def.Extensions.ContainsKey(KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME))
|
||
|
{
|
||
|
KHR_materials_pbrSpecularGlossinessExtension specGloss = def.Extensions[KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME] as KHR_materials_pbrSpecularGlossinessExtension;
|
||
|
|
||
|
if (specGloss.DiffuseTexture != null)
|
||
|
{
|
||
|
var texture = specGloss.DiffuseTexture.Index.Value;
|
||
|
material.SetTexture("_MainTex", CreateTexture(texture));
|
||
|
|
||
|
ApplyTextureTransform(specGloss.DiffuseTexture, material, "_MainTex");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
material.SetColor("_Color", specGloss.DiffuseFactor.ToUnityColor());
|
||
|
}
|
||
|
|
||
|
if (specGloss.SpecularGlossinessTexture != null)
|
||
|
{
|
||
|
var texture = specGloss.SpecularGlossinessTexture.Index.Value;
|
||
|
material.SetTexture("_SpecGlossMap", CreateTexture(texture));
|
||
|
material.EnableKeyword("_SPECGLOSSMAP");
|
||
|
|
||
|
ApplyTextureTransform(specGloss.SpecularGlossinessTexture, material, "_SpecGlossMap");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
material.SetVector("_SpecColor", specGloss.SpecularFactor.ToUnityVector3());
|
||
|
material.SetFloat("_Glossiness", (float)specGloss.GlossinessFactor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (def.CommonConstant != null)
|
||
|
{
|
||
|
material.SetColor("_AmbientFactor", def.CommonConstant.AmbientFactor.ToUnityColor());
|
||
|
|
||
|
if (def.CommonConstant.LightmapTexture != null)
|
||
|
{
|
||
|
material.EnableKeyword("LIGHTMAP_ON");
|
||
|
|
||
|
var texture = def.CommonConstant.LightmapTexture.Index.Value;
|
||
|
material.SetTexture("_LightMap", CreateTexture(texture));
|
||
|
material.SetInt("_LightUV", def.CommonConstant.LightmapTexture.TexCoord);
|
||
|
|
||
|
ApplyTextureTransform(def.CommonConstant.LightmapTexture, material, "_LightMap");
|
||
|
}
|
||
|
|
||
|
material.SetColor("_LightFactor", def.CommonConstant.LightmapFactor.ToUnityColor());
|
||
|
}
|
||
|
|
||
|
if (def.NormalTexture != null)
|
||
|
{
|
||
|
var texture = def.NormalTexture.Index.Value;
|
||
|
material.SetTexture("_BumpMap", CreateTexture(texture));
|
||
|
material.SetFloat("_BumpScale", (float) def.NormalTexture.Scale);
|
||
|
material.EnableKeyword("_NORMALMAP");
|
||
|
|
||
|
ApplyTextureTransform(def.NormalTexture, material, "_BumpMap");
|
||
|
}
|
||
|
|
||
|
if (def.OcclusionTexture != null)
|
||
|
{
|
||
|
var texture = def.OcclusionTexture.Index;
|
||
|
|
||
|
material.SetFloat("_OcclusionStrength", (float) def.OcclusionTexture.Strength);
|
||
|
|
||
|
if (def.PbrMetallicRoughness != null
|
||
|
&& def.PbrMetallicRoughness.MetallicRoughnessTexture != null
|
||
|
&& def.PbrMetallicRoughness.MetallicRoughnessTexture.Index.Id == texture.Id)
|
||
|
{
|
||
|
material.EnableKeyword("OCC_METAL_ROUGH_ON");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
material.SetTexture("_OcclusionMap", CreateTexture(texture.Value));
|
||
|
|
||
|
ApplyTextureTransform(def.OcclusionTexture, material, "_OcclusionMap");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (def.EmissiveTexture != null)
|
||
|
{
|
||
|
var texture = def.EmissiveTexture.Index.Value;
|
||
|
material.EnableKeyword("EMISSION_MAP_ON");
|
||
|
material.EnableKeyword("_EMISSION");
|
||
|
material.SetTexture("_EmissionMap", CreateTexture(texture));
|
||
|
material.SetInt("_EmissionUV", def.EmissiveTexture.TexCoord);
|
||
|
|
||
|
ApplyTextureTransform(def.EmissiveTexture, material, "_EmissionMap");
|
||
|
}
|
||
|
|
||
|
material.SetColor("_EmissionColor", def.EmissiveFactor.ToUnityColor());
|
||
|
|
||
|
materialWrapper = new MaterialCacheData
|
||
|
{
|
||
|
UnityMaterial = material,
|
||
|
UnityMaterialWithVertexColor = new UnityEngine.Material(material),
|
||
|
GLTFMaterial = def
|
||
|
};
|
||
|
|
||
|
materialWrapper.UnityMaterialWithVertexColor.EnableKeyword("VERTEX_COLOR_ON");
|
||
|
|
||
|
if (materialIndex > 0)
|
||
|
{
|
||
|
_assetCache.MaterialCache[materialIndex] = materialWrapper;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return materialIndex > 0 ? _assetCache.MaterialCache[materialIndex] : materialWrapper;
|
||
|
}
|
||
|
|
||
|
protected virtual UnityEngine.Texture CreateTexture(GLTF.Schema.Texture texture)
|
||
|
{
|
||
|
if (_assetCache.TextureCache[texture.Source.Id] == null)
|
||
|
{
|
||
|
var source = _assetCache.ImageCache[texture.Source.Id];
|
||
|
var desiredFilterMode = FilterMode.Bilinear;
|
||
|
var desiredWrapMode = UnityEngine.TextureWrapMode.Repeat;
|
||
|
|
||
|
if (texture.Sampler != null)
|
||
|
{
|
||
|
var sampler = texture.Sampler.Value;
|
||
|
switch (sampler.MinFilter)
|
||
|
{
|
||
|
case MinFilterMode.Nearest:
|
||
|
desiredFilterMode = FilterMode.Point;
|
||
|
break;
|
||
|
case MinFilterMode.Linear:
|
||
|
default:
|
||
|
desiredFilterMode = FilterMode.Bilinear;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (sampler.WrapS)
|
||
|
{
|
||
|
case GLTF.Schema.WrapMode.ClampToEdge:
|
||
|
desiredWrapMode = UnityEngine.TextureWrapMode.Clamp;
|
||
|
break;
|
||
|
case GLTF.Schema.WrapMode.Repeat:
|
||
|
default:
|
||
|
desiredWrapMode = UnityEngine.TextureWrapMode.Repeat;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (source.filterMode == desiredFilterMode && source.wrapMode == desiredWrapMode)
|
||
|
{
|
||
|
_assetCache.TextureCache[texture.Source.Id] = source;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var unityTexture = UnityEngine.Object.Instantiate(source);
|
||
|
unityTexture.filterMode = desiredFilterMode;
|
||
|
unityTexture.wrapMode = desiredWrapMode;
|
||
|
_assetCache.TextureCache[texture.Source.Id] = unityTexture;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return _assetCache.TextureCache[texture.Source.Id];
|
||
|
}
|
||
|
|
||
|
protected virtual void ApplyTextureTransform(TextureInfo def, UnityEngine.Material mat, string texName)
|
||
|
{
|
||
|
Extension extension;
|
||
|
if (_root.ExtensionsUsed != null &&
|
||
|
_root.ExtensionsUsed.Contains(ExtTextureTransformExtensionFactory.EXTENSION_NAME) &&
|
||
|
def.Extensions != null &&
|
||
|
def.Extensions.TryGetValue(ExtTextureTransformExtensionFactory.EXTENSION_NAME, out extension))
|
||
|
{
|
||
|
ExtTextureTransformExtension ext = (ExtTextureTransformExtension)extension;
|
||
|
|
||
|
Vector2 temp = ext.Offset.ToUnityVector2();
|
||
|
temp = new Vector2(temp.x, -temp.y);
|
||
|
mat.SetTextureOffset(texName, temp);
|
||
|
|
||
|
mat.SetTextureScale(texName, ext.Scale.ToUnityVector2());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected const string Base64StringInitializer = "^data:[a-z-]+/[a-z-]+;base64,";
|
||
|
|
||
|
protected virtual IEnumerator LoadImage(string rootPath, Image image, int imageID)
|
||
|
{
|
||
|
if (_assetCache.ImageCache[imageID] == null)
|
||
|
{
|
||
|
Texture2D texture = null;
|
||
|
if (image.Uri != null)
|
||
|
{
|
||
|
var uri = image.Uri;
|
||
|
|
||
|
Regex regex = new Regex(Base64StringInitializer);
|
||
|
Match match = regex.Match(uri);
|
||
|
if (match.Success)
|
||
|
{
|
||
|
var base64Data = uri.Substring(match.Length);
|
||
|
var textureData = Convert.FromBase64String(base64Data);
|
||
|
texture = new Texture2D(0, 0);
|
||
|
texture.LoadImage(textureData);
|
||
|
}
|
||
|
else if (_loadType == LoadType.Uri)
|
||
|
{
|
||
|
var www = UnityWebRequest.Get(Path.Combine(rootPath, uri));
|
||
|
www.downloadHandler = new DownloadHandlerTexture();
|
||
|
|
||
|
yield return www.Send();
|
||
|
|
||
|
// HACK to enable mipmaps :(
|
||
|
var tempTexture = DownloadHandlerTexture.GetContent(www);
|
||
|
if (tempTexture != null)
|
||
|
{
|
||
|
texture = new Texture2D(tempTexture.width, tempTexture.height, tempTexture.format, true);
|
||
|
texture.SetPixels(tempTexture.GetPixels());
|
||
|
texture.Apply(true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Debug.LogFormat("{0} {1}", www.responseCode, www.url);
|
||
|
texture = new Texture2D(16, 16);
|
||
|
}
|
||
|
}
|
||
|
else if (_loadType == LoadType.Stream)
|
||
|
{
|
||
|
var pathToLoad = Path.Combine(rootPath, uri);
|
||
|
var file = File.OpenRead(pathToLoad);
|
||
|
byte[] bufferData = new byte[file.Length];
|
||
|
file.Read(bufferData, 0, (int) file.Length);
|
||
|
#if !WINDOWS_UWP
|
||
|
file.Close();
|
||
|
#else
|
||
|
file.Dispose();
|
||
|
#endif
|
||
|
texture = new Texture2D(0, 0);
|
||
|
texture.LoadImage(bufferData);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
texture = new Texture2D(0, 0);
|
||
|
var bufferView = image.BufferView.Value;
|
||
|
var buffer = bufferView.Buffer.Value;
|
||
|
var data = new byte[bufferView.ByteLength];
|
||
|
|
||
|
var bufferContents = _assetCache.BufferCache[bufferView.Buffer.Id];
|
||
|
System.Buffer.BlockCopy(bufferContents, bufferView.ByteOffset, data, 0, data.Length);
|
||
|
texture.LoadImage(data);
|
||
|
}
|
||
|
|
||
|
_assetCache.ImageCache[imageID] = texture;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Load the remote URI data into a byte array.
|
||
|
/// </summary>
|
||
|
protected virtual IEnumerator LoadBuffer(string sourceUri, GLTF.Schema.Buffer buffer, int bufferIndex)
|
||
|
{
|
||
|
if (buffer.Uri != null)
|
||
|
{
|
||
|
byte[] bufferData = null;
|
||
|
var uri = buffer.Uri;
|
||
|
|
||
|
Regex regex = new Regex(Base64StringInitializer);
|
||
|
Match match = regex.Match(uri);
|
||
|
if (match.Success)
|
||
|
{
|
||
|
var base64Data = uri.Substring(match.Length);
|
||
|
bufferData = Convert.FromBase64String(base64Data);
|
||
|
}
|
||
|
else if (_loadType == LoadType.Uri)
|
||
|
{
|
||
|
var www = UnityWebRequest.Get(Path.Combine(sourceUri, uri));
|
||
|
|
||
|
yield return www.Send();
|
||
|
|
||
|
bufferData = www.downloadHandler.data;
|
||
|
}
|
||
|
else if (_loadType == LoadType.Stream)
|
||
|
{
|
||
|
var pathToLoad = Path.Combine(sourceUri, uri);
|
||
|
var file = File.OpenRead(pathToLoad);
|
||
|
bufferData = new byte[buffer.ByteLength];
|
||
|
file.Read(bufferData, 0, buffer.ByteLength);
|
||
|
#if !WINDOWS_UWP
|
||
|
file.Close();
|
||
|
#else
|
||
|
file.Dispose();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
_assetCache.BufferCache[bufferIndex] = bufferData;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get the absolute path to a gltf uri reference.
|
||
|
/// </summary>
|
||
|
/// <param name="gltfPath">The path to the gltf file</param>
|
||
|
/// <returns>A path without the filename or extension</returns>
|
||
|
protected static string AbsoluteUriPath(string gltfPath)
|
||
|
{
|
||
|
var uri = new Uri(gltfPath);
|
||
|
var partialPath = uri.AbsoluteUri.Remove(uri.AbsoluteUri.Length - uri.Query.Length - uri.Segments[uri.Segments.Length - 1].Length);
|
||
|
return partialPath;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get the absolute path a gltf file directory
|
||
|
/// </summary>
|
||
|
/// <param name="gltfPath">The path to the gltf file</param>
|
||
|
/// <returns>A path without the filename or extension</returns>
|
||
|
protected static string AbsoluteFilePath(string gltfPath)
|
||
|
{
|
||
|
var fileName = Path.GetFileName(gltfPath);
|
||
|
var lastIndex = gltfPath.IndexOf(fileName);
|
||
|
var partialPath = gltfPath.Substring(0, lastIndex);
|
||
|
return partialPath;
|
||
|
}
|
||
|
}
|
||
|
}
|