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 } }
// 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); } } }
private void PopulateUv( int channel, TiltBrush.GeometryPool pool, GlTF_Accessor accessor, Semantic semantic) { bool packVertId = m_layout.PackVertexIdIntoTexcoord1W && channel == 1; if (packVertId) { // Guaranteed by GlTF_VertexLayout Debug.Assert(m_layout.m_tbLayout.GetTexcoordInfo(channel).size == 3); Debug.Assert(m_layout.GetTexcoordSize(channel) == 4); } if (accessor == null) { return; } if (channel < 0 || channel > 3) { throw new ArgumentException("Invalid channel"); } TiltBrush.GeometryPool.TexcoordData texcoordData = pool.GetTexcoordData(channel); if (semantic == Semantic.XyIsUvZIsDistance && accessor.type != GlTF_Accessor.Type.VEC3) { throw new ArgumentException("XyIsUvZIsDistance semantic can only be applied to VEC3"); } bool flipY; if (semantic == Semantic.Unspecified && channel == 0 && accessor.type == GlTF_Accessor.Type.VEC2) { Debug.LogWarning("Assuming Semantic.XyIsUv"); semantic = Semantic.XyIsUv; } switch (semantic) { case Semantic.Position: case Semantic.Vector: case Semantic.Timestamp: flipY = false; break; case Semantic.XyIsUvZIsDistance: case Semantic.XyIsUv: flipY = true; break; default: throw new ArgumentException("semantic"); } switch (accessor.type) { case GlTF_Accessor.Type.SCALAR: throw new NotImplementedException(); case GlTF_Accessor.Type.VEC2: accessor.Populate(texcoordData.v2, flipY: flipY, calculateMinMax: false); break; case GlTF_Accessor.Type.VEC3: accessor.Populate(texcoordData.v3, flipY: flipY, calculateMinMax: false); break; case GlTF_Accessor.Type.VEC4: if (packVertId) { // In the vertexId case, we actually have a vec3, which needs to be augmented to a vec4. // TODO: this should happen at some higher level. int i = 0; var v4 = texcoordData.v3.ConvertAll <Vector4>((v => new Vector4(v.x, v.y, v.z, i++))); accessor.Populate(v4, flipY: flipY, calculateMinMax: false); } else { accessor.Populate(texcoordData.v4, flipY: flipY, calculateMinMax: false); } break; default: throw new ArgumentException("Unexpected accessor.type"); } }