Blender の Geometory Nodes でマンデルブロ集合

こんにちは｜こんばんは。カエルのアイコンで活動しております @kyamaz :frog: です。

## はじめに
数学的な対象を Blender の Geometory Nodes を使って扱う方法の紹介は動画ではありますが、Qiitaのエントリはあまり見かけません。下記のYouTube動画を参考にしてマンデルブロ集合を立体的に描いてみましょう。

https://www.youtube.com/watch?v=B5h1PrM8HpE

本稿を参考に Geometory Nodes を通すと下記のようなモデルが描けます。参考にした動画では、マンデルブロ集合特有の縞模様になる色付けやアニメーションまで解説がありますが、ここではモデリングの解説記事になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/8697cda4-5e34-2fba-17ea-4b373c1fd05b.png)

（Cameraの情報：Location X=6m,Y=-5m,Y=4m; Rotatuin X=60°,Y=0°,Z=50°／Lightの情報：Sun）


## Blender Geometry Nodes で描く
まず最初に、Scene Collection に Plane Mesh を追加します。[3D Viewport - Object Mode] にて「Add->Mesh->Plane」で追加し、[Outline - ViewLayer] にて名前を「mandelbrot」に変更しておきます。
このオブジェントを選択した状態で [Geometory Node Editor - Modifier] の「+NEW」でモディファイアを追加し、この名前を [Properties] にて「mandelbrot_displace」としておきます。

### Grid を準備する
複素平面をXY軸にマッピングするためのGridを準備します。いくつかの定数を次のように定めて利用します。

- SizeX：PlaneのX軸のサイズ
- SizeY：PlaneのY軸のサイズ
- VertecesXY：PlaneをGridにする分割数
- Scale：倍率（１未満で指定するとZoomIn、１以上でZoomOut）
- C0：描画中心の複素数の座標（細かく指定できるように小数点以下３桁ずつを`C0_upper`と`C0_lower`で指定、$c_0=0.256679+0.000875 i$）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/bcc1508e-af01-860e-d308-c5ff54774013.png)

次の「Named Attribute」にそれぞれの定数の値を保存します。

- `gridsize_x` :: Float <- SizeX
- `gridsize_y` :: Float <- SizeY
- `scale` :: Float <- Scale
- `c0` :: Vector <- C0_upper and C0_lower
- `uv_map` :: Vector <- Gridより（本稿では利用しない）

### パラメーターを初期化する
GridのなかのPointごとに処理されますが、そのPointに対応する複素数を上記の定数を使って求め、「Named Attribute」の `c` :: Vector にその値を保持します。また変数 `z` :: Vector の初期値にも `c` を代入しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/35dbce28-8ef8-f663-3291-548370cec42e.png)

その前に、繰り返し回数の `iteration_count` :: Integer と、発散するかのフラグ `isIn` :: Boolean も初期化しています。

### ループで計算処理を行う
前のキャプチャの一番右にある Iterations は階層的に処理を記述しています。次の２つのキャプチャのように。`Iterations` には `iterations_group`を 10 回分繰り返し、その中で `Iteration` を更に10 回分繰り返すようにします。つまり、10 × 10 = 100 回の繰り返し処理となっております。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/54ffa2c9-d159-f404-0220-95b14ec2e357.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/211d7e05-70ff-0720-0ada-e94dcdf27d17.png)

### z^2+c を計算する
$ f(z) = z^2+c $ を計算するパートは次のようになります。`escape condition`にあるように、`isIn` フラグと複素数 `z` の `length` $|z|<2$ かどうかで処理を分けています。どちらも真（`true`）のときは、

- `iteration_count` を `+1` する
- `z` を `z^2+c` の計算結果に変更する

の処理をします。いずれかが偽（`false`）のときには、`isIn` を偽 `false` にして、それ以外（`iteration_count`と`z`の値）は変更しません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/fb5e87b5-143a-aca7-3d16-df594d019ad5.png)

このPointごとに計算した値を確認したい場合は、次のキャプチャのよりに [Spreadsheet - Evaluated] にて確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/3a0bf8e2-c158-f2bc-8f1f-a2935f058722.png)

### 繰り返し回数を高さに反映する
$|z|<2$を満たさなくなるまでの繰り返し回数`iteration_count`を Grid の Point のオフセットとして計算します。`smooth coloring`と `shader preparation` で、そのオフセットの元となる値を求めています。
`displace` のなかの [Set Position] で Offset を設定しています。最後に、 [Set Shade Smooth] で表面をスムーズにする処理を行い、完成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/3fd66aa9-20ba-85c7-6703-691cc2f69845.png)

オフセットを求める際に、次のような計算を行なって、`iteration_count`に足しています。`z`の値を使うことでより滑らかになるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/1a9f95ab-25d7-e259-90a0-9d9008fbc914.png)

以上で、Geometory Nodes で設定する全体像となります。

## おわりに
:frog: は Blender がほぼ初心者でショートカット等の操作が頭に入っていないため、作成にはとても時間がかかってしまいましたが、試行錯誤しながら作成していけますし、数学の対象を 3D で表すのには、Blender は楽しいツールだと感じました。また他にも Python コードも使えますので、[Scripting] でも似たようなモデリングも可能ではないかと思います。本稿とは違う手法を試してみるのも面白いのではないでしょうか。

<details><summary>本稿の環境</summary>

本稿のために使用した環境は以下となります。

* macOS: Sonoma 14.5 (chip: Apple M1)
* Blender: 4.2.0
* PYTHON INTERACTIVE CONSOLE 3.11.7 [Clang 15.0.0]

</details>

ご一読いただきまして有り難うございます。
（●）（●） Happy Hacking!
/""　＿＿""＼