Nannouフレームワークに入門してみた

はじめに

Rust のクリエイティブコーディングフレームワークであるNannouに入門してみました! Rust 初心者 && 記事投稿は初めてなので,温かい目で見てください…

対象としている読者

  • Rust の環境構築が終わっている方
  • Rust の学習にチャレンジしてみたい方
  • クリエイティブコーディングにチャレンジしてみたい方

Rust の環境構築が終わっていない方は,下記を参考にしてください.

そもそもクリエイティブコーディングとは?

クリエイティブコーディングを簡単に説明すると, プログラミングを活用して, アート作品・アニメーション・イラスト・音楽・実体を持つ 作品などを作ってみよう!! という活動(ジャンル)のことです.

nannou フレームワークの概要

https://nannou.cc/

Nannou はオープンソースの下に開発されており, シンプルで高速かつ信頼性の高いコードで, アーティストが自分自身を表現する事を容易にすることを目的としたライブラリであり, アート・アニメーション・音楽等を作成する為の沢山の機能が揃っています.

フレームワーク名の由来

なぜ Rust を使用するのか?

Rust を使用することで,下記のメリットが得られます.

  1. 強力なメモリ安全性・型安全
  2. 依存関係を非常に簡単に処理できるパッケージマネージャ
  3. 洗練されたモジュールシステム
  4. 並行性と並列性のサポート
  5. C, C++と同等の高いパフォーマンスを発揮
  6. クロスコンパイルに対応している

特にクリエイティブコーディングにおいては,グラフィック描画などの, 計算量が多いため,このメリットはとても大きいです.

筆者の環境

筆者は WSL2 を使用しているため,VcXsrv を使用して,GUI を表示しています. 場合により,VcXsrv が使用するポートのファイアウォールを 開放する必要があります. VcXsrv の設定

  • Windows10 Home 64bit
  • WSL2
  • Ubuntu 22.04.3 LTS
  • Visual Studio Code 1.86.0
  • Rust 1.73.0
  • CPU: Intel Core i7-10700F
  • RAM: 32GB
  • GPU: RTX3060

早速コードを書いてみる.

とりあえずNannouGuideを見ながら, コーディングしていきます.

まずは,Cargo で Project を作成し,Nannou を導入し, ガイドに則って空のウィンドウを作成します.

$ cargo new nannou-sketch
$ cargo add nannou

[依存関係の追加]

[dependencies]
nannou = "0.19.0"

コードを見てみると,ソフトウェア設計パターンの一つである, MVC(Model-View-Controller)のような設計がされていますね

このコードを実行してみると…

// Nannouのアイテムを一括インポート.
use nannou::prelude::*;

/// アプリケーションのメインモデルを表します.
struct Model {}

fn main() {
    nannou::app(model).event(event).simple_window(view).run();
}

/// モデルを初期化します.
///
/// # Arguments
///
/// * `_app` - `App` 構造体への参照.
///
/// # Returns
///
/// 初期化された `Model` 構造体.
fn model(_app: &App) -> Model {
    Model {}
}

/// イベントがトリガーされたときにモデルを更新します.(イベントハンドラ)
///
/// # Arguments
///
/// * `_app` - `App` 構造体への参照.
/// * `_model` - `Model` 構造体への参照.
/// * `_event` - トリガーされたイベント.
fn event(_app: &App, _model: &mut Model, _event: Event) {}

/// アプリケーションの描画処理を行います.
///
/// # Arguments
///
/// * `_app` - `App` 構造体への参照.
/// * `_model` - `Model` 構造体への参照.
/// * `_frame` - ビューをレンダリングするフレーム.
fn view(_app: &App, _model: &Model, _frame: Frame) {}

空のウィンドウが表示されましたね.

Sketch と App の違い

新しい Nannou プロジェクトを作成する場合,プログラムを開始するには 2 つのオプションがあり,それが nannou::sketch と nannou::app です.

sketchapp
シンプル複雑
簡単な創作に向いている本格的な創作に向いている

app ではセットアップするための関数や構造体が, sketch に比べて多いですが,結局のところ大きな違いはなく, 両者の切り替えも簡単にできるので,あまり気にしなくても大丈夫です. これ以降の記事では,基本は nannou::app を使用します.

座標系

Nannou では,他のクリエイティブコーディングライブラリに比べて, 座標の指定が少し特殊であり, p5.js 等ではウィンドウ座標の原点が左上から始まりますが, nannou ではウィンドウの中心が原点に設定されています.

これは nannou がデカルト座標を使用して,Window を記述しているためです..

x 値は右に向かう程増加し,左に向かう程減少
y 値は上方向に向かう程増加し,下方向に向かう程減少

下記はウィンドウの中心に正方形を表示する例です.

fn view(app: &App, _model: &Model, frame: Frame) {
    // キャンバスを取得
    let draw = app.draw();

    // キャンバスの背景色を設定
    draw.background().color(WHITE);

    // 1辺が200の正方形を原点を中心として表示
    draw.rect().x_y(0.0, 0.0).w_h(200.0, 200.0).color(ORANGE);

    // フレームにレンダリング
    draw.to_frame(app, &frame).unwrap();
}
TIP

nannou では,通常,ウィンドウ空間内の位置を Pixel ではなく,Point で記述します.

アニメーション

Nannou では静止画だけでなく,アニメーションを作成する事も可能です. 下記は大きさが変化しながら画面内を移動する正円のアニメーション例です.

fn view(app: &App, _model: &Model, frame: Frame) {
    // キャンバスを取得
    let draw = app.draw();

    // キャンバスの背景色を設定
    draw.background().color(WHITE);

    // Windowの幅を取得
    let boundary = app.window_rect();

    // 正弦波を作成
    let sine = app.time.sin();

    // 速度を1/2にした正弦波を作成
    let slowersine = (app.time / 2.0).sin();

    // 正弦波をWindowのx,yの最大値・最小値にマッピングする
    let x = map_range(sine, -1.0, 1.0, boundary.left(), boundary.right());
    let y = map_range(slowersine, -1.0, 1.0, boundary.bottom(), boundary.top());

    // 正円を作成
    draw.ellipse().w_h(x / 2.0, y / 2.0).color(WHITE).x_y(x, y);

    // フレームにレンダリング
    draw.to_frame(app, &frame).unwrap();
}

画像描画(テクスチャ)

まずプロジェクトのルートに assets ディレクトリを作成し,画像ファイルを配置します.

$ mkdir assets
use nannou::prelude::*;

struct Model {
    // テクスチャ構造体を追加
    texture: wgpu::Texture,
}

fn main() {
    // アプリケーションを実行
    nannou::app(model).run();
}

fn model(app: &App) -> Model {
    // 512*512のウィンドウを作成
    app.new_window().size(512, 512).view(view).build().unwrap();

    // nannouにassetsを読み込む
    let assets = app.assets_path().unwrap();

    // 画像のパスを読み込む
    let img_path = assets.join("images").join("universe").join("quasar1.jpg");

    // 指定したパスから,テクスチャを読み込む
    let texture = wgpu::Texture::from_path(app, img_path).unwrap();

    // textureフィールドを持つModelインスタンスを作成
    Model { texture }
}

fn view(app: &App, model: &Model, frame: Frame) {
    // フレームを初期化
    frame.clear(BLACK);

    // キャンバスを取得
    let draw = app.draw();

    // テクスチャを描画
    draw.texture(&model.texture);

    // フレームにレンダリング
    draw.to_frame(app, &frame).unwrap();
}

サウンドアート

INFO

筆者の環境では,WSL2 を使用しておりサウンドデバイスの設定が困難なので, Nannou を使用する際は,Windows/Mac で実行するのがおすすめです. どうしても WSL2 で実行したい場合は,下記の記事を参考にすると解決できると思います. WSL2 でホスト OS の SoundDevice を使用する方法

実際に作品を作ってみる!

window の端に衝突した際にボールの色と方向を反転させて, 波紋を発生させるクリエイティブコーディングを使用した作品を作ってみました!

Source

まとめ

初めてNannouフレームワークにチャレンジしてみましたが, Rust の勉強にもなりますし,Rust を使用したクリエイティブコーディングに興味がある方は, 是非ともやってみてほしいです!

Nannou 自体は,とても使いやすかったのですが,まだまだ情報が少ないので,もっとこのフレームワークを使って作品を作る方が増えると嬉しいです! 今度はNannou_Audioを使用した,音楽作成にも挑戦してみたいですね!