急いでいる方向け結論
本文内使用バージョン:Blender 2.93.4
上図のようにノードを組むことで、[0,1][0,1]のいわゆる通常のUV座標に対して上下左右にシームレスにつながるノイズテクスチャをマッピングすることができます。
下図は、上のノードを用いたマテリアルを適用した正方形のモデルを、縦横に3つずつ、計9個並べた結果です。ノイズテクスチャが継ぎ目なく繰り返されていることがわかります。
この方法はノイズテクスチャ以外にも、ボロノイテクスチャ、マスグレイブテクスチャ(、ホワイトノイズテクスチャ)といった、4Dベクトルを入力として使用可能なテクスチャにも利用できます。ホワイトノイズテクスチャにはシームレスもクソもない。
詳細な説明は以下で行います。
はじめに
この記事はtraPアドベントカレンダー2021 8日目(11/20)の記事です。
こんにちは、@d_etteiu8383です。
突然ですが本日11月20日が何の日だかご存じでしょうか?そうです、真中らぁらさんの誕生日ですね。
プリパラといえば3Dダンスシーン、3DといえばBlender、ということで今回はBlenderのマテリアルノードでテクスチャをループさせる方法について紹介します。
目標
この記事の目標は、Blenderのマテリアルノードで「テクスチャを上下左右にループさせて(境界でも滑らかに接続されている)格子状にタイリングする」ことです。具体的な例を下図で示します。
まず、正方形のモデルに[0,1][0,1]の"普通のUV座標"を用いてノイズテクスチャをマッピングすると以下のようになります。




ごく普通の、よく見るノイズテクスチャですね。通常ならこれを用いて制作を進めてもなにも問題ありません。
が、極まれに"上下左右にループさせたい"場合があります。"普通のUV座標"を使ったノイズテクスチャを、下図のように並べると...

少しわかりにくいかもしれませんが、つなぎ目の部分が滑らかでなく、筋が入っているように見えています。
このような筋を生じさせずに、つまり境界でも滑らかな状態を保ちつつ、繰り返しパターンを作ることが今回の目標です。

考察
いきなりBlenderでの実装に入る前に、まずはどのような考え方が必要になるのかを確認しましょう。
1次元でループさせたい場合
いきなり平面(=2次元空間)でループさせるのは少し気が早そうです。まずは1次元のデータをループさせる方法について考えてみましょう。
具体的には、
「0から1の値を"一つ"受け取り(=1次元)、対応するノイズを出力するテクスチャ」をループさせる
ことを考えます。

つまり上図のようなノイズをイイ感じにループさせたいわけです。
まず思いつくのは、単純にこれを繰り返し並べる方法だと思います。

0から1に進んだ後、1から0まで一瞬でスキップするイメージです。しかしこれでは両端の値が一致せず、不連続になってしまいます。"普通のUV座標"を使ったノイズを単に繰り返したときに見えた"筋"の正体はこいつだったんですね。
両端の値を一致させる方法として、左右反転して繰り返し並べる方法があります。

0→1の向きのノイズの後に、1→0の向きのノイズを並べています。0から1まで進んだ後、1から0まで進むイメージです。これを繰り返せば、両端の値は一致するので不連続ではなくなります(連続になる)。が、境界部分で滑らかであることは保証されません。上図でも、"端"だった部分(0または1の所)が尖っています。これでは依然として"綺麗なループ"とはいえません。
で、いろいろ考えてみると、"単純に"一つの値を受け取ってノイズを返していては上手いことループできないことに気付きます。
どうしましょう...ループさせたいのにできないなんて......
ループ...loop......loopってなんだ...?
https://www.google.com/search?q=loop
ループは...輪......!?
そうです、なめらかにループさせたいのですから、なめらかな輪っかを用意する必要があるんです。
今まで0から1の値を1つ取る数直線上のノイズを考えていたので、当然0と1は離れ離れになっていました。結果的に、0から1まで進んだ後、1から0に向かってうまいこと戻る方法を考える必要がありました。
しかし、下図の様な"円環上でのノイズ"を考えれば、1から0に滑らかに移る方法を考える必要はありません。1と0が最初から繋がっているのですから。

ということで、ここからは円環上のノイズの生成方法を考える必要があります。
...考えましたか?答えはこう。
- "2次元の"ノイズテクスチャを用意して
- 円環状に切り抜く
これでぐるっと一周繋がったノイズの輪っかができました。この円環上のノイズが、無限にループさせることのできる滑らかなノイズとなります。
より具体的に記述すると、
2つの入力を受け取って1つのノイズを返す関数(=2次元のノイズテクスチャ)に対して、からの値を取るパラメータ(1次元の入力)を使って、
を考えれば、円環状のノイズを得ることができます。円のパラメータ表示を使って2次元空間内の単位円部分を利用している感じです。2次元のテクスチャを使用していますが、結果的には「0から1の値を"一つ"受け取り(=上式における)、対応するノイズを出力するテクスチャ(=上式における)」をループさせることに成功しています。
あまり分かりやすい例ではないかもしれませんが、イメージとしては下図のような感じです。2次元のノイズテクスチャ上を円状に移動する小球の高さをノイズとして取り出しています。
2次元でループさせたい場合
1次元でループするテクスチャを作りたい場合、2次元のテクスチャを利用する必要があることがわかりました。では、本題の2次元でループするテクスチャを作るには何が必要でしょうか?
1つの軸でループさせるのに2次元が必要で、今度は2つの軸でループさせたいので、4次元のテクスチャを利用する必要があります。
より具体的に記述すると、
4つの入力を受け取って1つのノイズを返す関数(=4次元のノイズテクスチャ)に対して、からの値を取るパラメータと(=2次元の入力)を使って、
を考えれば、2次元空間でループするテクスチャを得ることができます。
Blender内での実装
上の式をそのままBlenderのマテリアルノードで表現すればOKです。
UV座標(=2次元の入力)に対し、各値の倍をやに通して4次元のノイズテクスチャに渡すことで、シームレスにタイリングできるノイズの完成です。

おまけ
応用例
今回は縦横への物理的な接続をループさせる場合を考えましたが、応用すれば時間軸のループも簡単に実現できます。時間軸で1つの"輪っか"を作っているイメージ。


また、4次元の入力が行えるテクスチャであればループが行えるので、ボロノイテクスチャとマスグレイブテクスチャでも同様にループさせることが可能です。


ただし、ボロノイテクスチャでは"4次元空間を曲面で切り取ったボロノイテクスチャ"となっているので、ボロノイ境界は直線になりません。
数学的背景
そもそも二次元平面の両端をループさせる(=つなぎ合わせる)というのはどのような操作でしょうか?実際にやってみましょう。
ドーナツができました。より正確にはトーラスですね。つまり先ほどまでの議論は"どうすればきれいなトーラスを作ることができるか"という議論とほぼ同じでした。
最終的に得られた
4つの入力を受け取って1つのノイズを返す関数(=4次元のノイズテクスチャ)に対して、からの値を取るパラメータと(=2次元の入力)を使って、
を考えれば、2次元空間でループするテクスチャを得ることができます。
という結果は、実はClifford torusと呼ばれるトーラスの定義と(ほぼ)同じでした。
上の動画で示した平面からトーラスへの変形では、その表面が伸縮していることがわかります。はじめは綺麗だった市松模様が、最終的には場所によって大きさの異なる長方形になっていますね。この例が示すように、3次元空間内でトーラスを作ろうとするとどうしても"綺麗ではない"トーラスしか作れません。[1]一方4次元空間内では"綺麗な"トーラスを作ることができ、その一つが先ほど挙げたClifford torusです。だから4次元のテクスチャが必要だったんですね。
この辺りの議論はトーラス - Wikipediaとかでも紹介されているのでぜひご覧ください。
おわりに
最後までお読みいただきありがとうございました。明日のtraPアドベントカレンダー2021担当者は@ebiさんです。楽しみ~
"平坦"であることをここでは"綺麗な"と呼んでいます。 ↩︎