在上文中,會發現,調用3維柏林實現海水的波動效果,實在是難為CPU了,在這里,我們用着色器Cg語言來把相關計算過程移到GPU,看下效果。
先說下,原來紋理我們拿來只限於給模型着色,而在現代GPGPU中,有個比較重要的概念就是,紋理就是數組,想想也對,紋理原來我們放的是RGBA值,那么如果我們用來存取一些別的數據,而不是RGBA,在着色器中把紋理里數據取出來,而不是用於給模型着色,那能實現什么樣的功能了。
本文中主要功能都在着色器中,故下面先給出着色器代碼,包含原來的noise計算,還有動畫與着色,可以對比上文F#代碼來看。

1 //Fragment shader to write color for each pixel 2 3 #define ONE 0.00390625 4 #define ONEHALF 0.001953125 5 6 float fade(float t) { 7 //return t*t*(3.0-2.0*t); // Old fade 8 return t*t*t*(t*(t*6.0-15.0)+10.0); 9 // Improved fade 10 } 11 12 float noise(float3 P,sampler2D permTexture) 13 { 14 float3 Pi = ONE*floor(P)+ONEHALF; 15 float3 Pf = P-floor(P); 16 // Noise contributions from (x=0, y=0), z=0 and z=1 17 float perm00 = tex2D(permTexture, Pi.xy).a ; 18 float3 grad000 = tex2D(permTexture, float2(perm00, Pi.z)).rgb * 4.0 - 1.0; 19 float n000 = dot(grad000, Pf); 20 float3 grad001 = tex2D(permTexture, float2(perm00, Pi.z + ONE)).rgb * 4.0 - 1.0; 21 float n001 = dot(grad001, Pf - float3(0.0, 0.0, 1.0)); 22 // Noise contributions from (x=0, y=1), z=0 and z=1 23 float perm01 = tex2D(permTexture, Pi.xy + float2(0.0, ONE)).a ; 24 float3 grad010 = tex2D(permTexture, float2(perm01, Pi.z)).rgb * 4.0 - 1.0; 25 float n010 = dot(grad010, Pf - float3(0.0, 1.0, 0.0)); 26 float3 grad011 = tex2D(permTexture, float2(perm01, Pi.z + ONE)).rgb * 4.0 - 1.0; 27 float n011 = dot(grad011, Pf - float3(0.0, 1.0, 1.0)); 28 // Noise contributions from (x=1, y=0), z=0 and z=1 29 float perm10 = tex2D(permTexture, Pi.xy + float2(ONE, 0.0)).a ; 30 float3 grad100 = tex2D(permTexture, float2(perm10, Pi.z)).rgb * 4.0 - 1.0; 31 float n100 = dot(grad100, Pf - float3(1.0, 0.0, 0.0)); 32 float3 grad101 = tex2D(permTexture, float2(perm10, Pi.z + ONE)).rgb * 4.0 - 1.0; 33 float n101 = dot(grad101, Pf - float3(1.0, 0.0, 1.0)); 34 // Noise contributions from (x=1, y=1), z=0 and z=1 35 float perm11 = tex2D(permTexture, Pi.xy + float2(ONE, ONE)).a ; 36 float3 grad110 = tex2D(permTexture, float2(perm11, Pi.z)).rgb * 4.0 - 1.0; 37 float n110 = dot(grad110, Pf - float3(1.0, 1.0, 0.0)); 38 float3 grad111 = tex2D(permTexture, float2(perm11, Pi.z + ONE)).rgb * 4.0 - 1.0; 39 float n111 = dot(grad111, Pf - float3(1.0, 1.0, 1.0)); 40 // Blend contributions along x 41 float4 n_x = lerp(float4(n000, n001, n010, n011), float4(n100, n101, n110, n111), fade(Pf.x)); 42 // Blend contributions along y 43 float2 n_xy = lerp(n_x.xy, n_x.zw, fade(Pf.y)); 44 // Blend contributions along z 45 float n_xyz = lerp(n_xy.x, n_xy.y, fade(Pf.z)); 46 return n_xyz; 47 } 48 49 50 float turbulence(int octaves, float3 P, float lacunarity, float gain,sampler2D permTexture) 51 { 52 float sum = 0; 53 float scale = 1; 54 float totalgain = 1; 55 for(int i=0;i<octaves;i++){ 56 sum += totalgain*noise(P*scale,permTexture); 57 scale *= lacunarity; 58 totalgain *= gain; 59 } 60 61 return abs(sum); 62 } 63 64 65 struct v_Output { 66 float4 position : POSITION; 67 float pc : TEXCOORD0; 68 } 69 ; 70 v_Output main(float3 position : POSITION, 71 uniform float4x4 mvp, 72 uniform float time, 73 uniform sampler2D permTexture) 74 { 75 v_Output OUT; 76 float3 pf = float3(position.xz,time); 77 //float ty = noise(pf,permTexture); 78 //水 79 //float ty = turbulence(4,pf,2,0.5,permTexture); 80 //float yy = ty * 0.5 + 0.5; 81 //火 82 float ty = turbulence(4,pf,0.6,4,permTexture); 83 float yy = ty * 0.2 + 0.5; 84 float4 pos = float4(position.x,yy,position.z,1); 85 OUT.position = mul(mvp,pos); 86 OUT.pc = yy; 87 return OUT; 88 } 89 90 91 struct f_Output { 92 float4 color : COLOR; 93 } 94 ; 95 f_Output fragmentShader(v_Output vin) 96 { 97 f_Output OUT; 98 //水 99 //float3 color1 = float3(0.5*vin.pc, 0.8*vin.pc, 0.9*vin.pc); 100 //火 101 float3 color1 = float3(0.98*vin.pc, 0.6*vin.pc, 0.15*vin.pc); 102 OUT.color = float4(color1,1.0); 103 return OUT; 104 }
初看起來,可能感覺和上面沒什么相同點,我們來具體分析下,上面,ONE是0.00390625,其實就是1/256,而sampler2D是一個二維紋理,而這個二維紋理中,RGB填充的是對應前面的梯度表,A對應的是前面的排列表,再來看看,是不是感覺生成noise有很多相似的地方。可能有仔細看這代碼的同學吧,然后會發現,原來的梯度表和排列表應該都是一維啊,你這給的是二維紋理,對應不上啊,並且,原來獲取梯度索引用的是p.[p.[x0] + y0]這種,而在這個noise,用的是tex2D(permTexture, Pi.xy).a,明明差別很大啊?不要急,我們來看,這個二維紋理的生成過程。

1 //生成一張圖片RGBA源,用於測試 2 member this.CreateTexture() = 3 init() 4 let size = B * B 5 //生成二維紋理GRBA,紋理長與度分別為B。 6 let data = Array2D.init size 4 (fun yx i -> 7 let y = yx / 256 //每列索引 8 let x = yx % 256 //每行數據索引 9 let offest = yx * 4 10 let vp = p.[p.[x] + y] 11 let vg = pg.[vp] * 64.0 + vec3(64.0,64.0,64.0)//由-1到1,映射成0-128 12 if i = 0 then byte (vg.X) 13 else if i = 1 then byte (vg.Y) 14 else if i = 1 then byte (vg.Z) 15 else byte (vg.X) 16 ) 17 data
看到這里,大家是不是明白了上面二個問題,他把原來得到梯度索引的方法映射到一個二維數組上了。注意一點,顏色應該是0-256,但是這里是映射成0-128,所以在着色器中乘以的是4,得到的梯度又變成-1到1了,我前面所說映射成-1到3,應該是錯的,梯度應該是定了范圍到-1到1之間,這里如果有具體研究過的同學,歡迎告知。用了不使之看起來全是一個顏色,看不清變化的波形,故在頂點着色器中,給出一個noise值,用於片斷着色器中顏色賦值。
在着色器中,紋理已經不同以前是給物體着色,而是用於計算noise的梯度表與排列表,然后用模型的頂點x,z來做索引,這種用法也叫做頂點紋理拾取。最后在其中分別模擬了二種效果,一種是急流河水,一種是奔騰火山,用的是PerlinNoise,也就是着色器中的turbulence中指定不同的參數,具體分析前面也有說,這就不重復了。
最后,給出在form中更新Cg的參數的相關代碼。

1 override v.OnRenderFrame e = 2 base.OnRenderFrame e 3 GL.Clear(ClearBufferMask.ColorBufferBit ||| ClearBufferMask.DepthBufferBit) 4 let mutable lookat = Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY) 5 GL.MatrixMode(MatrixMode.Modelview) 6 GL.LoadMatrix(&lookat) 7 8 cgContext.EnableProfile() 9 let mv = lookat 10 mv.Transpose() 11 let mutable p = Matrix4.Identity 12 GL.GetFloat(GetPName.ProjectionMatrix,&p) 13 p.Transpose() 14 let mvp = Matrix4.Mult(p,mv) 15 cgContext.VertexParameter("mvp").SetMatrix(MatrixToArray1 mvp) 16 cgContext.VertexParameter("permTexture").EnableTexture() 17 let date = float (DateTime.Now.Millisecond /200) 18 cgContext.VertexParameter("time").Set(date) 19 plane.Draw() 20 cgContext.VertexParameter("permTexture").DisableTexture() 21 cgContext.DisableProfile() 22 23 v.SwapBuffers()
其中,傳入mvp,前面的模型導入有詳細說明,這里也不多說,然后是上面生成的紋理,最后是時間,這里200,差不多就是每秒更新5次,大家可以自己更改這個數,使之達到自己滿意的效果,下面給出急流河水與奔騰火山的效果圖。
上下分別水與火山二楨動畫,最后大家看下,是不是一點延遲都沒有,把noise的代碼移到GPU后,腰不疼了,腳不酸了。
完整代碼 http://files.cnblogs.com/zhouxin/CgTest.zip
和以前一樣,其中EDSF前后左右移動,鼠標右鍵加移動鼠標控制方向,空格上升,空格在SHIFT下降。如果想看上文的效果,也是CPU生成noise版本,只需要在上面的OnRenderFrame,不啟用着色器相關代碼就可以了。大家有興趣,可以試着改變其中給出的一些參數,來達到一個各式各樣的地面效果.