// Doesn't do material export; for that see ExportMeshPayload private GlTF_Node ExportMeshPayload_NoMaterial( BaseMeshPayload mesh, [CanBeNull] GlTF_Node parent, Matrix4x4?localXf = null) { ObjectName meshNameAndId = new ObjectName(mesh.legacyUniqueName); GeometryPool pool = mesh.geometry; Matrix4x4 xf = localXf ?? mesh.xform; // Create a Node and (usually) a Mesh, both named after meshNameAndId. // This is safe because the namespaces for Node and Mesh are distinct. // If we have already seen the GeometryPool, the Mesh will be reused. // In this (less common) case, the Node and Mesh will have different names. // We don't actually ever use the "VERTEXID" attribute, even in gltf1. // It's time to cut it away. // Also, in gltf2, it needs to be called _VERTEXID anyway since it's a custom attribute GlTF_VertexLayout gltfLayout = new GlTF_VertexLayout(G, pool.Layout); int numTris = pool.NumTriIndices / 3; if (numTris < 1) { return(null); } NumTris += numTris; GlTF_Mesh gltfMesh; // Share meshes for any repeated geometry pool. if (!m_meshCache.TryGetValue(pool, out gltfMesh)) { gltfMesh = new GlTF_Mesh(G); gltfMesh.name = GlTF_Mesh.GetNameFromObject(meshNameAndId); gltfMesh.PresentationNameOverride = mesh.geometryName; m_meshCache.Add(pool, gltfMesh); // Populate mesh data only once. AddMeshDependencies(meshNameAndId, mesh.exportableMaterial, gltfMesh, gltfLayout); gltfMesh.Populate(pool); G.meshes.Add(gltfMesh); } // The mesh may or may not be shared, but every mesh will have a distinct node to allow them // to have unique transforms. GlTF_Node node = GlTF_Node.GetOrCreate(G, meshNameAndId, xf, parent, out _); node.m_mesh = gltfMesh; node.PresentationNameOverride = mesh.nodeName; return(node); }
// 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); }
// 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); } } }
public GlTF_Attributes( GlTF_Globals G, ObjectName meshName, GlTF_VertexLayout layout) { // This prefix should be used with possibly-nonconforming gltf data: // - texcoords that are not 2-element or that don't contain texture coordinates // - made-up / mythical semantics like VERTEXID string nonconformingPrefix = (G.GltfCompatibilityMode) ? "_TB_UNITY_" : ""; m_layout = layout; { AttributeInfo positionInfo = layout.PositionInfo; positionAccessor = G.CreateAccessor( GlTF_Accessor.GetNameFromObject(meshName, "position"), positionInfo.accessorType, positionInfo.accessorComponentType); m_accessors.Add("POSITION", positionAccessor); } { if (layout.NormalInfo is AttributeInfo normalInfo) { normalAccessor = G.CreateAccessor( GlTF_Accessor.GetNameFromObject(meshName, "normal"), normalInfo.accessorType, normalInfo.accessorComponentType); // Genius particles put things that don't look like normals into the normal attribute. bool isNonconforming = (layout.m_tbLayout.normalSemantic == Semantic.Position); string prefix = isNonconforming ? nonconformingPrefix : ""; m_accessors.Add(prefix + "NORMAL", normalAccessor); } } { if (layout.ColorInfo is AttributeInfo cInfo) { colorAccessor = G.CreateAccessor( GlTF_Accessor.GetNameFromObject(meshName, "color"), cInfo.accessorType, cInfo.accessorComponentType, normalized: true); m_accessors.Add(G.Gltf2 ? "COLOR_0" : "COLOR", colorAccessor); } } { if (layout.TangentInfo is AttributeInfo tangentInfo) { tangentAccessor = G.CreateAccessor( GlTF_Accessor.GetNameFromObject(meshName, "tangent"), tangentInfo.accessorType, tangentInfo.accessorComponentType); m_accessors.Add("TANGENT", tangentAccessor); } } if (layout.PackVertexIdIntoTexcoord1W) { // The vertexid hack modifies the gl layout to extend texcoord1 so the vertexid // can be stuffed into it Debug.Assert(layout.m_tbLayout.GetTexcoordInfo(1).size == 3); Debug.Assert(layout.GetTexcoordSize(1) == 4); } GlTF_Accessor MakeAccessorFor(int texcoord) { var txcInfo = layout.GetTexcoordInfo(texcoord); if (txcInfo == null) { return(null); } Semantic tbSemantic = layout.m_tbLayout.GetTexcoordInfo(texcoord).semantic; string attrName = $"{nonconformingPrefix}TEXCOORD_{texcoord}"; // Timestamps are tunneled into us via a texcoord because there's not really a better way // due to GeometryPool limitations. But that's an internal implementation detail. I'd like // them to have a better attribute name in the gltf. if (tbSemantic == Semantic.Timestamp) { // For b/141876882; Poly doesn't like _TB_TIMESTAMP if (!G.Gltf2) { return(null); } attrName = "_TB_TIMESTAMP"; } var ret = G.CreateAccessor( GlTF_Accessor.GetNameFromObject(meshName, $"uv{texcoord}"), txcInfo.Value.accessorType, txcInfo.Value.accessorComponentType); m_accessors.Add(attrName, ret); return(ret); } texCoord0Accessor = MakeAccessorFor(0); texCoord1Accessor = MakeAccessorFor(1); texCoord2Accessor = MakeAccessorFor(2); texCoord3Accessor = MakeAccessorFor(3); if (G.GltfCompatibilityMode) { TiltBrush.GeometryPool.VertexLayout tbLayout = layout.m_tbLayout; switch (tbLayout.texcoord0.semantic) { case Semantic.Unspecified when tbLayout.texcoord0.size == 2: case Semantic.XyIsUv: case Semantic.XyIsUvZIsDistance: { GlTF_Accessor accessor = GlTF_Accessor.CloneWithDifferentType( G, texCoord0Accessor, GlTF_Accessor.Type.VEC2); m_accessors.Add("TEXCOORD_0", accessor); break; } } // No need to check the other texcoords because TB only ever puts texture coordinates // in texcoord0 } }