3D

2014年09月25日

Metalでボーンアニメーション!!

はてなブックマークに登録

3Dアプリの開発にはまずモデルの読み込みが必要となります。
Metalにはレンダリング機能はあるものの、ローダは備わっていません。
そこで今回はボーンアニメーションに対応したモデルローダ作成のノウハウ(特にMetalの特性にどう対応したか)を紹介したいと思います。

ソースコードはこちらです: http://log.blog.klab.org/support/20140925/Blacksmith-bone_animation_sample.zip
メインとなるファイルはBSModelとBSRendererです。
BSModelはモデルデータの管理やボーン計算を担当し、BSRendererはBSModelから頂点データやテクスチャ、ボーンマトリックス等を受け取ってレンダリングを行います。

04


まずはじめにモデルファイルのパースが必要となります。
3Dモデルのファイルフォーマットは世の中にいくつも存在するので、それら全ての仕様を把握して対応するのは大変です。
ですが、代表的な複数のフォーマットに対応したAssimpというオープンソースのライブラリがあります。
http://assimp.sourceforge.net/
Assimpは様々なフォーマットのファイルを読み込んで統一の形式に変換してくれるので、BSModelの中でパーサとしてこのライブラリを使わせていただきました。
Assimpの開発に関わっている方々に感謝いたします。

さて、ボーンアニメーションを再生するための頂点の位置計算にはいくつか方法があります。
 1)ボーンマトリックスをパラメータとして渡して頂点シェーダで計算する
 2)レンダリングパスを分けながら頂点シェーダで計算する
 3)頂点テクスチャにボーンマトリックスを格納して頂点シェーダで計算する
 4)CPUで計算する
まずはOpenGL(ES)を使う場合を考えてみましょう。
GPUを利用してハードウェアアクセラレーションを効かせられるのが1〜3の方法です。
もし1の方法で問題なく実行できる環境にあればそれがベストです。
しかし、OpenGL(ES)にはシェーダに渡せるパラメータの大きさに制限があり、ボーンマトリックスの数がそれを超えてしまうと正しく計算できません。
そこでそのような場合には2の方法を取りますが、レンダリングパスが増えてしまうのでパフォーマンスは落ちます。
3の方法はボーンマトリックスの情報をテクスチャに格納してシェーダに渡すというテクニカルな方法です。
テクスチャは先程のパラメータの大きさ制限を受けることはないので、大量のデータを格納してシェーダに渡すことが出来ます。
ただし、頂点シェーダでテクスチャをフェッチできるようになったのはOpenGLESでは3.0からなので、古い環境をサポートしなければならない場合は3の方法を取ることは出来ません。
4は環境に依存することのない方法で、ボーンが何本あろうとも有効な方法ですが、ハードウェアアクセラレーションが効かないので、頂点数に比例して処理時間がかかります。
VBOを使わない方法なので毎フレームメインメモリからの転送が必要で、その分のコストもかかってしまいます。

一方、Metalではシェーダに渡せるデータ容量の制限が大幅に緩和されています。
ですのでボーンアニメーションを実装する場合には特に気にすることなく1の方法を採用出来、実装が非常に簡単になります。
これはOpenGL(ES)と比べ、大きな強みと言えるでしょう。
シェーダはGPUProgram.metalに書かれており、vertexShaderWithBoneAnimation(…)がボーンマトリックスを元に頂点の位置計算を行う頂点シェーダです。

頂点の位置を計算する仕組みは整ったので、後はボーンマトリックスを準備しなくてはいけません。
ボーンマトリックスの計算自体はOpenGL使用時と特に変わりありませんが、Metal使用時のポイントが一つあります。
それはボーンマトリックスのバッファ(CPU/GPU shared memory buffer)を複数用意するということです。
CPUとGPUは非同期で動いています。
CPUでレンダリングコマンドを作成してコマンドキューにプッシュした後は、GPUが処理を終えて終了通知のコールバックが返ってくるまではGPUがボーンマトリックスのバッファにアクセスしています。
その間にCPUからボーンマトリックスのバッファに対して書き込みを行うと不整合が起きます。
しかしながらレンダリングコマンドを投げた後にGPUが非同期で働いている間、CPUに何もさせずに待っているのももったいない話です。
ですのでボーンマトリックスのバッファを複数用意しておき、GPUがある一つのバッファにアクセスしている間、その他のバッファに対してCPUで計算した結果を書き込むようにします。
こうすることでCPUとGPUが非同期で動いていることを活かしたパフォーマンスの引き出し方が可能になります。

以上で紹介したように、MetalではOpenGLよりも簡単にボーンアニメーションに対応することが可能で、よりパフォーマンスを上げる仕組みを採用することが出来ます。
OpenGL(ES)に対応した従来のミドルウェアは上記1〜4の方法をハードウェアスペックとボーンの数により切り替えることによってボーンアニメーションを実現していると私は予想しています。
全体的なパフォーマンスアップに埋もれて気づき難いかもしれませんが、ミドルウェアがMetalへの対応を行えばボーンアニメーションのパフォーマンスが向上することが予想されます。
ミドルウェアを使用する側もボーンの数を気にせずにモデリングできる様になるかと思います。
これからはiOSのゲームにヤマタノオロチの様な沢山のボーンを持ったキャラクターがガンガン登場するようになるかもしれません。
2014年09月19日

Metalのコードを読む前に知っておくと楽なこと

はてなブックマークに登録

iOSのDeveloperサイトでは基本的なものから応用的なものまでいくつかのMetalのサンプルがダウンロードできるようになっています。
早速Metalを学習しようと一番基本的なサンプルを紐解いてみるも、以下のように沢山のクラスが登場します。
 ・MTLDevice
 ・MTLBuffer
 ・MTLTexture
 ・MTLCommandQueue
 ・MTLCommandBuffer
 ・MTLRenderCommandEncoder
 ・MTLLibrary
 ・MTLFunction
 ・MTLRenderPipelineState
主なものを列挙してみましたが、数が多いだけに前提知識無しでいきなりサンプルを読み進めるのは困難です。
そこで今回はこれらのクラスがレンダリング全体の流れの中でどのような役割を担っているか図で整理しながら紹介したいと思います。
ここで紹介する流れは複雑なサンプルであっても同じであるし、コードを書く際にも役立つものと考えております。

まず、具体的にAPIの説明に入る前に、ハードウェアとレンダリングの流れを説明します。
46

モデルデータやテクスチャなどのオブジェクトは「shared CPU/GPU memory buffer」上に格納されます。
CPUはアプリケーションの実行において様々な仕事を担当しますが、GPUへの命令を作成して渡してあげるのも役割の1つです。
GPUがレンダリング処理をするために必要なデータやプログラムをコマンドと言う形にまとめて、コマンドキューにプッシュします。
コマンドキューからポップされたコマンドをGPUが受け取り、レンダリング処理を行って画面に出力します。
以上が簡単なレンダリング処理のイメージです。

それでは各APIの役割について説明していきます。

MTLDevice
ハードウェア全体を司る、言わばボス的な存在です。
モデルデータを保持するバッファや、テクスチャ領域をメモリ上に確保したいときはこのMTLDeviceに依頼する形になります。
また、コマンドキュー(後述のMTLCommandQueue)の管理も担当しています。

MTLBuffer
モデルデータ(頂点データ)やその他レンダリングに必要なパラメータを記憶しておく領域。
CPUからもGPUからもアクセス可能。

MTLTexture
レンダリングに必要なテクスチャを管理するためのクラスです。

MTLCommandQueue
レンダリング(またはコンピューティング)コマンドをGPUに流すためのキュー。
GPUのタスクが空き次第プッシュされた順にコマンドが実行されます。

MTLCommandBuffer
上記MTLCommandQueueにプッシュするコマンド本体。
コマンドは主にGPUで実行されるシェーダプログラムやテクスチャ・頂点データのポインタ、その他のパラメータのポインタなどの情報で構成されます。
レンダリング用のMTLCommandBufferのオブジェクトを作成するには次のMTLRenderCommandEncoderを使用します。
また、このコマンドはGPUで処理が完了した際に、CPU側にコールバックが返るようになっています。
コマンドをプッシュしてからコールバックが返ってくるまではMTLBufferの内容をCPU側で書き換えないようにするのが競合を防ぐポイントです。

MTLRenderCommandEncoder
レンダリングコマンドを作成するための役割を持つクラスです。
MTLBufferやMTLTextureのオブジェクトのポインタや、以下で紹介するMTLRenderPipelineStateのオブジェクトを元にコマンドを生成します。

MTLLibrary ・ MTLFunction ・ MTLRenderPipelineState
これら3つはシェーダやカーネル関数のようなGPUで実行されるプログラムに関するものです。
OpenGLでは実行時にシェーダをコンパイルしていましたが、Metalではアプリのビルド時に一緒にコンパイルされ、ライブラリというファイルにひとまとめでバンドルされます。
そのライブラリにアプリからアクセスするためのクラスがMTLLibraryです。
ライブラリからバーテックスシェーダ・フラグメントシェーダ・カーネル関数を一つ一つ取り出したものがMTLFunctionのオブジェクトです。
これらの関数をレンダリングコマンドとして使うためにMTLRenderPipelineStateに持たせてMTLRenderCommandEncoderに渡します。


以上、主なクラスについて整理してみました。
Metalのサンプルを読み解く際やプログラムを書くときに、紹介した図を思い浮かべて各クラスの役割を思い出してみるとスムーズに進むかと思います。
2014年09月18日

Metalの「shared CPU/GPU memory buffer」について

はてなブックマークに登録

iOS8のリリースにより、A7を搭載したiOS端末からはOpenGLESに代わる新グラフィックスAPIであるMetalが動くようになりました。
iOS8発表時のAppleのKeynoteで紹介されたとおり、MetalはOpenGLとくらべてAPIの層が薄くて最適化されているので高速に動作するようで、他の多くの記事でもこの事が書かれています。

しかし実際にMetalに触れてみると、単にAppleのハードウェアに最適化されていてオーバーヘッドが低く速いということに留まらず、ある一つの特長に気付きます。
それは「shared CPU/GPU memory buffer」つまりCPU/GPU間でメモリが共有されているというものです。
ここでは今までiOSの3Dアプリケーション開発に利用されていたOpenGLESでのメモリの扱い方と比較しつつ、CPU/GPU間でメモリが共有されることのメリットについて触れたいと思います。

まずiOS端末ではなく、PCにおけるOpenGLについて振り返ってみます。
OpenGLにおいてはGPUメモリにCPUから直接アクセスすることが出来ないようになっています。(GPUからメインメモリへも同様)
その理由として以下の2つが考えられます。
 1)物理的に離れており、アクセスするのに大きなオーバーヘッドがある。
 2)CPUとGPUは非同期で動いており、お互いのメモリ領域に自由にアクセスできてしまうと不整合が起きる。
1つ目はPCを自作されたことのある方ならよくご存知かと思いますが、メインメモリはマザーボード上のスロットに差し込み、GPUメモリはGPUと同じくグラフィックカード上に搭載されていて物理的には離れた位置に配置されます。
CPUで読み込んだモデルデータを実際にレンダリングするにはメインメモリからGPUメモリにデータを転送する必要がありましたが、上記の2つの理由により、一度GPUメモリに転送したモデルデータはCPUから書き換えが出来ないようになっています。
レンダリングの度に転送するのでは負荷が高いので、転送したデータを使いまわすVBO(VertexBufferObject)という仕組みが用意されています。
VBOはCPUから加工できないので、頂点の位置を変えたい・色を変えたい等の操作はShaderを作成してGPUに任せるしかありませんでした。
より柔軟にモデルをレンダリングするため、VBOを使わずにレンダリングの度にCPUで都度加工したデータをGPUメモリに転送するという手段もありますが、転送には大きなコストが掛かります。
ハイポリの大きなデータであればVBOを使わずに処理するのは現実的ではないでしょう。

iOS端末におけるOpenGLESについても見てみたいと思います。
iOS端末のGPUメモリはハードウェアのスペースが限られているという事情も影響してか、PCのように物理的に切り離されておらず、メインメモリ上の一部を共有する形で実装されています。
せっかく共有されているのでCPUから直接アクセス出来ても良いように思えますが、上記の2)の事情はiOS端末においても同様であり、またOpenGLESはiOS端末以外のモバイルデバイスのサポートも考慮しなくてはならず、やはりOpenGLの仕様が踏襲された形で制限がかかったままとなっています。

一方、MetalはOpenGLESのようにApple以外のモバイルデバイスの事情を考慮する必要がなく、GPUメモリにCPUから直接アクセスできる仕様になっています。
上記2)についても心配はありません。
CPUがレンダリング命令をパイプラインに流すまではGPUが処理を開始することはなく、またGPUが非同期で処理を終えた際にコールバックがCPU側に返るようになっています。
よって、GPUがメモリにアクセスするタイミングは明白(命令発行〜コールバックの間)であり、その間はCPUからのメモリアクセスを控えれば2)の心配は無いのです。

このようにMetalはプログラマの責任において、CPUからもGPUからも同じメモリ領域にアクセスできます。
そのためVBOという概念もありません。
CPU側で共有メモリのポインタを取得することが出来てデータの書き換えが可能であるし、GPUへ命令を流す際に頂点バッファのアドレスとして同じポインタを指定します。

以上で述べたように、MetalはOpenGLES利用時のCPUとGPUの壁を壊したと言っても過言ではなく、大きなアドバンテージと考える事ができます。
CPU/GPU間でメモリが共有されれば3Dレンダリングの可能性が広がります。
Metalが登場するまでは難しかった以下の様なことが考えられるのではないでしょうか?
 A)GPUが忙しく働いていてCPUリソースに空きがあるような場合には仕事を分散することが出来る
 B)モデルの動的な編集・パーツの追加&削除
A)はXcodeでCPU/GPUそれぞれのパフォーマンスをチェックしつつ、GPUのタスクを減らしてCPUに任せるチューニング方法です。
シェーダを書き換えてメインプログラムを追記する必要があるのでなかなか大変ではありますが、チューニングの方法の一つとしては有効だと思います。
B)の方はアイディア次第で色々なことが実現できます。
成長してゆく植物や姿・形を変えるモンスターも表現できるでしょうし、破壊シミュレーションや流体の表現にも役立つかもしれません。
リアルタイムなプロシージャル技術で成形したモデルも本格的に使えそうです。

ゲーム開発のためのミドルウェアもMetalへの対応を進めているようですが、そういった対応を待つだけでなく直接触れてみると低レイヤー部分の事情を知ることができるのでなかなか楽しめます(^^)
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント