iOS
Metal
Swift
swift4

Swift+MetalでEmitter

Swift+MetalでEmitter

Swift+Metalでエミッター(粒子放出器)のサンプルです。
某ガンダムのGNドライブ風です。
炎や煙も同じ仕組みで再現できます。
完成形↓
IMG_5738.PNG

表現するコツ

1.アルファブレンディングを有効にする。
2.depth stencilへの書き込みを禁止する。
3. Metalのpointを使って描画する。

コード

1. アルファブレンディング

半透明(透明)を有効にするにはアルファブレンディングを有効にします。
結果色 = sourceBlendFactor * 描画色 (BlendOperation) destinationAlphaBlendFactor * 元々の色
今回は単純な加算合成に設定します。

MetalEzRender.swift
        renderPipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
        renderPipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add
        renderPipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add
        renderPipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .one
        renderPipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .one
        renderPipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .one
        renderPipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .one

2.depth stencilへの書き込みを禁止する。

通常、depth stencilへ書き込みをします。
しかし、エフェクトを表示するときは書き込みを禁止した方が都合がいいです。
書き込みをした場合と、禁止した場合の結果は以下の通りです。
書き込みした場合↓
IMG_5738.PNG

書き込みを禁止した場合↓
IMG_5737.PNG

書き込みをした場合は、後ろのオブジェクトが表示されないため、透明色でも後ろにオブジェクトが描画されません。
しかし、depth stencilは深度情報に書き込まないため、遠いモノから描画する必要があります。

MetalEz.swift
        //depth stencilに書き込むとき
        let depthDescriptor = MTLDepthStencilDescriptor()
        depthDescriptor.depthCompareFunction = .less
        depthDescriptor.isDepthWriteEnabled = true
        depthStencilState = device.makeDepthStencilState(descriptor: depthDescriptor)

        //depth stencilに書き込まないとき
        let depthDescriptorForBlending = MTLDepthStencilDescriptor()
        depthDescriptorForBlending.depthCompareFunction = .less
        depthDescriptorForBlending.isDepthWriteEnabled = false
        depthStencilStateForBlending = device.makeDepthStencilState(descriptor: depthDescriptorForBlending)

3. Metalのpointを使って描画する。

OpenGLの場合は正方形を描画するために三角形が2枚必要でしたが、Metalはpointを使うことで、正方形が描画できるみたいです。
データ削減になっていいですね。

MetalEzRender.swift
    func draw(vaertex: MTLBuffer, frameUniformBuffer: MTLBuffer, texure: MTLTexture, count: Int) {
        mtlEz.mtlRenderCommandEncoder.setVertexBuffer(vaertex, offset: 0, index: 0)
        mtlEz.mtlRenderCommandEncoder.setVertexBuffer(frameUniformBuffer, offset: 0, index: 1)
        mtlEz.mtlRenderCommandEncoder.setFragmentTexture(texure, index: 0)
        mtlEz.mtlRenderCommandEncoder.drawPrimitives(type: .point, vertexStart: 0, vertexCount: count, instanceCount: 1)
    }

終わりに

depth stencilについては一般化して詳しく説明したいです(需要があればですが笑)