/// Authors USD Texture and PrimvarReader shader nodes for the given exportable material. /// /// Note that textureUris are stored as metadata and only the "Export Texture" is authored as a /// true texture in the shading network. This is due to the fact that the actual material textures /// are not currently exported with the USD file, but export textures are. /// /// Returns the texture path if a texture node was created, otherwise null. static string CreateAlphaTexture(USD.NET.Scene scene, string shaderPath, IExportableMaterial material) { // Currently, only export texture is previewed in USD. // Create an input parameter to read the texture, e.g. inputs:_MainTex. if (!material.HasExportTexture()) { return(null); } string texFile = SanitizeIdentifier(material.DurableName) + System.IO.Path.GetExtension(material.GetExportTextureFilename()); // Establish paths in the USD scene. string texturePath = GetTexturePath(material, "MainTex", shaderPath); string primvarPath = GetPrimvarPath(material, "uv", texturePath); // Create the texture Prim. var texture = new ExportTextureSample(); // Connect the texture to the file on disk. texture.file.defaultValue = new pxr.SdfAssetPath(texFile); texture.st.SetConnectedPath(primvarPath, "outputs:result"); scene.Write(texturePath, texture); if (scene.GetPrimAtPath(new pxr.SdfPath(primvarPath)) == null) { if (material.VertexLayout.texcoord0.size == 2) { var primvar = new PrimvarReader2fSample("uv"); scene.Write(primvarPath, primvar); } else if (material.VertexLayout.texcoord0.size == 3) { var primvar = new PrimvarReader3fSample("uv"); scene.Write(primvarPath, primvar); } else if (material.VertexLayout.texcoord0.size == 4) { var primvar = new PrimvarReader4fSample("uv"); scene.Write(primvarPath, primvar); } } return(texturePath); }
/// Authors a USD Xform prim at the given path with the given matrix transform. static void CreateXform(USD.NET.Scene scene, string path, Matrix4x4?xform = null) { var sample = new XformSample(); if (xform.HasValue) { sample.transform = xform.Value; } else { sample.transform = Matrix4x4.identity; } scene.Write(path, sample); }
/// Authors the root "Sketch" object to be exported, with sketch metadta. static void AddSketchRoot(USD.NET.Scene scene, string path) { var sample = CreateSketchRoot(); // Setup time to author default values, at no time sample. var oldTime = scene.Time; scene.Time = null; // Write the data. scene.Write(path, sample); // Restore the desired time. scene.Time = oldTime; }
// -------------------------------------------------------------------------------------------- // // Export Logic // -------------------------------------------------------------------------------------------- // /// Exports either all brush strokes or the given selection to the specified file. static public void ExportPayload(string outputFile) { // Would be nice to find a way to kick this off automatically. // Redundant calls are ignored. if (!InitUsd.Initialize()) { return; } // Unity is left handed (DX), USD is right handed (GL) var payload = ExportCollector.GetExportPayload(AxisConvention.kUsd); var brushCatalog = BrushCatalog.m_Instance; // The Scene object provids serialization methods arbitrary C# objects to USD. USD.NET.Scene scene = USD.NET.Scene.Create(outputFile); // The target time at which samples will be written. // // In this case, all data is being written to the "default" time, which means it can be // overridden by animated values later. scene.Time = null; // Bracketing times to specify the valid animation range. scene.StartTime = 1.0; scene.EndTime = 1.0; const string kGeomName = "/Geom"; const string kCurvesName = "/Curves"; string path = ""; AddSketchRoot(scene, GetSketchPath()); // Create: </Sketch> CreateXform(scene, GetStrokesPath()); // Create: </Sketch/Strokes> CreateXform(scene, GetModelsPath()); // Create: </Sketch/Models> // Main export loop. try { foreach (ExportUtils.GroupPayload group in payload.groups) { // Example: </Sketch/Strokes/Group_0> path = GetGroupPath(group.id); CreateXform(scene, path); // Example: </Sketch/Strokes/Group_0/Geom> CreateXform(scene, path + kGeomName); // Example: </Sketch/Strokes/Group_0/Curves> CreateXform(scene, path + kCurvesName); int iBrushMeshPayload = -1; foreach (var brushMeshPayload in group.brushMeshes) { ++iBrushMeshPayload; // Conditionally moves Normal into Texcoord1 so that the normal semantic is respected. // This only has an effect when layout.bFbxExportNormalAsTexcoord1 == true. // Note that this modifies the GeometryPool in place. FbxUtils.ApplyFbxTexcoordHack(brushMeshPayload.geometry); // Brushes are expected to be batched by type/GUID. Guid brushGuid = brushMeshPayload.strokes[0].m_BrushGuid; string brushName = "/" + SanitizeIdentifier(brushCatalog.GetBrush(brushGuid).DurableName) + "_"; // Example: </Sketch/Strokes/Group_0/Geom/Marker_0> string meshPath = path + kGeomName + brushName; // Example: </Sketch/Strokes/Group_0/Curves/Marker_0> string curvePath = path + kCurvesName + brushName; var geomPool = brushMeshPayload.geometry; var strokes = brushMeshPayload.strokes; var mat44 = Matrix4x4.identity; var meshPrimPath = new pxr.SdfPath(meshPath + iBrushMeshPayload.ToString()); var curvesPrimPath = new pxr.SdfPath(curvePath + iBrushMeshPayload.ToString()); // // Geometry // BrushSample brushSample = GetBrushSample(geomPool, strokes, mat44); // Write the BrushSample to the same point in the scenegraph at which it exists in Tilt // Brush. Notice this method is Async, it is queued to a background thread to perform I/O // which means it is not safe to read from the scene until WaitForWrites() is called. scene.Write(meshPrimPath, brushSample); // // Stroke Curves // var curvesSample = GetCurvesSample(payload, strokes, Matrix4x4.identity); scene.Write(curvesPrimPath, curvesSample); // // Materials // double?oldTime = scene.Time; scene.Time = null; string materialPath = CreateMaterialNetwork( scene, brushMeshPayload.exportableMaterial, GetStrokesPath()); BindMaterial(scene, meshPrimPath.ToString(), materialPath); BindMaterial(scene, curvesPrimPath.ToString(), materialPath); scene.Time = oldTime; } } // // Models // var knownModels = new Dictionary <Model, string>(); int iModelMeshPayload = -1; foreach (var modelMeshPayload in payload.modelMeshes) { ++iModelMeshPayload; var modelId = modelMeshPayload.modelId; var modelNamePrefix = "/Model_" + SanitizeIdentifier(modelMeshPayload.model.GetExportName()) + "_"; var modelName = modelNamePrefix + modelId; var xf = modelMeshPayload.xform; // Geometry pools may be repeated and should be turned into references. var geomPool = modelMeshPayload.geometry; var modelRootPath = new pxr.SdfPath(GetModelsPath() + modelName); // Example: </Sketch/Models/Model_Andy_0> CreateXform(scene, modelRootPath, xf); // Example: </Sketch/Models/Model_Andy_0/Geom> CreateXform(scene, modelRootPath + kGeomName); string modelPathToReference; if (knownModels.TryGetValue(modelMeshPayload.model, out modelPathToReference) && modelPathToReference != modelRootPath) { // Create an Xform, note that the world transform here will override the referenced model. var meshXf = new MeshXformSample(); meshXf.transform = xf; scene.Write(modelRootPath, meshXf); // Add a USD reference to previously created model. var prim = scene.Stage.GetPrimAtPath(modelRootPath); prim.GetReferences().AddReference("", new pxr.SdfPath(modelPathToReference)); continue; } // Example: </Sketch/Models/Geom/Model_Andy_0/Mesh_0> path = modelRootPath + kGeomName + "/Mesh_" + iModelMeshPayload.ToString(); var meshPrimPath = new pxr.SdfPath(path); var meshSample = new MeshSample(); GetMeshSample(geomPool, Matrix4x4.identity, meshSample); scene.Write(path, meshSample); scene.Stage.GetPrimAtPath(new pxr.SdfPath(path)).SetInstanceable(true); // // Materials // // Author at default time. double?oldTime = scene.Time; scene.Time = null; // Model materials must live under the model root, since we will reference the model. string materialPath = CreateMaterialNetwork( scene, modelMeshPayload.exportableMaterial, modelRootPath); BindMaterial(scene, meshPrimPath.ToString(), materialPath); // Continue authoring at the desired time index. scene.Time = oldTime; // // Setup to be referenced. // if (!knownModels.ContainsKey(modelMeshPayload.model)) { knownModels.Add(modelMeshPayload.model, modelRootPath); } } } catch { scene.Save(); scene.Close(); throw; } // Save will force a sync with all async reads and writes. scene.Save(); scene.Close(); }
/// Set the material:binding relationship on the mesh/curve to target the given materialPath. static void BindMaterial(USD.NET.Scene scene, string primPath, string materialPath) { scene.Write(primPath, new MaterialBindingSample(materialPath)); }
/// Authors a USD Material, Shader, parameters and connections between the two. /// The USD shader structure consists of a Material, which is connected to a shader output. The /// Shader consists of input parameters which are either connected to other shaders or in the case /// of public parameters, back to the material which is the public interface for the shading /// network. /// /// This function creates a material, shader, inputs, outputs, zero or more textures, and for each /// texture, a single primvar reader node to read the UV data from the geometric primitive. static string CreateMaterialNetwork(USD.NET.Scene scene, IExportableMaterial material, string rootPath = null) { var matSample = new ExportMaterialSample(); // Used scene object paths. string materialPath = GetMaterialPath(material, rootPath); string shaderPath = GetShaderPath(material, materialPath); string displayColorPrimvarReaderPath = GetPrimvarPath(material, "displayColor", shaderPath); string displayOpacityPrimvarReaderPath = GetPrimvarPath(material, "displayOpacity", shaderPath); // The material was already created. if (scene.GetPrimAtPath(materialPath) != null) { return(materialPath); } // Ensure the root material path is defined in the scene. scene.Stage.DefinePrim(new pxr.SdfPath(rootPath)); // Connect the materail surface to the output of the shader. matSample.surface.SetConnectedPath(shaderPath, "outputs:result"); scene.Write(materialPath, matSample); // Create the shader and conditionally connect the diffuse color to the MainTex output. var shaderSample = GetShaderSample(material); var texturePath = CreateAlphaTexture(scene, shaderPath, material); if (texturePath != null) { // A texture was created, so connect the opacity input to the texture output. shaderSample.opacity.SetConnectedPath(texturePath, "outputs:a"); } else { // TODO: currently primvars:displayOpacity is not multiplied when an alpha texture is // present. However, this only affects the USD preview. The correct solution // requires a multiply node in the shader graph, but this does not yet exist. scene.Write(displayOpacityPrimvarReaderPath, new PrimvarReader1fSample("displayOpacity")); shaderSample.opacity.SetConnectedPath(displayOpacityPrimvarReaderPath, "outputs:result"); } // Create a primvar reader to read primvars:displayColor. scene.Write(displayColorPrimvarReaderPath, new PrimvarReader3fSample("displayColor")); // Connect the diffuse color to the primvar reader. shaderSample.diffuseColor.SetConnectedPath(displayColorPrimvarReaderPath, "outputs:result"); scene.Write(shaderPath, shaderSample); // // Everything below is ad-hoc data, which is written using the low level USD API. // It consists of the Unity shader parameters and the non-exported texture URIs. // Also note that scene.GetPrimAtPath will return null when the prim is InValid, // so there is no need to call IsValid() on the resulting prim. // var shadeMaterial = new pxr.UsdShadeMaterial(scene.GetPrimAtPath(materialPath)); var shadeShader = new pxr.UsdShadeShader(scene.GetPrimAtPath(shaderPath)); if (material.SupportsDetailedMaterialInfo) { CreateShaderInputs(shadeShader, shadeMaterial, material.FloatParams); CreateShaderInputs(shadeShader, shadeMaterial, material.ColorParams); CreateShaderInputs(shadeShader, shadeMaterial, material.VectorParams); } CreateTextureUris(shadeShader.GetPrim(), material); return(materialPath); }