先日開催されていた「第一回ジェネラティブアート・アワード」で大賞を受賞させていただきましたので、その制作過程を思い出しながらメモや感想を残しておこうかと思います。
受賞作品は下記で公開しております。
また、2024/10/25~2024/11/24まで、山口情報芸術センター(YCAM)で展示もされるようです🙌
制作過程
なにをつくろうか
日々「つぶやきProcessing」でDaily Codingをしておりジェネラティブアートを作ることには慣れていたので、第一回ジェネラティブアート・アワードの開催を知った時から何か応募しようかなと思っていました。
せっかく応募するなら過去の成果物を応募するよりも新規の作品がいいかなぁとぼやっと思いながら、過去の自分の制作物を眺めてアイディアを練っていたところ、5月ごろに取り組んでいたピクセルを使った表現が改めて見てみて綺麗だったのと、各ピクセルの色を完全に計算で求めて模様を作っているのでアワードの掲げる「ジェネラティブアート」の定義にも当てはまりそうだなと感じ、これをモチーフに作品を作っていくことに決めました。
5月に作成した模様は、当時から「絨毯の模様みたいだなぁ」と感じていました。特に端っこの淵に現れる、マクロな視点で見ると規則的なんだけども、ミクロな視点で見ると不規則な感じの良い塩梅の部分が何とも言えない「スキ」なところでお気に入りでした。本来の絨毯が持つ美しく精緻な模様が、ドット絵として単純化されて濃縮表現されているように感じられたためです。この作品に限らないのですが、その作品が具体的に何を表しているのか何となくわかる「なんかそれっぽい」という感じはとても好きなのです。

というわけで、まずは絨毯模様をもう少し深堀してみようと思い、リミッター解除(日々のつぶやきProcessingの活動では260文字縛りなので)して、数式をいじったりパラメーターを増やしたり構成を考えたりしていきました。
いろいろな絨毯の画像をインターネットで調べて、「枠みたいなものが何層かあるとそれっぽいし、パターンの組み合わせも増やせそう」「真ん中になんかオブジェクトみたいなものを置くとそれっぽい」などと感じ、最終的に「矩形のエリアを3つ作る」「中央にオブジェクト(メダリオンというらしい)を配置したりしなかったりする」という方針にしました。

模様のアルゴリズムについて
模様の生成アルゴリズムについて備忘録もかねてこちらに簡単にまとめておきます。

コンピューターやスマホの画面を拡大するとドット(ピクセルと呼ばれます)で構成されており、その各ピクセルがそれぞれ1つの色を表示することで絵や文字などが表現されていることがわかります。
今回のカーペット模様も同様に「各ピクセルにどのように色を付けるか」という計算を行って描いており、これが今回の作品の重要ポイントでもあります(この「どのように」の部分をアルゴリズムと言います)

単純に縦横10ピクセルある場合を考えてみます。Processingやp5.jsは左上が原点(0)となり右下に向かって数字(座標)が増えていきます。
図では赤色が横方向(x座標)で、青色が縦方向(y座標)となります。
これから、各ピクセルに座標の数字を元にして計算を行い、色を付けていくことにします。

例えばx座標の数値をそのまま色として使ったとします。数値が低いところは黒、明るいところは白とすると、だんだん右に進むにつれて(つまりx座標が大きくなるにつれて)明るくなっていく模様を作ることができます。
数式で書くと ピクセルの色 = x座標
となります。

同様にy座標の数値をそのまま色として使うと、下に進むにつれて明るくなる模様となります。
数式で書くと ピクセルの色 = y座標
ですね。

x座標とy座標の値を足したもの使ってみます。すると右下に進むにつれて明るくなる模様となります。
数式で書くと ピクセルの色 = x座標 + y座標
ですね。

x座標とy座標の値をかけると、足し算よりも大きな値になるのでより明るくなりますね。
数式では ピクセルの色 = x座標 * y座標
です。

ピクセルの色 = x座標 - y座標
とすると左下はマイナスになってしまうので真っ暗になってしまいました。

そこで絶対値を使って ピクセルの色 = abs(x座標 - y座標)
としてみます。Processingやp5.jsではabs()
で絶対値を計算できます。これでマイナスの値をプラスにすることができました。
このように座標をパラメーターとして計算を行うことで、ピクセルに色を付けて模様を描くことができます。もう少し遊んでみることにします。

今度は割り算の余りを使ってみます。Processingやp5.jsでは %
で余りを計算することができます。例えば「7÷3のあまり」は 7 % 3
と書けます。
ピクセルの色 = (x座標 + y座標) % 2
としてみると市松模様となります。左上は「0+0を2で割ったあまりなので0となり暗い」その右となりは「1+0を2で割ったあまりなので1となり明るい」という感じで交互に0と1が現れるためですね。

ピクセルの色 = (x座標 + y座標) % 4
とすると黒、濃い灰色、薄い灰色、白が交互に現れてきます。4で割ったあまりは0、1、2、3のどれかになるからですね。
だんだん模様っぽくなってきました。

あまり難しいことを考えずに数式に適当な数字を入れたり計算の記号(演算子)を入れたりすると次々と模様が生成されていき楽しいものです。
ピクセルの色 = (x座標 * y座標) % 4
としてみたら小さな四角形が周期的に現れました。だんだんと数式を見ただけでは、人間にはぱっとはどういう模様になるのか想像できなくなってきてジェネラティブ味が増えてきたように感じます。

さて、ピクセルを少し増やしてみます。縦横とも10ピクセルでしたが、これを40ピクセルにしてみました。

演算子はよく知られている四則演算(+
-
*
/
)とあまり(%
)のほかにもいくつかあります。今回は一部のビット演算子を使ってみることにします。
論理和(|
)を使うと少し不思議な模様となります。単純に右下に行くつれて明るくなるのではなく、ところどころ規則的に暗くなるところが発生しています。
ちなみにここでは論理和の詳細は理解する必要はありません。+
なら明るくなっていく、*
はもっと急激に明るくなる、ぐらいの認識で、同じように|
は明るくなっていくけどところどころ暗くなる、というイメージが持てればOKです。

次は論理積(&
)です。右下に行くにつれて明るくなっていってはいますが、論理和よりも暗い部分が増えたように感じます。

そして排他的論理和(^
)です。論理和と似ていますが、真ん中あたりが暗くなっていることが分かります。
数式では ピクセルの色 = (x座標 ^ y座標)
となります。
あまりやビット演算子を活用すると、単純に明るくなったり暗くなったりするのではなく、少し複雑な模様が簡単に描けることが分かりました。これらを利用して引き続き模様を作り上げていきます。

ピクセルをさらに増やして縦横80ピクセルにしてみました。

ここで、原点を中央に移動させます。
ピクセルの色 = ((x座標 - 40) ^ (y座標 - 40))
のように、それぞれの座標から幅の半分(今回の場合は40ですね)を引くことで真ん中が座標0の原点となります。
右上と左下がマイナスの影響なのか真っ暗になってしまっていますね。

そこで絶対値を使います。ピクセルの色 = (abs(x座標 - 40) ^ abs(y座標 - 40))
とすることで、マイナスの値もプラスになるので綺麗に色が付きました。
原点を中心として、上下と左右で座標が鏡で映したように同じ値になりますので、上下左右対称となる模様となります。綺麗ですね。

次に周期を作っていこうと思います。abs(x座標 - 40)
は一番左が40で、右に進むにつれてだんだん0に近づき、真ん中で0となります。そして右に進むにつれて40に近づき、一番右では40となる、というものでした。
これをあまりをうまく使って2回分行えるようにしたいと思います。
abs(x座標 % 40 - 20)
とすると、一番左は20となり、右に進むにつれてだんだん0に近づき、1/4進んだところで0となります。そして右に進むと真ん中で20となります。これがもう一回繰り返されて3/4のところでまた0となり、一番右で20となります。
この辺りになってくると、感覚優先でまずコードを書いてみて「そうなるね~」って感じでも全然OKだと思います。いちいち計算するのも大変ですしね。

y座標の方も同じようにすることで、縦横に2周期つくることができました。
数式がだんだん長くなってきましたが、ピクセルの色 = (abs(x座標 % 40 - 20) ^ abs(y座標 % 40 - 20))
です

さらに、先ほどの数式に%
を追加してみます。今回は5の余りにしたところ、美しい模様が現れました。
ピクセルの色 = (abs(x座標 % 40 - 20) ^ abs(y座標 % 40 - 20)) % 5
となります。

今まで白黒で色を付けてきましたが、赤・緑・青の3色にそれぞれ色を付けていくことにします。
先ほど計算した値について、さらに2のあまりを赤色に使ってみることにします。単色でもやはり綺麗ですね。
ピクセルの色 = (abs(x座標 % 40 - 20) ^ abs(y座標 % 40 - 20)) % 5 % 2
となります。

同様に、緑色は3のあまりを使ってみます。

青色には4の余りを使ってみます。

赤・緑・青の模様はそれぞれ複雑なものとなりましたが、それらの色を重ねることで、さらに複雑な模様を持つ模様を作ることができます。
これでカーペットやキルトのような「いろいろな色がある程度規則的に並んでいる」という模様を作ることができました。
作った数式のパラメーターや演算子をいろいろ変更したり追加したりすることで無限の模様を生み出すことができます。例えば赤・緑・青の計算に使用した「あまりに使う数値」を変えるだけでも下記のようなバリエーションが得られます。






今回は2周期にしましたが、この周期の数値も変えると、さらにバリエーション豊かになります。






「Carpet Collection」では、こういったパラメーターのほかに、矩形やメダリオンの大きさや、変化をつけるために適当に加える値なども全て乱数で与えており、カーペット模様が出来上がっております。確率的に同じ模様は二度と現れないといえるほどのバリエーションが生み出される結果となりました。
カーペットを並べよう
カーペット画像ができたので、これを画面に並べてみたのですが、これだけでは少し物足りないなと感じていました。

どうしたものかと考えた挙句、いろいろな大きさのカーペットがあった方が面白そうだなと感じ、そしてそれらが隙間なく適当に並んでいたらなんかいい感じかも、と思いそのように作ってみることにしました
(私は理論とか設計とかあまり細かいことは考えず、だいたい「なんかいい感じかも!」ベースで物を作ることが多いです😅)
配置のアルゴリズムについて
もしかしたら効率的に配置するアルゴリズムを誰かが発明していてどこかにあるかもしれませんが、せっかくなので独自に作ってみることにしました。「1から全部自分で考えた」ってクリエイティブな分野だとなんかかっこいいですもんね。
(仕事だと車輪の発明になってしまうし、頭のいい人が考えたモノの方が数百倍素晴らしいものであるのが常なので、1から作るのはだいたいが無駄になるわけですが😥)

まず画面をグリッドとして考えます。今回は縦に10マスあると考えます。

一番左上のマスに乱数を使って適当な大きさの枠を用意します。今回は縦2マス、横3マスの枠ができたとします。

縦2マスの枠を作ったので、左上から2マス下のマスについて、同じように適当な枠を作ります。

枠を作って、その縦の大きさ分下のマスにまた枠を作って、というのを一番下まで作ります。
これで1列目が完了です。

次に2列目について考えます。上から8マス目が空欄であることが分かります。

2列目の8行目に、高さ1マスの枠を作成します。

このように順番に列を確認していき、空いているマスがあったら、空いている分までの乱数の高さの枠を作って最後の列まで埋めていきます。
この方法で画面全体を隙間なくカーペットで埋めていくことはできそうです。
しかし、この方針には欠点がありました。列ごとに空いているマスを見ていくため、「縦に大きな高さを持つカーペットが生成されにくい」というものです。このため、最初の方は大きなカーペットができあがるのですが、次第に小さなカーペットしか生成されず、それが画面の多数を占めるようになってしまいました。


問題を解決するために「予約」という仕組みを追加することにしました。
列をチェックする際、確率で「予約モード」を発生させます。
「予約モード」となると、乱数で決められた適当な行が予約行となります。

列ごとに枠を生成していくというルールは同じですが、「予約されている行」に関しては必ず「高さと幅が1の枠」ができるようにします。

「予約行」は必ず1マスの枠のみが作られます。
「予約行」のすべての行の終端が一列に揃ったら「予約モード」は終了します。

この結果、「予約行」であった行の終端が揃っているため、列のチェック時に大きな高さを持つ枠を作れるようになり、問題が解決となります。
こうして画面の途中でも大きなカーペットが生成されるようになりましたので、これで問題ないかと思い、完成した作品を眺めてチェックしていました。

すると今度は「予約行」に現れる1マスのカーペットが結構多くて気になってきてしまいました。
そこで「予約行の1マスタイルは、隣接している1マスタイルに確率で結合する」という仕組みを加えることで、この問題を回避することにしました。

起動画面を作ろう
完成した作品を毎日眺めていて、考えた通りのものが思った通りに動いているので、満足のいくものとなってきました。
これで大体良いかなと考えていたのですが、プログラムを実行したときに、画面全体にカーペットを生成して敷き詰めるために時間がかかり、最初の画面が出るまでに数秒かかってしまう、という課題に気が付きました。
そこでローディング画面を作ろうかなと考えました。
ゲームなどでよくある「Now Loading…」という文字とプログレスバーが表示されるようなものを作ろうと思っていたのですが、せっかく四角いカーペット模様が作れるので、それをプログレスバーにしたら面白そうかなと思い枠に当てはめてみました。カーペット模様が生成されるというテーマは今回の世界観にもあうので、悪くない考えかなぁと。
カーペットが生成されていくので、「Loading」じゃなくて縫うとか編む的な単語にしてもいいかなぁと思いChatGPT先生に添削してもらって「Weaving」を採用しました。

ソースコードの整理
アワードのページの「応募Q&A」にはアルゴリズムも評価されると書いてありました。
審査では視覚的な表象や体験以外に、プログラムコード内で記述されているアルゴリズムの部分も評価の対象になっている (後略)
このためソースにはアルゴリズムが分かるようにコメント等を書いておかないといけないなぁと思い、コメントを追記したり、アルゴリズムの説明をまとめたりしました。ただこれは将来の自分のためでもあります。1年も経てば細かいところは「これ何だっけ…、どうやって動いているんだっけ…🤔」ってなりますので…。

テスト
そんなわけで作品が完成したので、最後はテストです。受賞作品はどこかで展示されるっぽいことが示唆されていましたし、審査員の方が作品をチェックしている最中にたまたま変なことが起こっても嫌なのでこれはこれでとても重要な作業です。
乱数上限下限チェック
パラメーターに使っている乱数について、取りうる値を全パターンをチェックするのは現実的ではないので、乱数が取りうる一番小さい値と一番大きな値をそれぞれ固定で入れてみて、問題なさそうかチェックしました。特に割り算を使っている箇所などは、乱数が0に近い値になった時に意図しない挙動になりがちです。
色味のチェック
今回の審査基準には「色合い等の独創性」という項目があったのですが、正直色彩について深い造詣があるわけでもなくこだわりもそこまでないので「ぱっと見で変な色が無いか」をチェックしました。真っ黒だけとか、真っ赤っかとか。
前述の通り、赤・緑・青の色の組み合わせで作っているので、これら3つの色が少なすぎる場合は真っ暗になるし、色が大きすぎる場合は真っ白になったり単色になったりするので、最大と最小の色値をベースに若干の補正がかかるようにしてみました。

メモリリークとの闘い
1時間ぐらい作品を眺めて問題なく動作していたので、続けて一晩動かしてみることにしました。そうすると、ブラウザーがクラッシュしていたため、原因を探ることにしました。
タスクマネージャーでプロセスを見てみると、使用メモリがだんだん増えていたため、メモリリークしているのかなと思い、配列周りやオブジェクト周りの挙動を確認していったところ、p5.Graphics
は remove()
しないと内部に参照が残り続けるらしいことが分かったので、その対処を行いました。
https://p5js.org/reference/p5.Graphics/remove
C言語ならともかく、p5.jsでメモリリークの対応が必要になるとは思っていなかった…。
資材の完成
こうして資材が完成したので無事に提出を行いました。
提出資材に、remove()
し忘れがあったのでそれを微修正したバージョンを下記p5.js Web Editorに公開しておきます。稚拙ではありますが何かの参考になればと思います。
https://editor.p5js.org/haukun/sketches/YSmNwXCoe
感想
夏休みを利用して作品を制作していたのですが、何度も「ジェネラティブアートとは🤔」と悩みながらなんとか完成させたものなので、この度大賞をいただけたことは本当にうれしく思います。
審査員の方の選評を拝見したところ、アルゴリズムだけでなくソースコードまで評価いただいていたようで、大変光栄です。一方で、歴史的な経緯に関しては、正直なところ不勉強であったと感じており、反省しております。他の作品などでは、歴史的な視点やつながりを重視したものも見受けられ、そういった視点からのテーマ選びや制作も重要なのだなと改めて学びました。
最後に一般財団法人ジェネラティブアート振興財団と、今回のアワードの審査員の皆様に、心より感謝申し上げます。
おまけ
今回の作品を作る前に、実は自動ジグソーパズル+ジェネラティブアートみたいなものを作ろうと思っていて実装していたのですが、作ってみて眺めてみたらジェネラティブ味がなんか物足りなくて行き詰っていまして、リフレッシュを兼ねて過去の作品を見返したところから、本稿の作品制作がはじまっていたりします。一応ベースだけは動くのでこちらで供養。うーん、なんかアートというかムービーだよなぁ…。
コメント