随時追加していきます。
MTLTexture をコピーする
たとえばMTKView
で画像ファイルから読み込んできたテクスチャ(MTLTexture
)を描画したいときに、
currentDrawable.texture = aTexture
みたいなことはできない。
この記事でやったみたいに、パススルーシェーダを書いて書き込む方法もあるが、もっと手軽な方法として、 MTLBlitCommandEncoder
のコピー機能を使う方法がある。
let commandBuffer = commandQueue.makeCommandBuffer()
let blitEncoder = commandBuffer.makeBlitCommandEncoder()
blitEncoder.copy(from: fromTexture,
sourceSlice: 0,
sourceLevel: 0,
sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
sourceSize: MTLSizeMake(fromTexture.width, fromTexture.height, fromTexture.depth),
to: drawable.texture,
destinationSlice: 0,
destinationLevel: 0,
destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
blitEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
MTLTextureをリサイズする
Metal Performance ShadersフレームワークのMPSImageLanczosScale
というクラスを利用する。
下記記事を参照:
- Metalで画像をリサイズする - Qiita
MTLTextureを新規生成する
画像ファイルからMTKTextureLoader
で読み込んでくるのではなく、新規でテクスチャを生成するには、MTLTextureDescriptor
を使用する。
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: pixelFormat, width: width, height: height, mipmapped: true)
let texture = device.makeTexture(descriptor: textureDescriptor)
UIImage を MTLTexture に変換する
image
というUIImageオブジェクトがあるとして、MTKTextureLoaderを用いて次のように処理する。
guard let cgImage = image?.cgImage else {return}
do {
try texture = textureLoader.newTexture(with: cgImage, options: nil)
} catch {
fatalError("Could not load the texture. error: \(error).")
}
アセットからMTLTextureを生成する
アセットから読み込んですぐ描画したいのであれば、いったんUIImageオブジェクトをつくるのはCPUのムダ。MTKTextureLoader
には下記のようにasset nameを直接渡してテクスチャを生成するメソッドも用意されている。
func newTexture(withName name: String, scaleFactor: CGFloat, bundle: Bundle?, options: [String : NSObject]? = nil) throws -> MTLTexture
iOSデバイスがMetal Performance Shadersをサポートしているかを確認する
この記事の『Metal Feature Sets』の項に書いたとおり、当該iOSデバイスが、MetalをサポートしていてもMPSはサポートしていないこともありえます。(例:iPhone 5s)
以下のコードでチェックできます。
guard MPSSupportsMTLDevice(device) else {
print("Metal Performance Shaders not Supported on current Device")
return
}
MTLTextureにブラーをかける
Metal Performance ShadersフレームワークのMPSImageGaussianBlur
というクラスを利用する。
入力テクスチャ/出力テクスチャをそれぞれ指定する方法と、入力テクスチャそのものに出力(in place)する方法とがある。
たとえばMTKView
のcurrentDrawable.texture
に既に書き込まれているテクスチャにブラーを書けたい場合、in placeで以下のように処理できる。
guard let drawable = currentDrawable else {
return
}
let blur = MPSImageGaussianBlur(device: device, sigma: blurRadius)
let commandBuffer = commandQueue.makeCommandBuffer()
var drawableTexture = drawable.texture
_ = withUnsafeMutablePointer(to: &drawableTexture) { (texturePtr: UnsafeMutablePointer<MTLTexture>) in
blur.encode(commandBuffer: commandBuffer, inPlaceTexture: texturePtr, fallbackCopyAllocator: nil)
}
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
入力/出力をそれぞれ指定する場合は以下のようにする。
let blur = MPSImageGaussianBlur(device: device, sigma: blurSigma)
let commandBuffer = commandQueue.makeCommandBuffer()
blur.encode(commandBuffer: commandBuffer, sourceTexture: sourceTexture, destinationTexture: destinationTexture)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
(シグマ0〜100〜0を60 fpsで変化させてみた様子。AnimatedGIFというフォーマットの都合上、グラデーションが汚いですが、実際はすごく綺麗です。)
MTLTextureの転置(行と列の入れ替え)
Landscapeで入ってきたカメラ入力をPortraitにしたい場合とかに。MPSImageTranspose
を使う。引数等は一切なし。
MPSImageTranspose *transpose =[[MPSImageTranspose alloc] initWithDevice:commandQueue.device];
[transpose encodeToCommandBuffer:commandBuffer
sourceTexture:scrTexture
destinationTexture:dstTexture];
MTLTexture -> CIImage
CIImageにはMTLTextureを引数に渡せるイニシャライザが用意されているが、
let inputImage = CIImage(mtlTexture: texture, options: nil)
これだけだと上下反転した画像ができてしまう。
というわけで以下のようにy方向にフリップさせる。
let inputImage = CIImage(mtlTexture: texture, options: nil)?.applying(CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: CGFloat(texture.height)))
CIImage -> MTLTexture (Metalを利用)
Metalを利用してレンダリングするためのCIContextを用意しておく。
context = CIContext(mtlDevice: device)
CIImage
の内容をMTLTexture
に描画する。
let colorSpace = CGColorSpaceCreateDeviceRGB()
context.render(inputImage, to: toTexture, commandBuffer: commandBuffer, bounds: inputImage.extent, colorSpace: colorSpace)
CIImage -> MTLTexture (Core Graphicsを利用)
CGImageに変換してMTKTextureLoaderを用いてMTLTextureをロードする
let context = CIContext(options:nil)
guard let cgImage = context.createCGImage(image, from: image.extent) else {return}
let texture = try? textureLoader.newTexture(cgImage: cgImage)
CIImage を MTKView に描画する
上記の応用例。MTKView
のcurrentDrawable
のtexture
にCIImage
を書き込み、present
する。
let commandBuffer = commandQueue.makeCommandBuffer()
let colorSpace = CGColorSpaceCreateDeviceRGB()
context.render(outputImage, to: drawable.texture, commandBuffer: commandBuffer, bounds: outputImage.extent, colorSpace: colorSpace)
commandBuffer.present(drawable)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
CMSampleBuffer -> CVImageBuffer (CVPixelBuffer) -> MTLTexture
AVFoudationを用いて得られるカメラ入力をMetalでリアルタイム処理する際にこの変換が必要になる。詳しくは以下の記事を参照:
ここにはコードだけ貼っておく(リンク先にはObjective-C版のコードもあり)
-
CVImageBuffer
(CVPixelBuffer
)を取り出す
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
-
CVMetalTextureCache
を用意
var textureCache : CVMetalTextureCache?
CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, device, nil, &textureCache)
-
CVMetalTextureCache
を用いて、CVImageBuffer
から、CVMetalTexture
を生成
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
var imageTexture: CVMetalTexture?
let result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, imageBuffer, nil, pixelFormat, width, height, 0, &imageTexture)
- イメージバッファから、
MTLTexture
を取り出す
let texture = CVMetalTextureGetTexture(imageTexture)
Core Graphicsを利用する場合
let image = CIImage(cvPixelBuffer: buffer)
あとは「CIImage -> MTLTexture (Core Graphicsを利用)」 の方法で変換(MTKTextureLoaderを用いてアップロードする)
Core ImageとMetalを併用する
MTLTexture -> CIImage -> (CIFilterで画像処理) -> MTLTexture -> MTKViewに描画
という流れ。Core ImageはMetalとシームレスに統合できるよう実装されているので、CPUとGPUを行ったり来たりするようなムダはない。詳しくはこちらの記事を参照: Metalの恩恵は受けつつCore Imageで「手軽に」画像処理 - Qiita
(Core ImageのCIPixellate
フィルタをパラメータを変えつつ60fpsでMTKViewに描画)
MetalシェーダでSceneKitのマテリアルを描画する
SCNProgramというクラスを使う。
let program = SCNProgram()
program.fragmentFunctionName = "myVertex"
program.vertexFunctionName = "myFragment"
material.program = program
詳細は下記記事を参照:
(http://glslsandbox.com/e#36858.0 のGLSLをMSLに移行しつつSceneKitに描画)
(http://glslsandbox.com/e#37017.0 のGLSLをMSLに移行しつつSceneKitに描画)
人気の投稿
- MTLTexture をコピーする
- MTLTextureをリサイズする
- MTLTextureを新規生成する
- UIImage を MTLTexture に変換する
- アセットからMTLTextureを生成する
- iOSデバイスがMetal Performance Shadersをサポートしているかを確認する
- MTLTextureにブラーをかける
- MTLTextureの転置(行と列の入れ替え)
- MTLTexture -> CIImage
- CIImage -> MTLTexture (Metalを利用)
- CIImage を MTKView に描画する
- CMSampleBuffer -> CVImageBuffer (CVPixelBuffer) -> MTLTexture
- Core ImageとMetalを併用する
- MetalシェーダでSceneKitのマテリアルを描画する