1. 程式人生 > >unity3d 製造自己的水體water effect

unity3d 製造自己的水體water effect

first,I wish you a happy new year, and study in spring festival’s eve means you are hardworking,haha. I write in two languages. One passage write in Chineseone passage translate into English. My English is poor., If I write some thing wrong, welcome to correct my article.
首先祝各位看官新春快樂,能在大年三十看博文學習的都是勤奮的人。
我用了兩種語言,一段英文一段中文。我英語不太好,如果有英語語法錯誤,或者寫的不對的各位可以告訴我及時改正
I study some about water lately. Looks like this in the gif; It looks not bad, but it is incomplete and regret.This shader is surface shader, used the tessellation technology by DX11.Because of used the  tessellation ,this shader’s vertex function can’t pass parameter like position and normal, then, caused the water’s reflect light inaccuracy、can’t specular other things ,etc. The reason that can’t specular other things is can’t analysis reflection map. But just now I try to write a fragment shader that can use the tessellation, and succeed, then the shader can pass parameter. I will write it in the second blog article, this blog article just talk about water waves.
最近研究了一下水體,效果如下圖gif所示,乍看感覺還可以,但是有很多殘念,這個例子是用surface shader寫的,運用了曲面細分等技術。由於用了曲面細分,所以surface shader的vert不能傳出值,所以不能傳出頂點座標和normal值,造成了反光不準確,而且不能反射(大殘念,水體不能反射= =),不能反射的原因是不能解析指令碼傳入的反射貼圖reflection map。 但是寫本文的同時突然試了一下 fragment shader,居然好用,這樣也能傳出值了,就在本文的(二)中研究,今天的(一)只研究水體的波動。

Most of maves are caused by the action of wind, it’s better to use circular waves in the pond scence.we just talk about the directional waves.
大多數的波動是由於風吹動引起的,對於池塘中的水用圓形波更好一點,我們只討論方向波

Mentioned waves, we all think of sin、cos ,etc. This shader also used them.
if we just use sin , that effects looks awful.
說起波動,大家想到的肯定是sin,cos,等函式,我們的水體也是用它做一些變換。
如果單單是sin 未免有些單調,看起來也不真實。

so, in this shader, I used the Displacement mapping that I write a blog article about it before.
That can add some other waves in water.
所以本例中加入了之前寫過曲面細分中的貼圖置換(Displacement mapping),對水面加入一些雜波。
But why to use the tessellation?
our water in unity is a model. The plan model’s has little points, may be 100++. Used tessellation can add much more points, make the waves more smooth. The figure below is the waves not used tessellation.Looks like jag.
為什麼要加入曲面細分呢?
我們的水面是一個平面,也就是plane,unity中的plane模型頂點數顯而易見,看起來貌似100++,加入了曲面細分,頂點數就是原有的數倍,波浪看起來精緻圓滑,
下圖為未經曲面細分的水面,(= =;這不就是鋸齒嗎);

If you don’t want the real wave effect, you don’t need tessellation, like unity’s water4, water4 not wave truly, just some effects like normal, reflect ,etc. Brain always be cheated by eyes.

about tessellation you can see this I write before
如果你不想要真實可見的波浪效果,你大可不用曲面細分,如unity自帶的那種水體water4,water4等沒有真正的波動,只是normal,反光等對你眼睛的一種欺騙。
關於unity曲面細分的詳細可見博主之前的這篇文章



Above I talked my shader is use sin, there are also many good waves like this. I refer to the water in 《GPU Gems》 , it used Gerstner Waves, its reality. The real waves have sharper peaks and wider troughs.
剛剛說我的例子用的是sin波,還有很多很好的波形,參考了一下GPU gems中的水體,書上用的是Gerstner波,理由是—真實,真實的浪波比較尖,波谷比較寬

Gerstner’s function is:
Gerstner波的函式是:

then I try this

然後我就試了一下

That’s pretty good, the waves has many transformation, sharper peaks and wider troughs, with some small waves. Cause of used tessellation, the plane’s mesh and vertex is disappeared in unity, may be that is a bug in unity.
效果巨好,變化也多,明顯看出浪頭很尖,還有很多小浪波,由於是用了曲面細分導致水平面不顯示網格頂點,貌似是unity的bug,殘念。。
Here are the code, Cause of that can’t pass parameter, the water not have graet highlight, shade inaccurate, looks bad.
給出程式碼,有興趣的看官可以研究下Gerstner波。
因為surface shader不能傳出normal資訊,沒有高光,明暗的顯示,再好的波也看不出來= =;

		float wave(float x, float z, float timer)
		{
			float y = 0;
			float oct = _OCT;
			float fac = _FAC;
			float d = sqrt(x * x + z * z);// length(float2(x, z));
			for (oct; oct>0; oct--)
			{
				y -= fac * cos(timer * _SP + (1 / fac) * x * z * _WS);
				fac /= 2;

			}
			return 2 * _VS * d * y;

		}


Then again to talk about sin, although less detail than Gerstner. But I think that’s also great. It’s not troublesome.
再說回sin波,前面的例子是sin波的,沒有Gerstner波變化細緻,但我也很滿足了,本例的實現不需要像Gerstner波一樣需要疊加,也沒有Gerstner波那麼複雜,
首先讓我們研究下波:
1.    Wavelength(L): the crest-to-crest distance between waves;
2.    Amplitude(A): the height from the water plane to the wave crest.
3.    Speed(S):the distance the crest moves forward per second.
4.    Direction(D):the horizontal vector perpendicular to the wave front along which the crext travels.
1.    波長(L):波峰到波峰之間的距離。
2.    振幅(A):從水平面到波峰的高度。
3.    速度(S):每秒鐘波峰移動的距離。
4.    方向(D):運動方向,垂直於波陣面的水平方向;
We can use this to defined the wave:
 我們就可以通過這些引數定義波了
 
the code:
主要程式碼如下:

float time = _Time * _Speed/2;
float waveValueA = sin((v.vertex.x + v.vertex.z) * _Frequency + time * _Frequency) * _Amplitude;


That’s very easy, right?
很簡單對吧

Simply put how to find the normal ,although can’t pass it,
The normal is given by the cross product of the binormal and tangent,as:
N = B * T;
雖然傳不了normal值求normal也沒什麼意義,簡單說下求法,
求出副法線B和切線T向量,分別為x,z方向的偏導數,
向量N = B * T;
然後就ok了。
我們得到了普通的波如下:

Then, we used the Displacement mapping.
We used the texture in unity’s water ----water4 to be our displacement map.
之後就要雜波登場了,貼圖置換(Displacement mapping)。
我們用unity自帶water4中的貼圖作為貼圖置換的資源

You can extract the texture in the water4.
Then is the blog article I write before about tessellation.We define a _Displacement value to control the other waves’s amplitude.
Define a _SpeedStrength value to control the other waves’s speed and direction.
看官們可從water4中自取。
然後就是之前曲面細分的內容了,我們設定隨法線偏移度設為外部變數_Displacement來控制雜波的振幅,
又在外部定義了個引數_SpeedStrength來控制雜波移動速度和移動方向,
code:
此處程式碼如下:

			float d = tex2Dlod(_MainTex, float4(v.texcoord.xy+_SpeedStrength.xy*_Time.x,0,0)).r * _Displacement;			
			v.vertex.xyz += v.normal * d;


here is the effect:
之後就可以看效果了

That is the end effect about waves.
就是本例最終實現的波形。

正題結束。
Cause of the real waves have sharper peaks and wider troughs. I try to change the sin waves.That is 1- abs(sin);  
由於真實的浪波比較尖,波谷比較寬,博主右對sin波進行了小變換。就是1-(sin的絕對值),

Then we have the waves like the figure above, that’s more better than “Just sin”.
得到了這種波形,比普通的sin波好上許多。。。。
I will gonna to try the fragment shader water wish to have a better effect.
博主會繼續研究fragment shader爭取得到更好的效果,(= =;開學在即,在模電的煎熬中。。。逃太多了)
The waves here is over, the surf function will write in the second blog article, maybe I will write things about the refraction.
In the end, I wish you a happy spring festival, again.
波的研究結束了,具體的著色surf函式會在(二)中詳細討論,有關折射反射等等;
最後祝大家過個愉快的春節;

                                                                       ------------------------------by wolf96 

PBR:

講求基本演算法

plus篇

之前一直在用unity4.6寫shader,終於下定決心換unity5,然後發現,unity5的渲染比4強太多,
這次完成之前2月份自制拖著沒解決的normal問題,算出normal真的很簡單。
本次水體分別使用兩種波動演算法,一個是ShaderX6 中Szecsi 和 Arman的演算法,另一個是Gpu gems1裡的Gerstner波演算法。然後再用PBR渲染。然後還是曲面細分。
這次的效果:


Szecsi & Arman波動演算法

首先來看ShaderX6 中的波動演算法:
求波動速度,λ為波長,波長就是波峰到波峰之間的距離,速度v為每秒鐘波峰移動的距離。
    
然後是相函式,k為波動方向,運動方向,垂直於波陣面的水平方向,p為position,t為時間(_Time.y)
 
求得一個波的位移S,a為振幅,振幅就是從水平面到波峰的高度。
 

最終我們的水是多個波組合在一起的,所以最終結果為:
    

博主整共合了4個波

關鍵程式碼如下:

                               vv = sqrt(_G * _Lambda / (2 * _PIE));
				psi = 2 * _PIE / _Lambda *(dot(v.vertex.xyz, _K.xyz) + vv*_Time.y);
				s = lerp(-cos(psi), sin(psi), _A)*0.05;
				p.y += s;
				vv = sqrt(_G * _Lambda2 / (2 * _PIE));
				psi = 2 * _PIE / _Lambda2 *(dot(v.vertex.xyz, _K2.xyz) + vv*_Time.y);
				s = lerp(-cos(psi), sin(psi), _A2)*0.05;

				p.y += s;

				vv = sqrt(_G * _Lambda3 / (2 * _PIE));
				psi = 2 * _PIE / _Lambda3 *(dot(v.vertex.xyz, _K3.xyz) + vv*_Time.y);
				s = lerp(-cos(psi), sin(psi), _A3)*0.1;

				p.y += s;

				vv = sqrt(_G * _Lambda4 / (2 * _PIE));
				psi = 2 * _PIE / _Lambda4 *(dot(v.vertex.xyz, _K4.xyz) + vv*_Time.y);
				s = lerp(-cos(psi), sin(psi), _A4)*0.1;

				p.y += s;

				v.vertex.xyz = p.xyz;</span>

產生了波動效果:




加上之前的pbr,可以模擬各種液體

牛奶

血,等等



但是由於曲面細分,離近了看水面上還是有小面的細節,這點有待解決。

Gerstner波

然後Gerstner波

 

關鍵程式碼如下:

  float wave(float x, float z, float timer)  
    {  
        float y = 0;  
        float oct = _OCT;  
        float fac = _FAC;  
        float d = sqrt(x * x + z * z);// length(float2(x, z));  
        for (oct; oct>0; oct--)  
        {  
            y -= fac * cos(timer * _SP + (1 / fac) * x * z * _WS);  
            fac /= 2;  
      
        }  
        return 2 * _VS * d * y;  
      
    }  </span>

曲面細分帶來的小面不是很明顯,而且面不用分得很細就能看到平滑的效果

normal的生成

最後我們講講normal的生成,
Normal的生成有複雜的和簡單的,但本質上都是求偏導。
先看複雜的,來自GPU Gems1的42章
首先求出Jacobian
 




舉個例子(直接截圖了^ _ ^):
 
 
是不是很麻煩。。

法線本質上的求法就是這樣的,但是,我們有函式:ddx,ddy這兩個神器
我們只需要求出世界座標點,worldpos
就可以簡單地求出法線:
N = cross(ddx(worldpos),ddy(worldpos))
方法很簡單,不過別忘記,本質上還是上面的一大串。。。
切記:ddx,ddy只能在fragment shader中使用。

只有漫反射的水體如下:

                            --------   by wolf96 、