Swift+Metalで発光エフェクト

Swift+Metalで発光エフェクト(Unityで言う所のBloom)をお手軽に表現する手法。
完成形↓
IMG_5725.PNG

発光エフェクトをする手順

1.通常のレンダリングをする。
IMG_5728.PNG

2.発光させたい部分と色のマスクを作る。
IMG_5726.PNG

3.マスクにガウスボカシをかける。
IMG_5727.PNG

4.通常のレンダリングにボカした画像を足し合わせる。
IMG_5725.PNG

発光エフェクトを表現するプログラム

全てのプログラムはココ↓
https://github.com/fuziki/metalBloom

・手順1&2
レンダリングテクスチャを2枚用意する。
(通常のレンダリングとマスク用)

MetalEz.swift
func makeRenderTexture() -> MTLTexture {
        let texDesc = MTLTextureDescriptor()
        texDesc.width =  (mtkView.currentDrawable?.texture.width)!
        texDesc.height =  (mtkView.currentDrawable?.texture.height)!
        texDesc.depth = 1
        texDesc.textureType = MTLTextureType.type2D

        texDesc.usage = [MTLTextureUsage.renderTarget, MTLTextureUsage.shaderRead]
        texDesc.storageMode = .private
        texDesc.pixelFormat = .bgra8Unorm

        texDesc.usage = .unknown

        return device.makeTexture(descriptor: texDesc)!
}
MetalEz.swift
        viewRenderTexture = makeRenderTexture()        
        bloomRenderTexture = makeRenderTexture()

フラグメントシェーダーで2つのテクスチャにそれぞれ、レンダリングとマスクを書き込む。

Shaders.metal
struct FragmentOut {
    half4 color0 [[ color(0) ]];
    half4 color1 [[ color(1) ]];
};
fragment FragmentOut fragmentShader(VertexOut in [[stage_in]],
                                    texture2d<half>  diffuseTexture [[ texture(0) ]],
                                    texture2d<half>  bloomTexture [[ texture(1) ]])
{
    constexpr sampler defaultSampler;
    FragmentOut out;
    float lt = saturate(dot(in.normal, lightDirection));
    if (lt < 0.5) lt = 0.5;
    half4 color =  half4(diffuseTexture.sample(defaultSampler, float2(in.texcoord))*lt);
    out.color0 = color;
    if (is_null_texture(bloomTexture)) {
        out.color1 = half4(0,0,0,0);
    } else {
        out.color1 = bloomTexture.sample(defaultSampler, float2(in.texcoord));
    }
    return out;
}

・手順3
マスクにガウスボカシをする。MetalPerformanceShaderを使ってボカす。
書き込み元のテクスチャと書き込み先のテクスチャは同一

MetalEz.swift
            var myTexture: MTLTexture? = bloomRenderTexture
            let kernel = MPSImageGaussianBlur(device: device, sigma: 20.0)
            kernel.encode(commandBuffer: commandBuffer!,
                          inPlaceTexture: &myTexture!, fallbackCopyAllocator: nil)

・手順4
通常のレンダリングとエフェクトを加算合成する。
手順3と同じく、MetalPerformanceShaderを使う。
計算結果は'view.currentDrawable?.texture)!'に書き込む。
(MTLTextureは=演算子を使って渡せないので注意)

MetalEz.swift
            let addKernel = MPSImageAdd(device: device)
            addKernel.encode(commandBuffer: commandBuffer!,
                             primaryTexture: viewRenderTexture,
                             secondaryTexture: bloomRenderTexture,
                             destinationTexture: (view.currentDrawable?.texture)!)

おわりに

MTLTextureまわりが割とTipsがあるので、需要があれば、そのうちまとめます(多分)

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.