BoomBeach海水效果实现

你可以直接解压boom beach的ipa(安卓上就是apk文件)里面assets\shader目录里有对应代码实现(water.vsh,water.fsh).我是很久没看shader这块了,具体实现原理你直接搜一下关于water shader的实现,原理大概都差不多,看懂了原理再看他的shader代码也就可以实现出来了。
这也是OpenGL ES 2.0不好的地方,shader无法先离线编译为二进制码,必须运行时实时编译(我记得dx是可以的,所以成熟点的引擎都会吧shader直接预先编译打包掉),所以shader的源码都是可见的。这么看来这个缺点有时也是优点了~~
   

刚接触BoomBeach的时候,就十分欣赏他的海面效果,决心拿下SC的这项技术,经过几个月的努力总算有了成果。

        BB的海水说起来也并不难,就是用shader实现的,把他的包拿来解压就可以拿到shader源码和用到的texture,不再赘述。他的主岛场景和地图场景用到了不同的资源,但是原理是相同的,我就只讲主岛的海水实现了。

       

       这张texture就是玩家主岛海水的纹理。

       然后是顶点着色器:

[plain] view plain copy
  1. #ifdef GL_ES  
  2.     #ifdef SIMPLE  
  3.     precision mediump float;  
  4.     #else  
  5.     precision highp float;  
  6.     #endif  
  7. #else  
  8. #define highp  
  9. #define mediump  
  10. #define lowp  
  11. #endif  
  12.   
  13. //#define FOAM  
  14. #define SIMPLE  
  15.   
  16. #ifndef SIMPLE  
  17. //#ifndef MEDIUM  
  18. #define LIGHTMAP  
  19. //#endif // MEDIUM  
  20. #define REFLECTION  
  21. #endif // SIMPLE  
  22. #ifdef FOAM  
  23. #ifndef SIMPLE  
  24. #define USE_FOAM  
  25. #endif // SIMPLE  
  26. #endif // FOAM  
  27.   
  28. attribute vec4 a_position;  
  29. attribute vec2 a_texCoord;  
  30. attribute vec4 a_color;  
  31. // r = foam  
  32. // g = wave  
  33. // b = wind  
  34. // a = depth  
  35.   
  36. varying vec4 v_wave;  
  37. varying highp vec2 v_bumpUv1;  
  38. #ifdef USE_FOAM  
  39. varying highp vec2 v_foamUv;  
  40. varying float v_foamPower;  
  41. #endif  
  42. varying vec3 v_darkColor;  
  43. varying vec3 v_lightColor;  
  44. varying float v_reflectionPower;  
  45.   
  46. #ifdef LIGHTMAP  
  47. varying highp vec2 v_worldPos;  
  48. #endif  
  49.   
  50. // uniform   mat4 u_mvp;  
  51.   
  52. uniform highp float u_time;  
  53. uniform mediump float u_1DivLevelWidth;  
  54. uniform mediump float u_1DivLevelHeight;  
  55. uniform mediump float WAVE_HEIGHT;  
  56. uniform mediump float WAVE_MOVEMENT;  
  57.   
  58. uniform mediump vec3 SHORE_DARK;  
  59. uniform mediump vec3 SHORE_LIGHT;  
  60. uniform mediump vec3 SEA_DARK;  
  61. uniform mediump vec3 SEA_LIGHT;  
  62.   
  63. uniform mediump vec3 u_lightPos;  
  64.   
  65. void main()  
  66. {  
  67.     vec4 pos = a_position;  
  68.       
  69.     // Calculate new vertex position with wave  
  70.     float animTime = a_texCoord.y + u_time;  
  71.     highp float wave = cos(animTime);  
  72.     float waveHeightFactor = (wave + 1.0) * 0.5; // 0,1  
  73.       
  74.     pos.y += WAVE_MOVEMENT * waveHeightFactor * a_color.g * a_color.b;  
  75.       
  76.     pos.z += wave * WAVE_HEIGHT * a_color.b;  
  77.     gl_Position = CC_MVPMatrix * pos;  
  78.   
  79.     // Water alpha  
  80.     float maxValue = 0.55;//0.5;  
  81.     v_wave.x = 1.0 - (a_color.a - maxValue) * (1.0 / maxValue);  
  82.     v_wave.x = v_wave.x * v_wave.x;  
  83.     v_wave.x = v_wave.x * 0.8 + 0.2;  
  84.     v_wave.x -= wave * a_color.b * 0.1;  
  85.     v_wave.x = min(1.0, v_wave.x);  
  86.   
  87.     // UV coordinates  
  88.     vec2 texcoordMap = vec2(a_position.x * u_1DivLevelWidth, a_position.y * u_1DivLevelHeight) * 4.0;  
  89.     v_bumpUv1.xy =  texcoordMap + vec2(0.0, u_time * 0.005) * 1.5;          // bump uv  
  90. #ifdef USE_FOAM  
  91.     v_foamUv = (texcoordMap + vec2(u_time * 0.005)) * 5.5;  
  92.   
  93.     // Calculate foam params  
  94.     float foamFactor = a_color.r * 2.0 + pow(a_color.r, 3.0);  
  95.     // Add some wave tops  
  96.     foamFactor += min(5.0, pow((1.0 - waveHeightFactor) * a_color.g * 3.5, 3.0)) * 0.2 * (1.0 - a_color.r);  
  97.     v_foamPower = foamFactor * 0.8 * 3.0;//vec4(foamFactor * 0.6, foamFactor * 0.6, foamFactor * 0.8, 0.0);//foamFactor * 0.1);  
  98.       
  99.     float temppi = (a_color.a - maxValue) * (1.0 / maxValue);  
  100.     v_foamPower += min(1.0, min(1.0, max(0.0, -wave + 0.5) * 4.0) * temppi) * 0.5;  
  101.     v_foamPower = max(0.0, v_foamPower * a_color.b);  
  102.   
  103.     //v_wave.z = 0.0;  
  104. #endif  
  105.       
  106.     vec3 lightDir = normalize(vec3(-1.0, 1.0, 0.0));  
  107.     vec3 lightVec = normalize(u_lightPos - pos.xyz);  
  108.     v_wave.z = (1.0 - abs(dot(lightDir, lightVec)));  
  109.     v_wave.z = v_wave.z * 0.2 + (v_wave.z * v_wave.z) * 0.8;  
  110.     v_wave.z += 1.1 - (length(u_lightPos - pos.xyz) * 0.008);  
  111.     v_wave.w = (1.0 + (1.0 - v_wave.z * 0.5) * 7.0);  
  112.   
  113. #ifdef LIGHTMAP  
  114.     v_worldPos = vec2(pos.x * u_1DivLevelWidth, pos.y * u_1DivLevelHeight);  
  115. #endif  
  116.   
  117.     // Blend factor for normal maps  
  118.     v_wave.y = (cos((a_position.x + u_time) * a_position.y * 0.003 + u_time) + 1.0) * 0.5;  
  119.   
  120.     // Calculate colors  
  121.     float blendFactor = 1.0 - min(1.0, a_color.a * 1.6);  
  122.       
  123.     float tx = a_position.x * u_1DivLevelWidth - 0.5;  
  124.     float ty = a_position.y * u_1DivLevelHeight - 0.5;  
  125.       
  126.     // Here is the code that is commented out below done without a branch and is 2 cycles faster  
  127.     float tmp = (tx * tx + ty * ty) / (0.75 * 0.75);  
  128.     float blendFactorMul = step(1.0, tmp);  
  129.     tmp = pow(tmp, 3.0);  
  130.     // Can't be above 1.0, so no clamp needed  
  131.     float blendFactor2 = max(blendFactor - (1.0 - tmp) * 0.5, 0.0);  
  132.     blendFactor = mix(blendFactor2, blendFactor, blendFactorMul);  
  133.       
  134. //  if ((tx * tx + ty * ty) < (0.75 * 0.75)) {  
  135. //      float tmp = pow(((tx * tx + ty * ty) / (0.75 * 0.75)), 3.0);  
  136. //      blendFactor = clamp(blendFactor - (1.0 - tmp) * 0.5, 0.0, 1.0);  
  137. //  }  
  138.   
  139.     v_darkColor = mix(SHORE_DARK, SEA_DARK, blendFactor);  
  140.     v_lightColor = mix(SHORE_LIGHT, SEA_LIGHT, blendFactor);  
  141.   
  142.     v_reflectionPower = ((1.0 - a_color.a) + blendFactor) * 0.5;//blendFactor;  
  143.     // Put to log2 here because there's pow(x,y)*z in the fragment shader calculated as exp2(log2(x) * y + log2(z)), where this is is the log2(z)  
  144.     v_reflectionPower = log2(v_reflectionPower);  
  145. }  
        以及片元着色器:
[plain] view plain copy
  1. #ifdef GL_ES  
  2. precision mediump float;  
  3. #else  
  4. #define highp  
  5. #define mediump  
  6. #define lowp  
  7. #endif  
  8.   
  9. //#define FOAM  
  10. #define SIMPLE  
  11.   
  12. #ifndef SIMPLE  
  13. //#ifndef MEDIUM  
  14. #define LIGHTMAP  
  15. //#endif // MEDIUM  
  16. #define REFLECTION  
  17. #endif // SIMPLE  
  18. #ifdef FOAM  
  19. #ifndef SIMPLE  
  20. #define USE_FOAM  
  21. #endif // SIMPLE  
  22. #endif // FOAM  
  23.   
  24. uniform lowp sampler2D normal0;  
  25. #ifdef USE_FOAM  
  26.     uniform lowp sampler2D foam;  
  27. #endif  
  28.   
  29. varying vec4 v_wave;  
  30. varying highp vec2 v_bumpUv1;  
  31. #ifdef USE_FOAM  
  32. varying highp vec2 v_foamUv;  
  33. varying float v_foamPower;  
  34. #endif  
  35. varying vec3 v_darkColor;  
  36. varying vec3 v_lightColor;  
  37. varying float v_reflectionPower;  
  38.   
  39. #ifdef LIGHTMAP  
  40. uniform lowp sampler2D lightmap;  
  41. varying vec2 v_worldPos;  
  42. #endif  
  43.   
  44. void main()  
  45. {  
  46.     vec4 normalMapValue = texture2D(normal0, v_bumpUv1.xy);  
  47.     gl_FragColor = vec4(mix(v_lightColor, v_darkColor, (normalMapValue.x * v_wave.y) + (normalMapValue.y * (1.0 - v_wave.y))), v_wave.x)  
  48.       
  49.     #ifdef REFLECTION  
  50.       
  51. //  pow(x,y)*z is calculated here as exp2(log2(x) * y + log2(z))  
  52.       
  53.     + exp2(log2(((normalMapValue.z * v_wave.y) + (normalMapValue.w * (1.0 - v_wave.y))) * v_wave.z) * v_wave.w + v_reflectionPower);  
  54. //  + vec4(pow(((normalMapValue.z * v_wave.y) + (normalMapValue.w * (1.0 - v_wave.y))) * v_wave.z, v_wave.w)) * v_reflectionPower;  
  55.     #else  
  56.     ;  
  57.     #endif  
  58.     #ifdef USE_FOAM  
  59.     gl_FragColor = mix(gl_FragColor, vec4(0.95, 0.95, 0.95, gl_FragColor.a), min(1.0, texture2D(foam, v_foamUv).r * v_foamPower))  
  60.     #ifdef LIGHTMAP  
  61.      * (texture2D(lightmap, v_worldPos) * 1.3);  
  62.     #else  
  63.     ;  
  64.     #endif // LIGHTMAP  
  65.     #endif // USE_FOAM  
  66. }  

        接下来进入正题。

        首先BB的海水分为simple和非simple两个版本,用SIMPLE宏来控制,前者只有海水的波动效果,后者还有光的反射和浪花。想完美实现,需要传入正确的uniform和attribute。先拿uniform开刀吧:

        u_time是海水动起来的灵魂,随着每一帧递增,也可以用它来控制海水波动的快慢,换句话说,只有他的值不停改变,海水才能动起来;

        u_1DivLevelWidth和u_1DivLevelHeight一同控制海水的远近,他们的的值越小,海水就会拉得更近,例如我创建的海水容器是512*512的,但是我的场景是1024*1024,这时我就得对海水本身进行scale操作,这样就会把海水放大失真,这时就要调节这两个值来把海水拉远,就可以得到和512*512大小相同的效果;

        WAVE_HEIGHT顾名思义,就是浪高,它和wave值配合来产生层层海浪的效果(wave值之后介绍attribute的时候会讲到);

        WAVE_MOVEMENT同样,是浪的移动速度,它和wind值配合产生风吹海浪的效果(wind同上);

        接下来的SHORE_DARK,SHORE_LIGHT,SEA_DARK,SEA_LIGHT分别是岸的明暗rgb值和水的明暗rgb值,SC用了自己的调和系数分别对两个DARK和LIGHT进行mix,得到浅滩水的颜色和深水的颜色,并且完成渐变效果(BB的岸边海水淡绿,深海深蓝就是这么实现的),当然了,这个也配合了depth系数(depth同上,看到同上俩字是不是想杀了我微笑);

        u_lightPos了,就是光源位置,海水表面的反光就靠他了。

        u_mvp就是变换矩阵了,会opengl的都懂,不再赘述(因为现在开发的项目用了万恶的cocos2dx引擎,所以直接把CC_MVPMatrix拿来用了,使用自己引擎的同学一定要把这个改成自己的矩阵);

        最后是片元着色器用到的三个texture,normal0,foam,lightmap分别是海水纹理,浪花纹理和光照纹理,我最开始贴的那张应该是烘培过的,可以直接用到normal0和lightmap上,至于浪花的纹理SC用的是ktx材质,如果做2d的话,提取png来用吧。


        uniform到此结束,进入最重要的attribute。它用到的就是最基本的数据:顶点,纹理,颜色。

        顶点坐标我是把512*512切分成网格,再把每个小格划为两个三角形来绘制的,会opengl的依然都懂;

        至于纹理坐标更加简单了,和顶点一样切分就行了;

        最重要的就是颜色了,此处的颜色并不同于真正意义上的颜色,SC把rgba值都用作了海水的信息值,可以看到着色器代码中a_color值得各个分量都在各种wave啊foam啊等等的计算中去了,这实在是我觉得SC高明的地方。

        首先是r值,他的注释写的是foam,这个是用于浪花的,0-255,值越大浪越明显,比如海水绘制时某个顶点的位置上有个大礁石,就把这点的r值设大点吧(BB的岸边,沙滩,礁石,甚至包括新出的海鸥戏水,都可以用r值来做);

        然后是g,注释是wave,这个就是我之前提到的和WAVE_HEIGHT配合的,0-255,值越大,波动越明显,要注意的是,设g值时要注意参差不齐,某一些顶点高,某一些顶点低这样,否则很难出现海浪交替波动的效果;

        再然后是b值,注释是wind,也是我之前提过的,和WAVE_MOVEMENT配合产生风的效果,0-255,值越大越明显,效果就是脉冲式的移动,拿来做海水打到岸上再合适不过,个人也觉得参差不齐一些更加自然

        最后是a值,注释是depth,之前又提过了(咦?为什么要加个又呢),0-255,值越大水越浅,比如某个顶点处有个小小的浅滩,就把周围顶点的a值设大点吧,效果会比较自然。


        原理介绍完了,根据我之前的解释去用这个shader就可以做出海面啦,最后在说说我遇到的一些问题吧。

        BB用的是3d画布,而这个shader默认的水流方向是向下的,对SC而言,改个camera位置就可以得到右上方向的水流了,但是我等用2d引擎的屌丝就只好自己去变换了(我比较弱,也比较懒,直接用旋转倾斜之类的方法搞了,大神不妨直接去改shader,效果应该会更好);

        因为源码和参数是我不能直接提供的(看咱这职业道德),uniform也好,attribute也好需要大家多去实验,会有很多收获,uniform直接追踪gpu就可以得到(大神无视,大神一定有千百种办法),重点我觉得还是根据自己的地图来设置出最适合的a_color吧。


        本文的最后,再赞一下SuperCell这间公司吧,确实是业界顶尖公司,单单一个海面就是精雕细琢,我其实也只是学了个表皮,其实更重要的是深入学习原理吧,毕竟如果我哪天觉得想做一个自己的更好的海水效果,就该捉襟见肘了吧。最后贴上一张我在testcpp里的SIMPLE测试版效果吧:

      

        补充一张光照版的,也修改了颜色:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值