Animationの描画(補間なし)
compute shaderによるanimationの計算
Compute Shader
ray tracingパイプラインのみでレンダリングしているとvertex shaderでのアニメーションの計算を行わないので、compute shaderでの計算方法を模索していた。
とりあえず、ソースデータからデスティネーションデータにデータをコピーするところまでは成功。
GPU計算結果のデバッグ
shaderのデバッグで困るのが、計算結果のデバッグがIDEのデバッグ機能などで見れないこと。
GPU側のメモリまでは見に行けないので、デバッグ専用にGPU計算結果を一度CPU側のメモリに格納し直してから見直す必要がある。(と思う)
(他に簡単な方法があればご教授ください)
VkDeviceMemory(Local(CPU)) | => | VkDeviceMemory(Device(GPU)) | => | Data(CPU) |
CPU側で準備 | コピー | GPUで計算 | コピー | data |
local deviceとして使用しているdevice memoryから、compute shaderで計算後にデータを格納し直す。
方法としては、CPU=>GPUでメモリをコピーしている向きと逆向きでデータをコピーすればOK
vkMapMemory(device, bufferMemory, 0, dataSize, 0, &tmpBuffer); memcpy(data, tmpBuffer, dataSize); vkUnmapMemory(device, bufferMemory);
memcpy()関数自体はCで用意されている関数で
#include
void *memcpy(void *buf1, const void *buf2, size_t n);第一引数にコピー先のメモリブロックのポインタ
第二引数にコピー元のメモリブロックのポインタ
第三引数はコピーサイズ
https://bituse.info/c_func/56
なので、vkMapMemoryでVkDeviceMemory(Device(GPU))を指定した後、memcpy()のdataに、格納したい配列のアドレスを渡す。
colladaファイルからanimationを動かす方法
colladaファイルの特徴まとめ
- 行列はrow majorで格納されている => VulkanやOpenGLで用いる場合にはtransposeなどを行い、column majorへの変換が必要
- (実装方法によるが)
> INV_BIND_MATRIXは使わなくてもよい
collada ファイルのanimation
プログラムを実装してみた後の、個人的な理解をまとめる。
間違いや改善点などがあれば、適宜修正していきたい。
まず、言葉の定義として以下を用いる。
同じ意味でも別の用語が使われる場合が多々あるので、ここでは以下の用語で統一する。
https://community.khronos.org/t/need-help-with-skeletal-animation/68668/3
の、偉大な先人の記述を参考にさせていただいている。
OBJECT SPACE(BIND POSE) : poseのこと。最終的な描画に用いられる座標
BONE SPACE(LOCAL SPACE や JOINT SPACEとも呼ばれる) : 各jointのoriginalの位置を指す座標
JOINT MATRIX : 各jointについて定義されている、BONE SPACE -> OBJECT SPACEに変換する行列
ANIMATION MATRIX : 各jointについて定義されている、BONE SPACE -> OBJECT SPACEにanimationの位置に変換する行列
まず、library_geometriesを読み込んだだけのmeshは、OBJECT SPACEにある。
初期状態のmeshのJOINT MATRIXは colladaファイルにおいてはlibrary_visual_scene > node > matrix に格納されている。①
最終的なanimation poseはOBJECT SPACEにあり、その変換に必要なANIMATION MATRIXはcolladaファイルにおいては
ANIMATION MATRIXは BONE SPACE -> OBJECT MATRIXに変換する行列なので、まとめると
OBJECT SPACE | -> | BONE SPACE | -> | OBJECT SPACE |
mesh 情報 | ①の逆行列 | original bone | animation行列 | animation pose |
という経路での変換が必要。
なのでまずはmesh情報をBONE SPACEに戻す必要があるので①の行列の逆行列を使ってBONE SPACEに変換していく。
OBJECT SPACE -> BONE SPACE
ここで考慮が必要なのは、jointは階層構造になっており、行列は親の影響を受ける。
以下はサンプルとして、CPUでポジションを計算するプログラムで実装する場合には、再帰関数で実装すると都合がよさそう。
void objectToBone(Joint joint, glm::mat4 parentJointTransform){ glm::mat4 jointTransform = parentJointTransform * joint.jointMatrix; glm::mat4 invJointTransform = glm::inverse(jointTransform); for(auto p : joint.jointPositions){ bonePosition = invJointTransform * p; } for(auto &child : joint.children){ objectToBone(child, jointTransform); } }
といったイメージとなる。
ここで伝えたいことは、
BONE SPACE -> JOINT SPACE
先の関数を修正する。
void objectToBone(Joint joint, glm::mat4 parentJointTransform, glm::mat4 parentAnimationTransform){ parentJointTransform = parentJointTransform * joint.jointMatrix; glm::mat4 invJointTransform = glm::inverse(parentJointTransform); parentAnimationTransform = parentAnimationTransform * joint.animationMatrix; glm::mat4 finalTransform = parentAnimationTransform * invJointTransform; for(auto p : joint.jointPositions){ bonePosition = invJointTransform * p; newPosition += p.weight * finalTransform * bonePosition; } for(auto &child : joint.children){ objectToBone(child, jointTransform); } }
ここで伝えたいことは
- finalTransformの順番(Vulkan, OpenGLの場合)
- ここでは詳しく書いてはいないが、各点には影響を受けるjointの重さがあるので、それを加味してnewPositionの計算を行っている
ここでは座標変換を見るためにCPUで計算する関数を簡単に書いているが、実際にはshaderを用いて計算すると思うので、同様に展開していただけばと思う。
この変換を行えば、オブジェクトはanimationで定義されている位置に変換される。
アニメーションポーズのレンダリングの成功
アニメーションの1ポーズのレンダリングに、ようやく成功した。
colladaファイルからモデルを読み込み、vulkanでレンダリングしている。
何カ月もかかり、本当に長かった。
アニメーション用の座標変換などについても調べたので、これから記事にしたい。
それにしても、ようやく1ポーズが描けた。
今までは、関節が変に延びたり、首だけやたら長かったりと、本当に苦労した。
GitHub - TheThinMatrix/OpenGL-Animation: A simple example of skeletal animation using OpenGL (and LWJGL).
には最大限の感謝を!!
Phoenix 静止状態のレンダリング
Androidに表示した画面
colladaファイルからモデル情報を読み取り、その結果をそのまま表示した図
モデルの権利表記
This model was created by NORBERTO-3D https://sketchfab.com/3d-models/phoenix-bird-844ba0cf144a413ea92c779f18912042 LICENSE Creative Commons Attribution https://creativecommons.org/licenses/by/4.0/legalcode
colladaファイルについて
colladaファイルを選んだ理由は
- バイナリー形式でないので、自分のライブラリで読み込める
- animationをサポートしている
という理由からcolladaファイルを読み込んでキャラを動かせるようになることを目標に取り組んできた。
glTF 2.0でも同じことができるはずなので、ひと段落すれば取り組んでみてもよいかも。
ただ、xmlで書かれているcollada形式の3Dモデルを読み込んでから、Vulkan APIを用いてのレンダリングはとてもやることが多く、時間がかかりまくっている状況。
colladaファイルを読み取る過程で調べた、各属性の依存関係のようなものはいつかまとめて画像一枚で説明するようなものをブログにアップしたいと思っているが、animationの実装が終われば取り組んでみたい。
animation付きのcolladaファイルについて
3Dモデルはsketchfabというサイトに、絵師様たちがアップロードしてくださっている。
Sketchfab - The best 3D viewer on the web
3Dモデルはfbxの拡張子でアップされることが多いと思うが、fbxはバイナリ形式である。(グラフィクスライブラリ内部では、Autodesk社のSDKを用いてパースすると思われる。それかASCIIファイルに変換するか。)
個人的には、今作成している個人ライブラリにはオープンなフォーマットを使いたかったので、animationまで内包しているcollada形式で読み込みたいのだが、animationを保ったままfbxからcolladaに変換する方法を探すのにとても時間がかかった。(見つけた方法も、たぶんすべての3Dモデルをカバーできているわけではないと思ってる。)
Blenderを用いてfbxファイルをimportし、collada形式でexportすればmesh情報などは正確なのだが、出力されたcolladaファイルをBlenderでimportしてanimationを実行してみると、何故かバグった状態で実行される。
Blenderで読み込めないものを個人製作したエンジンで読み込めるわけがないので、Blender上で正確にanimationを実行できるようなcolladaファイルの作成の仕方を探していた。
結論的には
sketchfabでfbxではなくglTF形式でダウンロードし、BlenderでglTFをcolladaに変換する
方法で実現できた。
ここからanimation用の行列を読み取り、座標変換を繰り返す部分の勉強と実装が待っており、時間がかかると思うが、とりあえず(予想以上に時間がかかったが)正しい情報を持ったモデルが用意できたことには一安心。
Blenderでfbx => collada変換するとanimationがうまく変換できない
対処法
プログラムでモデルをレンダリングしたい方向けの記事。
モデルはsketchfabというサイトでモデルをダウンロードしていることを想定。
sketchfab.com
対処法としては
fbxフォーマットでなくglTF 2.0のフォーマットでダウンロードする。
その後glTF -> colladaで変換すると、アニメーションまで正確に変換できる。
fbxからのcollada変換(アニメーション付き)が、どう頑張ってもどう調べても上手くいかなかったので、結局元ファイルを変えるという選択肢しかなかった。
GL_EXT_nonuniform_qualifierについて
概要
vulkan + ray tracingのチュートリアルのコードを読んだときに気になっていた、GL_EXT_nonuniform_qualifierについて仕様の調査を行う。
仕様書
以下のGitHubが仕様書のようだ。
GLSL/GL_EXT_nonuniform_qualifier.txt at master · KhronosGroup/GLSL · GitHub
この仕様書内では以下のOpenGL Shading Language Specificationの仕様書を参照する。
https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf
Overview
This extension adds a "nonuniform" type qualifier and constructor, which
is required by the Vulkan API to be used when indexing descriptor
bindings with an index that is not dynamically uniform.This extension also allows arrays of resources declared using unsized
arrays to become run-time sized arrays.
とある。
nonuniformという修飾子を追加して、bindされるresourcesをdynamicに扱えるようにする。
layout(binding = 3, set = 0, scalar) buffer Vertices {Vertex3D v[];} vertices[];
例えばこのように、vulkan側ではレイアウトだけ定義しているresourcesをshader側でサイズの指定なしに宣言することができて
Vertex3D v0 = vertices[nonuniformEXT(objId)].v[ind.x];
shader内ではこのようにしてresourcesにアクセスできる。
verticesを複数の要素からなる配列として扱える。
Vukan API側のextension
Vulkan APIを使用するプログラムの側でも拡張機能を使用する対応が必要。
VK_EXT_descriptor_indexing(3)
device_extensions.push_back("VK_EXT_descriptor_indexing");
のようにVK_EXT_descriptor_indexingの拡張機能を有効にする。
Description
This extension adds several small features which together enable applications to create large descriptor sets containing substantially all of their resources, and selecting amongst those resources with dynamic (non-uniform) indexes in the shader. There are feature enables and SPIR-V capabilities for non-uniform descriptor indexing in the shader, and non-uniform indexing in the shader requires use of a new NonUniformEXT decoration defined in the SPV_EXT_descriptor_indexing SPIR-V extension.
GLSLでの内容と同じことが記述されているが、shader内にてdescriptor setで渡すresourcesにdynamicにアクセスし、使用できる。
Section 4.1.9 Arrays
Section 4.1自体はGLSLでの基本となる型を定義している章となる。
仕様書のこの章としては、サイズが定義されていない場合の扱いについて、書き足すものとなる。
以下で今の仕様と異なっている箇所をピックアップする。
An array whose size is not specified in a declaration is _unsized_.
Unsized arrays can either be implicitly sized or run-time sized.
A _run-time sized_ array has its size determined by a buffer or
descriptor set bound via the API.
宣言時に全体のサイズを定義しない箇所が追記されている。
この場合は配列の最大indexでもってサイズが定義されるか、(vulkan)APIによってランタイムで定められる。
Unsized arrays must not be passed as an argument to a function,
unsized arrayは関数に引数として渡せない。
Section 4.x Nonuniform qualifier
nonuniformEXT qualifierが追加となる。
nonuniformEXT修飾子は変数や式がdynamic uniformでないときに使用する。
The nonuniformEXT qualifier can also be used with constructor syntax to
assert that an expression is not dynamically uniform. For example:layout(location = 0) flat in int i;
layout(set = 0, binding = 0) uniform sampler2D tex[2];color = texture(tex[nonuniformEXT(i)], ...);
This constructor syntax takes a single argument of any type and returns
the value with the same type, qualified with nonuniformEXT.
サンプルコード内にもあるような使い方としては以上が挙げられ、解説されている。