頭と尻尾はくれてやる!

iOSアプリなどの開発日記です


MetalでMTLTextureを画面全体に表示する

Metalでid型のオブジェクトを画面いっぱいに表示したい、という時。
以前は画面からはみ出すような平面を用意し、フラグメントシェーダ内で画面上の座標を計算しそこからテクスチャ座標を得て描画していたのだが、、、
なるほど、こんなやり方があるんだな、と感心したのがAppleのサンプルコード(※1)にあった。
static const float kImagePlaneVertexData[16] = {
    -1.0, -1.0,  0.0, 1.0,
    1.0, -1.0,  1.0, 1.0,
    -1.0,  1.0,  0.0, 0.0,
    1.0,  1.0,  1.0, 0.0,
};
↑平面を構成する4つの頂点。x,y, u,vのデータが4つある。
これをMTLPrimitiveTypeTriangleStripで描く。
{
    [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
}
MTLPrimitiveTypeTriangleではないのでどの頂点を使うか、の情報も不要。なるほど!
vertex ImageColorInOut vertexFunction(ImageVertex in [[stage_in]])
{
    ImageColorInOut out;
    
    out.position = float4(in.position, 0.0, 1.0);
    
    out.texCoord = in.texCoord;
    
    return out;
}


fragment float4 fragmentFunction(ImageColorInOut in [[stage_in]],
                                            texture2d texture [[ texture(kTextureIndexColor) ]])

{
    constexpr sampler colorSampler(mip_filter::linear,
                                   mag_filter::linear,
                                   min_filter::linear);

    float4 color = float4(texture.sample(colorSampler, in.texCoord).rgb , 1.0);
    
    return  color;
}
↑シェーダではこの程度の記述で画面いっぱいにテクスチャが表示されることになる。すごい!

実行結果のスクショ

↑正方形のテクスチャを表示させた結果。

人様のコードを読むのって勉強になるよね。



※1 Appleのサンプルコード↓
Creating Face-Based AR Experiences | Apple Developer Documentation


MTLTextureDescriptorのusageを複数指定する

Metalで画面ではなくMTLTextureに描く
↑この続き。

処理の流れの図
↑Metalでこういう処理をやろうとした。

Render functionsというのは頂点シェーダとフラグメントシェーダで、
Compute functionsというのはいわゆるコンピュートシェーダを意味してる。

Render functions1はオブジェクトを描くが出力先は画面ではなくid <MTLTexture>型のバッファ。ここではtexture1としてる。

Compute functionsはこのtexture1を読み込んで画像処理した結果を別のtexture2に出力する。

Render functions2はこのtexture2を読み込んでオブジェクトのテクスチャとして使い、画面にレンダリングする。


ここで、texture1,2のusage (MTLTextureDescriptorのプロパティ)はどうするの?というお話。

結論から言うと、
{
    textureDescriptor1.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
}
↑最初のtexture1はレンダリングの出力先であり、かつコンピュートシェーダで読み込まれるのでその両方を指定する。
{
    textureDescriptor2.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
}
↑もう一つのtexture2はコンピュートシェーダの出力先なので’ShaderWrite’、Render functions2で読み込まれるので’ShaderRead’を指定する。


正しく指定しないとXcodeから実機デバイスにビルド・実行すると実行時に
Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)
↑例えばこのようなログを吐いて落ちる。

ところが、その直後にデバイスのアプリを起動すると意図通りに動く。
Xcodeは(特にアップデート後など)意味不明の警告を出したりすることがあるので、あまり気にしないでいいかと思ってしまいそうだが、今回はXcodeは間違っていなかった。

実行結果のスクショ

↑こちらが今回の処理の結果。
Render functions1で立方体を描画(テクスチャはLena)。
それをCompute functionsでぼかし処理。
Render functions2でぼかした画像をテクスチャとして再度立方体を画面に描画してる。
Lenaの画像が前記事のよりぼけている。


Metalで画面ではなくMTLTextureに描く

MetalでMTKViewオブジェクトなどの画面ではなくMTLTextureに描くことを試してみた。
だいぶ昔にOpenGL ESでやった時はオフスクリーンレンダリング(※1)と言ってたかな。
以前Metalで影を表現するために光源位置から見たシーンの深度情報をMTLTextureに書き込む、というのはやったことあったのだが、その場合と異なったところなどをメモ。
{
    id <MTLTexture> outTexture;//インスタンス変数
}
-(void)setupOutTexture
{
    MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:WIDTH height:HEIGHT mipmapped:NO];//—(1)
    textureDescriptor.usage = MTLTextureUsageRenderTarget;//—(2)
    outTexture = [device newTextureWithDescriptor:textureDescriptor];//—(3)
}
(1)で指定するピクセルフォーマットをカラー画像用に。深度情報を書き込んだ時はMTLPixelFormatDepth32Floatを使っていたが、今回はカラー画像なのでMTLPixelFormatBGRA8Unormで。
(2)こういうのを書け、と実行時にエラーが出たので追加。
(3)textureDescriptorからテクスチャ用バッファを作成。


MTLRenderPipelineDescriptorオブジェクト自体は画面に描こうがMTLTextureに描こうが特に変わらないが、MTLRenderPassDescriptorは出力先を画面じゃなくてMTLTextureと記述する必要がある。
{
    MTLRenderPassDescriptor *outTextureRenderPassDescriptor;//インスタンス変数
}
-(void)setupOutTextureRenderPassDescriptor
{
    outTextureRenderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];

    MTLRenderPassColorAttachmentDescriptor *attachment = outTextureRenderPassDescriptor.colorAttachments[0];
    attachment.texture = outTexture;
    attachment.loadAction = MTLLoadActionClear;
    attachment.storeAction = MTLStoreActionStore;
    attachment.clearColor = MTLClearColorMake(0.0f, 104.0/255.0, 5.0/255.0, 1.0f);//green
}
↑描画時にはここで作成したoutTextureRenderPassDescriptorからid <MTLRenderCommandEncoder>を作成してレンダリングする。

実行結果のスクショ

↑実行結果。テクスチャ画像におなじみのLenaを使いMTLTextureに立方体を描き、それをテクスチャとする立方体を描いている(ARKitのARFaceAnchorから立方体の位置姿勢を決めているので後方におっさんがいるけどここでは関係ない)。
なんのこっちゃ、という感じだが処理の流れは意図通りにできていることを確認した。



※1 ↓オフスクリーンレンダリング。な、なつかしい。このサイトにはホントお世話になった。
床井研究室 - 第26回 レンダリング画像をテクスチャに使う


ARKitのface trackingはiPhoneが横向きでも可能

iPhone X以降のフロントカメラはTrueDepth Camera(※1)と呼ばれ顔認証のFace IDに使われる。
この Face ID はiPhoneを縦持ちしないと機能しない(iPadは横向きでも可能)。
なので、最近やってるARSCNFaceGeometryで顔の頂点データを取得(※2)するのもiPhoneは縦向きじゃないとダメなのだろうと思っていた。

ふと、デバイス(iPhone XS Max)を横向きにして試してみた。アプリはいつもの(?)目を大きくして”メガネ”オブジェクトを表示するものだ(顔を見失うと目が小さくなり、メガネは止まる)。



↑横に置いて試した動画。
デバイスから1mくらいまでは普通にトラッキングできてる。
案外遠くでも顔を追いかけられている。

どういうこと???

これだけきっちりリアルタイムでface trackingできるのは画像処理だけじゃなくて赤外線カメラなども使って処理してるんだろうな、と思っていたんだが、、、?

暗いところでface trackingのスクショ

↑試しに暗いところで使ってみたらface trackingできない!(上の画像は撮影後輝度処理をして見えやすくしている)

おまけに、明るいところでドットプロジェクターや赤外線カメラの位置(※3)を何かで押さえててもちゃんと動く!


つまり、Face IDで認証する時の処理とは全く違っているってことか?face trackingにFace IDほどの精度は不要だろうだから照射するドット数を減らすとかやってるのかな、くらいに思っていた。
顔の表情(頂点座標)までリアルタイムで取得するのを画像処理だけでやってるってこと???
謎すぎる、、、



※1
Cameras

※2 ↓ARFaceTrackingConfigurationをサポートしてるのはTrueDepth Cameraを持ってるデバイスだけ、とある。
ARFaceTrackingConfiguration - ARKit | Apple Developer Documentation

※3
iPhone顔認証を実現する『TrueDepthカメラ』の仕組みと未来とは?|TIME&SPACE by KDDI




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