シェーダにランダムな値を送って動きをつける

前回までの記事でシェーダのHello Worldである三角形を表示することができました。

今回はランダムな値を引数に追加してシャーダに動きをつけてみます、時間経過で滑らかなアニメーションをさせるのが一般的で記事もよく書かれているのでもっと簡単にできるようにランダムな値を渡します。

完成形はこんな感じです。

コード

ShaderTypes.header

enumにcaseを追加します、ランダムな値は1つだけ送るのでstructには追加しません。

#ifndef ShaderTypes_h
#define ShaderTypes_h

#include <simd/simd.h>

enum {
    kShaderVertexInputIndexVertices = 0,
    kShaderVertexInputIndexViewPortSize = 1,
    kShaderVertexInputIndexRandVal = 2,
};

typedef struct {
    vector_float2 position;
    vector_float4 color;
} ShaderVertex;

#endif /* ShaderTypes_h */

MetalView

今回は色を変化させたいので赤一色だけを渡していた部分をrgbそれぞれの色を渡すように修正します。

//三角形の頂点の座標を定義
let red = vector_float4(1.0, 0.0, 0.0, 1.0)
let green = vector_float4(0.0, 1.0, 0.0, 1.0)
let blue = vector_float4(0.0, 0.0, 1.0, 1.0)

let width = Float(min(size.width, size.height))
self.vertices = [ShaderVertex(position: vector_float2(0.0, width/4.0), color: red),
                 ShaderVertex(position: vector_float2(-width/4.0, -width/4.0), color: green),
                 ShaderVertex(position: vector_float2(width/4.0, -width/4.0), color: blue)]

setVertexBytesのところに追加でランダムな値を送るようにします。

//ランダムな値をセットする
var randVal = Float.random(in: 0.0...1.0)
encoder.setVertexBytes(&randVal,
                       length: MemoryLayout<Float>.size,
                       index: kShaderVertexInputIndexRandVal)

Shader.metal

色を渡されたランダムな値で割って小数点以下を切り捨てています。ちょーシンプル!

typedef struct {
    float4 position [[position]];
    float4 color;
    float4 randVal;
} RasterizerData;

vertex RasterizerData vertexShader(uint vertexID [[vertex_id]],
                                   constant ShaderVertex *vertices [[buffer(kShaderVertexInputIndexVertices)]],
                                   constant vector_float2 *viewportSize [[buffer(kShaderVertexInputIndexViewPortSize)]],
                                   constant float &randVal [[buffer(kShaderVertexInputIndexRandVal)]]) {
    RasterizerData result = {};
    result.position = float4(0.0, 0.0, 0.0, 1.0);
    result.position.xy = vertices[vertexID].position / (*viewportSize);
    result.color = vertices[vertexID].color;
    
    result.randVal = float4(randVal, randVal, randVal, 1.0);
    return result;
}

fragment float4 fragmentShader(RasterizerData in [[stage_in]]) {
    return metal::floor(in.color / in.randVal);
}