柏林噪聲進階-advanced perlin noise
柏林噪聲進階
翻譯:則卷大明
原文連結:http://iquilezles.org/www/articles/morenoise/morenoise.htm
原作者 i.q.
一、簡介
在這裡我會寫一些關於柏林噪聲的有趣的事實。是的,非常難以置信,你可以在這裡找到其他地方沒有的資訊,少有人談起的關於柏林噪聲的導數,像怎麼解析式地對他們進行求解和怎樣使用它們。所以,為什麼不去做呢?讓我們來看看。
噪聲導數可以通過非常簡單的方法來達到稍微修改傳統的fbm形狀(fbm:分型布朗運動)的目的。注意,為了獲取更好的多樣性,依賴於一個規律性的fbm。
解析式的噪聲導數的計算比中心差分法更快更準確。基於分形求和函式(fractal sum function)(例如脊背噪聲,湍流圖等),解析式的法線可以用於完整的高度圖的計算。
這張圖是通過直接步進(raymarching)過程式函式渲染的(沒有法線圖,沒有材質),僅僅使用了漫反射光照和霧。(譯者注:相應的過程式函式也即fbm,可以到shadertoy中搜索i.q.的作品檢視)
二、導數
我們用n(x,y,z)來表示3d柏林噪聲。當然,它在任何數量的維度上都是相同的。我們採取常用的符號來表示導數:
(3d)噪聲函式基於一個給定晶格的隨機值的(3線性)線性插值。例如:
通常u(x)、v(y)、w(z)是一個立方或者五次方多項式的形式:
或者
n(x, y, z)可以展開成如下公式:
其中
現在導數可以被很簡單地計算出來,例如,對於x軸:
其中
或者,它依賴於你選擇了三次方還是五次方函式。
因此,它非常簡單地構造了一個函式,然後返回這個噪聲值,以及在一個函式中計算三個導數,這使得它相比較於中心差分法而言非常的高效,中心差分法慢了5倍。
void dnoise3f( float *vout, const float x, const float y, const float z )
{
int i, j, k;
float u, v, w;
iGetIntegerAndFractional( x, &i, &u );
iGetIntegerAndFractional( y, &j, &v );
iGetIntegerAndFractional( z, &k, &w );
const float du = 30.0f*u*u*(u*(u-2.0f)+1.0f);
const float dv = 30.0f*v*v*(v*(v-2.0f)+1.0f);
const float dw = 30.0f*w*w*(w*(w-2.0f)+1.0f);
u = u*u*u*(u*(u*6.0f-15.0f)+10.0f);
v = v*v*v*(v*(v*6.0f-15.0f)+10.0f);
w = w*w*w*(w*(w*6.0f-15.0f)+10.0f);
const float a = myRandomMagic( i+0, j+0, k+0 );
const float b = myRandomMagic( i+1, j+0, k+0 );
const float c = myRandomMagic( i+0, j+1, k+0 );
const float d = myRandomMagic( i+1, j+1, k+0 );
const float e = myRandomMagic( i+0, j+0, k+1 );
const float f = myRandomMagic( i+1, j+0, k+1 );
const float g = myRandomMagic( i+0, j+1, k+1 );
const float h = myRandomMagic( i+1, j+1, k+1 );
const float k0 = a;
const float k1 = b - a;
const float k2 = c - a;
const float k3 = e - a;
const float k4 = a - b - c + d;
const float k5 = a - c - e + g;
const float k6 = a - b - e + f;
const float k7 = - a + b + c - d + e - f - g + h;
vout[0] = k0 + k1*u + k2*v + k3*w + k4*u*v + k5*v*w + k6*w*u + k7*u*v*w;
vout[1] = du * (k1 + k4*v + k6*w + k7*v*w);
vout[2] = dv * (k2 + k5*w + k4*u + k7*w*u);
vout[3] = dw * (k3 + k6*u + k5*v + k7*u*v);
}
三、改進的fbm(分形布朗運動)
fbm(分形布朗運動)通常通過柏林噪聲函式的分形求和來實現。
通常w=1/2以及s=2或者其他接近的值。
(當s=2每個迭代稱作一個"octave倍頻程"-兩倍的頻率,就像音樂)。全部的導數是在這種情況下每個倍頻程的導數的加權和。如果你用柏林噪聲實現了一個脊背圖或者其他的變種,你可以以正確的方式輕鬆地聯合這些導數,除非你使用了一個不連續形狀的函式,例如fabsf()。(譯者注:如果此處不是很理解,可以參考維基百科中FBM的定義)
現在,在這個方案中的一個簡單的修改足以給地形增添許多豐富多變的形狀,有平坦地區和許多粗糙的區域。
圖左(使用u(x)三次方函式渲染,注意不連續的人工痕跡)
圖右(使用五次方渲染)
這個地形的計算使用下列分形求和(fractal sum)的變種:
float w = 0.5f;
float dx = 0.0f;
float dz = 0.0f;
for( int i=0; i < ioct ; i++ )
{
float n[3];
dnoise2f( n, x, y );[1]
dx += n[1];
dz += n[2];
f += w * n[0] / (1.0f + dx*dx + dz*dz); // replace with "w * n[0]" for a classic fbm()
w *= 0.5f;
x *= 2.0f;
y *= 2.0f;
}
左圖使用u(x)的三次方版本來計算,另外一個使用五次方。注意左圖中有一些不連續的人工痕跡,由於u(x)函式的二階導數是不連續的。
其他導數的使用也可以產生有趣的形狀:
// value noise, and its analytical derivatives
vec3 noised( in vec2 x )
{
vec2 p = floor(x);
vec2 f = fract(x);
vec2 u = f*f*(3.0-2.0*f);
float a = texture2D(iChannel0,(p+vec2(0.5,0.5))/256.0,-100.0).x;
float b = texture2D(iChannel0,(p+vec2(1.5,0.5))/256.0,-100.0).x;
float c = texture2D(iChannel0,(p+vec2(0.5,1.5))/256.0,-100.0).x;
float d = texture2D(iChannel0,(p+vec2(1.5,1.5))/256.0,-100.0).x;
return vec3(a+(b-a)*u.x+(c-a)*u.y+(a-b-c+d)*u.x*u.y,
6.0*f*(1.0-f)*(vec2(b-a,c-a)+(a-b-c+d)*u.yx));
}
其中iChannel0是一張256*256的噪點圖。返回的vec3型別中,x表示perlin-noise的值,yz值的計算則是perlin-noise的導數的一個應用。
原文作者未做權利宣告,視為共享智慧財產權進入公共領域,自動獲得授權。