在捕鱼游戏项目中

希望使用shader丰富海洋背景效果

在网上找了几个不同的效果

本文就写一下使用折射效果的体会

效果视频http://v.douyin.com/JfVfmU/

转载请注明出处https://www.cnblogs.com/billyrun/articles/9222345.html

 

shader代码如下

#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
varying vec2 v_texCoord;
uniform sampler2D u_normalMap; 
varying vec4 v_fragmentColor;

vec3 waveNormal(vec2 p) { 
    // 避免法线图边缘穿帮
    // fract返回小数部分 mod取模
    // 0.1 0.2 ... 0.9 1.0 0.9 ... 0.1 0.0 0.1 0.2 ...
    float x = mod(p.x , 2.0) < 1.0 ? fract(p.x) : 1.0 - fract(p.x);
    float y = mod(p.y , 2.0) < 1.0 ? fract(p.y) : 1.0 - fract(p.y);
    //vec3 normal = texture2D(u_normalMap, p).xyz;  
    vec3 normal = texture2D(u_normalMap, vec2(x , y)).xyz;  
    normal = -1.0 + normal * 2.0;  
    normal.x *= 0.1;
    normal.y *= 0.1;
    return normalize(normal);  
    //return vec3(0,0,1);
}  

void main() {  
    float timeFactor = 0.1;  
    float offsetFactor = 0.5;  
    float refractionFactor = 0.7;  
    
    // simple UV animation  
    vec3 normal = waveNormal(v_texCoord + vec2(time * 1.0 * timeFactor, time * 0.1 * timeFactor));  
    
    // simple calculate refraction UV offset  
    vec2 p = -1.0 + 2.0 * v_texCoord;  
    // 眼睛位置 位于中心点正上方 
    vec3 eyePos = vec3(0, 0, 10);  
    vec3 inVec = normalize(vec3(p, 0) - eyePos);  
    vec3 refractVec = refract(inVec, normal, refractionFactor);  //根据入射向量,法线,折射系数计算折射向量
    vec2 v_texCoordN = v_texCoord;
    v_texCoordN += refractVec.xy * offsetFactor;    
    //v_texCoordN.x -= CC_Time.y*timeFactor *0.6; //移动水面贴图,可选
    
    gl_FragColor = texture2D(CC_Texture0, v_texCoordN);
} 

 

法线获取函数

waveNormal函数返回每个纹理坐标所对应的法线

假设水面是纯平的,那么每一处法线都竖直向上,即vec3(0,0,1)

为了达到较为逼真的效果,水面应该是起伏不平的

这里使用一张法线贴图来提供数据

图片每个像素的RGB值被用作法线坐标

图片是蓝色的,RGB约为0.1,0.1,1.0,对应法线坐标xyz就是基本竖直向上但略有波动

所以也不难理解

waveNormal中做了部分数值改动

x和y分量分别*=0.1是因为使用的纹理图RG的值有点偏大,对应xy波动太剧烈

取模求余mod和fract保留小数部分是为了让法线采样坐标平滑的保持在0~1之间

避免穿帮的波动

因为虽然法线贴图的采样会使用gl.REPEAT,但从1.0增到0.0还是会出现明显抖动

 

法线参数

vec3 normal = waveNormal(v_texCoord + vec2(time * 1.0 * timeFactor, time * 0.1 * timeFactor));

参数基于片段的纹理坐标,并通过时间参数设置偏移

以上所设置的参数,会带来较大的横向波动和少量的纵向波动,适用于横版游戏

 

反射计算

首先将纹理坐标从0~1转换为-1~1的数值

vec2 p = -1.0 + 2.0 * v_texCoord;

然后根据假设的眼睛位置,计算光线方向

vec3 eyePos = vec3(0, 0, 10);
vec3 inVec = normalize(vec3(p, 0) - eyePos);

这里inVec与z轴的夹角是很小的

因为p的范围是(-1,-1)~(1,1)

因此inVec与z轴的夹角范围应该是0~8角度atan(1.414/10)

注意normalize会把方向向量变换为 单位向量

vec3 refractVec = refract(inVec, normal, refractionFactor);

根据法线计算折射向量

这个向量会比0~8度还要小,物理学没记错的话应该是0~8*0.7左右

而refractVec的值因为是从单位向量inVec计算得来的,因此还是单位向量

大概范围是(+-0.05 , +-0.05 , 0.9+)这样

vec2 v_texCoordN = v_texCoord;
v_texCoordN += refractVec.xy * offsetFactor;

最后处理纹理采样坐标,做偏移处理,可以看出来偏移的量很少,0.05的话也就是1/20

而且还乘了0.5

 

当法线始终竖直向上(0,0,1)时,可以比较一下原图和使用反射后的区别

可以明显看到,使用了折射之后,左右边缘部分gl.CLAMP掉了(上下其实也会有,没有在显示区域内)

正是因为折射偏移的影响

而在同时使用法线贴图和时间变换后

图片会展现出良好的动态波动效果

当然,最后要记得把左右边缘像上下底边一样,不要处于显示区域内部

控制时间参数的变化可以明显改变波动速度,操作十分简单方便

目前设置法线数据可以使用以下api

if (cc.sys.isNative) {
  glProgram_state.setUniformTexture('samplerNameXXX' , tex)
} else {
  var locNormalSample = program._glContext.getUniformLocation(program._programObj, 'samplerNameXXX');
  program.setUniformLocationWith1i(locNormalSample, textureUnitIndex);
  cc.gl.bindTexture2DN(textureUnitIndex, tex);
}

 

参考文献

http://www.cocoachina.com/bbs/read.php?tid-1693873-page-1.html