この記事は 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の学習が必要になります。
実装したソースコードはここにおいています
2. 3Dモデルの作成
3Dモデルを作成するには各頂点の位置ベクトル、法線ベクトル、テクスチャ座標、色データそしてインデックス配列が必要になります。
もっとも敷居の低いPlaneモデルを作成します。
2.1 構造体で各データを定義する。
nannouではインデックス配列を除いて3Dモデルのデータは構造体と配列の組み合わせで定義する必要があります。
なおここから先はシェーダーも含めて次元の高いものから実装します。
色データ
色データはR,G,B,Aの4次元タプルで実装します。
pub struct Color {
color: (f32, f32, f32, f32),
}
構造体を使ったRGBA実装は以下のようになります
pub const COLOR: [Color; 4] = [
Color {
color: (1.0, 1.0, 0.0, 1.0),
},
etc...
];
位置ベクトル
位置ベクトルはx,y,zの3次元ベクトルで実装します。
pub struct Vertex {
position: (f32, f32, f32),
}
構造体を使った位置ベクトル実装は以下のようになります
pub const VERTICES: [Vertex; 4] = [
Vertex {
position: (-1.0 * SCALE, 1.0 * SCALE, 0.0 * SCALE),
},
etc...
];
法線ベクトル
法線ベクトルも位置ベクトルと同じです。
pub struct Normal {
normal: (f32, f32, f32),
}
構造体を使った法線ベクトル実装は以下のようになります
planeの場合法線ベクトルはすべて同じ方向を向いているのでわかりやすいと思います😄
pub const NORMALS: [Normal; 4] = [
Normal {
normal: (0.0, 0.0, 1.0),
},
etc...
];
テクスチャ座標
テクスチャ座標は2次元ベクトルになります。
pub struct TexCoord {
texcoord: (f32, f32),
}
構造体を使ったテクスチャ座標実装は以下のようになります
pub const TEXCOORD: [TexCoord; 4] = [
TexCoord {
texcoord: (1.0, 0.0),
},
etc...
];
最後にインデックス配列を実装します。インデックス配列は構造体で定義する必要はありません。
pub const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
残念なことにレンダリングパイプラインの説明をカットします。必ずどこかで書きます😌。
2.2 シェーダーを書く
シェーダーはGLSLを使います、バージョンは4.5を使用しますまたこの記事ではGLSLの細かい説明は行いません。wgpuではGLSLを扱う場合はSPIR-Vバイナリに変換する必要があります。
SPIR-Vについては関連記事を最後にリンクとして貼り付けます。
頂点シェーダー
#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);
}
フラグメントシェーダー
#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が出力されます。
無事Planeが描かれています。
3. レイマーチング
Planeにレイマーチングのコードを貼り付けます。
使うレイマーチングのコードは自分が過去に作ったものを流用しています。ただしバージョンに合わせて修正しています。
無事レイマーチングを使ったMorphineが出力されていると思います。
4. 注意すべきこと
GLSLで編集したコードはSPIR-Vでコンパイルしないと反映されません。
SPIR-VでGLSLを扱うには比較的新しいバージョンでなければコンパイルエラーが出ます、そのためProcesingやp5.jsで作ったシェーダーではうまくコンパイルされないので注意が必要です。
5. 実装や記事の改善点
実はplaneがうまく接合されていないので要修正です。
レンダリングパイプラインを使っているのにその説明を省略しているのでやはり要修正です。
plane以外のモデルも作成する。
wgpuやWebGPUについて深く学習する。
3D周りについて深く学習する。
以上です。
参考文献
nannou
nannouの公式サイト
nannouのソースコード(サンプル付き)
GLSLとレイマーチング
WebGLメインですがGLSLも記載しています。
wgld.org
PCD Tokyo2020(PCD Japan)のときのWorkshop資料です。
Processingユーザーのためのシェーダーアート入門
SPIR-V
以下はHLSLですが導入手順なども載っています(残念ですがWindows限定になります😔)。
HLSLからSPIR-Vバイナリを生成する
以下はKhronosGroupの公式GitHubです。
SPIRV-Cross
他にも参考にした本などはありますが上記をメインにしています。