Chango的數學Shader世界(十九)RayMarch雲層(一) —— 造型,優化

目的:

在ue4中RayMarch雲層效果。

本篇只考慮雲層造型。

 

參考:

1.IQ在shadertoy上的Clouds和其引用

https://www.shadertoy.com/view/XslGRr

https://www.shadertoy.com/view/4sfGzS

2.shadertoy上的Simplex3d

https://www.shadertoy.com/view/XtBGDG

 

觀察:

目前大部分shader對於雲的形狀沒有物理層面上的推導,都使用三維噪聲然後利用fbm。

 

分析:

假設我們現在使用Simplex 3d噪聲,結合fbm造出雲的形狀,那麼很簡單。Simplex noise在此不說,網上都有。

float fbm(float3 p)
{
	float f;
    f  = 0.50000*simplex3D( p ); p = p*2.01;
    f += 0.25000*simplex3D( p ); p = p*2.02; //from iq
    f += 0.12500*simplex3D( p ); p = p*2.03;
    f += 0.06250*simplex3D( p ); p = p*2.04;
    f += 0.03125*simplex3D( p );
	return clamp(f,0,1);
}

float GainCloud(float3 pos,float CloudSample,float maxHeight,float a,float b)
{
	if(pos.z>0&&pos.z<maxHeight)
	{
		return mfbm(pos/CloudSample,a,b);
	}
	else
	{
		return 0;
	}

但問題很明顯:

在我的筆記本1070上,僅ray雲層就基本在20fps左右。更別說應用到複雜場景下。

我們在shader中實時手動計算出Simplex3d的操作,稱作Procedual的。

相反的,我們知道一個固定的3d pos對應了固定的simplex3dnoise。能否離線儲存下來,利用LUT(LookupTable)查詢之類的呢?

對於小型雲朵,顯然可以使用2d分層紋理,或使用3d紋理。

但如果我希望一個大場景,例如主角在穿梭雲層,這種就不適合了。大場景離線存儲,空間又是個問題。

然後,IQ大神的操作讓我看傻眼了:

儘管可能由於光照模型的原因,有些模糊,但穿梭在其中沒有明顯的重複感。

最重要的是,它在webGL上都能跑到40-60FPS,如果刪1,2個fbm,還能更快。

1.IQ的3d noise

從單張rand圖上生成。

float msimplex3D(float3 x,float a,float b)
{
	float3 p = floor(x/a);
	float3 f = frac(x/b);
	//chango: cubic interpolation
	f = f*f*(3.0-2.0*f);
	
	float2 uv = (p.xy+float2(37.0,17.0)*p.z) + f.xy;
    float2 rg = Texture2DSample(Material.Texture2D_0,Material.Texture2D_0Sampler,(uv+0.5)/256).yx;
	return -1+2*clamp(lerp( rg.x, rg.y, f.z ),0,1);
}

其中關鍵在於:

1.
uv = (p.xy+float2(37.0,17.0)*p.z) + f.xy;
2.
(uv+0.5)/256
3.
.yx
4.
lerp( rg.x, rg.y, f.z )

而且,被採樣貼圖必須是一張256*256的rand貼圖,g通道爲r通道加上(37,17)。

原文原文引用的shadertoy裏一大堆評論,但沒有一個真正說清楚這部分的,iq也沒有解釋。有些關於生成貼圖的算法的評論是錯誤的。網上也找不到解釋。

還希望知道原因的讀者賜教。

我將我用ue4材質系統畫的貼圖放上來,(讀者要是實現,追求精細最好還是得自己畫):

從大致的思路來講,單位立方體(縮放後)最底下和最上面是uv有偏移的noise圖,中間再通過f.z插值。最後*2-1來減少雲的密度。

無*2-1:

有*2-1:

 

至於爲什麼這個偏移是(37,17),因爲shadertoy系統提供噪聲圖就是這樣偏移來的。

至於爲什麼要保證r,g兩通道也保持同樣偏移。我沒思考出原因。

對於這個0.5,去掉雲會變得不連續。可能這個(0.5,0.5)+xxx跟Warp模式下紋理尋址有關。

應該是用來作邊界融合。因爲如果近從我們原來生成的rand圖生成noise,並不能保證上下左右無縫拼接。而像下圖一樣,將邊界包含在rand圖內後,由於生成noise圖時會融合,邊界連續起來。

我將0.5改爲0.1,0.3,效果也一樣,基本上證明了我的猜想。

講道理應該放在/256以外。我改成

(uv)/256+0.1

也不影響。因爲只要x,y分別都有一點偏移,橫邊界和豎邊界就能包含進來。所以IQ原來的代碼也沒錯,但是太蛋疼了。

 

 

--更新

我找到了一篇類似的,可以參考,並對比理解iq的代碼。

https://stackoverflow.com/questions/30596055/glsl-using-a-2d-texture-for-3d-perlin-noise-instead-of-procedural-3d-noise

https://www.shadertoy.com/view/MtBGDt

對比之下,iq不需要二次tex2D,效率提升很多

對比之下,IQ的p.xy+f.xy就是上圖的p.xy,也就是未偏移UV。

而g相對r的偏移對應了b_offset的+1偏移

 

結語

總的來說,ray雲層造型,目前多數是fbm。從生成3d噪聲上而言,procedual式,或離線緩存式是正解,但分別有計算量太大和空間量太大的缺點。IQ的2d紋理方法是一種hack,目前還未完全理解。它在實時渲染上的表現是完美的。

下節完善雲層的光照模型。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章