2次元Value Noiseとその微分

タグについては多分この属性の人が見ることが多いんじゃないかな…と思ってつけました。
GLSLによる実装は後日できたら追記します…
※GLSLによる実装追加しました(動作確認はしてません)

iq氏のサイト
https://www.iquilezles.org/www/articles/morenoise/morenoise.htm
には3次元Value Noiseとその微分に付いて書かれていますが、2次元Value Noiseについては触れられていません。

直感的には、3次元における高さ方向に増える点について、2次元平面でのノイズ点を複製すれば補完結果は2次元Value Noiseと等価なものが得られる気がするのでそれをやってもいいのですが、無駄な計算するのもあれですし、あとはValue Noiseのしくみとかについて書いてある日本語記事が多くは無い気がしたのでせっかくだからまとめようと思った次第です。

# Value Noiseとは。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244678/bed49f95-fed3-d72d-f5e6-2821ca43ca2c.png)

Value Noiseとは求めたい点(上図だと赤点)の場所のnoiseの値**X**を、周りに存在する4つの格子点のnoise値を補完して求めることでいい感じになめらかなノイズを得る手法です。
(図では直線で補完していますが、実際は滑らかさを出すために3次や5次の関数で補完します)

Gradient Noiseより原理がわかりやすいのが好きです。
この周囲4点のノイズ値はどうやって出すねん！というのはGLSL 疑似乱数などでググってください。

# 二次元Value Noiseの計算と微分。

図を書くのが大変なのでここからは平面図で行きます。
まずnoiseを求めたい点p(x,y)について、

$$
x = I_x + F_x \\\
y = I_y + F_y \\\
(I_x,I_yはx,yの整数部分、F_x,F_yはx,yの小数部分を表す)
$$

と分解します。すると次のような図がかけます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244678/3b23f6da-6c53-32c0-66c5-2d19a1554f67.png)

ということで、Pの補完値を求めるためには、左下から反時計周りに4点

$A(I_x,I_y)$
$B(I_x+1,I_y)$
$C(I_x+1,I_y+1)$
$D(I_x,I_y+1)$

のノイズ値がわかれば良いことがわかりました。**点、A,B,C,Dのノイズ値をそれぞれa,b,c,dとします。**

さて、補完値を求めていきます。一気に補完するのは大変なので、まずはX軸で補完した後にそれらを更に補完します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244678/4c0f2ed5-1a1b-1036-35a8-468a76503f14.png)

ということで、上の図のようにAとBの中点$P_{AB}$とCとDの中点$P_{CD}を考えて、これらの値を求めていきます。
単純にaとb、cとdの値を混ぜれば良いのですがこのときのMix率として線形ではなく下に示すような関数のどちらかを使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244678/4426e850-a210-b298-7a2e-d9b83d96eef6.png)

青が3次式、橙が5次式での補完式です。見てもらえれば分かる通り(0,0),(0.5,0.5),(1,1)を通る関数になっています。この式のうちどちらを使うかですが、5次式の方が後に提案されたもので基本的に良いのでこっち使っておけばいいと思います。

$$
M(x) = 6x^5-15x^4+10x^3
$$

のとき、

$$
u=M(F_x)
$$
とします。$F_x$は前述の通り点Pのx座標の小数部分です。これをもとに値を補完します。
補完した$P_{AB}$でのノイズ値を$V_{AB}$と表す感じにすると、

$$
V_{AB} = (1-u)a+ub\\\
V_{CD} = (1-u)d+uc
$$

になります。(uaじゃなくて(1-u)aになるのに注意)

さてこの２点が求まると、次は$P_{AB}$と$P_{CD}$の間で補完をしてあげれば求めたい点Pでのノイズ値が求まるというわけです。

$$
v=M(F_y)
$$

とすると、

```math
\begin{align}
V_P &= (1-v)V_{AB}+vV_{CD} \\\
&= (1-v) [ (1-u)a+ub) ] + v[(1-u)d+uc]\\\
&= a+(b-a)u+(d-a)v+(a-b+c-d)uv
\end{align}
```

になります。これでValueノイズを求めることができました。次はこの微分値について求めます。
微分はchain ruleによって、

$$
\frac{\partial V_P}{\partial x} = \frac{\partial V_P}{\partial u}\frac{\partial u}{\partial x}\\\
\\\
\frac{\partial V_P}{\partial y} = \frac{\partial V_P}{\partial v}\frac{\partial v}{\partial y}
$$

となるのでこれを計算します。

$$
\frac{\partial V_P}{\partial u} = (b-a)+(a-b+c-d)v \\\
\frac{\partial V_P}{\partial u} = (d-a)+(a-b+c-d)u
$$

であり、

$$
\frac{\partial u}{\partial x} = \frac{dM(x)}{dx} = 30x^4-60x^3+30x^2\\\
\frac{\partial v}{\partial y} = \frac{dM(y)}{dy} = 30y^4-60y^3+30y^2
$$

のようにもう一つの式も定まるため、以上の２つをかけた上で$x=F_x,y=F_y$地点での値をひたすら代入すれば微分も求めることができます。

# GLSL実装

一応実装しました。動作確認などはまだしていないのであしからず…

```glsl

//2Dvalue noise

float vNoise2D(in vec2 x){
    vec2 p = floor(x);
    vec2 w = fract(x);

    vec2 u = w*w*w*(w*(w*6.0-15.0)+10.0);

    float a = RandomFunc(p+vec2(0.0,0.0));
    float b = RandomFunc(p+vec2(1.0,0.0));
    float c = RandomFunc(p+vec2(1.0,1.0));
    float d = RandomFunc(p+vec2(0.0,1.0));

    float k1 = b-a;
    float k2 = d-a;
    float k3 = a-b+c-d;

    return a+k1*u.x+k2*u.y+k3*u.x*u.y;

}

//2D value noise derivative
vec2 vNoise2D_deriv(in vec2 x){
    vec2 p = floor(x);
    vec2 w = fract(x);

    vec2 u = w*w*w*(w*(w*6.0-15.0)+10.0);
    vec2 du = 30.0*w*w*(w*(w-2.0)+1.0);

    float k1 = b-a;
    float k2 = d-a;
    float k3 = a-b+c-d;

    return vec2((k1+k3*u.y)*du.x,(k2+k3*u.x)*du.y);
}
```