/// <summary> /// Exports the given texture to the destination texture path and wires up the preview surface. /// </summary> /// <returns> /// Returns the path to the USD texture object. /// </returns> protected static string SetupTexture(Scene scene, string usdShaderPath, Material material, PreviewSurfaceSample surface, string destTexturePath, string textureName, string textureOutput) { #if UNITY_EDITOR var srcPath = UnityEditor.AssetDatabase.GetAssetPath(material.GetTexture(textureName)); srcPath = srcPath.Substring("Assets/".Length); srcPath = Application.dataPath + "/" + srcPath; var fileName = System.IO.Path.GetFileName(srcPath); var filePath = System.IO.Path.Combine(destTexturePath, fileName); System.IO.File.Copy(srcPath, filePath, overwrite: true); // Make file path baked into USD relative to scene file and use forward slashes. filePath = ImporterBase.MakeRelativePath(scene.FilePath, filePath); filePath = filePath.Replace("\\", "/"); var uvReader = new PrimvarReaderSample <Vector2>(); uvReader.varname.defaultValue = new TfToken("st"); scene.Write(usdShaderPath + "/uvReader", uvReader); var tex = new TextureReaderSample(filePath, usdShaderPath + "/uvReader.outputs:result"); scene.Write(usdShaderPath + "/" + textureName, tex); return(usdShaderPath + "/" + textureName + ".outputs:" + textureOutput); #else // Not supported at run-time, too many things can go wrong // (can't encode compressed textures, etc). throw new System.Exception("Not supported at run-time"); #endif }
public static Texture2D ImportConnectedTexture <T>(Scene scene, Connectable <T> connection, bool isNormalMap, SceneImportOptions options, out string uvPrimvar) { uvPrimvar = null; // TODO: look for the expected texture/primvar reader pair. var textureSample = new TextureReaderSample(); var connectedPrimPath = scene.GetSdfPath(connection.connectedPath).GetPrimPath(); Texture2D result = null; scene.Read(connectedPrimPath, textureSample); if (textureSample.file.defaultValue != null && !string.IsNullOrEmpty(textureSample.file.defaultValue.GetResolvedPath())) { if (OnResolveTexture != null) { result = OnResolveTexture(textureSample.file.defaultValue, isNormalMap, options); } else { result = DefaultTextureResolver(textureSample.file.defaultValue, isNormalMap, options); } } Connectable <Vector2> st = textureSample.st; if (st != null && st.IsConnected() && !string.IsNullOrEmpty(st.connectedPath)) { var pvSrc = new PrimvarReaderSample <Vector2>(); scene.Read(new pxr.SdfPath(textureSample.st.connectedPath).GetPrimPath(), pvSrc); if (pvSrc.varname != null && pvSrc.varname.defaultValue != null) { // Ask the mesh importer to load the specified texcoord. // This must be a callback, since materials-to-meshes are one-to-many. uvPrimvar = pvSrc.varname.defaultValue; } } return(result); }
public static Texture2D ImportConnectedTexture <T>(Scene scene, Connectable <T> connection, bool isNormalMap, SceneImportOptions options, out string uvPrimvar) { uvPrimvar = null; // TODO: look for the expected texture/primvar reader pair. var textureSample = new TextureReaderSample(); var connectedPrimPath = scene.GetSdfPath(connection.connectedPath).GetPrimPath(); Texture2D result = null; scene.Read(connectedPrimPath, textureSample); if (textureSample.file.defaultValue != null && !string.IsNullOrEmpty(textureSample.file.defaultValue.GetResolvedPath())) { if (OnResolveTexture != null) { result = OnResolveTexture(textureSample.file.defaultValue, isNormalMap, options); } else { result = DefaultTextureResolver(textureSample.file.defaultValue, isNormalMap, options); } } Connectable <Vector2> st = textureSample.st; if (st != null && st.IsConnected() && !string.IsNullOrEmpty(st.connectedPath)) { var pvSrc = new PrimvarReaderSample <Vector2>(); scene.Read(new pxr.SdfPath(textureSample.st.connectedPath).GetPrimPath(), pvSrc); if (pvSrc.varname != null) { if (pvSrc.varname.IsConnected()) { var connPath = new pxr.SdfPath(pvSrc.varname.GetConnectedPath()); var attr = scene.GetAttributeAtPath(connPath); if (attr != null) { var value = attr.Get(scene.Time); uvPrimvar = pxr.UsdCs.VtValueToTfToken(value).ToString(); } else { Debug.LogWarning("No primvar name was provided at the connected path: " + connPath); uvPrimvar = ""; } } else if (pvSrc.varname.defaultValue != null) { // Ask the mesh importer to load the specified texcoord. // This must be a callback, since materials-to-meshes are one-to-many. uvPrimvar = pvSrc.varname.defaultValue; } } } return(result); }
public static void ReadWriteTest() { // Game plan: // 1. Create a cube // 2. Create a material // 3. Create a shader // 4. Create a texture // 5. Connect the material to the shader's output // 6. Connect the shader's albedo parameter to the texture's output // 7. Connect the texture to the source file on disk // 8. Write all values // 9. Bind the cube to the material var scene = Scene.Create(); var cubePath = "/Model/Geom/Cube"; var materialPath = "/Model/Materials/SimpleMat"; var shaderPath = "/Model/Materials/SimpleMat/PreviewMaterial"; var texturePath = "/Model/Materials/SimpleMat/TextureReader"; var primvarReaderPath = "/Model/Materials/SimpleMat/UvReader"; var cube = new CubeSample(); cube.size = 1; var material = new MaterialSample(); material.surface.SetConnectedPath(shaderPath, "outputs:result"); var shader = new PreviewSurfaceSample(); shader.diffuseColor.defaultValue = Vector3.one; shader.diffuseColor.SetConnectedPath(texturePath, "outputs:rgb"); var texture = new TextureReaderSample(); texture.file.defaultValue = new SdfAssetPath(@"C:\A\Bogus\Texture\Path.png"); var primvarReader = new PrimvarReaderSample <Vector2>(); primvarReader.varname.defaultValue = new TfToken("st"); scene.Write("/Model", new XformSample()); scene.Write("/Model/Geom", new XformSample()); scene.Write("/Model/Materials", new XformSample()); scene.Write(cubePath, cube); scene.Write(materialPath, material); scene.Write(shaderPath, shader); scene.Write(texturePath, texture); scene.Write(primvarReaderPath, primvarReader); MaterialSample.Bind(scene, cubePath, materialPath); var material2 = new MaterialSample(); var shader2 = new PreviewSurfaceSample(); var texture2 = new TextureReaderSample(); var primvarReader2 = new PrimvarReaderSample <Vector2>(); scene.Read(materialPath, material2); scene.Read(shaderPath, shader2); scene.Read(texturePath, texture2); scene.Read(primvarReaderPath, primvarReader2); var param = shader2.GetInputParameters().First(); AssertEqual(shader.diffuseColor.connectedPath, param.connectedPath); AssertEqual("diffuseColor", param.usdName); AssertEqual(shader.diffuseColor.defaultValue, param.value); AssertEqual("_DiffuseColor", param.unityName); AssertEqual(material.surface.defaultValue, material2.surface.defaultValue); AssertEqual(material.surface.connectedPath, material2.surface.connectedPath); AssertEqual(shader.diffuseColor.defaultValue, shader2.diffuseColor.defaultValue); AssertEqual(shader.diffuseColor.connectedPath, shader2.diffuseColor.connectedPath); AssertEqual(shader.id, shader2.id); AssertEqual(texture.file.defaultValue, texture2.file.defaultValue); AssertEqual(primvarReader.varname.defaultValue, primvarReader.varname.defaultValue); PrintScene(scene); }
/// <summary> /// Exports the given texture to the destination texture path and wires up the preview surface. /// </summary> /// <returns> /// Returns the path to the USD texture object. /// </returns> protected static string SetupTexture(Scene scene, string usdShaderPath, Material material, PreviewSurfaceSample surface, Vector4 scale, string destTexturePath, string textureName, string textureOutput, ConversionType conversionType = ConversionType.None) { // We have to handle multiple cases here: // - file exists on disk // - file is a supported format => can be directly copied // - file is not in a supported format => need to blit / export // - file is only in memory // - a Texture2D // - a Texture // - a RenderTexture // - needs special care if marked as Normal Map // (can probably only be detected in an Editor context, and heuristically at runtime) // => need to blit / export // - file is not supported at all (or not yet) // - a 3D texture // => needs to be ignored, log Warning bool textureIsExported = false; string filePath = null; string fileName = null; var srcTexture2d = material.GetTexture(textureName); bool needsConversion = false; switch (conversionType) { case ConversionType.None: break; case ConversionType.UnpackNormal: #if UNITY_EDITOR if (UnityEditor.AssetDatabase.Contains(srcTexture2d)) { // normal needs to be converted if the one on disk isn't really a normal map // (e.g. created from greyscale) UnityEditor.TextureImporter importer = (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath( UnityEditor.AssetDatabase.GetAssetPath(srcTexture2d)); if (importer.textureType != UnityEditor.TextureImporterType.NormalMap) { Debug.LogWarning("Texture " + textureName + " is set as NormalMap but isn't marked as such", srcTexture2d); } UnityEditor.TextureImporterSettings dst = new UnityEditor.TextureImporterSettings(); importer.ReadTextureSettings(dst); // if this NormalMap is created from greyscale we will export the NormalMap from memory. if (dst.convertToNormalMap) { needsConversion = true; break; } } #endif break; default: needsConversion = true; break; } #if UNITY_EDITOR // only export from disk if there's no need to do any type of data conversion here if (!needsConversion) { var srcPath = UnityEditor.AssetDatabase.GetAssetPath(srcTexture2d); if (!string.IsNullOrEmpty(srcPath)) { #if UNITY_2019_2_OR_GREATER // Since textures might be inside of packages for various reasons we should support that. // Usually this would just be "Path.GetFullPath(srcPath)", but USD export messes with the CWD (Working Directory) // and so we have to do a bit more path wrangling here. if (srcPath.StartsWith("Packages")) { var pi = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(srcPath); srcPath = pi.resolvedPath + srcPath.Substring(("Packages/" + pi.name).Length); } #endif if (srcPath.StartsWith("Assets")) { srcPath = Application.dataPath + "/" + srcPath.Substring("Assets/".Length); } fileName = System.IO.Path.GetFileName(srcPath); filePath = System.IO.Path.Combine(destTexturePath, fileName); if (System.IO.File.Exists(srcPath)) { // USDZ officially only supports png / jpg / jpeg // https://graphics.pixar.com/usd/docs/Usdz-File-Format-Specification.html var ext = System.IO.Path.GetExtension(srcPath).ToLowerInvariant(); if (ext == ".png" || ext == ".jpg" || ext == ".jpeg") { System.IO.File.Copy(srcPath, filePath, overwrite: true); if (System.IO.File.Exists(filePath)) { textureIsExported = true; } } } } } #endif if (!textureIsExported) { // Since this is a texture we can't directly export from disk, we need to blit it and output it as PNG. // To avoid collisions, e.g. with multiple different textures named the same, each texture gets a pseudo-random name. // This will also avoid collisions when exporting multiple models to the same folder, e.g. with a a RenderTexture called "RT" // in each of them that might look different between exports. // TODO Future work could, if necessary, generate a texture content hash to avoid exporting identical textures multiple times // (Unity's content hash isn't reliable for some types of textures unfortunately, e.g. RTs) #if UNITY_EDITOR if (srcTexture2d is Texture2D) { fileName = srcTexture2d.name + "_" + srcTexture2d.imageContentsHash.ToString(); } else { fileName = srcTexture2d.name + "_" + Random.Range(10000000, 99999999).ToString(); } #else fileName = srcTexture2d.name + "_" + Random.Range(10000000, 99999999).ToString(); #endif filePath = System.IO.Path.Combine(destTexturePath, fileName + ".png"); // TODO extra care has to be taken of Normal Maps etc., since these are in a converted format in memory (for example 16 bit AG instead of 8 bit RGBA, depending on platform) // An example of this conversion in a shader is in Khronos' UnityGLTF implementation. // Basically, the blit has do be done with the right unlit conversion shader to get a proper "file-based" tangent space normal map back // Blit the texture and get it back to CPU // Note: Can't use RenderTexture.GetTemporary because that doesn't properly clear alpha channel bool preserveLinear = false; switch (conversionType) { case ConversionType.UnpackNormal: preserveLinear = true; break; } var rt = new RenderTexture(srcTexture2d.width, srcTexture2d.height, 0, RenderTextureFormat.ARGB32, preserveLinear ? RenderTextureReadWrite.Linear : RenderTextureReadWrite.Default); var resultTex2d = new Texture2D(srcTexture2d.width, srcTexture2d.height, TextureFormat.ARGB32, true, preserveLinear ? true : false); var activeRT = RenderTexture.active; try { RenderTexture.active = rt; GL.Clear(true, true, Color.clear); // conversion material if (_metalGlossChannelSwapMaterial == null) { _metalGlossChannelSwapMaterial = new Material(Shader.Find("Hidden/USD/ChannelCombiner")); } if (_normalChannelMaterial == null) { _normalChannelMaterial = new Material(Shader.Find("Hidden/USD/NormalChannel")); } _metalGlossChannelSwapMaterial.SetTexture("_R", srcTexture2d); _metalGlossChannelSwapMaterial.SetTexture("_G", srcTexture2d); _metalGlossChannelSwapMaterial.SetTexture("_B", srcTexture2d); _metalGlossChannelSwapMaterial.SetTexture("_A", srcTexture2d); switch (conversionType) { case ConversionType.None: Graphics.Blit(srcTexture2d, rt); break; case ConversionType.SwapRASmoothnessToBGRoughness: _metalGlossChannelSwapMaterial.SetVector("_Invert", new Vector4(0, 1, 0, 1)); // invert resulting g channel, make sure alpha is 1 _metalGlossChannelSwapMaterial.SetVector("_RScale", new Vector4(0, 0, 0, 0)); _metalGlossChannelSwapMaterial.SetVector("_GScale", new Vector4(0, 0, 0, 1)); // use a channel from _G texture for resulting g _metalGlossChannelSwapMaterial.SetVector("_BScale", new Vector4(1, 0, 0, 0)); // use r channel from _B texture for resulting b _metalGlossChannelSwapMaterial.SetVector("_AScale", new Vector4(0, 0, 0, 0)); Graphics.Blit(srcTexture2d, rt, _metalGlossChannelSwapMaterial); break; case ConversionType.InvertAlpha: _metalGlossChannelSwapMaterial.SetVector("_Invert", new Vector4(0, 0, 0, 1)); // invert alpha result _metalGlossChannelSwapMaterial.SetVector("_RScale", new Vector4(1, 0, 0, 0)); // use all color channels as-is _metalGlossChannelSwapMaterial.SetVector("_GScale", new Vector4(0, 1, 0, 0)); _metalGlossChannelSwapMaterial.SetVector("_BScale", new Vector4(0, 0, 1, 0)); _metalGlossChannelSwapMaterial.SetVector("_AScale", new Vector4(0, 0, 0, 1)); Graphics.Blit(srcTexture2d, rt, _metalGlossChannelSwapMaterial); break; case ConversionType.MaskMapToORM: // Input is RGBA (Metallic, Occlusion, Detail, Smoothness) // Output is RGB1 (Occlusion, Roughness = 1 - Smoothness, Metallic, 1) _metalGlossChannelSwapMaterial.SetVector("_Invert", new Vector4(0, 1, 0, 1)); // smoothness to roughness, solid alpha _metalGlossChannelSwapMaterial.SetVector("_RScale", new Vector4(0, 1, 0, 0)); _metalGlossChannelSwapMaterial.SetVector("_GScale", new Vector4(0, 0, 0, 1)); _metalGlossChannelSwapMaterial.SetVector("_BScale", new Vector4(1, 0, 0, 0)); _metalGlossChannelSwapMaterial.SetVector("_AScale", new Vector4(0, 0, 0, 0)); Graphics.Blit(srcTexture2d, rt, _metalGlossChannelSwapMaterial); break; case ConversionType.UnpackNormal: Graphics.Blit(srcTexture2d, rt, _normalChannelMaterial); break; } resultTex2d.ReadPixels(new Rect(0, 0, srcTexture2d.width, srcTexture2d.height), 0, 0); resultTex2d.Apply(); System.IO.File.WriteAllBytes(filePath, resultTex2d.EncodeToPNG()); if (System.IO.File.Exists(filePath)) { textureIsExported = true; } } finally { RenderTexture.active = activeRT; rt.Release(); GameObject.DestroyImmediate(rt); GameObject.DestroyImmediate(resultTex2d); } } if (!textureIsExported) { var tmpTex2d = new Texture2D(1, 1, TextureFormat.ARGB32, true); try { tmpTex2d.SetPixel(0, 0, Color.white); tmpTex2d.Apply(); System.IO.File.WriteAllBytes(filePath, tmpTex2d.EncodeToPNG()); if (System.IO.File.Exists(filePath)) { textureIsExported = true; } } finally { GameObject.DestroyImmediate(tmpTex2d); } } if (textureIsExported) { // Make file path baked into USD relative to scene file and use forward slashes. filePath = ImporterBase.MakeRelativePath(scene.FilePath, filePath); filePath = filePath.Replace("\\", "/"); var uvReader = new PrimvarReaderSample <Vector2>(); uvReader.varname.defaultValue = new TfToken("st"); scene.Write(usdShaderPath + "/uvReader", uvReader); var usdTexReader = new TextureReaderSample(filePath, usdShaderPath + "/uvReader.outputs:result"); usdTexReader.wrapS = new Connectable <TextureReaderSample.WrapMode>( TextureReaderSample.GetWrapMode(srcTexture2d.wrapModeU)); usdTexReader.wrapT = new Connectable <TextureReaderSample.WrapMode>( TextureReaderSample.GetWrapMode(srcTexture2d.wrapModeV)); if (scale != Vector4.one) { usdTexReader.scale = new Connectable <Vector4>(scale); } // usdTexReader.isSRGB = new Connectable<TextureReaderSample.SRGBMode>(TextureReaderSample.SRGBMode.Auto); scene.Write(usdShaderPath + "/" + textureName, usdTexReader); return(usdShaderPath + "/" + textureName + ".outputs:" + textureOutput); } else { Debug.LogError( "Texture wasn't exported: " + srcTexture2d.name + " (" + textureName + " from material " + material, srcTexture2d); return(null); } }