Swift+MetalでEmitter
Swift+Metalでエミッター(粒子放出器)のサンプルです。
某ガンダムのGNドライブ風です。
炎や煙も同じ仕組みで再現できます。
完成形↓
表現するコツ
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へ書き込みをします。
しかし、エフェクトを表示するときは書き込みを禁止した方が都合がいいです。
書き込みをした場合と、禁止した場合の結果は以下の通りです。
書き込みした場合↓
書き込みをした場合は、後ろのオブジェクトが表示されないため、透明色でも後ろにオブジェクトが描画されません。
しかし、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)
}