さて、この記事は前回の記事
「ポケポケのホログラム表現に挑戦①」
の続きであります
この記事が初見の方は最初にそちらを読んでもらうとより理解が深まるかと思います
ポケポケのホログラム表現に挑戦②
今回のテーマは「プロシージャル」です。プロシージャルとは実際にテクスチャを事前に用意するのではなく、計算によってテクスチャを生成する事です。
今回も例によって
以下の記事に基づいて考えていこうと思います
マーブル背景
記事の一部を抜粋します

さて、このマーブル模様。テクスチャじゃないんです。どうやって書いてるんでしょう?
キーワードは
- セルラーノイズ
- 影響値→等高線
- PD制御(なにそれ)
です。一つずつやればまぁ完ぺきとはいかなくとも何とかなるでしょ
さぁ、頑張って実装していきましょう
セルラーノイズ
えっ!?ノイズ?ノイズなんて役に立つの?と思われるかもしれませんが、CGではノイズの利用は必須だと思ってください。
興味がある方はこのサイトを読んでみましょう
https://thebookofshaders.com/11/?lan=jp
様々な模様がノイズをもとにして作られています。まずは普通にランダムなノイズを作ってみましょう。本当はXorShiftをやりたいのですが変なパターンが出てうまくいかなかったため、いつものfrac(sin(でやろうと思います
https://x.com/CTsuchinoko/status/2001155931091189943
いつもの
普通のノイズ
float GenerateRandomNoise(float2 st)
{
return frac(sin(dot(st.xy, float2(12.9898, 78.233))) * 43758.5453123);
}
この乱数の値を濃淡値として画面上に出力すると当然こうなります
float r = GenerateRandomNoise(input.uv);
return float4(r, r, r, 1);
ブロックノイズ
次にこれを特定の大きさの格子状にぶった切ります

もちろん線は必要ないです。区分けをするだけです
仮に0.1ごとにぶった切るならこう(アス比考慮しています)
uint3 dims;
tex.GetDimensions(0,dims.x,dims.y,dims.z);
float aspect = (float) dims.x / (float)dims.y;
float2 gridSize = float2(0.1, 0.1*aspect);
float2 gridUV = floor(input.uv / gridSize);
float r = GenerateRandomNoise(gridUV);
return float4(r, r, r, 1);

これがいわゆるブロックノイズですね。でも今回作りたいのはセルラーノイズ。いったいどういうノイズなんでしょうか
セルラーノイズ(飽体ノイズの一種)とは?
ブロックノイズやバリューノイズは最初のノイズをもとに加工して作るものでした。それにたいしてセルラーノイズは幾何学的なノイズを生成して、そこから多様な模様を生み出すものです。
飽体ノイズにはセルラーノイズのほかにウォーリーノイズなんてのもあるようです
それはともかくセルラーノイズですね
ブロックノイズを作る際に格子状の領域に分けましたが、そのひとつひとつを「セル」と言います。
ではセルラーノイズの作り方の手順ですが、こうします
- まず、それぞれのセル内のランダムな位置の点を乱数で生成します
- 生成のシードは格子点ごとに固定とする
- 現在のピクセルから見た直線距離を測る
- このとき現在のピクセルが属するセルを含めて9近傍(8近傍+1)測る
- その9近傍内の点から最も近い点を選択する
- その距離をもとに塗りつぶし等の処理を行う
このような形です
(Claudeによる図)

(GenSparkによる図)

ちょっとこれ両方の図とも微妙なんですが、間違えないで欲しいのは8近傍ではなく9近傍のセルなのを忘れないでください
AIに作らせたら分かりづらいため、結局自分でプログラム書きましたが



こんな感じで最短の矢印を求めます(赤いのが最短矢印)
で、この赤の矢印の長さを濃淡値に変換するとこうなります。

グリッドを外すと、こう

これがセルラーノイズです
今回はクソデカグリッドくんなので4分割でこんな感じ

マーブルシェーダ
つぎに、「ポイントに影響度を持たせて、距離と影響度をもとに色を決定」する

とあるけど、距離はともかく影響度ってなんだ?
記事の説明を読むと、プラスとマイナスで影響度を計算するとあるため、恐らくそれぞれのセルに+かーを割り当て、それを9近傍セルで合計すると言ったところだろう。
で、この影響度には距離によるウェイトがかかると考えると先ほどのセルラーノイズで作った距離関数が使えそうだ
疑似コード
float totalAffect = 0.0f;
float totalWeight = 0.0f;
for(int yidx=minYIdx;yidx<=maxYIdx;++yidx){
for(int xidx=minXIdx;xidx<=maxXIdx;++xidx){
int pidx = yidx * grid_columns + xidx;
float2 pos = float2( (float)x ,(float)y );
float len = distance(points[pidx],pos);
float weight = 1.0f / (len*len + 0.01f);
totalAffect += affectValues[pidx] * weight;
totalWeight += weight;
}
}
float affect = (totalAffect / totalWeight)*0.5+0.5;//-1~1になっているため範囲を修正
するとこうなりました

いい感じに見えるかもしれませんが、セルごとにエッジが残ってしまっている箇所があります。これは恐らくですがセル数が9を超えてしまうとその隣のセルとの整合性が合わずにこれが発生しているものと思われます。例えば4x4ならばこう

↓動画
https://x.com/CTsuchinoko/status/2005132376054350257
セル数9ならばこう

ってんんー?やっぱりエッジ出とるやんけー!!
もともとセルラーノイズの9近傍は沢山のセルがあった場合のメモリの効率化だったため、マーブルシェーダの場合はもう割り切って「全てのセル」の距離を測る事にする。総セル数が4個~16個とかなら問題ないでしょ
ということで9近傍の制約を取っ払ったのがこちら

動画↓
https://x.com/CTsuchinoko/status/2005136753733271898
歪み
さて、次にこれを歪ませるわけですが、どうしましょうか・・・?

パッと見た感じ「せん断変形」+中心からの距離による回転を行っているように見えます
せん断変形
せん断変形とは長方形を平行四辺形にするように、上辺と下辺を互い違いにずらす変形です
行列で書くとこう
\begin{pmatrix}
1&m\\
0&1
\end{pmatrix}
\begin{pmatrix}
x\\
y
\end{pmatrix}=
\begin{pmatrix}
x+my\\
y
\end{pmatrix}
中心からの距離で回転
まず、中心(cnt)を計算しますがこれは大したことがなく、中心からの距離(dist)も現在のピクセルとの距離を測っておけばいい
float2 cnt=float2(0.5,0.5);
float dist = float2(cnt,uv);
次にこれを用いて回転角とします。つまり
float theta=dist*0.01;
後はこれを用いて回転させるだけですが、最初に求めておいた中心点を中心にするのを忘れずに
\begin{pmatrix}
1&0&cx\\
0&1&cy\\
0&0&1
\end{pmatrix}
\begin{pmatrix}
cos\theta&-sin\theta&0\\
sin\theta&cos\theta&0\\
0&0&1
\end{pmatrix}
\begin{pmatrix}
1&0&-cx\\
0&1&-cy\\
0&0&1
\end{pmatrix}
\begin{pmatrix}
x\\
y\\
1
\end{pmatrix}
あとはこれをもとに、テクスチャを適用しましょう
テクスチャを適用

あとはこれですね。さすがにこれはプロシージャルじゃなくて外から持ってきましょう。プロシージャルでもいいんですが、流石にメンドクサイので資料の奴を真似て作りました(AIに作らせました)

やることは簡単です。前回までで、赤と青の模様を作っていますが、これは0~1を色に変換しただけの事ですので、0~1をUV座標のUとして取り扱えば勝手にマーブル模様になります
↓動画
https://x.com/CTsuchinoko/status/2005157060904997110
はい、ここまで進んだ段階で時間的に厳しくなったため、一旦ここで区切ります。
と言う事で、次回の③をしばらくお待ちください
書けました


