zoukankan      html  css  js  c++  java
  • 柏林噪声实践(二) 水与火,顶点纹理拾取

      在上文中,会发现,调用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 }
    着色器 三维noise实现动画

      初看起来,可能感觉和上面没什么相同点,我们来具体分析下,上面,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,不启用着色器相关代码就可以了。大家有兴趣,可以试着改变其中给出的一些参数,来达到一个各式各样的地面效果.

  • 相关阅读:
    随笔2
    随笔
    关于updateElement接口
    随笔1
    本地访问正常,服务器访问乱码 记录
    Redis (error) NOAUTH Authentication required.解决方法
    tomcat启动很慢 停留在 At least one JAR was scanned for TLDs yet contained no TLDs.
    微信公众号消息回复
    微信公众号 报token验证失败
    idea中web.xml报错 Servlet should have a mapping
  • 原文地址:https://www.cnblogs.com/zhouxin/p/3513041.html
Copyright © 2011-2022 走看看