頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


ARKitで眼球をオブジェクトとして描画してみる

Metalでテクスチャを持つdaeファイルを描画する

↑これに関連する話。

ARKitのARSCNFaceGeometryで、
{
    ARSCNFaceGeometry *arscnFaceGeometry = [ARSCNFaceGeometry faceGeometryWithDevice:device fillMesh:NO];
}
ARSCNFaceGeometryオブジェクトを作成する時にfillMeshをNOにすると両目と口の部分のメッシュを描画しない顔になる(今まではfillMeshをYESにして肌などと同じようにiPhoneのフロントカメラ画像を描画していた)。
そこで、fillMeshをNOにし目の部分を”くり抜いて”、顔メッシュの向こう側に眼球のオブジェクトを置くとそれらしく見えるのか?を試してみた。

ちなみに眼球の位置はARFaceAnchorクラスのrightEyeTransformなどで取得できる。

原点を示す図

↑リファレンスによると、取得できるsimd_float4x4型のマトリックスは眼球の中心基準とのこと。顔の中心(ARFaceAnchorのtransform)からの相対位置になる。

眼球のオブジェクト

↑ということで代わりになる眼球のオブジェクトをBlenderで作成。というかManuelBastioniLABから借りたもの。

↑テスト動画。

なんとなくいけてるように見えるかもしれないが、そういうところだけをアップしていると言った方がいいかも。
くり抜かれた目の部分が実際のとずれるので不自然になることがあり、使えないかなあという印象。唇の時と同じだ。
あと、rightEyeTransform/leftEyeTransform で「寄り目」が正しく取得できず、目が中央に寄らない。
スポンサーサイト



Metalでテクスチャを持つdaeファイルを描画する

dae形式のデータをMetalで描画する場合、

scn/daeファイルから3DオブジェクトをMetalで描画する

↑ここではdaeファイルからSCNNodeオブジェクトとした後にMTKMeshオブジェクトを作成した。

今回、テクスチャ付きのdaeファイルを描こうとしたらすんなりといかなかった。

Xcodeで見たdaeファイル

↑Xcodeでプロジェクトに追加したdaeファイルを見てみるとこのように見える。
半球の表面にメモを書いてる。作成はBlender。

テクスチャ画像

↑ちなみにテクスチャ画像。
-(void)setupEyeballTexture
{
    MTKTextureLoader *textureLoader = [[MTKTextureLoader alloc] initWithDevice:device];
    
    UIImage *image = [UIImage imageNamed:@"parts.scnassets/eyeballTexture.png"];
    
    NSError *error = nil;
    eyeballTexture = [textureLoader newTextureWithCGImage:image.CGImage options:nil error:&error];
}
↑テクスチャの読み込みはこんな感じ(eyeball、つまり眼球をイメージしてる)。
これをフラグメントシェーダに渡し、描画してみると、、、

表示結果(間違い)

↑んー、、、?微妙なずれ方だな、、、全く表示されないわけでもないからテクスチャ座標がでたらめというわけでもないだろうし、、、何が起きてるんだろう?

いろいろと調べてようやく判明したのは、テクスチャ画像の上下を逆にすればOK。

頂点シェーダでフラグメントシェーダに渡す内容を
{
    //out.texCoords = in.texCoord;
    out.texCoords = float2(in.texCoord.x , 1.0-in.texCoord.y);
}
↑例えばこのようにして上下反転すれば、、、

表示結果(正常)

↑意図通りに表示された。
シェーダで余計な処理はしたくない!と思えばテクスチャ画像をあらかじめ上下反転させておいてもOK。

そういえばそういうものだったな、と後で気付いたわ。


MTLTextureに値を出力してコンピュートシェーダで利用したい

Metalでオブジェクトを画面ではなくテクスチャ(id<MTLTexture>型)に描く、という手法がある。色だけでなく描画に必要なデータ(深度など)を記録することもできる。

Deferred Lighting | Apple Developer Documentation
↑こちらが参考になる。

このような手法でテクスチャに描いたものをコンピュートシェーダで画像処理しようとした。


↑実行結果の例はこちらなのだが、
1) 背景と顔部分のメッシュ(目を変形済み)をカラー画像用のテクスチャに出力
2) なめらかにしたい肌の頂点には1.0という値を与えてその結果をテクスチャに出力
3) 唇部分の頂点には1.0という値を与えてその結果をテクスチャに出力
4) コンピュートシェーダで全てのテクスチャから画像を作成する
という流れになっている。

XcodeのGPU Frame Capture and Metal API Validation Toolsで出力内容を確認(上の動画とは別なものだけど)すると、、、

MTLTextureの内容

↑このようになっている。
左の画像がフロントカメラ画像と顔メッシュをrgbで描いたもので、
真ん中が肌をなめらかにしたい部分、
右が唇部分を示す。
(口のところの緑は不明)
コンピュートシェーダでは右二つのテクスチャを利用して肌を滑らかにしたり唇を赤くする位置を把握し、それらの処理を行う。

出力結果

↑参考までにその結果はこのようになる。


この2)および3)のデータ用テクスチャは
MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Float width:ViewWidth height:ViewHeight mipmapped:NO];
↑このようにMTLPixelFormatR32Floatを使っている。
先ほどのデバッグツールには32ビットもいらんやろ!と言われるのだがそれは置いといて、、、

問題はこの画像をコンピュートシェーダで参照する時だ。
kernel void compositionKernelFunction(
                                      texture2d<half, access::read>  rgbTexture   [[ texture(kIndexRGB) ]],
                                      texture2d<half, access::read>  lipInfoTexture   [[ texture(kIndexLipInfo) ]],
…
{
    …
    half4 lipRate = lipInfoTexture.read(uint2(centerX,centerY));
    if (lipRate.r > 0.999) {
        …
}
↑このような感じでコンピュートシェーダでテクスチャ画像を参照しようとするとhalf4のrで値を得ることはできる(halfではなくfloatでも動いた)。

でもなんか気持ち悪い。
そもそもMTLTextureDescriptorで画像のフォーマットにMTLPixelFormatR32Floatと指定しているのになんでrgbaなんだよ?

それじゃあ、と、、、
depth2d<float, access::read>  lipInfoTexture   [[ texture(kCTIndexColoring) ]]
↑depth2dで受け取ると
float lipRate = lipInfoTexture.read(uint2(centerX,centerY));
↑このようにfloatで値を得ることができる。
でも、depth2dってそもそも深度記述用のじゃないの?
しかもリファレンスによるとdepth2dの場合はfloatでないとダメと書いてるので、16bitのhalfとかだと使えないしなあ。


どちらにせよなんだか気持ち悪い(え?画像が一番気持ち悪い?)。
結局どう記述するのがよいのか今の所はっきりせず。


iOSでUIViewをpdfファイルにする

How to Convert UIView to PDF within iOS? - Stack Overflow
↑こちらを参考に下のようなクラスメソッドを作成した。
+(BOOL)saveAsPDFForView:(UIView *)targetView filePath:(NSString *)filePath
{
    NSMutableData *pdfData = [NSMutableData data];
    
    UIGraphicsBeginPDFContextToData(pdfData, targetView.bounds, nil);
    UIGraphicsBeginPDFPage();
    CGContextRef pdfContext = UIGraphicsGetCurrentContext();
    
    [targetView.layer renderInContext:pdfContext];
    
    UIGraphicsEndPDFContext();

    BOOL isOk = [pdfData writeToFile:filePath atomically:YES];
    
    return isOk;
 }
ただリンク先にあるようにこの手法だとベクター形式ではなくラスター形式になるとのこと。
出力したpdfファイルを拡大すると確かに最後は粗くなる。
なんとなくpdfというとベクターなのかと漠然と思ってた。


GPUで何やってるのかを確認するXcodeの機能

ただでさえややこしいMetalで凝ったことをしようとすると、うまくいかない時にどこが悪いのかを探すのに苦労する。ところが、、、

Metalのデバッグまとめ(随時更新) - Qiita

↑このページを発見。そんなのあったんだー、と思いさっそく試してみた。

Xcodeのカメラボタン

実行中にXcodeに表示される↑このカメラボタン(※1)を押してみる。
いろいろわかるのだが、特に感動したのが処理途中のMTLTextureも画像として確認できるのだ!

MTLTextureDescriptorのusageを複数指定する

↑例えばこの記事では、

処理の流れの図
↑このような処理をした(同記事中の画像)。
当然途中のMTLTextureを知ることはできないのだけど、Xcodeのこの機能を使うと、、、

MTLTextureの内容

↑最初のLenaのテクスチャとする立方体をMTLTextureに書き込んだ内容を見ることができる(※2)。



別なプロジェクトだけど、メッシュを表示するところでも

メッシュ情報

↑こんな感じで三角形や頂点の一つ一つの情報を確認することができる。


なんかすごくね!?
これを使いこなせるといろいろと捗りそうな気がする!




※1 MetalKitやSceneKitを使う場合には表示されてたけど、そうでない場合は表示されなかった。

MTKViewに表示する内容

※2 ↑MTKViewへの出力画像。最終的にはMTLTextureの内容をコンピュートシェーダでぼかし、それをテクスチャとする立方体と背景画像とあわせて画面にレンダリングしてる。





Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.