[Metal] iOS Metalのちょっとしたメモ

  • 1
    いいね
  • 2
    コメント

細かい部分をちょくちょくメモ予定。

float4x4の設定

シェーダ内でfloat4x4の値を生成するとき、CPU側のバッファでは普通に配列で作ったりしていたのでそのままの感覚でやったらビルドエラー。
float4型のものを4つ、指定する必要があるみたい。(まぁ冷静に考えれば当たり前なんだけど)

float4x4 mat = float4x4(float4( 1,  2,  3,  4),
                        float4( 5,  6,  7,  8),
                        float4( 9, 10, 11, 12),
                        float4(13, 14, 15, 16));

ちなみにCPU側から送る場合は、Objective-C++を使うならsimd::float4x4とかを使って、シェーダ側でも同じ型をそのまま使うのが手っ取り早そうです。
が、Objctive-Cでそれをやる場合は、2次元配列にして渡してやることで対応できます。

static const float mat[4][4] = {
    { 1,  2,  3,  4},
    { 5,  6,  7,  8},
    { 9, 10, 11, 12},
    {13, 14, 15, 16},
};

Metalシェーダの[[]]セマンティクス

セマンティクスっていうのか分からないけど、Cg/HLSLでいうセマンティクス的な感じのもの。

ドキュメントはこちら。

結構量があるので、詳細はドキュメントを。

フラグメント(ピクセル)を破棄する(discard)

フラグメントシェーダでピクセルを破棄するには以下のようにします。

fragment half4 frag(VertexOut input [[stage_in]]) {
    half4 color = half4(input.color);

    // なにがしかの条件を判定する
    if (anyCondition) {
        discard_fragment();
    }

    return color;
}

ドキュメントには以下のようにあります。

The following Metal function is used to terminate a fragment:

void discard_fragment(void)
Marks the current fragment as being terminated, and the output of the fragment function for this fragment is discarded.

つまり、ピクセルを破棄したい場合にdiscard_fragment()を実行することで、該当のピクセルを破棄することができるというわけです。
ただ見てもらうと分かる通り、 色は普通に設定しつつそれをreturnしています。

discard_fragment()を実行することで破棄される、というのがなんとなくモヤっとしますね。
(OpenGLだとdiscardキーワードでそれを示す)

gl_FragCoord相当の処理

Metalでは以下のように使用します。
例は画面サイズを元にpositionを-1〜1に正規化する処理です。

struct VertexOut {
    float4 position [[position]];
};

struct Uniforms {
    float2 resolution;
};

fragment half4 frag(VertexOut input [[stage_in]],
                      constant Uniforms &uniforms [[buffer(0)]]) {
    float2 position = (input.position.xy * 2.0 - uniforms.resolution) / min(uniforms.resolution.x, uniforms.resolution.y);

    // ...
}

Metalは座標が上下逆?

ちょっと検証しきれてないですが、↑のサンプルでWebGLではうまく動いていたものが微妙に違う挙動をしていたので確認したところ、position.yの値を逆転(-1をかける)(1を引く)したら想定通りの挙動になりました。どうやら上下が逆のようです。

[2016.11.27 追記]
コメントで指摘もらいましたが、内容的には1を引くのが正しそうです。すみません。

ちなみに以下のようにして画面に対しての色を出力してみるとWebGLと比べて上下が逆になりました。
(ただ、iOSのOpenGLも逆になっていた気がするのでiOS側の仕様かもしれません)

fragment half4 frag(VertexOut input [[stage_in]], constant Uniforms &uniforms  [[buufer(0)]]) {
    float2 resolution = float2(uniforms.resolution[0], uniforms.resolution[1]);
    float2 position = (input.position.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
    return half4(position.x, position.y, 0.0, 1.0);
}

ちなみにWebGLで同様のことをやったのがこちら。
実際に動いているやつ

webgl-ver
#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

void main( void ) {

    vec2 position = ( gl_FragCoord.xy  * 2.0 -  resolution ) / min(resolution.x, resolution.y);

    gl_FragColor = vec4(position, 0.0, 1.0);

}