MetalとSwiftで遊ぶ「プロシージャルモデリング的な何か」

この記事は[NCC Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ncc)の2日目の記事です.

アドベントカレンダー内で勝手にやってるMetalの連載も2日目です．

# 勝手にやってるMetal連載

最近Metalを初めて楽しくなっちゃったのでたくさんoutputしてみる会です．

1. [Rendererを作る](https://qiita.com/micky_dtl/items/7177c667e5a5d6bb777a)
2. プロシージャルモデリングをして表示する ←　イマココ
3. フラグメントシェーダで遊ぶ

# プロシージャルモデリングっぽいことをする．

## プロシージャルモデリングとは...🤔

プロシージャルモデリングとは，「ルール・セットによって3Dモデルを生成すること」らしいです．

CG界隈は全然専門ではないので，とりあえず「ローカル座標系をプログラミングで作成する」くらいの意味で捉えています🙄

はい，やっていき．

## 2Dでやって見る

まずは2DでModelを作ってみます．

目標はこんな感じ．
正n角形をたくさん並べてエモい絵を作ってみましょう．
これが**自分のiPhoneの上で動く**んです．控えめに言って楽しすぎる．
iPhoneXのフレームが少ない端末の上で動くのめっちゃ綺麗です．新型iPadPro欲しい...

![IMG_0116.jpg](https://qiita-image-store.s3.amazonaws.com/0/76493/bbd06c0d-9178-f501-26cb-522ec8d38f6c.jpeg)

正3角形から正13角形まで並べて，縦にも


この多角形を，なんとか三角形の集合で作りたいと思います．
正6角形だったら下のような感じ．円周上のn点を用いて正n角形を作っていきたいと思います．

![IMG_0162.jpg](https://qiita-image-store.s3.amazonaws.com/0/76493/80f2764a-9c65-4c7d-c02b-3a2d21e3c278.jpeg)

この画像上の**頂点座標**と**各頂点のインデックス**を利用すれば


* `[0,1,2]`
* `[0,2,3]`
* `[0,3,4]`
* `[0,4,5]`
* `[0,5,6]`
* `[0,6,1]`

の頂点をつなげた三角形を用意すれば，正n角形を作れそうです．

## Modelを定義する

まずはモデルのProtocolから定義しておきたいと思います．
それぞれのVertex(頂点)は

* `offset`(ポリゴンの中心の位置)
* `position`(中心からの相対位置)
    * この場合は円周上の座標
* `Color`（頂点の色）

の情報を保持しています．
`offset + position`が**ローカル座標系**となります．
カンタンに後述しますが，ポリゴン自体は`offset`の場所に来て欲しいが．**回転縮小などの処理を適用したい**という人のために，`position`を分離して宣言しています．

また，今回の作成するモデルの最小構成として，

* `vertices`(モデルの各頂点)
* `indices`(モデルを構成する各3角形ポリゴンを構成する頂点のIndex)
* `triangles`
    * indicesの順番でverticesを並べたもの

``` structures.swift

typealias Position = float3
typealias Color = float4

struct Vertex {
    var offset: float3
    var position: float3
    var color: float4
}

protocol MAModel {
    var vertices: [Vertex]! { get }
    var indices: [Int]! { get }
    var triangles: [Vertex]! { get }
}

```

``` RegularPolygon.swift

struct RegularPolygon: MAModel {
    
    // Model Protocol
    var vertices: [Vertex]!
    var indices: [Int]!
    
    // RegularPolygonのデータ
    var r: Float!
    var n: Int!
    
    // Triangleデータ
    var triangles: [Vertex]! { return indices.map({i->Vertex in vertices[i]})}
    
    init(center: Position, r: Float, n: Int, Color c: Color) {
        self.r = r
        self.n = n
        vertices = [Vertex]()
        indices = [Int]()
        generatePolygon(center: center, r: r, n: n, color: c)
    }
    
    mutating private func generatePolygon(center: Position, r: Float, n: Int, color: Color) {
        vertices = []
        indices = []
        vertices.append(Vertex(offset: center, position: Position(0,0,0), color: color))
        for i in Array(0..<n).map({n -> Float in Float(n)}) {
            let theta = 2 * Float.pi * (i / Float(n))
            let x = r * cos(theta)
            let y = r * sin(theta)
            vertices.append(Vertex(offset: center, position: float3(x, y ,0), color: color) )
        }
        Array(0..<n).map({i->[Int] in [0, 1+i, 1+(i+1)%n]}).forEach({indices += $0})
    }
}


```

このポリゴンを，ViewControllerのクラスで呼び出して，Rendererに渡してあげましょう．

``` RegularPolygonsVC.swift

class RegularPolygonsVC: UIViewController {
    private var renderer: Renderer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        guard let mtlDevice = MTLCreateSystemDefaultDevice() else {
            print("Metal is not supported on this device")
            return
        }
        let mtkView = MTKView(frame: view.frame, device: mtlDevice)
        mtkView.device = mtlDevice
        mtkView.framebufferOnly = true
        mtkView.preferredFramesPerSecond = 60
        renderer = Renderer(metalKitView: mtkView)
        
        view = mtkView
        
        var polygonVertices = [Vertex]()
        for i in -5..<6 {
            for j in -5..<6 {
                let x = Float(i)/Float(5)
                let y = Float(j)/Float(5)
                let regPoly = RegularPolygon(center: float3(x, y, 0), r: 0.08, n: 8+i, Color: Color(x: (1+x)/2, y: (1+y)/2, z: 0.7, w: 1.0))
                polygonVertices += regPoly.triangles
            }
        }
        renderer.setVertices(polygonVertices)
        renderer.start()
    }
}

```

また，今回の設計上，[前回](https://qiita.com/micky_dtl/items/7177c667e5a5d6bb777a)で作成したRendererから変更点があります．
MTKViewDelegateの`draw`メソッドの中の`drawIndexedPrimitives`が`drawPrimitives`に変わってます

``` Renderer.swift

//        commandEncoder?.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0)

        commandEncoder?.drawPrimitives(type: .lineStrip, vertexStart: 0, vertexCount: vertices.count)

```

モデリングした感じがわかるように,`.lineStrip`と`.triangle`を設定したのを並べてみました．

<img width="1000" src="https://gyazo.com/2fa953052fab3fb50e03a2310f8c661b.raw">

## vertexシェーダで回転させる．

`MAModel`ないで，わざわざ`offset`プロパティを持たせてみましたが，これはvertexシェーダ内でそれぞれのポリゴンを回転させるためです．

↓`.metal`だとシンタックスハイライトがつかないので， `.cpp`にしてみた

vertexシェーダ内でそれぞれのポリゴンの`Position`プロパティ(半径rの円周上)に`rotateMatrix`という変数(時間t分だけ回転させる行列)を掛け合わせることで，単位円周上で回転させてから，offsetを足し，本来ポリゴンの中心が来て欲しいところに移動させます．

``` shaders.cpp

#include <metal_stdlib>
using namespace metal;

struct VertexIn {
    float3 offset;
    float3 position;
    float4 color;
};

struct Uniforms {
    float time;
    float aspectRatio;
    float2 touch;
};
struct VertexOut {
    float4 pos [[ position ]];
    float4 color;
    float time;
    float2 touch;
};

vertex VertexOut vertexDay2(constant VertexIn *vertexIn [[buffer(0)]],
                            constant Uniforms &uniforms [[buffer(1)]],
                            uint vid [[ vertex_id ]]) {
    float t = uniforms.time;
    float4x4 rotateMatrix = float4x4(float4(cos(t), sin(2.0*t), 0, 0),
                                  float4(-sin(3.0*t), cos(4.0*t), 0, 0),
                                  float4(0,0,1,0),
                                  float4(0,0,0,1));
    
    float3 offset = vertexIn[vid].offset;
    float3 position = vertexIn[vid].position;
    float4 rotatedPos = float4(offset, 0.0) + rotateMatrix * float4(position, 1.0);
    float4 color = vertexIn[vid].color;
    VertexOut out;
    
    rotatedPos.y *= uniforms.aspectRatio;
    
    out.pos = rotatedPos;
    out.color = color;
    out.time = uniforms.time;
    out.touch = uniforms.touch;
    return out;
}

fragment half4 fragmentDay2(VertexOut vertexIn [[stage_in]]) {
    float4 c = vertexIn.color;
    return half4(c);
}

```

## 3Dでやって見る...?

こんなツイートをしていましたが，これもプロシージャルモデリングですね笑笑

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">最終手段すぎる <br>
<img width="512" src="https://pbs.twimg.com/media/DrtjlJSUwAAMt4v.jpg"></p>&mdash; Mi↑ki↓ (@miki_fms) <a href="https://twitter.com/miki_fms/status/1061551631655854080?ref_src=twsrc%5Etfw">November 11, 2018</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


## Metalを使った表現の楽しいところ．

MSL(Metal Shading Language)を使って描画をするのはとても楽しいです．
個人的にとてもお気に入りなのは，この記事で書いたようなshadingのためのリソースをSwiftを使ってかけるところです．
WebGLでJSを使って色々設定，宣言するのがあまり得意じゃないので，Swiftでできるshader環境は，とても楽しいのです...！！

## ソースコード

[連載用のgithub](https://github.com/math-miki/metaladventCalendar/)にあげてあります！Day2です．