本节书摘来自异步社区《Android 应用案例开发大全(第3版)》一书中的第2章,第2.8节壁纸中的着色器开发,作者 吴亚峰 , 苏亚光 , 于复兴,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.8 壁纸中的着色器开发
前面已经对3D动态壁纸——百纳水族馆的相关类进行了简要的介绍。本节将对本案例中用到的相关着色器进行介绍。本案例中用到的着色器共有四对,即气泡着色器、背景着色器、鱼类着色器及珍珠贝着色器。下面就对本壁纸中用到的着色器的开发进行一一介绍。
2.8.1 气泡的着色器
气泡着色器分为顶点着色器与片元着色器,下面便分别对气泡着色器的顶点着色器和片元着色器的开发进行详细介绍。
(1)首先介绍的是气泡着色器中的顶点着色器的开发,其详细代码如下。
1 uniform mat4 uMVPMatrix; //总变换矩阵
2 attribute vec3 aPosition; //顶点位置
3 attribute vec2 aTexCoor; //顶点纹理坐标
4 varying vec2 vTextureCoord; //用于传递给片元着色器的变量
5 void main(){
6 gl_Position = uMVPMatrix * vec4(aPosition,1);//根据总变换矩阵计算此次绘制的顶点位置
7 vTextureCoord = aTexCoor; //将接收的纹理坐标传递给片元着色器
8 }
第1~4行是着色器中接收数据传递数据的声明。接收Java代码部分的总变换矩阵、顶点位置及顶点纹理坐标。并将顶点纹理坐标从顶点着色器传递到片元着色器中。
第5~8行该顶点着色器的主要作用就是根据Java传递过来的模型本身的顶点位置aPosition与总变换矩阵计算出gl_Position,每顶点执行一次。
(2)完成顶点着色器的开发后,下面开发的是气泡的片元着色器,其详细代码如下。
1 precision mediump float;
2 uniform sampler2D sTexture; //纹理内容数据
3 varying vec2 vTextureCoord; //接收从顶点着色器过来的参数
4 void main(){
5 vec4 finalColor=texture2D(sTexture, vTextureCoord); //将计算出的颜色给此片元
6 gl_FragColor = finalColor; //给此片元颜色值
7 }
第1~7行该片元着色器的作用主要为根据从顶点着色器传递过来的纹理坐标数据vTextureCoord和从Java代码部分传递过来的sTexture计算片元的最终颜色值,并将最终颜色值赋值给着色器内建输出变量gl_FragColor,每片元执行一次。
说明
因为背景的着色器代码与上述气泡着色器的代码基本一致,故在此不再详细介绍背景的着色器。读者可自行查看随书光盘中的源代码,其位置在项目目录assets/shader/目录下的back_vertex.sh与back_frag.sh。
2.8.2 珍珠贝的着色器
前面已经为读者介绍了珍珠贝模型的加载方法,但仅是一个带骨骼动画的珍珠贝,并不能使用户感觉真实,因为现实世界中是有阳光的,所以,我们用着色器给珍珠贝增加了灯光,这样就会出现水族馆中真实感超强的珍珠贝。下面对珍珠贝的着色器进行详细的介绍,其具体代码如下。
(1)首先介绍的是珍珠贝着色器中的顶点着色器的开发,具体代码如下。
1 uniform mat4 uMVPMatrix; //总变换矩阵
2 uniform mat4 uMMatrix; //变换矩阵
3 uniform vec3 uLightLocation; //光源位置
4 uniform vec3 uCamera; //摄像机位置
5 attribute vec3 aPosition; //顶点位置
6 attribute vec3 aNormal; //顶点法向量
7 attribute vec2 aTexCoor; //顶点纹理坐标
8 varying vec4 ambient; //用于传递给片元着色器的环境光
9 varying vec4 diffuse; //用于传递给片元着色器的散射光
10 varying vec4 specular; //用于传递给片元着色器镜面反射光
11 varying vec2 vTextureCoord; //用于传递给片元着色器的变量
12 //定位光光照计算的方法
13 void pointLight( //定位光光照计算的方法
in vec3 normal, //法向量
15 inout vec4 ambient, //环境光最终强度
16 inout vec4 diffuse, //散射光最终强度
17 inout vec4 specular, //镜面光最终强度
in vec3 lightLocation, //光源位置
in vec4 lightAmbient, //环境光强度
in vec4 lightDiffuse, //散射光强度
in vec4 lightSpecular //镜面光强度
22 ){
23 ambient=lightAmbient; //直接得出环境光的最终强度
24 vec3 normalTarget=aPosition+normal; //计算变换后的法向量
25 vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4
(aPosition,1)).xyz;
26 newNormal=normalize(newNormal); //对法向量规格化
27 //计算从表面点到摄像机的向量
28 vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);
29 //计算从表面点到光源位置的向量vp
30 vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
31 vp=normalize(vp); //格式化vp
32 vec3 halfVector=normalize(vp+eye); //求视线与光线的半向量
33 float shininess=50.0; //粗糙度,越小越光滑
34 float nDotViewPosition=max(0.0,dot(newNormal,vp)); //求法向量与vp的点积与0的最大值
35 diffuse=lightDiffuse*nDotViewPosition; //计算散射光的最终强度
36 float nDotViewHalfVector=dot(newNormal,halfVector); //法线与半向量的点积
37 float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess));//镜面反射光强度因子
38 specular=lightSpecular*powerFactor; //计算镜面光的最终强度
39 }
40 void main(){
41 gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
42 vec4 ambientTemp, diffuseTemp, specularTemp;//存放环境光、散射光、镜面反射光临时变量
43 pointLight(normalize(aNormal),ambientTemp,diffuseTemp,specularTemp,
uLightLocation,vec4(0.3,0.3,0.3,1.0),vec4(0.9,0.9,0.9,1.0),vec4(0.4,0.4,0.4,1.0));
44 ambient=ambientTemp; //将环境光传递给片元着色器
45 diffuse=diffuseTemp; //将散射光传递给片元着色器
46 specular=specularTemp; //将镜面反射光传递给片元着色器
47 vTextureCoord = aTexCoor; //将接收的纹理坐标传递给片元着色器
48 }
第1~11行是顶点着色器中全局变量的声明,相比于气泡的顶点着色器,它主要增加了变化矩阵、光源位置、摄像机位置以及顶点法向量的引用。此处还声明了传递给片元着色器3种通道光的变量,分别是环境光变量、散射光变量、镜面反射光变量。
第13~38行是计算光照的3种光的最终强度。首先直接计算出环境光最终强度,其中最重要的是在进行计算前要对顶点法向量进行变换,将法向量变换到当前的姿态下。然后计算各种所需数据,最后计算出镜面光和镜面反射光的最终强度。
第40~48行是顶点着色器的main方法,其中首先调用了pointLight方法将3种通道光的强度值传递给片元着色器,并将接收到的顶点纹理坐标传递给片元着色器,以供片元着色器计算每片片元的最后颜色值。
(2)之前介绍了珍珠贝顶点着色器的开发,下面为读者详细介绍珍珠贝着色器中片元着色器的开发。其详细代码如下。
1 precision mediump float;
2 varying vec2 vTextureCoord; //接收从顶点着色器过来的参数
3 uniform sampler2D sTexture; //纹理内容数据
4 varying vec4 ambient; //环境光
5 varying vec4 diffuse; //散射光
6 varying vec4 specular; //镜面光
7 void main() {
8 vec4 finalColorDay; //给此片元从纹理中采样出颜色值
9 finalColorDay= texture2D(sTexture, vTextureCoord);//给此片元从纹理中采样出颜色值
10 //综合3个通道光的最终强度及片元的颜色计算出最终片元的颜色并传递给管线
11 gl_FragColor=finalColorDay*ambient+finalColorDay*specular+finalColorDay*
diffuse;
12 }
第1~12行是珍珠贝着色器中片元着色器的代码。它根据从顶点着色器传递过来的顶点纹理坐标,对Java代码部分传递过来的片元颜色值进行采样,并且接收顶点着色器传递过来的3个通道光值,计算出片元的最终颜色值,然后再将其传递给渲染管线。
2.8.3 鱼类的着色器
前面已为读者介绍了鱼类模型加载。单纯的一个鱼类的骨骼动画并不能使水族馆看起来真实,所以前面已为鱼类着色器传递了一张明暗纹理图为此做准备。在鱼类着色器中,我们为鱼类添加了灯光,并为鱼类本身采取了多重纹理采样绘制。
(1)首先介绍的是鱼类的顶点着色器,由于本着色器中对鱼类灯光的设置与上节中对珍珠贝的灯光设置一致,故不再赘述,请读者自行查看随书光盘中的源代码。本小节将着重介绍对多重纹理采样绘制的实现。其具体代码如下。
1 uniform mat4 uMVPMatrix; //总变换矩阵
2 uniform mat4 uMMatrix; //变换矩阵
3 uniform vec3 uLightLocation; //光源位置
4 uniform vec3 uCamera; //摄像机位置
5 attribute vec3 aPosition; //顶点位置
6 attribute vec3 aNormal; //顶点法向量
7 attribute vec2 aTexCoor; //顶点纹理坐标
8 varying vec3 vNormal; //将顶点法向量传给片元着色器
9 varying vec4 ambient; //将环境光传给片元着色器
10 varying vec4 diffuse; //将散射光传给片元着色器
11 varying vec4 specular; //将镜面反射光传给片元着色器
12 varying vec2 vTextureCoord; //用于传递给片元着色器的变量
13 varying vec3 vPosition; //将顶点传给片元着色器
14 ......//该处省略了计算定向光照的方法pointLight,读者可自行查阅随书光盘中的源代码
15 void main(){
16 gl_Position = uMVPMatrix * vec4(aPosition,1);//根据总变换矩阵计算此次绘制的顶点位置
17 //该处省略了调用pointLight方法与传递3个光的通道变量代码,读者可自行查阅随书光盘中的源代码
18
19 vec3 normalTarget=aPosition+aNormal; //计算变换后的法向量
20 vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4
(aPosition,1)).xyz;
21 vNormal=normalize(newNormal); //对法向量规格化
22 vTextureCoord = aTexCoor;//将接收的纹理坐标传递给片元着色器
23 vPosition=(uMMatrix*vec4(aPosition,1)).xyz; //计算物理世界中顶点位置
24 }
第1~13行是着色器中对全局变量的声明,主要包括总变换矩阵、变换矩阵、光源位置、摄像机位置、顶点位置以及顶点法向量的引用等,还有对传递给片元着色器的相关变量声明。
第19~21行是对Java代码部分传递过来的顶点法向量进行计算。对法向量进行变换,将法向量变换到当前的姿态下,并传递给片元着色器。
第22~23行将Java代码部分传递过来的顶点纹理坐标传递给片元着色器,然后根据鱼类本身的顶点计算出顶点在物理世界的坐标并传递到片元着色器。
(2)介绍完鱼类的顶点着色器后,下面将介绍鱼类的片元着色器,此片元着色器实现了鱼类身体上的明暗效果与灯光特效。下面着重介绍明暗效果的实现,其具体代码如下所示。
1 precision mediump float;
2 varying vec2 vTextureCoord; //接收从顶点着色器过来的参数
3 uniform sampler2D sTexture; //本身纹理内容数据
4 uniform sampler2D sTextureHd; //明暗纹理内容数据
5 varying vec3 vNormal; //接收顶点着色器的法向量
6 varying vec3 vPosition; //接收顶点着器的顶点
7 varying vec4 ambient; //接收顶点着色器环境光
8 varying vec4 diffuse; //接收顶点着色器散射光
9 varying vec4 specular; //接收顶点着色镜面光
10 void main(){
11 float f;
12 vec4 finalColorDay; //鱼类本身纹理颜色
13 vec4 finalColorNight; //采样明暗纹理颜色
14 vec4 finalColorzj; //混合后的纹理颜色
15 finalColorDay= texture2D(sTexture, vTextureCoord);//给此片元从纹理中采样出颜色值
16 vec2 tempTexCoor=vec2((vPosition.x+20.8)/5.2,(vPosition.z+18.0)/2.5);
//8*8重复纹理
17 if(vNormal.y>0.2){ //鱼类动态相对上半身
18 finalColorNight = texture2D(sTextureHd, tempTexCoor);//采样出明暗纹理颜色值
f=(finalColorNight.r+finalColorNight.g+finalColorNight.b)/3.0;
//取3个颜色值平均值
20 }else if(vNormal.y<=0.2&&vNormal.y>=-0.2){ //过渡区域混合颜色值
21 if(vNormal.y>=0.0&&vNormal.y<=0.2){ //平滑过渡
22 finalColorNight = texture2D(sTextureHd,
23 tempTexCoor)*(1.0-2.5*(0.20-vNormal.y));//采样出过渡颜色
f=(finalColorNight.r+finalColorNight.g
25 +finalColorNight.b)/3.0; //取3个颜色值平均值
26 }else if(vNormal.y<0.0&&vNormal.y>=-0.2){ //平滑过渡
27 finalColorNight = texture2D(sTextureHd, //采样出过渡颜色
28 tempTexCoor)*(0.5+2.5*vNormal.y);
f=(finalColorNight.r+finalColorNight.g
30 +finalColorNight.b)/3.0; //取3个颜色值平均值
31 }}else if(vNormal.y<-0.2){ //鱼类动态相对下半身
f=0.0;
33 }
34 finalColorzj =finalColorDay*(1.0+f*1.5); //算出混合后的片元颜色
35 //综合3个通道光的最终强度及片元的颜色计算出最终片元的颜色并传递给管线
36 gl_FragColor=finalColorzj*ambient+finalColorzj*specular+finalColorzj*
diffuse;
37 }
第1~9行接收Java代码部分传过来的鱼类本身纹理内容数据和鱼类明暗采样纹理内容数据,接收顶点着色器传递过来的法向量、顶点数据与环境光、散射光、镜面反射光及顶点纹理坐标数据,用于片元着色器对每一片元颜色的计算。
第11~16行首先声明一个浮点数变量f,再声明3个用于采样颜色存储的浮点数向量。然后根据鱼类模型的顶点在物理世界中的坐标,计算出在88明暗采样纹理图中对应的顶点纹理坐标S、T,为后面的采样颜色值做准备。
第17~33行根据顶点着色器传递过来的顶点法向量计算出明暗纹理的采样颜色值。当然鱼类不能全身都有明暗,因此根据其法向量规定大于0.2的为全部纹理明暗采样颜色值,在0.2~0.2之间将明暗采样颜色值从1逐渐降为0,使之平滑过渡,小于0.2取采样值为0。然后计算出混合后的颜色值,再综合3个通道光计算出片元的最终颜色值,送入渲染管线。