nannouで3Dプログラミングとレイマーチング


この記事は Rust Advent Calendar 2021 の 10日目の記事です。

## 0. はじめに

nannouで3Dを扱うにはどうしたら良いの？と言うことに疑問を持って書いています。
Processing、p5.js、WebGLやWebGL2、GLSLそしてOpenGL経験者であれば多少わかると思います。
僕自身もまだnannouやwgpu(WebGPU)は学習中であり不完全なところもありますがご容赦ください。
またこの記事ではwgpuの詳細やレンダリングパイプラインなどの高度な説明は行っていません。

## 1. nannouとは？

nannouとはRustで書かれたクリエイティブコーディングライブラリーです。
クリエイティブコーディング環境といえばProcessingやp5.jsをイメージしますが簡単にいえばこれらと同じようなものであるイメージしていただければ良いと思います。Rustで書かれているのでプログラミングもRustで行っていきます、そのため**ある程度Rustの学習が必要になります**。

**実装したソースコードは**[ここにおいています](https://github.com/HosodaMath/ArtOFCode/tree/main/rayMarch/raymarching_with_rust_and_wgpu/src)

## 2. 3Dモデルの作成

3Dモデルを作成するには各頂点の位置ベクトル、法線ベクトル、テクスチャ座標、色データそしてインデックス配列が必要になります。

もっとも敷居の低いPlaneモデルを作成します。

### 2.1　構造体で各データを定義する。

nannouではインデックス配列を除いて3Dモデルのデータは構造体と配列の組み合わせで定義する必要があります。

なおここから先はシェーダーも含めて次元の高いものから実装します。

#### 色データ

色データはR,G,B,Aの4次元タプルで実装します。

```rust
pub struct Color {
    color: (f32, f32, f32, f32),
}
```

構造体を使ったRGBA実装は以下のようになります

```rust
pub const COLOR: [Color; 4] = [
  Color {
    color: (1.0, 1.0, 0.0, 1.0),
  },
  etc...
];
```

#### 位置ベクトル

位置ベクトルはx,y,zの3次元ベクトルで実装します。

```rust
pub struct Vertex {
    position: (f32, f32, f32),
}
```

構造体を使った位置ベクトル実装は以下のようになります

```rust
pub const VERTICES: [Vertex; 4] = [
  Vertex {
    position: (-1.0 * SCALE, 1.0 * SCALE, 0.0 * SCALE),
  },
  etc...
];
```

#### 法線ベクトル

法線ベクトルも位置ベクトルと同じです。

```rust
pub struct Normal {
    normal: (f32, f32, f32),
}
```

構造体を使った法線ベクトル実装は以下のようになります

planeの場合法線ベクトルはすべて同じ方向を向いているのでわかりやすいと思います😄

```rust
pub const NORMALS: [Normal; 4] = [
  Normal {
    normal: (0.0, 0.0, 1.0),
  },
  etc...
];
```

#### テクスチャ座標

テクスチャ座標は2次元ベクトルになります。

```rust
pub struct TexCoord {
    texcoord: (f32, f32),
}
```

構造体を使ったテクスチャ座標実装は以下のようになります

```rust
pub const TEXCOORD: [TexCoord; 4] = [
  TexCoord {
    texcoord: (1.0, 0.0),
  },
  etc...
];
```

最後にインデックス配列を実装します。インデックス配列は構造体で定義する必要はありません。

```rust
pub const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
```
残念なことにレンダリングパイプラインの説明をカットします。**必ずどこかで書きます😌**。

### 2.2　シェーダーを書く

シェーダーはGLSLを使います、バージョンは4.5を使用しますまたこの記事ではGLSLの細かい説明は行いません。wgpuではGLSLを扱う場合は**SPIR-Vバイナリに変換**する必要があります。
SPIR-Vについては関連記事を最後にリンクとして貼り付けます。

#### 頂点シェーダー

```glsl
#version 450

layout(location = 0) in vec4 aColor;
layout(location = 1) in vec3 aPosition;
layout(location = 2) in vec3 aNormal;
layout(location = 3) in vec2 aTexcoord;

layout(location = 0) out vec4 vColor;
layout(location = 1) out vec3 vNormal;
layout(location = 2) out vec2 vTexCoord;
layout(location = 3) out vec2 vResolution;
layout(location = 4) out float vTime;

layout(set = 0, binding = 0) uniform Data {
    mat4 world_matrix;
    mat4 view_matrix;
    mat4 projection_matrix;
    vec2 u_resolution;
    float u_time;
} uniforms;

void main() {
    vTime = uniforms.u_time;
    vResolution = uniforms.u_resolution;
    vColor = aColor;
    vTexCoord = aTexcoord;
    vNormal = aNormal;
    mat4 worldview = uniforms.view_matrix * uniforms.world_matrix;
    gl_Position = uniforms.projection_matrix * worldview * vec4(aPosition, 1.0);
}

```

#### フラグメントシェーダー

```glsl
#version 450

layout(location=0) in vec4 vColor;
layout(location=1) in vec3 vNormal;
layout(location=2) in vec2 vTexCoord;
layout(location=3) in vec2 vResolution;
layout(location=4) in float vTime;

layout(location=0) out vec4 outColor;

const float PI2 = 6.28318530718;

void main(){
    vec2 coord = vTexCoord;
    coord = (2.0 * gl_FragCoord.xy - vResolution) / min(vResolution.x, vResolution.y);

    vec4 bgColor = vColor;
        
    outColor = bgColor;
}

```

実行すると以下の白いPlaneが出力されます。

![white.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672574/11672a1c-4348-7131-c228-fde32d491b9a.png)

無事Planeが描かれています。

## 3. レイマーチング

Planeにレイマーチングのコードを貼り付けます。
使うレイマーチングのコードは自分が[過去に作った](https://github.com/HosodaMath/ArtOFCode/tree/main/rayMarch/rayMarchMorphine2/shader/texture)ものを流用しています。ただしバージョンに合わせて修正しています。

無事レイマーチングを使ったMorphineが出力されていると思います。

![raymarching1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672574/1b6288e1-d93f-e496-35b8-996c6e2164fa.png)


![raymarching2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672574/0262b8ca-b7c9-520b-0f8a-7a0f3c4b3fe4.png)



## 4. 注意すべきこと

GLSLで編集したコードはSPIR-Vでコンパイルしないと反映されません。

SPIR-VでGLSLを扱うには比較的新しいバージョンでなければコンパイルエラーが出ます、そのためProcesingやp5.jsで作ったシェーダーではうまくコンパイルされないので注意が必要です。

## 5. 実装や記事の改善点

実はplaneがうまく接合されていないので**要修正**です。

レンダリングパイプラインを使っているのにその説明を省略しているのでやはり**要修正**です。

plane以外のモデルも作成する。

wgpuやWebGPUについて深く学習する。

3D周りについて深く学習する。

以上です。



## 参考文献

### nannou

[nannouの公式サイト](https://nannou.cc/)
[nannouのソースコード（サンプル付き）](https://github.com/nannou-org/nannou)

### GLSLとレイマーチング

WebGLメインですがGLSLも記載しています。
[wgld.org](https://wgld.org/)

PCD Tokyo2020(PCD Japan)のときのWorkshop資料です。
[Processingユーザーのためのシェーダーアート入門](https://github.com/aadebdeb/PCD_Tokyo_2020_Workshop)

### SPIR-V

以下はHLSLですが導入手順なども載っています（残念ですがWindows限定になります😔）。
[HLSLからSPIR-Vバイナリを生成する](https://www.shader.jp/?page_id=2176)

以下はKhronosGroupの公式GitHubです。
[SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross)

他にも参考にした本などはありますが上記をメインにしています。