Aqoole_Hateenaの技術日記

vulkan+raytraceで色々描いてます

morphing animation glTF

morphing

glTFのモーフィングアニメーションを読み込み、androidで描画することができた。

モデルの権利表記
https://sketchfab.com/3d-models/yard-grass-3a67e76decc849c694c228eb590a9902
ライセンス:CC Atribution
https://creativecommons.org/licenses/by/4.0/legalcode

苦労した点

なぜか、compute shader内のバッファーで、vec3で読み取ろうとすると正確な値がとってこれなかった。
バッファーがfloatであるとして読み取るとうまくいったが、なぜなのだろう。
この手のような、用意しているデータや計算方法は間違ってないけど、glslの構造上エラーとなるみたいな場合、調査が難航する。

課題

ようやくレイトレーシングの本題に入ってこれるのだが、今はrayが到達しきれない部分を白色にして誤魔化しているので、この部分まで綺麗にレンダリングできるように工夫する。

glTFでmorphing (alpha処理後)

glTFモデルのmorphing

前回の記事
aqoole-hateena.hatenablog.com
からalphaがglTFファイルにあるalpha cutoffよりも小さい場合に、描画しない処理を追加したものがこちら。

モデルの権利表記
https://sketchfab.com/3d-models/yard-grass-3a67e76decc849c694c228eb590a9902
ライセンス:CC Atribution
https://creativecommons.org/licenses/by/4.0/legalcode

課題

  • レイトレーシングでオブジェクトが重なった場合の処理が難しく、草の中心部が白くなっているのはオブジェクトが重なってレイが到達しない場合に、一律に白色にしているから。

 ここの部分をより自然な表現にしたい。

  • 邪魔な部分を省いて分かったが、アニメーションが不自然なので、blenderで再生したときと同じアニメーションにする

glTFでmorphing (マテリアル処理なし)

Morphing

morphingのアニメーションに挑戦してみた。
それっぽくはなっているが、、、合ってるのか?

モデル
https://sketchfab.com/3d-models/yard-grass-3a67e76decc849c694c228eb590a9902
ライセンス:CC Atribution
https://creativecommons.org/licenses/by/4.0/legalcode

alpha value

glTFのモデルは(それ以外もそうかもしれないが)meterialの属性の中にalpha valueの情報がありそうなので、マテリアルの処理も追加して動画のモデルから黒抜きをしたい。
(アニメーションの動きの処理を優先したので、マテリアルを読み込むところはまだ実装してなかった)

glTFでのアニメーション

glTFでアニメーション

colladaに引き続き、glTFでもアニメーションを行うことができた。

モデルの権利表記

配布元リポジトリ
https://github.com/TheThinMatrix/OpenGL-Animation

Unlicense license
https://unlicense.org/

glTFでanimationを行うときの注意点

nodeとjointとの関係のようなものは別の機会にまとめたいと思うが、注意点としては以下のようなものがある。

  • inverse bind matrixはjointの順番に並んでいる
  • animationのtargetはnodeの番号
  • inverse bind matrixは親nodeからの継承も含めて計算されている
  • rotationのQuaternionからの変換はwikiにある変換を用いればよい

Rotation formalisms in three dimensions - Wikipedia


colladaでanimationを行う際には
[Animation] colladaファイルの読み取りからAnimationを行うまで - Qiita
でまとめた通り

OBJECT SPACE -> BONE SPACE -> OBJECT SPACE
geometry (J_{A} * J_{B1} * J_{C1})^{-1} b A_{A} * A_{B1} * A_{C1} p

となるが、glTFでは

OBJECT SPACE -> BONE SPACE -> OBJECT SPACE
geometry J_{C1}^{-1} b A_{A} * A_{B1} * A_{C1} p

となる。

glTFでのアニメーション

glTFでアニメーション

colladaに引き続き、glTFでもアニメーションを行うことができた。

モデルの権利表記

配布元リポジトリ
https://github.com/TheThinMatrix/OpenGL-Animation

Unlicense license
https://unlicense.org/

glTFでanimationを行うときの注意点

nodeとjointとの関係のようなものは別の機会にまとめたいと思うが、注意点としては以下のようなものがある。

  • inverse bind matrixはjointの順番に並んでいる
  • animationのtargetはnodeの番号
  • inverse bind matrixは親nodeからの継承も含めて計算されている
  • rotationのQuaternionからの変換はwikiにある変換を用いればよい

Rotation formalisms in three dimensions - Wikipedia


colladaでanimationを行う際には
[Animation] colladaファイルの読み取りからAnimationを行うまで - Qiita
でまとめた通り

OBJECT SPACE -> BONE SPACE -> OBJECT SPACE
geometry (J_{A} * J_{B1} * J_{C1})^{-1} b A_{A} * A_{B1} * A_{C1} p

となるが、glTFでは

OBJECT SPACE -> BONE SPACE -> OBJECT SPACE
geometry J_{C1}^{-1} b A_{A} * A_{B1} * A_{C1} p

となる。

glTFでのモデルの描画

glTFでモデルの描画

joint情報を読み込み、静止モデルの描画をしてみた。

モデルの権利表記

配布元リポジトリ
https://github.com/TheThinMatrix/OpenGL-Animation

Unlicense license
https://unlicense.org/

glTFフォーマットの読み込み方

tiny glTFを用いてglTFのモデルを読み込んでいる。ライセンスはMITライセンス。
https://github.com/syoyo/tinygltf

使い方は以下のgit hubにあるコードを参考にした。ライセンスはApache 2.0。
https://github.com/techlabxe/vk_raytracing_book_1/blob/master/Common/include/util/VkrModel.h
https://github.com/techlabxe/vk_raytracing_book_1/tree/master/Common/src/util

glTFの構造については仕様書にまとめられている。
glTF™ 2.0 Specification

アニメーションの読み込みと描画に成功すれば、情報を改めてまとめ直したいと思っているが、とりあえずコードは以下のようなもの。

    using namespace tinygltf;
    std::vector<glm::uvec4> tmpJoint;
    std::vector<glm::vec4> tmpWeight;
    const uint8_t* jointSrc;
    const glm::vec4* weightSrc;
    for(auto& primitive : model.meshes[0].primitives){
        //each primitive
        Geometry geo = {};
        for(auto& attr : primitive.attributes) {
            std::string attName = attr.first;
            //positions
            if (std::regex_search(attName, std::regex("position", std::regex::icase))) {
                const auto &posAccr = model.accessors[attr.second];
                const auto &posBufView = model.bufferViews[posAccr.bufferView];
                size_t offsetByte = posAccr.byteOffset + posBufView.byteOffset;
                const auto *src = reinterpret_cast<const glm::vec3 *>(&(model.buffers[posBufView.buffer].data[offsetByte]));
                size_t vertexSize = posAccr.count;
                for (uint32_t i = 0; i < vertexSize; i++) {
                    geo.positions.emplace_back(src[i]);
                }
                //indices
                const auto &indexAccr = model.accessors[primitive.indices];
                const auto &indexBufView = model.bufferViews[indexAccr.bufferView];
                size_t indexOffsetByte = indexAccr.byteOffset + indexBufView.byteOffset;
                const auto *indexSrc = reinterpret_cast<const uint16_t *>(&(model.buffers[indexBufView.buffer].data[indexOffsetByte]));
                for (uint32_t i = 0; i < indexAccr.count; i++)
                    geo.indices.emplace_back((uint32_t) indexSrc[i]);
            }
            //texture coord
            if (std::regex_search(attName, std::regex("texcoord", std::regex::icase))) {
                const auto &tcAccr = model.accessors[attr.second];
                const auto &tcBufView = model.bufferViews[tcAccr.bufferView];
                size_t offsetByte = tcAccr.byteOffset + tcBufView.byteOffset;
                const auto *tcSrc = reinterpret_cast<const glm::vec2 *>(&model.buffers[tcBufView.buffer].data[offsetByte]);
                for (uint32_t i = 0; i < tcAccr.count; i++)
                    geo.texCoords.emplace_back(tcSrc[i]);
            }
            //joint information
            if(std::regex_search(attName, std::regex("joint", std::regex::icase))){
                const auto& jointAccr = model.accessors[attr.second];
                const auto& jointBufView = model.bufferViews[jointAccr.bufferView];
                size_t offsetByte = jointAccr.byteOffset + jointBufView.byteOffset;
                //in case of acc.componentType = TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE 
                jointSrc = reinterpret_cast<const uint8_t*>(&model.buffers[jointBufView.buffer].data[offsetByte]);
                for(uint32_t i = 0; i < jointAccr.count; i++){
                    uint32_t index = i * 4;
                    glm::uvec4 u(jointSrc[index], jointSrc[index + 1], jointSrc[index + 2], jointSrc[index + 3]);
                    tmpJoint.emplace_back(u);
                }
            }
            //weight information
            if(std::regex_search(attName, std::regex("weight", std::regex::icase))){
                const auto& weightAccr = model.accessors[attr.second];
                const auto& weightBufView = model.bufferViews[weightAccr.bufferView];
                size_t offsetByte = weightAccr.byteOffset + weightBufView.byteOffset;
                weightSrc = reinterpret_cast<const glm::vec4*>(&model.buffers[weightBufView.buffer].data[offsetByte]);
                for(uint32_t i = 0; i < weightAccr.count; i++)
                    tmpWeight.emplace_back(weightSrc[i]);
            }
        }
        mGeometries.emplace_back(geo);
    }

今後の作業

joint情報を読み込んで、compute shaderで頂点を計算するところまでできたので、次はanimationの情報を読み込んでモデルを動かせるようにしたい。
以前にcolladaで同じことをしたが、かなり難しく時間がかかったので覚悟はしているが、手元のスマホ(android)でモデルを動かせるようになりたい。

Animationの完成

Animationの実装

今までの実装では時間の取得の仕方が間違っていた。
時間の表示を正常にするとコマ飛びすることなくアニメーションが表示された。

時間の取得方法

実装当初に色々調べた際に、あまり深く調べることなく以下のstack overflowのanswerをそのまま流用していた。
How to get the current time in native Android code? - Stack Overflow

#include

// from android samples
/* return current time in milliseconds */
static double now_ms(void) {

struct timespec res;
clock_gettime(CLOCK_REALTIME, &res);
return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;

}

しかしコメント文にあるように、このコードではミリセカンドの単位で時間が取得できる。
アニメーションを行うときにはセカンドの単位で表示していたので、1000倍の速さについていけずコマ飛びしていた。

timespec構造体

仕様は以下に書かれてある。
cpprefjp.github.io

tv_sec エポックからの経過秒。値は0以上
tv_nsec ナノ秒単位で表される秒未満の値 値の範囲は[0, 999'999'999]

tv_nsecはナノセカンドの単位で整数値で取得されるので、 1.0 * 10^{-9}を掛ける必要がある。
つまり clock_gettime を実行したときの時刻tは

double t = res.tv_sec + res.tv_nsec / 1e9;

で取得できる。