/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;
using System.Linq;
using UnityEngine;
using OVRSimpleJSON;
using System.Threading.Tasks;
///
/// This is a lightweight glTF model loader that is guaranteed to work with models loaded from the Oculus runtime
/// using OVRPlugin.LoadRenderModel. It is not recommended to be used as a general purpose glTF loader.
///
public enum OVRChunkType
{
JSON = 0x4E4F534A,
BIN = 0x004E4942,
}
public enum OVRTextureFormat
{
NONE,
KTX2,
PNG,
JPEG,
}
///
/// This enum represents a simplified representation on how Texture Filter quality is implemented in Unity.
/// The values set in this enum are NOT random and are directly used by ApplyTextureQuality() and DetectTextureQuality()
/// to get/set the correspondent setup in Unity.
///
public enum OVRTextureQualityFiltering
{
None = -1,
Bilinear = 0,
Trilinear = 1,
Aniso2x = 2,
Aniso4x = 3,
Aniso8x = 4,
Aniso16x = 5,
}
public struct OVRBinaryChunk
{
public Stream chunkStream;
public uint chunkLength;
public long chunkStart;
}
public struct OVRMeshData
{
public Mesh mesh;
public Material material;
public OVRMeshAttributes baseAttributes;
public OVRMeshAttributes[] morphTargets;
}
public struct OVRMaterialData
{
public Shader shader;
public int textureId;
public OVRTextureData texture;
public Color baseColorFactor;
}
public struct OVRGLTFScene
{
public GameObject root;
public List nodes;
public Dictionary animationNodes;
public Dictionary animationNodeLookup;
public List morphTargetHandlers;
}
public struct OVRTextureData
{
public byte[] data;
public int width;
public int height;
public OVRTextureFormat format;
public TextureFormat transcodedFormat;
public string uri;
}
public struct OVRMeshAttributes
{
public Vector3[] vertices;
public Vector3[] normals;
public Vector4[] tangents;
public Vector2[] texcoords;
public Color[] colors;
public BoneWeight[] boneWeights;
}
public class OVRGLTFLoader
{
private JSONNode m_jsonData;
private Stream m_glbStream;
private OVRBinaryChunk m_binaryChunk;
private List m_Nodes;
private Dictionary m_InputAnimationNodes;
//
private Dictionary m_AnimationLookup;
//
private Dictionary m_morphTargetHandlers;
private Shader m_Shader = null;
private Shader m_AlphaBlendShader = null;
private OVRTextureQualityFiltering m_TextureQuality = OVRTextureQualityFiltering.Bilinear; // = Unity default
private float m_TextureMipmapBias = 0.0f; // = shader default
public static readonly Vector3 GLTFToUnitySpace = new Vector3(-1, 1, 1);
public static readonly Vector3 GLTFToUnityTangent = new Vector4(-1, 1, 1, -1);
public static readonly Vector4 GLTFToUnitySpace_Rotation = new Vector4(1, -1, -1, 1);
private static Dictionary InputNodeNameMap = new Dictionary
{
{ "button_a", OVRGLTFInputNode.Button_A_X },
{ "button_x", OVRGLTFInputNode.Button_A_X },
{ "button_b", OVRGLTFInputNode.Button_B_Y },
{ "button_y", OVRGLTFInputNode.Button_B_Y },
{ "button_oculus", OVRGLTFInputNode.Button_Oculus_Menu },
{ "trigger_front", OVRGLTFInputNode.Trigger_Front },
{ "trigger_grip", OVRGLTFInputNode.Trigger_Grip },
{ "thumbstick", OVRGLTFInputNode.ThumbStick },
};
public Func textureUriHandler;
public OVRGLTFLoader(string fileName)
{
m_glbStream = File.Open(fileName, FileMode.Open);
}
public OVRGLTFLoader(byte[] data)
{
m_glbStream = new MemoryStream(data, 0, data.Length, false, true);
}
public OVRGLTFScene LoadGLB(bool supportAnimation, bool loadMips = true)
{
OVRGLTFScene scene = new OVRGLTFScene();
m_Nodes = new List();
m_InputAnimationNodes = new Dictionary();
m_AnimationLookup = new Dictionary();
m_morphTargetHandlers = new Dictionary();
int rootNodeId = 0;
if (ValidateGLB(m_glbStream))
{
byte[] jsonChunkData = ReadChunk(m_glbStream, OVRChunkType.JSON);
if (jsonChunkData != null)
{
string json = System.Text.Encoding.ASCII.GetString(jsonChunkData);
m_jsonData = JSON.Parse(json);
}
uint binChunkLength = 0;
bool validBinChunk = ValidateChunk(m_glbStream, OVRChunkType.BIN, out binChunkLength);
if (validBinChunk && m_jsonData != null)
{
m_binaryChunk.chunkLength = binChunkLength;
m_binaryChunk.chunkStart = m_glbStream.Position;
m_binaryChunk.chunkStream = m_glbStream;
if (m_Shader == null)
{
Debug.LogWarning("A shader was not set before loading the model. Using default mobile shader.");
m_Shader = Shader.Find("Legacy Shaders/Diffuse");
}
if (m_AlphaBlendShader == null)
{
Debug.LogWarning(
"An alpha blend shader was not set before loading the model. Using default transparent shader.");
m_AlphaBlendShader = Shader.Find("Unlit/Transparent");
}
rootNodeId = LoadGLTF(supportAnimation, loadMips);
if (rootNodeId < 0)
{
m_glbStream.Close();
return scene;
}
}
}
m_glbStream.Close();
scene.nodes = m_Nodes;
scene.root = new GameObject("GLB Scene Root");
scene.animationNodes = m_InputAnimationNodes;
scene.animationNodeLookup = m_AnimationLookup;
scene.morphTargetHandlers = m_morphTargetHandlers.Values.ToList();
foreach (GameObject node in m_Nodes)
{
if (node.transform.parent == null)
{
node.transform.SetParent(scene.root.transform);
}
}
scene.root.transform.Rotate(Vector3.up, 180.0f);
return scene;
}
public void SetModelShader(Shader shader)
{
m_Shader = shader;
}
public void SetModelAlphaBlendShader(Shader shader)
{
m_AlphaBlendShader = shader;
}
///
/// All textures in the glb will be loaded with the following setting. The default is Bilinear.
/// Once loaded, textures will be read-only on GPU memory.
///
/// The quality setting.
public void SetTextureQualityFiltering(OVRTextureQualityFiltering loadedTexturesQuality)
{
m_TextureQuality = loadedTexturesQuality;
}
///
/// All textures in the glb will be preset with this MipMap value. The default is 0.
/// Only supported when MipMaps are loaded and the provided shader has a property named "_MainTexMMBias"
///
/// The value for bias. Value is clamped between [-1,1]
public void SetMipMapBias(float loadedTexturesMipmapBiasing)
{
m_TextureMipmapBias = Mathf.Clamp(loadedTexturesMipmapBiasing, -1.0f, 1.0f);
}
///
/// Decodes the Texture Quality setting from the input Texture2D properties' values.
///
/// The input Texture2D
/// The enum TextureQualityFiltering representing the quality.
public static OVRTextureQualityFiltering DetectTextureQuality(in Texture2D srcTexture)
{
OVRTextureQualityFiltering quality = OVRTextureQualityFiltering.None;
switch (srcTexture.filterMode)
{
case FilterMode.Point:
quality = OVRTextureQualityFiltering.None;
break;
case FilterMode.Bilinear:
goto default;
case FilterMode.Trilinear:
if (srcTexture.anisoLevel <= 1)
quality = OVRTextureQualityFiltering.Trilinear;
// In theory, aniso supports values between 2-16x, but in reality GPUs and gfx APIs implement
// powers of 2 (values in between have no change)
else if (srcTexture.anisoLevel < 4)
quality = OVRTextureQualityFiltering.Aniso2x;
else if (srcTexture.anisoLevel < 8)
quality = OVRTextureQualityFiltering.Aniso4x;
else if (srcTexture.anisoLevel < 16)
quality = OVRTextureQualityFiltering.Aniso8x;
else
quality = OVRTextureQualityFiltering.Aniso16x;
break;
default:
quality = OVRTextureQualityFiltering.Bilinear;
break;
}
return quality;
}
///
/// Applies the input Texture Quality setting into the ref Texture2D provided as input. Texture2D must not be readonly.
///
/// The quality level to apply
/// The destination Texture2D to apply quality setting to
public static void ApplyTextureQuality(OVRTextureQualityFiltering qualityLevel, ref Texture2D destTexture)
{
if (destTexture == null)
return;
switch (qualityLevel)
{
case OVRTextureQualityFiltering.None:
destTexture.filterMode = FilterMode.Point;
destTexture.anisoLevel = 0;
break;
case OVRTextureQualityFiltering.Bilinear:
destTexture.filterMode = FilterMode.Bilinear;
destTexture.anisoLevel = 0;
break;
case OVRTextureQualityFiltering.Trilinear:
destTexture.filterMode = FilterMode.Trilinear;
destTexture.anisoLevel = 0;
break;
default: // for higher values
destTexture.filterMode = FilterMode.Trilinear;
// In theory, aniso supports values between 2-16x, but in reality GPUs and gfx APIs implement
// powers of 2 (values in between have no change)
// given the enum value, this gives aniso x2 x4 x8 x16
destTexture.anisoLevel = Mathf.FloorToInt(Mathf.Pow(2.0f, (int)qualityLevel - 1));
break;
}
}
private bool ValidateGLB(Stream glbStream)
{
// Read the magic entry and ensure value matches the glTF value
int uint32Size = sizeof(uint);
byte[] buffer = new byte[uint32Size];
glbStream.Read(buffer, 0, uint32Size);
uint magic = BitConverter.ToUInt32(buffer, 0);
if (magic != 0x46546C67)
{
Debug.LogError("Data stream was not a valid glTF format");
return false;
}
// Read glTF version
glbStream.Read(buffer, 0, uint32Size);
uint version = BitConverter.ToUInt32(buffer, 0);
if (version != 2)
{
Debug.LogError("Only glTF 2.0 is supported");
return false;
}
// Read glTF file size
glbStream.Read(buffer, 0, uint32Size);
uint length = BitConverter.ToUInt32(buffer, 0);
if (length != glbStream.Length)
{
Debug.LogError("glTF header length does not match file length");
return false;
}
return true;
}
private byte[] ReadChunk(Stream glbStream, OVRChunkType type)
{
uint chunkLength;
if (ValidateChunk(glbStream, type, out chunkLength))
{
byte[] chunkBuffer = new byte[chunkLength];
glbStream.Read(chunkBuffer, 0, (int)chunkLength);
return chunkBuffer;
}
return null;
}
private bool ValidateChunk(Stream glbStream, OVRChunkType type, out uint chunkLength)
{
int uint32Size = sizeof(uint);
byte[] buffer = new byte[uint32Size];
glbStream.Read(buffer, 0, uint32Size);
chunkLength = BitConverter.ToUInt32(buffer, 0);
glbStream.Read(buffer, 0, uint32Size);
uint chunkType = BitConverter.ToUInt32(buffer, 0);
if (chunkType != (uint)type)
{
Debug.LogError("Read chunk does not match type.");
return false;
}
return true;
}
private int LoadGLTF(bool supportAnimation, bool loadMips)
{
if (m_jsonData == null)
{
Debug.LogError("m_jsonData was null");
return -1;
}
var scenes = m_jsonData["scenes"];
if (scenes.Count == 0)
{
Debug.LogError("No valid scenes in this glTF.");
return -1;
}
// Create GameObjects for each node in the model so that they can be referenced during processing
var nodes = m_jsonData["nodes"].AsArray;
for (int i = 0; i < nodes.Count; i++)
{
var jsonNode = m_jsonData["nodes"][i];
GameObject go = new GameObject(jsonNode["name"]);
m_Nodes.Add(go);
}
// Limit loading to just the first scene in the glTF
var mainScene = scenes[0];
var rootNodes = mainScene["nodes"].AsArray;
// Load all nodes (some models like e.g. laptops use multiple nodes)
foreach (JSONNode rootNode in rootNodes)
{
int rootNodeId = rootNode.AsInt;
ProcessNode(m_jsonData["nodes"][rootNodeId], rootNodeId, loadMips);
}
if (supportAnimation)
ProcessAnimations();
return rootNodes[0].AsInt;
}
private void ProcessNode(JSONNode node, int nodeId, bool loadMips)
{
// Process the child nodes first
var childNodes = node["children"];
if (childNodes.Count > 0)
{
for (int i = 0; i < childNodes.Count; i++)
{
int childId = childNodes[i].AsInt;
m_Nodes[childId].transform.SetParent(m_Nodes[nodeId].transform);
ProcessNode(m_jsonData["nodes"][childId], childId, loadMips);
}
}
string nodeName = node["name"].ToString();
if (nodeName.Contains("batteryIndicator"))
{
GameObject.Destroy(m_Nodes[nodeId]);
return;
}
if (node["mesh"] != null)
{
var meshId = node["mesh"].AsInt;
OVRMeshData meshData = ProcessMesh(m_jsonData["meshes"][meshId], loadMips);
if (node["skin"] != null)
{
var renderer = m_Nodes[nodeId].AddComponent();
renderer.sharedMesh = meshData.mesh;
renderer.sharedMaterial = meshData.material;
var skinId = node["skin"].AsInt;
ProcessSkin(m_jsonData["skins"][skinId], renderer);
}
else
{
var filter = m_Nodes[nodeId].AddComponent();
filter.sharedMesh = meshData.mesh;
var renderer = m_Nodes[nodeId].AddComponent();
renderer.sharedMaterial = meshData.material;
}
if (meshData.morphTargets != null)
{
m_morphTargetHandlers[nodeId] = new OVRGLTFAnimationNodeMorphTargetHandler(meshData);
}
}
var translation = node["translation"].AsArray;
var rotation = node["rotation"].AsArray;
var scale = node["scale"].AsArray;
if (translation.Count > 0)
{
Vector3 position = new Vector3(
translation[0] * GLTFToUnitySpace.x,
translation[1] * GLTFToUnitySpace.y,
translation[2] * GLTFToUnitySpace.z);
m_Nodes[nodeId].transform.position = position;
}
if (rotation.Count > 0)
{
Vector3 rotationAxis = new Vector3(
rotation[0] * GLTFToUnitySpace.x,
rotation[1] * GLTFToUnitySpace.y,
rotation[2] * GLTFToUnitySpace.z);
rotationAxis *= -1.0f;
m_Nodes[nodeId].transform.rotation =
new Quaternion(rotationAxis.x, rotationAxis.y, rotationAxis.z, rotation[3]);
}
if (scale.Count > 0)
{
Vector3 scaleVec = new Vector3(scale[0], scale[1], scale[2]);
m_Nodes[nodeId].transform.localScale = scaleVec;
}
}
private OVRMeshData ProcessMesh(JSONNode meshNode, bool loadMips)
{
OVRMeshData meshData = new OVRMeshData();
int totalVertexCount = 0;
var primitives = meshNode["primitives"];
int[] primitiveVertexCounts = new int[primitives.Count];
for (int i = 0; i < primitives.Count; i++)
{
var jsonPrimitive = primitives[i];
var jsonAttrbite = jsonPrimitive["attributes"]["POSITION"];
var jsonAccessor = m_jsonData["accessors"][jsonAttrbite.AsInt];
primitiveVertexCounts[i] = jsonAccessor["count"];
totalVertexCount += primitiveVertexCounts[i];
}
int[][] indicies = new int[primitives.Count][];
// Begin async processing of material and its texture
OVRMaterialData matData = default(OVRMaterialData);
Task transcodeTask = null;
var jsonMaterial = primitives[0]["material"];
if (jsonMaterial != null)
{
matData = ProcessMaterial(jsonMaterial.AsInt);
matData.texture = ProcessTexture(matData.textureId);
transcodeTask = Task.Run(() => { TranscodeTexture(ref matData.texture); });
}
OVRMeshAttributes attributes = new OVRMeshAttributes();
OVRMeshAttributes[] morphTargetAttributes = null;
int vertexOffset = 0;
for (int i = 0; i < primitives.Count; i++)
{
var jsonPrimitive = primitives[i];
int indicesAccessorId = jsonPrimitive["indices"].AsInt;
var jsonAccessor = m_jsonData["accessors"][indicesAccessorId];
OVRGLTFAccessor indicesReader = new OVRGLTFAccessor(jsonAccessor, m_jsonData);
indicies[i] = new int[indicesReader.GetDataCount()];
indicesReader.ReadAsInt(m_binaryChunk, ref indicies[i], 0);
FlipTraingleIndices(ref indicies[i]);
attributes = ReadMeshAttributes(jsonPrimitive["attributes"], totalVertexCount, vertexOffset);
// morph targets
var jsonAttribute = jsonPrimitive["targets"];
if (jsonAttribute != null)
{
morphTargetAttributes = new OVRMeshAttributes[jsonAttribute.Count];
for (var ii = 0; ii < jsonAttribute.Count; ii++)
{
morphTargetAttributes[ii] = ReadMeshAttributes(jsonAttribute[ii], totalVertexCount, vertexOffset);
}
}
vertexOffset += primitiveVertexCounts[i];
}
Mesh mesh = new Mesh();
mesh.vertices = attributes.vertices;
mesh.normals = attributes.normals;
mesh.tangents = attributes.tangents;
mesh.colors = attributes.colors;
mesh.uv = attributes.texcoords;
mesh.boneWeights = attributes.boneWeights;
mesh.subMeshCount = primitives.Count;
int baseVertex = 0;
for (int i = 0; i < primitives.Count; i++)
{
mesh.SetIndices(indicies[i], MeshTopology.Triangles, i, false, baseVertex);
baseVertex += primitiveVertexCounts[i];
}
mesh.RecalculateBounds();
meshData.mesh = mesh;
meshData.morphTargets = morphTargetAttributes;
if (morphTargetAttributes != null)
{
meshData.baseAttributes = attributes;
}
if (transcodeTask != null)
{
transcodeTask.Wait();
meshData.material = CreateUnityMaterial(matData, loadMips);
}
return meshData;
}
private static void FlipTraingleIndices(ref int[] indices)
{
for (int i = 0; i < indices.Length; i += 3)
{
int a = indices[i];
indices[i] = indices[i + 2];
indices[i + 2] = a;
}
}
private OVRMeshAttributes ReadMeshAttributes(JSONNode jsonAttributes, int totalVertexCount, int vertexOffset)
{
OVRMeshAttributes results = new OVRMeshAttributes();
var jsonAttribute = jsonAttributes["POSITION"];
if (jsonAttribute != null)
{
results.vertices = new Vector3[totalVertexCount];
var jsonAccessor = m_jsonData["accessors"][jsonAttribute.AsInt];
OVRGLTFAccessor dataReader = new OVRGLTFAccessor(jsonAccessor, m_jsonData);
dataReader.ReadAsVector3(m_binaryChunk, ref results.vertices, vertexOffset, GLTFToUnitySpace);
}
jsonAttribute = jsonAttributes["NORMAL"];
if (jsonAttribute != null)
{
results.normals = new Vector3[totalVertexCount];
var jsonAccessor = m_jsonData["accessors"][jsonAttribute.AsInt];
OVRGLTFAccessor dataReader = new OVRGLTFAccessor(jsonAccessor, m_jsonData);
dataReader.ReadAsVector3(m_binaryChunk, ref results.normals, vertexOffset, GLTFToUnitySpace);
}
jsonAttribute = jsonAttributes["TANGENT"];
if (jsonAttribute != null)
{
results.tangents = new Vector4[totalVertexCount];
var jsonAccessor = m_jsonData["accessors"][jsonAttribute.AsInt];
OVRGLTFAccessor dataReader = new OVRGLTFAccessor(jsonAccessor, m_jsonData);
dataReader.ReadAsVector4(m_binaryChunk, ref results.tangents, vertexOffset, GLTFToUnityTangent);
}
jsonAttribute = jsonAttributes["TEXCOORD_0"];
if (jsonAttribute != null)
{
results.texcoords = new Vector2[totalVertexCount];
var jsonAccessor = m_jsonData["accessors"][jsonAttribute.AsInt];
OVRGLTFAccessor dataReader = new OVRGLTFAccessor(jsonAccessor, m_jsonData);
dataReader.ReadAsVector2(m_binaryChunk, ref results.texcoords, vertexOffset);
}
jsonAttribute = jsonAttributes["COLOR_0"];
if (jsonAttribute != null)
{
results.colors = new Color[totalVertexCount];
var jsonAccessor = m_jsonData["accessors"][jsonAttribute.AsInt];
OVRGLTFAccessor dataReader = new OVRGLTFAccessor(jsonAccessor, m_jsonData);
dataReader.ReadAsColor(m_binaryChunk, ref results.colors, vertexOffset);
}
jsonAttribute = jsonAttributes["WEIGHTS_0"];
if (jsonAttribute != null)
{
results.boneWeights = new BoneWeight[totalVertexCount];
var jsonAccessor = m_jsonData["accessors"][jsonAttribute.AsInt];
OVRGLTFAccessor weightReader = new OVRGLTFAccessor(jsonAccessor, m_jsonData);
var jointAttribute = jsonAttributes["JOINTS_0"];
var jointAccessor = m_jsonData["accessors"][jointAttribute.AsInt];
OVRGLTFAccessor jointReader = new OVRGLTFAccessor(jointAccessor, m_jsonData);
Vector4[] weights = new Vector4[weightReader.GetDataCount()];
Vector4[] joints = new Vector4[jointReader.GetDataCount()];
weightReader.ReadAsBoneWeights(m_binaryChunk, ref weights, 0);
jointReader.ReadAsVector4(m_binaryChunk, ref joints, 0, Vector4.one);
for (int w = 0; w < weights.Length; w++)
{
results.boneWeights[vertexOffset + w].boneIndex0 = (int)joints[w].x;
results.boneWeights[vertexOffset + w].boneIndex1 = (int)joints[w].y;
results.boneWeights[vertexOffset + w].boneIndex2 = (int)joints[w].z;
results.boneWeights[vertexOffset + w].boneIndex3 = (int)joints[w].w;
results.boneWeights[vertexOffset + w].weight0 = weights[w].x;
results.boneWeights[vertexOffset + w].weight1 = weights[w].y;
results.boneWeights[vertexOffset + w].weight2 = weights[w].z;
results.boneWeights[vertexOffset + w].weight3 = weights[w].w;
}
}
return results;
}
private void ProcessSkin(JSONNode skinNode, SkinnedMeshRenderer renderer)
{
Matrix4x4[] inverseBindMatrices = null;
if (skinNode["inverseBindMatrices"] != null)
{
int inverseBindMatricesId = skinNode["inverseBindMatrices"].AsInt;
var jsonInverseBindMatrices = m_jsonData["accessors"][inverseBindMatricesId];
OVRGLTFAccessor dataReader = new OVRGLTFAccessor(jsonInverseBindMatrices, m_jsonData);
inverseBindMatrices = new Matrix4x4[dataReader.GetDataCount()];
dataReader.ReadAsMatrix4x4(m_binaryChunk, ref inverseBindMatrices, 0, GLTFToUnitySpace);
}
if (skinNode["skeleton"] != null)
{
var skeletonRootId = skinNode["skeleton"].AsInt;
renderer.rootBone = m_Nodes[skeletonRootId].transform;
}
Transform[] bones = null;
if (skinNode["joints"] != null)
{
var joints = skinNode["joints"].AsArray;
bones = new Transform[joints.Count];
for (int i = 0; i < joints.Count; i++)
{
bones[i] = m_Nodes[joints[i]].transform;
}
}
renderer.sharedMesh.bindposes = inverseBindMatrices;
renderer.bones = bones;
}
private OVRMaterialData ProcessMaterial(int matId)
{
OVRMaterialData matData = new OVRMaterialData();
var jsonMaterial = m_jsonData["materials"][matId];
var jsonAlphaMode = jsonMaterial["alphaMode"];
bool alphaBlendMode = jsonAlphaMode != null && jsonAlphaMode.Value == "BLEND";
var jsonPbrDetails = jsonMaterial["pbrMetallicRoughness"];
matData.baseColorFactor = Color.white; // GLTF Default
var jsonBaseColorFactor = jsonPbrDetails["baseColorFactor"];
if (jsonBaseColorFactor != null)
{
matData.baseColorFactor = new Color(jsonBaseColorFactor[0].AsFloat, jsonBaseColorFactor[1].AsFloat,
jsonBaseColorFactor[2].AsFloat, jsonBaseColorFactor[3].AsFloat);
}
var jsonBaseColor = jsonPbrDetails["baseColorTexture"];
if (jsonBaseColor != null)
{
int textureId = jsonBaseColor["index"].AsInt;
matData.textureId = textureId;
}
else
{
var jsonTextrure = jsonMaterial["emissiveTexture"];
if (jsonTextrure != null)
{
int textureId = jsonTextrure["index"].AsInt;
matData.textureId = textureId;
}
}
matData.shader = alphaBlendMode ? m_AlphaBlendShader : m_Shader;
return matData;
}
private OVRTextureData ProcessTexture(int textureId)
{
var jsonTexture = m_jsonData["textures"][textureId];
int imageSource = -1;
var jsonExtensions = jsonTexture["extensions"];
if (jsonExtensions != null)
{
var baisuExtension = jsonExtensions["KHR_texture_basisu"];
if (baisuExtension != null)
{
imageSource = baisuExtension["source"].AsInt;
}
}
else
{
imageSource = jsonTexture["source"].AsInt;
}
var jsonSource = m_jsonData["images"][imageSource];
OVRTextureData textureData = new OVRTextureData();
var jsonSourceUri = jsonSource["uri"].Value;
if (!String.IsNullOrEmpty(jsonSourceUri))
{
textureData.uri = jsonSourceUri;
return textureData;
}
int sampler = jsonTexture["sampler"].AsInt;
var jsonSampler = m_jsonData["samplers"][sampler];
int bufferViewId = jsonSource["bufferView"].AsInt;
var jsonBufferView = m_jsonData["bufferViews"][bufferViewId];
OVRGLTFAccessor dataReader = new OVRGLTFAccessor(jsonBufferView, m_jsonData, true);
switch (jsonSource["mimeType"].Value)
{
case "image/ktx2":
textureData.data = dataReader.ReadAsTexture(m_binaryChunk);
textureData.format = OVRTextureFormat.KTX2;
break;
case "image/png":
textureData.data = dataReader.ReadAsTexture(m_binaryChunk);
textureData.format = OVRTextureFormat.PNG;
break;
default:
Debug.LogWarning($"Unsupported image mimeType '{jsonSource["mimeType"].Value}'");
break;
}
return textureData;
}
private void TranscodeTexture(ref OVRTextureData textureData)
{
if (!String.IsNullOrEmpty(textureData.uri))
{
return;
}
if (textureData.format == OVRTextureFormat.KTX2)
{
OVRKtxTexture.Load(textureData.data, ref textureData);
}
else if (textureData.format == OVRTextureFormat.PNG)
{
// fall back to unity Texture2D.LoadImage, which will override dimensions & format.
}
else
{
Debug.LogWarning("Only KTX2 textures can be trascoded.");
}
}
private Material CreateUnityMaterial(OVRMaterialData matData, bool loadMips)
{
Material mat = new Material(matData.shader);
mat.color = matData.baseColorFactor;
if (loadMips && mat.HasProperty("_MainTexMMBias"))
mat.SetFloat("_MainTexMMBias", m_TextureMipmapBias);
Texture2D texture = null;
if (matData.texture.format == OVRTextureFormat.KTX2)
{
texture = new Texture2D(matData.texture.width, matData.texture.height, matData.texture.transcodedFormat,
loadMips);
texture.LoadRawTextureData(matData.texture.data);
}
else if (matData.texture.format == OVRTextureFormat.PNG)
{
texture = new Texture2D(2, 2, TextureFormat.RGBA32, loadMips);
texture.LoadImage(matData.texture.data);
}
else if (!String.IsNullOrEmpty(matData.texture.uri))
{
texture = textureUriHandler?.Invoke(matData.texture.uri, mat);
}
if (!texture) return mat;
ApplyTextureQuality(m_TextureQuality, ref texture);
texture.Apply(updateMipmaps: false, makeNoLongerReadable: true);
mat.mainTexture = texture;
return mat;
}
private OVRGLTFInputNode GetInputNodeType(string name)
{
foreach (var item in InputNodeNameMap)
{
if (name.Contains(item.Key))
{
return item.Value;
}
}
return OVRGLTFInputNode.None;
}
private void ProcessAnimations()
{
var animations = m_jsonData["animations"];
var animationIndex = 0;
foreach (JSONNode animation in animations.AsArray)
{
//We don't need animation name at this moment
//string name = animation["name"].ToString();
var animationNodeLookup = new Dictionary();
var channels = animation["channels"].AsArray;
foreach (JSONNode channel in channels)
{
int nodeId = channel["target"]["node"].AsInt;
OVRGLTFInputNode inputNodeType = GetInputNodeType(m_Nodes[nodeId].name);
if (!animationNodeLookup.TryGetValue(nodeId, out var animationNode))
{
m_morphTargetHandlers.TryGetValue(nodeId, out var morphTargetHandler);
animationNode = animationNodeLookup[nodeId] = new OVRGLTFAnimatinonNode(m_jsonData, m_binaryChunk,
inputNodeType, m_Nodes[nodeId],
morphTargetHandler);
}
if (inputNodeType != OVRGLTFInputNode.None)
{
if (!m_InputAnimationNodes.ContainsKey(inputNodeType))
{
m_InputAnimationNodes[inputNodeType] = animationNode;
}
}
animationNode.AddChannel(channel, animation["samplers"]);
}
m_AnimationLookup[animationIndex] = animationNodeLookup.Values.ToArray();
animationIndex++;
}
}
}