// =============================================================================================== // The MIT License (MIT) for UnityFBXExporter // // UnityFBXExporter was created for Building Crafter (http://u3d.as/ovC) a tool to rapidly // create high quality buildings right in Unity with no need to use 3D modeling programs. // // Copyright (c) 2016 | 8Bit Goose Games, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // =============================================================================================== using UnityEngine; using System.Text; using System.Collections.Generic; using System.Linq; using System.IO; #if UNITY_EDITOR using UnityEditor; #endif namespace UnityFBXExporter { public class FBXUnityMaterialGetter { /// /// Finds all materials in a gameobject and writes them to a string that can be read by the FBX writer /// /// Parent GameObject being exported. /// The path to export to. /// Materials which were written to this fbx file. /// The material objects to write to the file. /// The connections to write to the file. public static void GetAllMaterialsToString(GameObject gameObj, string newPath, bool copyMaterials, bool copyTextures, out Material[] materials, out string matObjects, out string connections) { StringBuilder tempObjectSb = new StringBuilder(); StringBuilder tempConnectionsSb = new StringBuilder(); // Need to get all unique materials for the submesh here and then write them in //@cartzhang modify.As meshrender and skinnedrender is same level in inherit relation shape. // if not check,skinned render ,may lost some materials. Renderer[] meshRenders = gameObj.GetComponentsInChildren(); List uniqueMaterials = new List(); // Gets all the unique materials within this GameObject Hierarchy for(int i = 0; i < meshRenders.Length; i++) { for(int n = 0; n < meshRenders[i].sharedMaterials.Length; n++) { Material mat = meshRenders[i].sharedMaterials[n]; if(uniqueMaterials.Contains(mat) == false && mat != null) { uniqueMaterials.Add(mat); } } } for (int i = 0; i < uniqueMaterials.Count; i++) { Material mat = uniqueMaterials[i]; // We rename the material if it is being copied string materialName = mat.name; if(copyMaterials) materialName = gameObj.name + "_" + mat.name; int referenceId = Mathf.Abs(mat.GetInstanceID()); tempObjectSb.AppendLine(); tempObjectSb.AppendLine("\tMaterial: " + referenceId + ", \"Material::" + materialName + "\", \"\" {"); tempObjectSb.AppendLine("\t\tVersion: 102"); tempObjectSb.AppendLine("\t\tShadingModel: \"phong\""); tempObjectSb.AppendLine("\t\tMultiLayer: 0"); tempObjectSb.AppendLine("\t\tProperties70: {"); tempObjectSb.AppendFormat("\t\t\tP: \"Diffuse\", \"Vector3D\", \"Vector\", \"\",{0},{1},{2}", mat.color.r, mat.color.g, mat.color.b); tempObjectSb.AppendLine(); tempObjectSb.AppendFormat("\t\t\tP: \"DiffuseColor\", \"Color\", \"\", \"A\",{0},{1},{2}", mat.color.r, mat.color.g, mat.color.b); tempObjectSb.AppendLine(); // TODO: Figure out if this property can be written to the FBX file // if(mat.HasProperty("_MetallicGlossMap")) // { // Debug.Log("has metallic gloss map"); // Color color = mat.GetColor("_Color"); // tempObjectSb.AppendFormat("\t\t\tP: \"Specular\", \"Vector3D\", \"Vector\", \"\",{0},{1},{2}", color.r, color.g, color.r); // tempObjectSb.AppendLine(); // tempObjectSb.AppendFormat("\t\t\tP: \"SpecularColor\", \"ColorRGB\", \"Color\", \" \",{0},{1},{2}", color.r, color.g, color.b); // tempObjectSb.AppendLine(); // } if(mat.HasProperty("_SpecColor")) { Color color = mat.GetColor("_SpecColor"); tempObjectSb.AppendFormat("\t\t\tP: \"Specular\", \"Vector3D\", \"Vector\", \"\",{0},{1},{2}", color.r, color.g, color.r); tempObjectSb.AppendLine(); tempObjectSb.AppendFormat("\t\t\tP: \"SpecularColor\", \"ColorRGB\", \"Color\", \" \",{0},{1},{2}", color.r, color.g, color.b); tempObjectSb.AppendLine(); } if(mat.HasProperty("_Mode")) { Color color = Color.white; switch((int)mat.GetFloat("_Mode")) { case 0: // Map is opaque break; case 1: // Map is a cutout // TODO: Add option if it is a cutout break; case 2: // Map is a fade color = mat.GetColor("_Color"); tempObjectSb.AppendFormat("\t\t\tP: \"TransparentColor\", \"Color\", \"\", \"A\",{0},{1},{2}", color.r, color.g, color.b); tempObjectSb.AppendLine(); tempObjectSb.AppendFormat("\t\t\tP: \"Opacity\", \"double\", \"Number\", \"\",{0}", color.a); tempObjectSb.AppendLine(); break; case 3: // Map is transparent color = mat.GetColor("_Color"); tempObjectSb.AppendFormat("\t\t\tP: \"TransparentColor\", \"Color\", \"\", \"A\",{0},{1},{2}", color.r, color.g, color.b); tempObjectSb.AppendLine(); tempObjectSb.AppendFormat("\t\t\tP: \"Opacity\", \"double\", \"Number\", \"\",{0}", color.a); tempObjectSb.AppendLine(); break; } } // NOTE: Unity doesn't currently import this information (I think) from an FBX file. if(mat.HasProperty("_EmissionColor")) { Color color = mat.GetColor("_EmissionColor"); tempObjectSb.AppendFormat("\t\t\tP: \"Emissive\", \"Vector3D\", \"Vector\", \"\",{0},{1},{2}", color.r, color.g, color.b); tempObjectSb.AppendLine(); float averageColor = (color.r + color.g + color.b) / 3f; tempObjectSb.AppendFormat("\t\t\tP: \"EmissiveFactor\", \"Number\", \"\", \"A\",{0}", averageColor); tempObjectSb.AppendLine(); } // TODO: Add these to the file based on their relation to the PBR files // tempObjectSb.AppendLine("\t\t\tP: \"AmbientColor\", \"Color\", \"\", \"A\",0,0,0"); // tempObjectSb.AppendLine("\t\t\tP: \"ShininessExponent\", \"Number\", \"\", \"A\",6.31179285049438"); // tempObjectSb.AppendLine("\t\t\tP: \"Ambient\", \"Vector3D\", \"Vector\", \"\",0,0,0"); // tempObjectSb.AppendLine("\t\t\tP: \"Shininess\", \"double\", \"Number\", \"\",6.31179285049438"); // tempObjectSb.AppendLine("\t\t\tP: \"Reflectivity\", \"double\", \"Number\", \"\",0"); tempObjectSb.AppendLine("\t\t}"); tempObjectSb.AppendLine("\t}"); string textureObjects; string textureConnections; SerializedTextures(gameObj, newPath, mat, materialName, copyTextures, out textureObjects, out textureConnections); tempObjectSb.Append(textureObjects); tempConnectionsSb.Append(textureConnections); } materials = uniqueMaterials.ToArray(); matObjects = tempObjectSb.ToString(); connections = tempConnectionsSb.ToString(); } /// /// Serializes textures to FBX format. /// /// Parent GameObject being exported. /// The path to export to. /// Materials that holds all the textures. /// The string with the newly serialized texture file. /// The string to connect this to the material. private static void SerializedTextures(GameObject gameObj, string newPath, Material material, string materialName, bool copyTextures, out string objects, out string connections) { // TODO: FBX import currently only supports Diffuse Color and Normal Map // Because it is undocumented, there is no way to easily find out what other textures // can be attached to an FBX file so it is imported into the PBR shaders at the same time. // Also NOTE, Unity 5.1.2 will import FBX files with legacy shaders. This is fix done // in at least 5.3.4. StringBuilder objectsSb = new StringBuilder(); StringBuilder connectionsSb = new StringBuilder(); int materialId = Mathf.Abs(material.GetInstanceID()); Texture mainTexture = material.GetTexture("_MainTex"); string newObjects = null; string newConnections = null; // Serializeds the Main Texture, one of two textures that can be stored in FBX's sysytem if(mainTexture != null) { SerializeOneTexture(gameObj, newPath, material, materialName, materialId, copyTextures, "_MainTex", "DiffuseColor", out newObjects, out newConnections); objectsSb.AppendLine(newObjects); connectionsSb.AppendLine(newConnections); } if(SerializeOneTexture(gameObj, newPath, material, materialName, materialId, copyTextures, "_BumpMap", "NormalMap", out newObjects, out newConnections)) { objectsSb.AppendLine(newObjects); connectionsSb.AppendLine(newConnections); } connections = connectionsSb.ToString(); objects = objectsSb.ToString(); } private static bool SerializeOneTexture(GameObject gameObj, string newPath, Material material, string materialName, int materialId, bool copyTextures, string unityExtension, string textureType, out string objects, out string connections) { StringBuilder objectsSb = new StringBuilder(); StringBuilder connectionsSb = new StringBuilder(); Texture texture = material.GetTexture(unityExtension); if(texture == null) { objects = ""; connections = ""; return false; } string originalAssetPath = ""; #if UNITY_EDITOR originalAssetPath = AssetDatabase.GetAssetPath(texture); #else Debug.LogError("Unity FBX Exporter can not serialize textures at runtime (yet). Look in FBXUnityMaterialGetter around line 250ish. Fix it and contribute to the project!"); objects = ""; connections = ""; return false; #endif string fullDataFolderPath = Application.dataPath; string textureFilePathFullName = originalAssetPath; string textureName = Path.GetFileNameWithoutExtension(originalAssetPath); string textureExtension = Path.GetExtension(originalAssetPath); // If we are copying the textures over, we update the relative positions if(copyTextures) { int indexOfAssetsFolder = fullDataFolderPath.LastIndexOf("/Assets"); fullDataFolderPath = fullDataFolderPath.Remove(indexOfAssetsFolder, fullDataFolderPath.Length - indexOfAssetsFolder); string newPathFolder = newPath.Remove(newPath.LastIndexOf('/') + 1, newPath.Length - newPath.LastIndexOf('/') - 1); textureName = gameObj.name + "_" + material.name + unityExtension; textureFilePathFullName = fullDataFolderPath + "/" + newPathFolder + textureName + textureExtension; } long textureReference = FBXExporter.GetRandomFBXId(); // TODO - test out different reference names to get one that doesn't load a _MainTex when importing. objectsSb.AppendLine("\tTexture: " + textureReference + ", \"Texture::" + materialName + "\", \"\" {"); objectsSb.AppendLine("\t\tType: \"TextureVideoClip\""); objectsSb.AppendLine("\t\tVersion: 202"); objectsSb.AppendLine("\t\tTextureName: \"Texture::" + materialName + "\""); objectsSb.AppendLine("\t\tProperties70: {"); objectsSb.AppendLine("\t\t\tP: \"CurrentTextureBlendMode\", \"enum\", \"\", \"\",0"); objectsSb.AppendLine("\t\t\tP: \"UVSet\", \"KString\", \"\", \"\", \"map1\""); objectsSb.AppendLine("\t\t\tP: \"UseMaterial\", \"bool\", \"\", \"\",1"); objectsSb.AppendLine("\t\t}"); objectsSb.AppendLine("\t\tMedia: \"Video::" + materialName + "\""); // Sets the absolute path for the copied texture objectsSb.Append("\t\tFileName: \""); objectsSb.Append(textureFilePathFullName); objectsSb.AppendLine("\""); // Sets the relative path for the copied texture // TODO: If we don't copy the textures to a relative path, we must find a relative path to write down here if(copyTextures) objectsSb.AppendLine("\t\tRelativeFilename: \"/Textures/" + textureName + textureExtension + "\""); objectsSb.AppendLine("\t\tModelUVTranslation: 0,0"); // TODO: Figure out how to get the UV translation into here objectsSb.AppendLine("\t\tModelUVScaling: 1,1"); // TODO: Figure out how to get the UV scaling into here objectsSb.AppendLine("\t\tTexture_Alpha_Source: \"None\""); // TODO: Add alpha source here if the file is a cutout. objectsSb.AppendLine("\t\tCropping: 0,0,0,0"); objectsSb.AppendLine("\t}"); connectionsSb.AppendLine("\t;Texture::" + textureName + ", Material::" + materialName + "\""); connectionsSb.AppendLine("\tC: \"OP\"," + textureReference + "," + materialId + ", \"" + textureType + "\""); connectionsSb.AppendLine(); objects = objectsSb.ToString(); connections = connectionsSb.ToString(); return true; } } }