# BoomBeach海水效果实现

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

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

然后是顶点着色器：

#ifdef GL_ES
#ifdef SIMPLE
precision mediump float;
#else
precision highp float;
#endif
#else
#define highp
#define mediump
#define lowp
#endif

//#define FOAM
#define SIMPLE

#ifndef SIMPLE
//#ifndef MEDIUM
#define LIGHTMAP
//#endif // MEDIUM
#define REFLECTION
#endif // SIMPLE
#ifdef FOAM
#ifndef SIMPLE
#define USE_FOAM
#endif // SIMPLE
#endif // FOAM

attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
// r = foam
// g = wave
// b = wind
// a = depth

varying vec4 v_wave;
varying highp vec2 v_bumpUv1;
#ifdef USE_FOAM
varying highp vec2 v_foamUv;
varying float v_foamPower;
#endif
varying vec3 v_darkColor;
varying vec3 v_lightColor;
varying float v_reflectionPower;

#ifdef LIGHTMAP
varying highp vec2 v_worldPos;
#endif

// uniform   mat4 u_mvp;

uniform highp float u_time;
uniform mediump float u_1DivLevelWidth;
uniform mediump float u_1DivLevelHeight;
uniform mediump float WAVE_HEIGHT;
uniform mediump float WAVE_MOVEMENT;

uniform mediump vec3 SHORE_DARK;
uniform mediump vec3 SHORE_LIGHT;
uniform mediump vec3 SEA_DARK;
uniform mediump vec3 SEA_LIGHT;

uniform mediump vec3 u_lightPos;

void main()
{
vec4 pos = a_position;

// Calculate new vertex position with wave
float animTime = a_texCoord.y + u_time;
highp float wave = cos(animTime);
float waveHeightFactor = (wave + 1.0) * 0.5; // 0,1

pos.y += WAVE_MOVEMENT * waveHeightFactor * a_color.g * a_color.b;

pos.z += wave * WAVE_HEIGHT * a_color.b;
gl_Position = CC_MVPMatrix * pos;

// Water alpha
float maxValue = 0.55;//0.5;
v_wave.x = 1.0 - (a_color.a - maxValue) * (1.0 / maxValue);
v_wave.x = v_wave.x * v_wave.x;
v_wave.x = v_wave.x * 0.8 + 0.2;
v_wave.x -= wave * a_color.b * 0.1;
v_wave.x = min(1.0, v_wave.x);

// UV coordinates
vec2 texcoordMap = vec2(a_position.x * u_1DivLevelWidth, a_position.y * u_1DivLevelHeight) * 4.0;
v_bumpUv1.xy =  texcoordMap + vec2(0.0, u_time * 0.005) * 1.5;			// bump uv
#ifdef USE_FOAM
v_foamUv = (texcoordMap + vec2(u_time * 0.005)) * 5.5;

// Calculate foam params
float foamFactor = a_color.r * 2.0 + pow(a_color.r, 3.0);
foamFactor += min(5.0, pow((1.0 - waveHeightFactor) * a_color.g * 3.5, 3.0)) * 0.2 * (1.0 - a_color.r);
v_foamPower = foamFactor * 0.8 * 3.0;//vec4(foamFactor * 0.6, foamFactor * 0.6, foamFactor * 0.8, 0.0);//foamFactor * 0.1);

float temppi = (a_color.a - maxValue) * (1.0 / maxValue);
v_foamPower += min(1.0, min(1.0, max(0.0, -wave + 0.5) * 4.0) * temppi) * 0.5;
v_foamPower = max(0.0, v_foamPower * a_color.b);

//v_wave.z = 0.0;
#endif

vec3 lightDir = normalize(vec3(-1.0, 1.0, 0.0));
vec3 lightVec = normalize(u_lightPos - pos.xyz);
v_wave.z = (1.0 - abs(dot(lightDir, lightVec)));
v_wave.z = v_wave.z * 0.2 + (v_wave.z * v_wave.z) * 0.8;
v_wave.z += 1.1 - (length(u_lightPos - pos.xyz) * 0.008);
v_wave.w = (1.0 + (1.0 - v_wave.z * 0.5) * 7.0);

#ifdef LIGHTMAP
v_worldPos = vec2(pos.x * u_1DivLevelWidth, pos.y * u_1DivLevelHeight);
#endif

// Blend factor for normal maps
v_wave.y = (cos((a_position.x + u_time) * a_position.y * 0.003 + u_time) + 1.0) * 0.5;

// Calculate colors
float blendFactor = 1.0 - min(1.0, a_color.a * 1.6);

float tx = a_position.x * u_1DivLevelWidth - 0.5;
float ty = a_position.y * u_1DivLevelHeight - 0.5;

// Here is the code that is commented out below done without a branch and is 2 cycles faster
float tmp = (tx * tx + ty * ty) / (0.75 * 0.75);
float blendFactorMul = step(1.0, tmp);
tmp = pow(tmp, 3.0);
// Can't be above 1.0, so no clamp needed
float blendFactor2 = max(blendFactor - (1.0 - tmp) * 0.5, 0.0);
blendFactor = mix(blendFactor2, blendFactor, blendFactorMul);

//	if ((tx * tx + ty * ty) < (0.75 * 0.75)) {
//		float tmp = pow(((tx * tx + ty * ty) / (0.75 * 0.75)), 3.0);
//		blendFactor = clamp(blendFactor - (1.0 - tmp) * 0.5, 0.0, 1.0);
//	}

v_darkColor = mix(SHORE_DARK, SEA_DARK, blendFactor);
v_lightColor = mix(SHORE_LIGHT, SEA_LIGHT, blendFactor);

v_reflectionPower = ((1.0 - a_color.a) + blendFactor) * 0.5;//blendFactor;
// 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)
v_reflectionPower = log2(v_reflectionPower);
}

以及片元着色器：

#ifdef GL_ES
precision mediump float;
#else
#define highp
#define mediump
#define lowp
#endif

//#define FOAM
#define SIMPLE

#ifndef SIMPLE
//#ifndef MEDIUM
#define LIGHTMAP
//#endif // MEDIUM
#define REFLECTION
#endif // SIMPLE
#ifdef FOAM
#ifndef SIMPLE
#define USE_FOAM
#endif // SIMPLE
#endif // FOAM

uniform lowp sampler2D normal0;
#ifdef USE_FOAM
uniform lowp sampler2D foam;
#endif

varying vec4 v_wave;
varying highp vec2 v_bumpUv1;
#ifdef USE_FOAM
varying highp vec2 v_foamUv;
varying float v_foamPower;
#endif
varying vec3 v_darkColor;
varying vec3 v_lightColor;
varying float v_reflectionPower;

#ifdef LIGHTMAP
uniform lowp sampler2D lightmap;
varying vec2 v_worldPos;
#endif

void main()
{
vec4 normalMapValue = texture2D(normal0, v_bumpUv1.xy);
gl_FragColor = vec4(mix(v_lightColor, v_darkColor, (normalMapValue.x * v_wave.y) + (normalMapValue.y * (1.0 - v_wave.y))), v_wave.x)

#ifdef REFLECTION

//	pow(x,y)*z is calculated here as exp2(log2(x) * y + log2(z))

+ exp2(log2(((normalMapValue.z * v_wave.y) + (normalMapValue.w * (1.0 - v_wave.y))) * v_wave.z) * v_wave.w + v_reflectionPower);
//	+ vec4(pow(((normalMapValue.z * v_wave.y) + (normalMapValue.w * (1.0 - v_wave.y))) * v_wave.z, v_wave.w)) * v_reflectionPower;
#else
;
#endif
#ifdef USE_FOAM
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))
#ifdef LIGHTMAP
* (texture2D(lightmap, v_worldPos) * 1.3);
#else
;
#endif // LIGHTMAP
#endif // USE_FOAM
}


接下来进入正题。

首先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值设大点吧，效果会比较自然。