• 柏林噪声实践(二) 水与火,顶点纹理拾取


      在上文中,会发现,调用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,不启用着色器相关代码就可以了。大家有兴趣,可以试着改变其中给出的一些参数,来达到一个各式各样的地面效果.

  • 相关阅读:
    如何学习自动化测试?
    Jenkins中,执行py文件,python找包的路径(找不到自定义包的问题解决)
    数据库的架构设计
    iOS密码框的实现方式
    UISearchController 的大坑
    <第三方>TGRefreshO按照QQ的刷新方式下拉刷新
    关于项目颜色和字体的宏定义
    <iOS 导航栏>第一节:导航栏透明方法实现代码
    <iOS小技巧>UIview指定设置控件圆角
    关于这次KPL春季决赛的感悟
  • 原文地址:https://www.cnblogs.com/zhouxin/p/3513041.html
Copyright © 2020-2023  润新知