UnityのTextureのポインタをSwiftだけで作ったNative Pluginに渡して処理する
- Swift だけで、Unity の iOS の Native Plugin を作る方法を紹介しました
- Unity の Texture をネイティブで処理したい場合があります
- 例えば、Texture を mp4 で録画する Native Plugin を実装することが可能です
- Unity には、Texture.GetNativeTexturePtr 関数が用意されており、Texture2D や RenderTexture からも直接 Native で処理できる Texture のポイントを取得することが可能です
- iOS では、
id<MTLTexture>
を取得することが可能であり、今回はこれを Swift で受け取って、処理する方法の紹介です
今回のサンプル
- リポジトリはこちらです
- [Swift だけで、Unity の iOS の Native Plugin を作る](Swift だけで、Unity の iOS の Native Plugin を作る方法) の内容を前提知識にしています
- 今回は、Unity の Texture をアルバムに保存する機能を作ります
- 左の画像の(小さいですが)真ん中のボタンを押すと、右の画像が保存されます
処理の流れ
- Unity で適当な Texture を用意する
- Texture.GetNativeTexturePtr() を使って、Native で処理するポインタを取得する
== ここまで C# ここから Swift === - void ポインタを受け取るので、MTLTexture 型にキャストする
- MTLTexture -> UIImage 変換する
- アルバムに保存する
Unity の実装
- 任意の Texture が用意できれば良いです
- ここでは、RenderTexture のパターンで進めます
- すでに、存在する Texture を利用する場合は、
RenderTexture の用意
の章は読み飛ばしてください -
Native の Texture のポインタを取得して、Native に渡す
から固有の処理です
RenderTexture の用意
- RenderTexture を作成します
- Color Format が R8G8B8A8_UNorm になっていることを確認します
- サイズは任意のサイズで大丈夫です
- 新規のカメラを追加し、先ほど作成した、RenderTexture を Target に設定します
- 新規の Button を追加し、下記のスクリプトを設定します
using System;
using System.Runtime.InteropServices;
using UnityEngine;
public class Button : MonoBehaviour
{
#if UNITY_IOS
[DllImport("__Internal")]
private static extern void swiftPmPlugin_saveImage(IntPtr mtlTexture);
#endif
public RenderTexture texture;
public void onTap()
{
Debug.Log("Button tapped!");
#if UNITY_IOS
swiftPmPlugin_saveImage(texture.GetNativeTexturePtr());
#endif
}
}
Native の Texture のポインタを取得して、Native に渡す
- Native の関数を宣言します
- 今回は、iOS のみサポートです
-
[DllImport("__Internal")]
をつけた、static extern な関数を定義します
#if UNITY_IOS
[DllImport("__Internal")]
private static extern void swiftPmPlugin_saveImage(IntPtr mtlTexture);
#endif
- C# では、この関数に
texture.GetNativeTexturePtr()
を渡すだけです
#if UNITY_IOS
swiftPmPlugin_saveImage(texture.GetNativeTexturePtr());
#endif
Swift の実装
C# (c) の呼び出し口を作る
-
@_cdecl
を付けて Swift で関数を定義します - C# の定義的に、void* なので、UnsafeRawPointer 型として受け取ります
- MTLTexture にキャストして、保存する関数に渡します
@_cdecl("swiftPmPlugin_saveImage")
public func swiftPmPlugin_saveImage(_ texturePtr: UnsafeRawPointer?) {
guard let texturePtr = texturePtr else { return }
let mtlTexture: MTLTexture = __bridge(texturePtr)
SwiftPmPlugin.saveImage(mtlTexture: mtlTexture)
}
MTLTexture -> UIImage
- デフォルトの pixel format は rgba8Unorm なのですが、Unity は srgb でレンダリングしているので、フォーマットを修正します
- MTLTexture -> CIImage -> CGImage -> UIImage に変換します
- UIImageWriteToSavedPhotosAlbum を使ってアルバムに保存します
class SwiftPmPlugin {
static func saveImage(mtlTexture: MTLTexture) {
let mtlTexture2 = mtlTexture.makeTextureView(pixelFormat: .rgba8Unorm_srgb)!
let ci = CIImage(mtlTexture: mtlTexture2, options: nil)!
let context = CIContext()
let cg = context.createCGImage(ci, from: ci.extent)!
let ui = UIImage(cgImage: cg)
UIImageWriteToSavedPhotosAlbum(ui, nil, nil, nil)
}
}
Framework の出力と設定
- こちらの記事の
Framework でビルドする
の章以降と同じ手順で実行ビルド可能です
- アルバムの追加のために、Unity でビルドした後に、Xcode でアプリをビルドする前に、Info.plist に
Privacy - Photo Library Additions Usage Description
の設定をします - ビルドしたアプリで、ボタンを押した時に、画像がアルバムに保存されているはずです
おわりに
- Unity と Swift の Texture のやりとりは、GetNativeTexturePtr を使うことで簡易に実現可能です
- Texture のサイズやフォーマット、mipmap などの情報も、MTLTexture に入っているので、個別に取得する必要がないのが便利です
- また、Texture2D に書き写して、ReadPixels したバイト配列をコピーするよりポインタ渡しの方が経済的なのかなと思っています
- Unity の Texture を Swift で処理するのは、応用の幅が広いので、活用して行きたいです