《Android 应用案例开发大全(第3版)》——第2章,第2.8节壁纸中的着色器开发

简介:

本节书摘来自异步社区《Android 应用案例开发大全(第3版)》一书中的第2章,第2.8节壁纸中的着色器开发,作者 吴亚峰 , 苏亚光 , 于复兴,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.8 壁纸中的着色器开发
前面已经对3D动态壁纸——百纳水族馆的相关类进行了简要的介绍。本节将对本案例中用到的相关着色器进行介绍。本案例中用到的着色器共有四对,即气泡着色器、背景着色器、鱼类着色器及珍珠贝着色器。下面就对本壁纸中用到的着色器的开发进行一一介绍。

2.8.1 气泡的着色器
气泡着色器分为顶点着色器与片元着色器,下面便分别对气泡着色器的顶点着色器和片元着色器的开发进行详细介绍。

(1)首先介绍的是气泡着色器中的顶点着色器的开发,其详细代码如下。

代码位置:见随书光盘源代码/第2章/MyWallPaper/assets/shader/目录下的bubble_vertex.sh。

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)完成顶点着色器的开发后,下面开发的是气泡的片元着色器,其详细代码如下。

代码位置:见随书光盘源代码/第2章/MyWallPaper/assets/shader/目录下的bubble_frag.sh。

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)首先介绍的是珍珠贝着色器中的顶点着色器的开发,具体代码如下。

代码位置:见随书光盘源代码/第2章/MyWallPaper/assets/shader/目录下的vertex.sh。

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)之前介绍了珍珠贝顶点着色器的开发,下面为读者详细介绍珍珠贝着色器中片元着色器的开发。其详细代码如下。

代码位置:见随书光盘源代码/第2章/MyWallPaper/assets/shader/目录下的frag.sh。

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)首先介绍的是鱼类的顶点着色器,由于本着色器中对鱼类灯光的设置与上节中对珍珠贝的灯光设置一致,故不再赘述,请读者自行查看随书光盘中的源代码。本小节将着重介绍对多重纹理采样绘制的实现。其具体代码如下。

代码位置:见随书光盘源代码/第2章/MyWallPaper/assets/shader/目录下的fish_vertex.sh。

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)介绍完鱼类的顶点着色器后,下面将介绍鱼类的片元着色器,此片元着色器实现了鱼类身体上的明暗效果与灯光特效。下面着重介绍明暗效果的实现,其具体代码如下所示。

代码位置:见随书光盘源代码/第2章/MyWallPaper/assets/shader/目录下的fish_frag.sh。

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个通道光计算出片元的最终颜色值,送入渲染管线。

相关文章
|
21天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
46 19
|
21天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
49 14
|
24天前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
22天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
在数字时代,掌握安卓应用开发技能是进入IT行业的关键。本文将引导读者从零基础开始,逐步深入安卓开发的世界,通过实际案例和代码示例,展示如何构建自己的第一个安卓应用。我们将探讨基本概念、开发工具设置、用户界面设计、数据处理以及发布应用的全过程。无论你是编程新手还是有一定基础的开发者,这篇文章都将为你提供宝贵的知识和技能,帮助你在安卓开发的道路上迈出坚实的步伐。
31 5
|
21天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
22天前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
22天前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
25天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
29天前
|
搜索推荐 前端开发 Android开发
安卓开发中的自定义视图——打造个性化用户界面
在安卓应用开发的广阔天地里,自定义视图是实现个性化界面设计的重要手段。通过深入理解安卓绘图基础、触摸事件处理和布局机制,开发者可以突破标准控件的限制,创造出独一无二的用户体验。本文将引导你探索自定义视图的核心概念,提供实用的代码示例,并分享如何有效地解决开发过程中可能遇到的挑战。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创新和个性化的大门。
|
29天前
|
XML 数据库 Android开发
探索Android开发:从入门到精通的旅程
在这篇文章中,我们将一起踏上一段激动人心的旅程,通过深入浅出的方式,解锁Android开发的秘密。无论你是编程新手还是有经验的开发者,本文都将为你提供宝贵的知识和技能,帮助你构建出色的Android应用。我们将从基础概念开始,逐步深入到高级技巧和最佳实践,最终实现从初学者到专家的转变。让我们开始吧!
43 3