2025.04.06

[p5.js]パーリンノイズからカールノイズを生成して、簡易流体シミュレーションを行う

[p5.js]パーリンノイズからカールノイズを生成して、簡易流体シミュレーションを行う

Warning: 数式について、正確なレンダリングができていません。LaTeXで書かれているので読み取ってください。

はじめに

私のウェブサイトのトップページにおける背景が寂しかったので、流体シミュレーション的な表現を埋め込みたいと考えていた。

調べていたところ、この記事においてthree.jsを使っている流体シミュレーションを行なっていた。 参考にしようと思い、閲覧していたところ、shaderの知識が必要とされた。加えて、処理が重そう(印象)だったので別のアプローチを試みた。

そこで、カールノイズなる、流体の動きをシュミレーションできるようなノイズがあることを確認した。 今回はこのアプローチで行こうと思う。

実装したもの

▶︎ カールノイズによる流体シミュレーション

参考にした論文

Curl-Noise for Procedural Fluid Flow

実装していく / The Method

基本的には論文に沿って進めていく。

基本の実装

前提の共有から始める。

パーリンノイズを出力する関数 N(x,y)\mathrm{N}(x, y) について、 もちろん入力はベクトル、返り値はスカラー。

今度、速度場 v\overrightarrow{v} を定義する。 速度場 v\overrightarrow{v} には、ポテンシャル場 ψ\overrightarrow{\psi} (後ほど定義する)のカールを使用する。

今回は2次元で行う。 2次元でのポテンシャル場は単純なスカラー場(今回はパーリンノイズを用いる)を使う。

つまり、

ψ(x,y)=N(x,y)\psi(x, y) = \mathrm{N}(x, y)

速度場 v\overrightarrow{v} には、ポテンシャル場 ψ\overrightarrow{\psi} (後ほど定義する)のカールを使用する。

より、

v(x,y)=(δψδy,δψδx,)\overrightarrow{v}(x, y) = \left( \frac{\delta \psi}{\delta y}, - \frac{\delta \psi}{\delta x}, \right)

もちろん、偏微分を実装する時は、離散的に行わなければならない。 ここでは有限差分近似(中心差分)を行う。

そして、カールを使用していることにより、常に 速度場の発散が0となる。これは、非圧縮性流体を表現する。

[応用] アニメーション対応させる

パーリンノイズを時間に応じて変化させることで対応する。

パーリンノイズを出力する関数を N(x,y,t)\mathrm{N}(x, y, t) とする。(時間に関する入力を追加した。)

おわりに

カールノイズによる流体シミュレーションを実装した。 完璧に主観だが、実際の流体のような滑らかさで満足いく水準のシミュレーションはできなかった。 パラメタを弄ることでもう少しリアルな表現ができるかもしれない。Ï 改善の余地あり。

付録

p5.jsで記述した、カールノイズを生成する関数

p.noise関数は位置情報と時間を引数に取り、スカラーを返す。

//-------------------------------------
// GLobal Variables
//-------------------------------------

// Noise Map
//---------------------------------------
let isNoiseMapDisplayed = false;
let noiseScale = 1 / 1000;
let noiseStrength = .8;
let noiseOffset = p.createVector(0, 0);

// Time 
//---------------------------------------
let time = 0;
let timeScale = 0.001;

//-------------------------------------
// Create Noise Map
//-------------------------------------

/*
 * @param 
    - position: p5.Vector (The unscaled position)
 * @return
    - curl: p5.Vector
 * @description
    - Generates a curl noise vector based on the given position.
 */
function createCurlNoise(position) {
    let epsilon = 0.001;

    let n1 = p.noise(
        (position.x + epsilon) * noiseScale + noiseOffset.x,
        position.y * noiseScale + noiseOffset.y,
        time
    );

    let n2 = p.noise(
        (position.x - epsilon) * noiseScale + noiseOffset.x,
        position.y * noiseScale + noiseOffset.y,
        time
    );

    // Apply the centered difference formula
    // to calculate the gradient in the x direction
    let gradientX = (n1 - n2) / (2 * epsilon);

    let n3 = p.noise(
        position.x * noiseScale + noiseOffset.x,
        (position.y + epsilon) * noiseScale + noiseOffset.y,
        time
    );

    let n4 = p.noise(
        position.x * noiseScale + noiseOffset.x,
        (position.y - epsilon) * noiseScale + noiseOffset.y,
        time
    );

    // Apply the centered difference formula
    // to calculate the gradient in the y direction
    let gradientY = (n3 - n4) / (2 * epsilon);

    let curl = p.createVector(
        gradientY,
        -gradientX
    );

    curl.normalize();
    curl.mult(noiseStrength);

    return curl;
}