Apple Engine

Apple, iPhone, iOS, その周辺のことについて

iOS で SceneKit を試す(Swift 3) その87 - SceneKit でマルチパスレンダリングを行う SCNTechnique を試す

SceneKit では他のゲームエンジンと同様にシーンを複数回レンダリングし画像を合成するマルチパスレンダリングを行うことができる。

内容的には結構複雑なので、こんなことができるよというのがわかってもらえれば良いかと。

 

マルチパスレンダリングの使用用途は?

主に2タイプある。

 

レンダリングされたカラーバッファを入力として使用し、それをフラグメントシェーダーで処理するポストプロセッシング

カラーグレーディングやディスプレースメントマッピングなどに使用。

 

もう一つは、最初にレンダリングしパスとして仲介するバッファにキャプチャ、そのバッファを使用して追加の描画パスを実行して最終出力イメージを作成する遅延シェーディング

モーションブラーやスクリーンスペースアンビエントオクルージョン(SSAO)などに使用。

 

公式ドキュメントでの遅延シェーディングを使用した SSAO の例。

  • 最初のレンダリング 0 パス目で色情報と深度情報を作成
  • 1 パス で 0 パス で作成した深度情報とシーンの法線情報を設定
  • 2 パスで 1 パスから作成した情報から SSAO の影の処理とノイズのテクスチャを作成
  • 3 パス目で 2 パスと 0 パスの色情報とを合成し最終画像を作る

f:id:x67x6fx74x6f:20170904154407p:plain

SCNTechnique - SceneKit | Apple Developer Documentation

 

実装するには?

Metal か GLSL のシェーダーを作成、plist などでそれらのシェーダーとともに初期設定をして、その plist を呼び出す。
そして、その plist を SCNTechnique に設定するとマルチパスレンダリング実行される。

今回 plist を使用しているが、SCNTechnique 初期化の際 NSDictionary 渡すだけなので、JSON から変換してもよいし、直接 NSDictionary に設定値を書き込んでもよい。

 

コードを書いてみる

かなり複雑なことが可能なのだが、今回は簡単な画面全体をドット絵っぽくするサンプルをつくる。
内容としては 0 パス目で画像を取得し、1 パス目で縮小された画像を等倍で表示するだけのもの。

f:id:x67x6fx74x6f:20170904154617p:plain

 

てことで、いつも通り、Xcode の Game テンプレートで SceneKit を選択し作成する。

 

Metal シェーダーを設定する

メニューバーの File > New > File… か Project Navigator (Command + 1) 右クリックからの New File… もしくは Command + N で新規ファイルを作成を行い、
Metal File を選ぶか、テキストファイルとして作成して .metal ファイルを変更する。

今回は Pixelate.metal というファイル名にした。

作成したら以下のコードを書く。

#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>

struct custom_vertex_t
{
    float4 position [[attribute(SCNVertexSemanticPosition)]];
};

struct out_vertex_t
{
    float4 position [[position]];
    float2 uv;
};

constexpr sampler s = sampler(coord::normalized,
                              address::repeat,
                              filter::nearest);

vertex out_vertex_t pixelate_pass_through_vertex(custom_vertex_t in [[stage_in]],
                                                 constant SCNSceneBuffer& scn_frame [[buffer(0)]])
{
    out_vertex_t out;
    out.position = in.position;
    out.uv = float2((in.position.x + 1.0) * 0.5 , (in.position.y + 1.0) * -0.5);
    return out;
};

fragment half4 pixelate_pass_through_fragment(out_vertex_t vert [[stage_in]],
                                              texture2d<float, access::sample> colorSampler [[texture(0)]])
{
    float4 fragment_color = colorSampler.sample( s, vert.uv);
    return half4(fragment_color);
};

ヴァーテックスの構造体とフラグメントの構造体を作成し、レンダー画像の再サンプルの再ニアレストネイバーを使用するように指定。
pixelate_pass_through_vertex を情報を取得し、pixelate_pass_through_fragment で色の情報を返している。

 

plist を設定する

新規ファイルで plist を選択するか、テキストファイルを .plist にして作成。

今回は Pixelate.plist として保存した。

plist をそのまま設定するのが面倒なので、
Project Navigator から Pixelate.plist 右クリックして Open As > Source Code で表示を XML にして、
以下のコードを書く

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>passes</key>
    <dict>
        <key>pixelate_scene</key>
        <dict>
            <key>draw</key>
            <string>DRAW_SCENE</string>
            <key>inputs</key>
            <dict/>
            <key>outputs</key>
            <dict>
                <key>color</key>
                <string>color_scene</string>
            </dict>
            <key>colorStates</key>
            <dict>
                <key>clear</key>
                <true/>
                <key>clearColor</key>
                <string>sceneBackground</string>
            </dict>
        </dict>
        <key>resample_pixelation</key>
        <dict>
            <key>draw</key>
            <string>DRAW_QUAD</string>
            <key>program</key>
            <string>doesntexist</string>
            <key>metalVertexShader</key>
            <string>pixelate_pass_through_vertex</string>
            <key>metalFragmentShader</key>
            <string>pixelate_pass_through_fragment</string>
            <key>inputs</key>
            <dict>
                <key>colorSampler</key>
                <string>color_scene</string>
            </dict>
            <key>outputs</key>
            <dict>
                <key>color</key>
                <string>COLOR</string>
            </dict>
        </dict>
    </dict>
    <key>sequence</key>
    <array>
        <string>pixelate_scene</string>
        <string>resample_pixelation</string>
    </array>
    <key>targets</key>
    <dict>
        <key>color_scene</key>
        <dict>
            <key>type</key>
            <string>color</string>
            <key>size</key>
            <string>64x114</string>
        </dict>
    </dict>
    <key>symbols</key>
    <dict/>
</dict>
</plist>

target の color_space で画像を変更するサイズを指定。
passes で pixelate_scene でレンダリング情報を取得し、resample_pixelation で Metal シェーダーの処理を設定。
sequence で pixelate_scene を 0 パス目で処理、その後の 1パス目で resample_pixelation の処理を行うよう指定している。

 

SCNView に SCNTechnique を適応する

GameViewController.swift を開き、viewDidLoad へ以下のコードを書く。

plist から NSDictionary に変換、SCNTechnique へ初期化、SCNView に SCNTechnique を渡す。

if let path = Bundle.main.path(forResource: "Pixelate", ofType: "plist") {
    if let dict = NSDictionary(contentsOfFile: path)  {
        let dict2 = dict as! [String : AnyObject]
        let technique = SCNTechnique(dictionary:dict2)
        scnView.technique = technique
    }
}

 

ガウシアンブラーを試してみる

4パスで処理する必要があるのため Metal の処理の部分で若干厳しさはある。

f:id:x67x6fx74x6f:20170904154654p:plain

 

まとめ

公式ドキュメントの PDF (英語) で Metal Shader を覚えて、SCNTechnique の使用方法を覚える必要があるため大変だが、ここら辺がひと通りできるようになると、SceneKit や Metal での表現の幅が広がるので試してみると良いかもしれない。

https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf

SCNTechnique - SceneKit | Apple Developer Documentation

 

今回はここまで。