BioErrorLog Tech Blog

試行錯誤の記録

Generative ArtでNFTアート画像生成 | p5.js, Processing

GenerativeArtNFTで使ったアート画像生成ロジックをメモします。

はじめに

こんにちは、@bioerrorlogです。

先日、@hoosan16さん@ClankPanさんと参加したDfinityのハッカソンの一環として、Generative ArtのNFTを作成しました。

github.com

※ハッカソンで提出したメインプロダクト - Flippyがデモ用途で利用するNFT実装です。

本記事では、このGenerativeArtNFTのアート画像生成ロジックをまとめておきます。

Generative ArtでNFTアート画像生成

やりたいこと

TokenIdをinputとして無限で多様なGenerative Artを生成する、というのがやりたいことです。

アプローチとしては、TokenIdのhash値からパラメータを抽出し、Generative Artを生成するようにしました。 Generative Artの生成にはp5.jsを使っています。

アート模様はパーリンノイズをベースにして、カラーもhash値に応じて変わるようにする、というのが当初のアイデアです。

元バージョン: Reactコンポーネントとしてのプロトタイプ

もともとはフロントサイドで画像生成する想定だったので、Reactコンポーネントの形でプロトタイプを実装しました。

github.com

import React from 'react';
import p5 from 'p5';
import md5 from 'md5';

const sketch = (p: p5) => {
  const TokenId = 1 // pseudo TokenId

  const hashedNftSeed = md5(String(TokenId))
  console.log(hashedNftSeed)

  const getSlicedNum = (x: number, y: number) : number => {
    return parseInt(hashedNftSeed.slice(x, y), 16)
  }

  const lineLengthSeed = getSlicedNum(0, 2) + 1; // 1~257

  const strokeColorV1 = getSlicedNum(2, 4); // 0~255
  const strokeColorV2 = getSlicedNum(4, 6); // 0~255
  const strokeColorV3 = getSlicedNum(6, 8); // 0~255

  const noiseScale = (getSlicedNum(8, 11) % 1000) / 10000; //0.0001~0.1000

  const rotateSeed = getSlicedNum(11, 13); // 0~255

  const backgroundColorV1 = getSlicedNum(15, 17); // 0~255
  const backgroundColorV2 = getSlicedNum(17, 19); // 0~255
  const backgroundColorV3 = getSlicedNum(19, 21); // 0~255

  const drawIterateVal = getSlicedNum(21, 26) + 100000; // 100000~1000000

  const randomSeedVal = getSlicedNum(26, 29); // 0~4096
  const noiseSeedVal = getSlicedNum(29, 32); // 0~4096

  p.setup = () => {
    p.createCanvas(500, 500).parent('p5sketch');
    p.background(backgroundColorV1, backgroundColorV2, backgroundColorV3, 255);
  
    p.randomSeed(randomSeedVal);
    p.noiseSeed(noiseSeedVal);
   
    for (let i = 0; i < drawIterateVal; i++){
      const x = p.random(p.width);
      const y = p.random(p.height);
      const noiseFactor = p.noise(x*noiseScale, y*noiseScale);
      const lineLength = noiseFactor * lineLengthSeed;
      
      p.push();
      p.translate(x, y);
      p.rotate(noiseFactor * p.radians(rotateSeed));
      p.stroke(strokeColorV1, strokeColorV2, strokeColorV3, 3);
      p.strokeWeight(1);
      p.line(0, 0, lineLength, lineLength);
      p.pop();
    }
  }
}

// ref. https://discourse.processing.org/t/instance-mode-creating-two-canvas/14121
new p5(sketch);

const DemoNft: React.FC = () => {
  return (
    <div id = "p5sketch">
      {/* p5 instance will be created here --> */}
    </div>
  );
}

export default DemoNft;

TokenIdがハードコードされているのは、プロトタイプだったからということでご容赦を。。

TokenIdをhash化した上で、アート画像を生成するための各パラメータをhash値のsliceから割り当てる、という方法をとってみました。

発想はシンプルですが、それぞれのTokenIdから全く異なるテイストのアート画像を生成することに成功しています。

出力アート画像例:

入力TokenId, 左上から順に1~4, 5~8

入力TokenId, 左上から順に1000~1003, 1004~1007

入力TokenId, 左上から順に1000000001~1000000001, 1000000005~1000000005

最終バージョン: ts-nodeで実行

上記をベースとしつつも、修正した上で最終プロダクトに利用しています。

  1. Reactコンポーネントではなく、ts-nodeスクリプトとして実行できる形にする
  2. アート画像生成ロジック/出力結果を軽くする

フロントエンド側で都度アート画像生成をさせるのは処理が重すぎるので、あらかじめ生成したアート画像をCanisterにアップロードするよう設計を変更しました。

それでもまだ表示速度に問題があったので、生成画像サイズを軽くする変更を加えています。

最終的なソースコードはこちら: github.com

おわりに

以上、GenerativeArtNFTで使ったアート画像生成ロジックをメモしました。

久しぶりにGenerative Artなコードを書きましたが、普通のエンジニアリングとはまた違った試行錯誤が求められて、やはり楽しいですね。

調べてみると、最近はまたいろいろなクリエイティブコーディングツールがでてきているようです。 Rustで書けるnannouなんかも面白そう。

Generative Art人工生命は以前から興味の対象でしたので、また遊びがてら書いていきたいところです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

Flippy (NFT Barter) | Devpost

GitHub - Japan-DfinityInfoHub/generative-art-nft

GitHub - Japan-DfinityInfoHub/nft-barter

NFTの絵柄を生成するコード · Issue #2 · Japan-DfinityInfoHub/generative-art-nft · GitHub

GitHub - bioerrorlog/p5-in-react: PoC for demo NFT implementation

home | p5.js

Perlin noise - Wikipedia