// Returns the gltf mesh that corresponds to the payload, or null. // Currently, null is only returned if the payload's 'geometry pool is empty. // Pass a localXf to override the default, which is to use base.xform public GlTF_Node ExportMeshPayload( SceneStatePayload payload, BaseMeshPayload meshPayload, [CanBeNull] GlTF_Node parent, Matrix4x4?localXf = null) { var node = ExportMeshPayload_NoMaterial(meshPayload, parent, localXf); if (node != null) { IExportableMaterial exportableMaterial = meshPayload.exportableMaterial; if (!G.materials.ContainsKey(exportableMaterial)) { var prims = node.m_mesh?.primitives; var attrs = (prims != null && prims.Count > 0) ? prims[0].attributes : null; if (attrs != null) { ExportMaterial(payload, meshPayload.MeshNamespace, exportableMaterial, attrs); Debug.Assert(G.materials.ContainsKey(exportableMaterial)); } } } return(node); }
// This does once-per-light work, as well as once-per-material-per-light work. // So this ends up being called multiple times with the same parameters, except // for matObjName. // matObjName is the name of the material being exported // lightObjectName is the name of the light public void ExportLight(LightPayload payload, IExportableMaterial exportableMaterial) { ObjectName lightNodeName = new ObjectName(payload.legacyUniqueName); // does need to be unique // Add the light to the scene -- this does _not_ need to be done per-material. // As a result, the node will generally have already been created. GlTF_Node node = GlTF_Node.GetOrCreate(G, lightNodeName, payload.xform, null, out _); node.lightNameThatDoesNothing = payload.name; // The names of the uniforms can be anything, really. Named after the light is the most // logical choice, but note that nobody checks that two lights don't have the same name. // Thankfully for Tilt Brush, they don't. // This should probably have used a guaranteed-unique name from the start but I don't want // to change it now because it'd break my diffs and be kind of ugly. string lightUniformPrefix = payload.name; AddUniform(exportableMaterial, lightUniformPrefix + "_matrix", GlTF_Technique.Type.FLOAT_MAT4, GlTF_Technique.Semantic.MODELVIEW, node); // Add light color. GlTF_Material mtl = G.materials[exportableMaterial]; var val = new GlTF_Material.ColorKV { key = lightUniformPrefix + "_color", color = payload.lightColor }; mtl.values.Add(val); AddUniform(exportableMaterial, val.key, GlTF_Technique.Type.FLOAT_VEC4, GlTF_Technique.Semantic.UNKNOWN, node); }
// Public only for use by GlTF_Globals public GlTF_Material(GlTF_Globals globals, IExportableMaterial exportableMaterial) : base(globals) { this.ExportableMaterial = exportableMaterial; this.name = GlTF_Material.GetNameFromObject(exportableMaterial); // PresentationNameOverride is set by GlTF_Globals in order to make it unique-ish }
// Adds material and sets up its dependent technique, program, shaders. This should be called // after adding meshes, but before populating lights, textures, etc. // Pass: // hack - attributes of some mesh that uses this material public void AddMaterialWithDependencies( IExportableMaterial exportableMaterial, string meshNamespace, GlTF_Attributes hack) { GlTF_Material gltfMtl = G.CreateMaterial(meshNamespace, exportableMaterial); // Set up technique. GlTF_Technique tech = GlTF_Writer.CreateTechnique(G, exportableMaterial); gltfMtl.instanceTechniqueName = tech.name; GlTF_Technique.States states = null; if (m_techniqueStates.ContainsKey(exportableMaterial)) { states = m_techniqueStates[exportableMaterial]; } if (states == null) { // Unless otherwise specified the preset, enable z-buffering. states = new GlTF_Technique.States(); states.enable = new[] { GlTF_Technique.Enable.DEPTH_TEST }.ToList(); } tech.states = states; AddAllAttributes(tech, exportableMaterial, hack); tech.AddDefaultUniforms(G.RTCCenter != null); // Add program. GlTF_Program program = new GlTF_Program(G); program.name = GlTF_Program.GetNameFromObject(exportableMaterial); tech.program = program.name; foreach (var attr in tech.attributes) { program.attributes.Add(attr.name); } G.programs.Add(program); // Add vertex and fragment shaders. GlTF_Shader vertShader = new GlTF_Shader(G); vertShader.name = GlTF_Shader.GetNameFromObject(exportableMaterial, GlTF_Shader.Type.Vertex); program.vertexShader = vertShader.name; vertShader.type = GlTF_Shader.Type.Vertex; vertShader.uri = ExportFileReference.CreateHttp(exportableMaterial.VertShaderUri); G.shaders.Add(vertShader); GlTF_Shader fragShader = new GlTF_Shader(G); fragShader.name = GlTF_Shader.GetNameFromObject(exportableMaterial, GlTF_Shader.Type.Fragment); program.fragmentShader = fragShader.name; fragShader.type = GlTF_Shader.Type.Fragment; fragShader.uri = ExportFileReference.CreateHttp(exportableMaterial.FragShaderUri); G.shaders.Add(fragShader); }
// Export a single shader vector uniform public void ExportShaderUniform( IExportableMaterial exportableMaterial, string name, Vector4 value) { GlTF_Material mtl = G.materials[exportableMaterial]; var vec_val = new GlTF_Material.VectorKV { key = name, vector = value }; mtl.values.Add(vec_val); AddUniform(exportableMaterial, vec_val.key, GlTF_Technique.Type.FLOAT_VEC4, GlTF_Technique.Semantic.UNKNOWN); }
// Should be called per material. public void ExportAmbientLight(IExportableMaterial exportableMaterial, Color color) { GlTF_Material mtl = G.materials[exportableMaterial]; var val = new GlTF_Material.ColorKV { key = "ambient_light_color", color = color }; mtl.values.Add(val); AddUniform(exportableMaterial, val.key, GlTF_Technique.Type.FLOAT_VEC4, GlTF_Technique.Semantic.UNKNOWN); }
static void CreateTextureUris(pxr.UsdPrim shaderPrim, IExportableMaterial material) { if (material.SupportsDetailedMaterialInfo) { foreach (var kvp in material.TextureUris) { var attr = shaderPrim.CreateAttribute(new pxr.TfToken("info:textureUris:" + kvp.Key), SdfValueTypeNames.String); attr.Set(kvp.Value); } } }
// Export a single shader float uniform public void ExportShaderUniform( IExportableMaterial exportableMaterial, string name, float value) { GlTF_Material mtl = G.materials[exportableMaterial]; var float_val = new GlTF_Material.FloatKV { key = name, value = value }; mtl.values.Add(float_val); AddUniform(exportableMaterial, float_val.key, GlTF_Technique.Type.FLOAT, GlTF_Technique.Semantic.UNKNOWN); }
/// Collects data from the exportable material and converts it to a ShaderSample. static ExportShaderSample GetShaderSample(IExportableMaterial material) { var shaderSample = new ExportShaderSample(); shaderSample.useSpecularWorkflow.defaultValue = 1; shaderSample.roughness.defaultValue = 0.5f; shaderSample.specularColor.defaultValue = new Vector3(.1f, .1f, .1f); shaderSample.durableName = material.DurableName; shaderSample.uniqueName = material.UniqueName; shaderSample.uriBase = material.UriBase; if (material.SupportsDetailedMaterialInfo) { shaderSample.vertShaderUri = material.VertShaderUri; shaderSample.fragShaderUri = material.FragShaderUri; shaderSample.enableCull = material.EnableCull; if (material.FloatParams.ContainsKey("SpecColor")) { var c = material.ColorParams["SpecColor"].linear; shaderSample.specularColor.defaultValue = new Vector3(c.r, c.g, c.b); } if (material.FloatParams.ContainsKey("Color")) { var c = material.ColorParams["Color"].linear; shaderSample.diffuseColor.defaultValue = new Vector3(c.r, c.g, c.b); } if (material.FloatParams.ContainsKey("Shininess")) { shaderSample.roughness.defaultValue = 1.0f - material.FloatParams["Shininess"]; } } shaderSample.blendMode = material.BlendMode; shaderSample.emissiveFactor = material.EmissiveFactor; shaderSample.vertexLayout = new ExportVertexLayout(); shaderSample.vertexLayout.bUseColors = material.VertexLayout.bUseColors; shaderSample.vertexLayout.bUseNormals = material.VertexLayout.bUseNormals; shaderSample.vertexLayout.bUseTangents = material.VertexLayout.bUseTangents; shaderSample.vertexLayout.bUseVertexIds = material.VertexLayout.bUseVertexIds; shaderSample.vertexLayout.normalSemantic = material.VertexLayout.normalSemantic; shaderSample.vertexLayout.uv0Semantic = material.VertexLayout.texcoord0.semantic; shaderSample.vertexLayout.uv0Size = material.VertexLayout.texcoord0.size; shaderSample.vertexLayout.uv1Semantic = material.VertexLayout.texcoord1.semantic; shaderSample.vertexLayout.uv1Size = material.VertexLayout.texcoord1.size; return(shaderSample); }
// Should be called per-material. private void AddLights(IExportableMaterial exportableMaterial, ExportUtils.SceneStatePayload payload) { #if DEBUG_GLTF_EXPORT Debug.LogFormat("Exporting {0} lights.", payload.lights.elements.Count); #endif foreach (var light in payload.lights.lights) { // A light requires a node for the matrix, but has no mesh. ExportLight(light, exportableMaterial); } // We include the ambient light color in the material (no transform needed). ExportAmbientLight(exportableMaterial, RenderSettings.ambientLight); }
/// 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); }
/// Adds a texture parameter + uniform to the specified material. /// As a side effect, auto-creates textures, images, and maybe a sampler if necessary. /// Pass: /// matObjName - the material /// texParam - name of the material parameter to add /// fileRef - file containing texture data public void AddTextureToMaterial( IExportableMaterial exportableMaterial, string texParam, ExportFileReference fileRef) { GlTF_Material material = G.materials[exportableMaterial]; GlTF_Sampler sampler = GlTF_Sampler.LookupOrCreate( G, GlTF_Sampler.MagFilter.LINEAR, GlTF_Sampler.MinFilter.LINEAR_MIPMAP_LINEAR); // The names only matter for gltf1, so keep them similar for easier diffing. // Essentially, this names the image and texture after the first material that wanted them. string matNameAndParam = $"{exportableMaterial.UniqueName:D}_{texParam}"; var img = GlTF_Image.LookupOrCreate(G, fileRef, proposedName: matNameAndParam); var tex = GlTF_Texture.LookupOrCreate(G, img, sampler, proposedName: matNameAndParam); material.values.Add(new GlTF_Material.TextureKV(key: texParam, texture: tex)); // Add texture-related parameter and uniform. AddUniform(exportableMaterial, texParam, GlTF_Technique.Type.SAMPLER_2D, GlTF_Technique.Semantic.UNKNOWN, null); }
// Adds a glTF uniform, as described by name, type, and semantic, to the given technique tech. If // node is non-null, that is also included (e.g. for lights). private void AddUniform( IExportableMaterial exportableMaterial, string name, GlTF_Technique.Type type, GlTF_Technique.Semantic semantic, GlTF_Node node = null) { //var techName = GlTF_Technique.GetNameFromObject(matObjName); var tech = GlTF_Writer.GetTechnique(G, exportableMaterial); GlTF_Technique.Parameter tParam = new GlTF_Technique.Parameter(); tParam.name = name; tParam.type = type; tParam.semantic = semantic; if (node != null) { tParam.node = node; } tech.parameters.Add(tParam); GlTF_Technique.Uniform tUniform = new GlTF_Technique.Uniform(); tUniform.name = "u_" + name; tUniform.param = tParam.name; tech.uniforms.Add(tUniform); }
// Adds to gltfMesh the glTF dependencies (primitive, material, technique, program, shaders) // required by unityMesh, using matObjName for naming the various material-related glTF // components. This does not add any geometry from the mesh (that's done separately using // GlTF_Mesh.Populate()). // // This does not create the material either. It adds a reference to a material that // presumably will be created very soon (if it hasn't previously been created). private void AddMeshDependencies( ObjectName meshName, IExportableMaterial exportableMaterial, GlTF_Mesh gltfMesh, GlTF_VertexLayout gltfLayout) { GlTF_Primitive primitive = new GlTF_Primitive( new GlTF_Attributes(G, meshName, gltfLayout)); GlTF_Accessor indexAccessor = G.CreateAccessor( GlTF_Accessor.GetNameFromObject(meshName, "indices_0"), GlTF_Accessor.Type.SCALAR, GlTF_Accessor.ComponentType.USHORT, isNonVertexAttributeAccessor: true); primitive.indices = indexAccessor; if (gltfMesh.primitives.Count > 0) { Debug.LogError("More than one primitive per mesh is unimplemented and unsupported"); } gltfMesh.primitives.Add(primitive); // This needs to be a forward-reference (ie, by name) because G.materials[exportableMaterial] // may not have been created yet. primitive.materialName = GlTF_Material.GetNameFromObject(exportableMaterial); }
/// Memoized version of CreateFbxMaterial /// Guarantees 1:1 correspondence between IEM, FbxMaterial, and FbxMaterial.name public FbxSurfaceMaterial GetOrCreateFbxMaterial( string meshNamespace, IExportableMaterial exportableMaterial) { // Unity's able to ensure a 1:1 correspondence between FBX materials and generated Unity // materials. However, users like TBT who go through the OnAssignMaterialModel interface cannot // distinguish between "two unique materials with the same name" and "one material being // used multiple times". // // Since TBT can't detect reference-equality of FbxMaterial, we have to help it by // making name-equality the same as reference-equality. IOW distinct materials need // distinct names. if (m_createdMaterials.TryGetValue(exportableMaterial, out FbxSurfaceMaterial mtl)) { return(mtl); } else { FbxSurfaceMaterial newMtl = ExportFbx.CreateFbxMaterial( this, meshNamespace, exportableMaterial, m_createdMaterialNames); m_createdMaterials[exportableMaterial] = newMtl; return(newMtl); } }
// Returns the name used by the actual GlTF_Material. // This is what is referred to throughout the code as "matName" or "material.name". public static string GetNameFromObject(IExportableMaterial exportableMaterial) { return($"material_{exportableMaterial.UniqueName:D}"); }
// Updates glTF technique tech by adding all relevant attributes. // Pass: // mesh - (optional) the attributes of some mesh that uses this material, for sanity-checking. private void AddAllAttributes( GlTF_Technique tech, IExportableMaterial exportableMaterial, GlTF_Attributes mesh) { GlTF_VertexLayout layout = new GlTF_VertexLayout(G, exportableMaterial.VertexLayout); if (mesh != null) { GlTF_VertexLayout meshLayout = mesh.m_layout; if (layout != meshLayout) { if (meshLayout.GetTexcoordSize(2) > 0) { // We funnel timestamps in through GeometryPool.texcoord2 and write them out as // _TB_TIMESTAMP. Thus, the Pool's layout has a texcoord2 but the material's layout does // not. This is a mismatch between the mesh data and the material props, but: // 1. Timestamp isn't intended to be funneled to the material, so it's correct that the // material layoutdoesn't have texcoord2 // 2. It's fine if material attrs are a subset of the mesh attrs // 3. This only affects gltf1; only materials with techniques need to enum their attrs. // Maybe this check should be layout.IsSubset(meshLayout). /* ignore this mismatch */ } else { Debug.LogWarning($"Layout for {exportableMaterial.DurableName} doesn't match mesh's"); } } } // Materials are things that are shared across multiple meshes. // Material creation shouldn't depend on data specific to a particular mesh. // But it does. It's a hack. // Rather than do something reasonable like this: // // Create material's technique's attributes based on layout // Create mesh and its accessors based on layout // // We do this: // // Create mesh and its accessors based on layout // Lazily create the material used by the mesh // Create material's technique's attributes based on the accessors // of the last mesh we created AddAttribute("position", layout.PositionInfo.techniqueType, GlTF_Technique.Semantic.POSITION, tech); if (layout.NormalInfo != null) { AddAttribute("normal", layout.NormalInfo.Value.techniqueType, GlTF_Technique.Semantic.NORMAL, tech); } if (layout.ColorInfo != null) { AddAttribute("color", layout.ColorInfo.Value.techniqueType, GlTF_Technique.Semantic.COLOR, tech); } if (layout.TangentInfo != null) { AddAttribute("tangent", layout.TangentInfo.Value.techniqueType, GlTF_Technique.Semantic.TANGENT, tech); } // TODO: remove; this accessor isn't used. Instead, shaders use texcoord1.w if (layout.PackVertexIdIntoTexcoord1W) { AddAttribute("vertexId", GlTF_Technique.Type.FLOAT /* hardcoded, but this is gong away */, GlTF_Technique.Semantic.UNKNOWN, tech); } for (int i = 0; i < 4; ++i) { var texcoordInfo = layout.GetTexcoordInfo(i); if (texcoordInfo != null) { GlTF_Technique.Semantic semantic = GlTF_Technique.Semantic.TEXCOORD_0 + i; AddAttribute($"texcoord{i}", texcoordInfo.Value.techniqueType, semantic, tech); } } }
// Helper for GetModelMeshPayloads() -- this is the level 4 iteration. // Pass: // mesh / meshMaterials - the Mesh to generate geometry for // payload - the payload being exported // prefab - the owner "prefab" // meshIndex - the index of this Mesh within its owner "prefab" static IEnumerable <PrefabGeometry> GetPrefabGameObjMeshes( Mesh mesh, Material[] meshMaterials, SceneStatePayload payload, Model prefab, int meshIndex) { if (meshMaterials.Length != mesh.subMeshCount) { throw new ArgumentException("meshMaterials length"); } string exportName = prefab.GetExportName(); IExportableMaterial[] exportableMaterials = meshMaterials.Select( mat => prefab.GetExportableMaterial(mat)).ToArray(); VertexLayout?[] layouts = exportableMaterials.Select(iem => iem?.VertexLayout).ToArray(); // TODO(b/142396408) // Is it better to write color/texcoord that we don't need, just to satisfy the layout? // Or should we remove color/texcoord from the layout if the Mesh doesn't have them? // Right now we do the former. I think the fix should probably be in the collector; // it shouldn't return an exportableMaterial that is a superset of what the mesh contains. GeometryPool[] subMeshPools = GeometryPool.FromMesh( mesh, layouts, fallbackColor: Color.white, useFallbackTexcoord: true); for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++) { // See if this material is a Blocks material. Material mat = meshMaterials[subMeshIndex]; // At import time, ImportMaterialCollector now ensures that _every_ UnityEngine.Material // imported also comes with an IExportableMaterial of some kind (BrushDescriptor for // Blocks/TB, and DynamicExportableMaterial for everything else). This lookup shouldn't fail. IExportableMaterial exportableMaterial = exportableMaterials[subMeshIndex]; if (exportableMaterial == null) { Debug.LogWarning($"Model {prefab.HumanName} has a non-exportable material {mat.name}"); continue; } GeometryPool geo = subMeshPools[subMeshIndex]; // TODO(b/140634751): lingering sanity-checking; we can probably remove this for M24 if (geo.Layout.bUseColors) { Debug.Assert(geo.m_Colors.Count == geo.NumVerts); } // Important: This transform should only be applied once per prefab, since the pools are // shared among all the instances, and cannot include any mesh-local transformations. // If the pools share data (which is currently impossible), then additionally // the transform should only be applied once to each set of vertex data. ExportUtils.ConvertUnitsAndChangeBasis(geo, payload); if (payload.reverseWinding) { // There are many ways of reversing winding; choose the one that matches Unity's fbx flip ExportUtils.ReverseTriangleWinding(geo, 1, 2); } yield return(new PrefabGeometry { model = prefab, name = exportName + "_" + meshIndex + "_" + subMeshIndex, pool = geo, exportableMaterial = exportableMaterial, }); } }
// Pass: // meshNamespace - A string used as the "namespace" of the mesh that owns this material. // Useful for uniquifying names (texture file names, material names, ...) in a // human-friendly way. // hack - attributes of some mesh that uses this material private void ExportMaterial( SceneStatePayload payload, string meshNamespace, IExportableMaterial exportableMaterial, GlTF_Attributes hack) { // // Set culling and blending modes. // GlTF_Technique.States states = new GlTF_Technique.States(); m_techniqueStates[exportableMaterial] = states; // Everyone gets depth test states.enable = new[] { GlTF_Technique.Enable.DEPTH_TEST }.ToList(); if (exportableMaterial.EnableCull) { states.enable.Add(GlTF_Technique.Enable.CULL_FACE); } if (exportableMaterial.BlendMode == ExportableMaterialBlendMode.AdditiveBlend) { states.enable.Add(GlTF_Technique.Enable.BLEND); // Blend array format: [srcRGB, dstRGB, srcAlpha, dstAlpha] states.functions["blendFuncSeparate"] = new GlTF_Technique.Value(G, new Vector4(1.0f, 1.0f, 1.0f, 1.0f)); // Additive. states.functions["depthMask"] = new GlTF_Technique.Value(G, false); // No depth write. // Note: If we switch bloom to use LDR color, adding the alpha channels won't do. // GL_MIN would be a good choice for alpha, but it's unsupported by glTF 1.0. } else if (exportableMaterial.BlendMode == ExportableMaterialBlendMode.AlphaBlend) { states.enable.Add(GlTF_Technique.Enable.BLEND); // Blend array format: [srcRGB, dstRGB, srcAlpha, dstAlpha] // These enum values correspond to: [ONE, ONE_MINUS_SRC_ALPHA, ONE, ONE_MINUS_SRC_ALPHA] states.functions["blendFuncSeparate"] = new GlTF_Technique.Value(G, new Vector4(1.0f, 771.0f, 1.0f, 771.0f)); // Blend. states.functions["depthMask"] = new GlTF_Technique.Value(G, true); } else { // Standard z-buffering: Enable depth write. states.functions["depthMask"] = new GlTF_Technique.Value(G, true); } // First add the material, then export any per-material attributes, such as shader uniforms. AddMaterialWithDependencies(exportableMaterial, meshNamespace, hack); // Add lighting for this material. AddLights(exportableMaterial, payload); // // Export shader/material parameters. // foreach (var kvp in exportableMaterial.FloatParams) { ExportShaderUniform(exportableMaterial, kvp.Key, kvp.Value); } foreach (var kvp in exportableMaterial.ColorParams) { ExportShaderUniform(exportableMaterial, kvp.Key, kvp.Value); } foreach (var kvp in exportableMaterial.VectorParams) { ExportShaderUniform(exportableMaterial, kvp.Key, kvp.Value); } foreach (var kvp in exportableMaterial.TextureSizes) { float width = kvp.Value.x; float height = kvp.Value.y; ExportShaderUniform(exportableMaterial, kvp.Key + "_TexelSize", new Vector4(1 / width, 1 / height, width, height)); } // // Export textures. // foreach (var kvp in exportableMaterial.TextureUris) { string textureName = kvp.Key; string textureUri = kvp.Value; ExportFileReference fileRef; if (ExportFileReference.IsHttp(textureUri)) { // Typically this happens for textures used by BrushDescriptor materials fileRef = CreateExportFileReferenceFromHttp(textureUri); } else { fileRef = ExportFileReference.GetOrCreateSafeLocal( G.m_disambiguationContext, textureUri, exportableMaterial.UriBase, $"{meshNamespace}_{Path.GetFileName(textureUri)}"); } AddTextureToMaterial(exportableMaterial, textureName, fileRef); } }
// Example: /Sketch/Strokes/Materials/Material_Light static string GetMaterialPath(IExportableMaterial material, string rootPath) { return(rootPath + "/Materials/Material_" + SanitizeIdentifier(material.DurableName)); }
// Example: /Sketch/Strokes/Materials/Material_Light/Shader_Light static string GetShaderPath(IExportableMaterial material, string parentMaterialPath) { return(parentMaterialPath + "/Shader_" + SanitizeIdentifier(material.DurableName)); }
/// 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); }
// Example: /Sketch/Strokes/Materials/Material_Light/Shader_Light/Texture_MainTex static string GetTexturePath(IExportableMaterial material, string textureName, string parentShaderPath) { return(parentShaderPath + "/Texture_" + SanitizeIdentifier(textureName)); }
internal static FbxSurfaceMaterial CreateFbxMaterial( FbxExportGlobals G, string meshNamespace, IExportableMaterial exportableMaterial, HashSet <string> createdMaterialNames) { string materialName; if (exportableMaterial is BrushDescriptor) { // Toolkit uses this guid (in "N" format) to look up a BrushDescriptor. // See Toolkit's ModelImportSettings.GetDescriptorForStroke materialName = $"{exportableMaterial.UniqueName:N}_{meshNamespace}_{exportableMaterial.DurableName}"; } else if (exportableMaterial is DynamicExportableMaterial dem) { // Comes from {fbx,obj,gltf,...} import from {Poly, Media Library} // This is a customized version of a BrushDescriptor -- almost certainly // Pbr{Blend,Opaque}{Double,Single}Sided with maybe an added texture and // some of its params customized. // TBT will merge the material created by Unity for this FbxMaterial with // the premade material it has for the parent guid. materialName = $"{dem.Parent.m_Guid:N}_{meshNamespace}_{dem.DurableName}"; } else { Debug.LogWarning($"Unknown class {exportableMaterial.GetType().Name}"); materialName = $"{meshNamespace}_{exportableMaterial.DurableName}"; } // If only ExportFbx were a non-static class we could merge it with FbxExportGlobals materialName = ExportUtils.CreateUniqueName(materialName, createdMaterialNames); FbxSurfaceLambert material = FbxSurfaceLambert.Create(G.m_scene, materialName); material.Ambient.Set(new FbxDouble3(0, 0, 0)); material.Diffuse.Set(new FbxDouble3(1.0, 1.0, 1.0)); if (exportableMaterial.EmissiveFactor > 0) { material.EmissiveFactor.Set(exportableMaterial.EmissiveFactor); material.Emissive.Set(new FbxDouble3(1.0, 1.0, 1.0)); } if (exportableMaterial.BlendMode != ExportableMaterialBlendMode.None) { var blendMode = FbxProperty.Create(material, Globals.FbxStringDT, "BlendMode"); switch (exportableMaterial.BlendMode) { case ExportableMaterialBlendMode.AlphaMask: blendMode.SetString(new FbxString("AlphaMask")); material.TransparencyFactor.Set(0.2); break; case ExportableMaterialBlendMode.AdditiveBlend: blendMode.SetString(new FbxString("AdditiveBlend")); break; } } // Export the texture if (exportableMaterial.HasExportTexture()) { // This is not perfectly unique, but it is good enough for fbx export // better would be to use <durable>_<guid> but that's ugly, and nobody uses // the textures anyway, so... let's leave well enough alone for now. string albedoTextureName = exportableMaterial.DurableName; var fullTextureDir = Path.Combine(G.m_outputDir, kRelativeTextureDir); if (!Directory.Exists(fullTextureDir)) { if (!FileUtils.InitializeDirectoryWithUserError(fullTextureDir)) { throw new IOException("Cannot write textures"); } } string src = exportableMaterial.GetExportTextureFilename(); var textureFileName = albedoTextureName + ".png"; var textureFilePath = Path.Combine(fullTextureDir, textureFileName); FileInfo srcInfo = new FileInfo(src); if (srcInfo.Exists && !new FileInfo(textureFilePath).Exists) { srcInfo.CopyTo(textureFilePath); } FbxFileTexture texture = FbxFileTexture.Create(G.m_scene, albedoTextureName + "_texture"); texture.SetFileName(textureFilePath); texture.SetTextureUse(FbxTexture.ETextureUse.eStandard); texture.SetMappingType(FbxTexture.EMappingType.eUV); texture.SetMaterialUse(FbxFileTexture.EMaterialUse.eModelMaterial); texture.UVSet.Set(new FbxString("uv0")); material.Diffuse.ConnectSrcObject(texture); material.TransparentColor.ConnectSrcObject(texture); } else { foreach (var kvp in exportableMaterial.TextureUris) { string parameterName = kvp.Key; string textureUri = kvp.Value; if (ExportFileReference.IsHttp(textureUri)) { // fbx can't deal with http references to textures continue; } ExportFileReference fileRef = ExportFileReference.GetOrCreateSafeLocal( G.m_disambiguationContext, textureUri, exportableMaterial.UriBase, $"{meshNamespace}_{Path.GetFileName(textureUri)}"); AddTextureToMaterial(G, fileRef, material, parameterName); } } return(material); }
// Example: /Sketch/Strokes/Materials/Material_Light/Shader_Light/Texture_MainTex/Primvar_Uv static string GetPrimvarPath(IExportableMaterial material, string primvarName, string parentTexturePath) { return(parentTexturePath + "/Primvar_" + SanitizeIdentifier(primvarName)); }