【CG】glTF-SDK C++のシリアライズでUVとテクスチャを適用する

Pocket
LinkedIn にシェア
LINEで送る

Microsoftが公開してくれている3Dデータ・フォーマットのglTF形式をインポート、エクスポートができるライブラリmicrosoft / glTF-SDK – Github。ですが、サンプル諸々がすごい貧弱で、特にエクスポートの情報量諸々が少ない。単体テストコードが一応あるけど、三角形1枚に赤色でちょっとメタルなPBRマテリアルをベタッと貼っただけのソースコード。それしかない。

基本的なのにテクスチャを割り当てるサンプルソースがない。
ググっても誰も使っていからか、見つからない。世界でmasafumiさんという方しか使ってないのか? ってくらい情報が見つからない。やっぱりtinygltfがメジャーなのでしょうか。
  glTF SDKを使おうかと思ってるが… – 新 masafumi’s Diary

SDKのテストコードをgrepしまくってそれらしい方法やら、仕様書とglTFファイルの中身を見つつ勘でプログラムしてなんとか突破したのでメモ。

やり方

Image、Texture、TexCoordsの3つを設定します。
・Image、Textureは。
・TexCoordsはvertexと基本同じ。

実装

glTF-SDKのGLTFSDK.Samples/Serialize/Source/main.cppを改造したものです。

    void Sample2::CreateTriangleResources1(Document& document, BufferBuilder& bufferBuilder,
                                            std::string& accessorIdIndices, std::string& accessorIdPositions,
                                            std::string& accessorIdTexCoords) // yanai add
    {
        // Create all the resource data (e.g. triangle indices and
        // vertex positions) that will be written to the binary buffer
        const char* bufferId = nullptr;

        // Specify the 'special' GLB buffer ID. This informs the GLBResourceWriter that it should use
        // the GLB container's binary chunk (usually the desired buffer location when creating GLBs)
        if (dynamic_cast<const GLBResourceWriter*>(&bufferBuilder.GetResourceWriter()))
        {
            bufferId = GLB_BUFFER_ID;
        }

        // Create a Buffer - it will be the 'current' Buffer that all the BufferViews
        // created by this BufferBuilder will automatically reference
        bufferBuilder.AddBuffer(bufferId);

        // Create a BufferView with a target of ELEMENT_ARRAY_BUFFER (as it will reference index
        // data) - it will be the 'current' BufferView that all the Accessors created by this
        // BufferBuilder will automatically reference
        bufferBuilder.AddBufferView(BufferViewTarget::ELEMENT_ARRAY_BUFFER);

        // Add an Accessor for texCoords -->
        std::vector<float> texcoords = {
            0.0f, 0.0f,
            2.0f, 0.0f,
            0.0f, 2.0f,
            2.0f, 0.0f
        };
        accessorIdTexCoords = bufferBuilder.AddAccessor(texcoords, { TYPE_VEC2, COMPONENT_FLOAT }).id;
        // texCoords end <--
        
        // Add an Accessor for the indices
        std::vector<uint16_t> indices = {
            0, 1, 2,
            0, 2, 3, // yanai
            0, 1, 3,
            1, 2, 3
        };

        // Copy the Accessor's id - subsequent calls to AddAccessor may invalidate the returned reference
        accessorIdIndices = bufferBuilder.AddAccessor(indices, { TYPE_SCALAR, COMPONENT_UNSIGNED_SHORT }).id;

        // Create a BufferView with target ARRAY_BUFFER (as it will reference vertex attribute data)
        bufferBuilder.AddBufferView(BufferViewTarget::ARRAY_BUFFER);

        // Add an Accessor for the positions
        std::vector<float> positions = {
            0.0f, 0.0f, 0.0f, // Vertex 0
            1.0f, 0.0f, 0.0f, // Vertex 1
            0.0f, 1.0f, 0.0f, // Vertex 2
            0.0f, 0.0f, 1.0f  // Vertex 3 yanai
        };

        std::vector<float> minValues(3U, std::numeric_limits<float>::max());
        std::vector<float> maxValues(3U, std::numeric_limits<float>::lowest());

        const size_t positionCount = positions.size();

        // Accessor min/max properties must be set for vertex position data so calculate them here
        for (size_t i = 0U, j = 0U; i < positionCount; ++i, j = (i % 3U))
        {
            minValues[j] = std::min(positions[i], minValues[j]);
            maxValues[j] = std::max(positions[i], maxValues[j]);
        }

        accessorIdPositions = bufferBuilder.AddAccessor(positions,
            { TYPE_VEC3, COMPONENT_FLOAT, false, minValues, maxValues }).id;

        // Add all of the Buffers, BufferViews and Accessors that were created using BufferBuilder to
        // the Document. Note that after this point, no further calls should be made to BufferBuilder
        bufferBuilder.Output(document);
    }

    void Sample2::CreateTriangleEntities1(Document& document,
                                        const std::string& accessorIdIndices, 
                                        const std::string& accessorIdPositions,
                                        const std::string& accessorIdTexCoords) // yanai add
    {
        // Create a very simple glTF Document with the following hierarchy:
        //  Scene
        //     Node
        //       Mesh (Triangle)
        //         MeshPrimitive
        //           Material (Blue)
        // 
        // A Document can be constructed top-down or bottom up. However, if constructed top-down
        // then the IDs of child entities must be known in advance, which prevents using the glTF
        // SDK's automatic ID generation functionality.

        // Construct a image //
        Microsoft::glTF::Image image;
        image.id = "0";
        image.uri = "BoomBox_baseColor.png";
        auto imageId = document.images.Append(image, AppendIdPolicy::GenerateOnEmpty).id;

        // Construct a texture //
        Texture texture;
        texture.imageId = imageId;
        texture.id = "0";
        auto textureId = document.textures.Append(texture, AppendIdPolicy::GenerateOnEmpty).id;        

        // Attach texture id
        // Construct a Material
        Material material;
        material.metallicRoughness.baseColorFactor = Color4(1.0f, 1.0f, 1.0f, 1.0f);//yanai
        material.metallicRoughness.metallicFactor = 0.2f;
        material.metallicRoughness.roughnessFactor = 0.4f;
        material.doubleSided = true;
        material.metallicRoughness.baseColorTexture.textureId = textureId;

        // Add it to the Document and store the generated ID
        auto materialId = document.materials.Append(material, AppendIdPolicy::GenerateOnEmpty).id;

        // Construct a MeshPrimitive. Unlike most types in glTF, MeshPrimitives are direct children
        // of their parent Mesh entity rather than being children of the Document. This is why they
        // don't have an ID member.
        MeshPrimitive meshPrimitive;
        meshPrimitive.materialId = materialId;
        meshPrimitive.indicesAccessorId = accessorIdIndices;
        meshPrimitive.attributes[ACCESSOR_POSITION] = accessorIdPositions;
        meshPrimitive.attributes[ACCESSOR_TEXCOORD_0] = accessorIdTexCoords; // yanai add

        // Construct a Mesh and add the MeshPrimitive as a child
        Mesh mesh;
        mesh.primitives.push_back(meshPrimitive);
        // Add it to the Document and store the generated ID
        auto meshId = document.meshes.Append(mesh, AppendIdPolicy::GenerateOnEmpty).id;

        // Construct a Node adding a reference to the Mesh
        Node node;
        node.meshId = meshId;
        // Add it to the Document and store the generated ID
        auto nodeId = document.nodes.Append(node, AppendIdPolicy::GenerateOnEmpty).id;

        // Construct a Scene
        Scene scene;
        scene.nodes.push_back(nodeId);
        // Add it to the Document, using a utility method that also sets the Scene as the Document's default
        document.SetDefaultScene(std::move(scene), AppendIdPolicy::GenerateOnEmpty);
    }

補足説明

TexCoords

インデックスindicesの数×2/3にします。
bufferBuilder.AddAccessor()で登録します、型はTYPE_VEC2, COMPONENT_FLOATです。
返り値が文字列型で返ってきますので、そいつがTexCoordsのidです。

std::vector texcoords = {
0.0f, 0.0f,
2.0f, 0.0f,
0.0f, 2.0f,
2.0f, 0.0f
};
accessorIdTexCoords = bufferBuilder.AddAccessor(texcoords, { TYPE_VEC2, COMPONENT_FLOAT }).id;

最終的にはidであるaccessorIdTexCoordsをMeshPrimitiveに登録します。属性型はACCESSOR_TEXCOORD_0。

MeshPrimitive meshPrimitive;
meshPrimitive.materialId = materialId;
meshPrimitive.indicesAccessorId = accessorIdIndices;
meshPrimitive.attributes[ACCESSOR_POSITION] = accessorIdPositions;
meshPrimitive.attributes[ACCESSOR_TEXCOORD_0] = accessorIdTexCoords; // yanai add

Image, Texture

Microsoft::glTF::ImageとMicrosoft::glTF::Textureの利用方法です。クラスのメンバーと宣言を見ているとわかってくると思います。

画像Imageは、単独で登録します。
テクスチャTextureは、imageIdを紐付けておき、自身のtextureIdをマテリアルに紐付けます。

Imageの方はuriが必須で、.gltfファイルの相対パスです。NormalやRoughness、Metallicマップも同じ手順です。
idは必須で、基本は0からのユニークな番号を指定し、文字列型で登録します。
両方ともdocumentへのAppendが必要です。

// Construct a image
Image image;
image.id = “0”;
image.uri = “BoomBox_baseColor.png”;
auto imageId = document.images.Append(image, AppendIdPolicy::GenerateOnEmpty).id;

// Construct a texture
Texture texture;
texture.imageId = imageId;
texture.id = “0”;
auto textureId = document.textures.Append(texture, AppendIdPolicy::GenerateOnEmpty).id;

// Attach texture id
material.metallicRoughness.baseColorTexture.textureId = textureId;

おわりに

冷静に振り返れば「作っているのは単純なJSON形式のファイルだ」ということですから、APIのパターンも見えてきますね。
あとはglTFの仕様書も併せてにらめっこすると見えてくる。なんか謎解きのオリエンテーリングみたいだった。

ちなみにGithubからソースをCloneして、Visual Studio 2019でビルドしてもエラーになります。こういうところから見るにやる気をあんまり感じなくもないですね。TinyGltfに乗り換えようかな。

ビルドエラーの対処方法

●rapidjson.temporaryパッケージが無いエラー
 エラー内容に書かれたフルパスと同じになるようにパッケージを自分で補完する。
 1. gltfSDKフォルダ以下に[Packages]フォルダを作成。
 2. フォルダにあるrapidjson.temporaryフォルダを1.で作成したフォルダにコピー。
 3. たぶんglTFSDK.cppとかもエラーが出ていると思うので、同じように必要なパッケージのフォルダをコピーします。

●std::experimental::filesystemでエラー
【対応】コンパイルエラーのファイルに #include  を追加。または[プロジェクトの設定]で明示的に[C++17準拠]にする

●std::all_ofでエラー
【対応】コンパイルエラーのファイルに #include  を追加。

ソースコード

プロジェクト一式をGithubに公開しています。
  https://github.com/christinayan01/demo/tree/master/glTF-SDK_serialize_texture

参考サイト

microsoft / glTF-SDK – Github
syoyo / tinygltf – Github
KhronosGroup/glTF – Github
glTF SDKを使おうかと思ってるが… – 新 masafumi’s Diary
MicrosoftのglTF-SDKにシリアライズ/デシリアライズのサンプルが来た – 新 masafumi’s Diary

お気に召していただけましたか?

購入によるお支払(ご支援)も承っています。お値段は200円からお好きな価格を。

ご注意:返金のご対応はいたしかねます。

JPY

管理人が読んだおすすめの建築本

    

コメントを残す

BackToTop