1.6 吹气膨胀特效
通过前面几个小节的学习,读者应该对顶点着色器的使用有了一定的了解。本节将进一步给出一个通过使用顶点着色器实时改变3D模型中顶点的位置,以实现物体吹气膨胀效果的案例。
1.6.1 特效基本原理
介绍本节案例的具体开发之前,首先需要了解一下本节案例实现吹气膨胀特效的基本原理,如图1-20所示。
从图1-20中可以看出,实现吹气膨胀特效时,由顶点着色器根据收到的参数将当前处理的顶点位置沿当前顶点的法向量方向移动一定的距离。每次处理时移动距离的大小由传入的参数控制,这样就可以非常方便地实现吹气膨胀的效果了。
▲图1-20 吹气膨胀特效的基本原理
1.6.2 特效开发步骤
上一小节介绍了实现物体吹气膨胀特效的基本原理,本小节将给出一个基于此原理开发的实现人物头部3D模型不断吹气膨胀的案例Sample1_6,其运行效果如图1-21所示。
▲图1-21 案例Sample1_6的运行效果图
了解了本节案例的运行效果后,接下来将对本节案例的具体开发过程进行简单介绍。由于本案例中的大部分代码与本书前面的很多案例非常类似,因此,这里仅给出本案例中有代表性的部分,具体内容如下所列。
(1)首先介绍的是用于在程序运行过程中不断修改吹气膨胀程度系数(fatFacror变量)的drawSelf方法,此方法来自于LoadedObjectVertexNormalTexture类。LoadedObjectVertexNormal Texture类在本书第一卷的第9章中介绍过,其对象表示从Obj文件中加载的3D模型。本案例中用于加载包含了人物头部的3D模型,其中drawSelf方法的具体代码如下。
代码位置:见随书光盘中源代码/第1章/Sample1_6/com/bn/Sample1_6目录下的LoadedObjectVertexNormalTexture.java。
1 public void drawSelf(int texId){ 2 fatFacror+=fatFacrorStep; //计算新的膨胀系数 3 if(fatFacror>0.05f||fatFacror<0){ //若膨胀系数达到上限或下限 4 fatFacrorStep=-fatFacrorStep; //将膨胀系数的符号置反 5 } 6 ……//此处省略了部分代码,与本书前面案例中的类似,有兴趣的读者可以自行查看随书光盘 7 GLES20.glUniform1f(muFatFactor, fatFacror); //将膨胀系数传入着色器 8 ……//此处省略了部分代码,与本书前面案例中的类似,有兴趣的读者可以自行查看随书光盘 9 }
提示
上述代码的主要功能为不断地修改物体的吹气膨胀系数,并把膨胀系数传入着色器程序。
(2)接着介绍的是接收吹气膨胀系数,并根据系数将顶点位置沿法向量方向移动一定距离的顶点着色器,其具体代码如下。
代码位置:见随书光盘中源代码/第1章/Sample1_6/assets目录下的vertex.sh。
1 uniform float uFatFactor; //接收的吹气膨胀系数 2 uniform mat4 uMVPMatrix; //总变换矩阵 3 uniform mat4 uMMatrix; //变换矩阵 4 uniform vec3 uLightLocation; //光源位置 5 uniform vec3 uCamera; //摄像机位置 6 attribute vec3 aPosition; //顶点位置 7 attribute vec3 aNormal; //顶点法向量 8 attribute vec2 aTexCoor; //顶点纹理坐标 9 varying vec4 ambient; //用于传递给片元着色器的环境光最终强度 10 varying vec4 diffuse; //用于传递给片元着色器的散射光最终强度 11 varying vec4 specular; //用于传递给片元着色器的镜面光最终强度 12 varying vec2 vTextureCoord; //用于传递给片元着色器的纹理坐标 13 void pointLight( //定位光光照计算的方法 14 in vec3 normal, //法向量 15 inout vec4 ambient, //环境光最终强度 16 inout vec4 diffuse, //散射光最终强度 17 Inout vec4 specular, //镜面光最终强度 18 in vec3 lightLocation, //光源位置 19 in vec4 lightAmbient, //光源环境光强度 20 in vec4 lightDiffuse, //光源散射光强度 21 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 { 42 //根据总变换矩阵计算绘制此顶点的位置,在计算时将顶点位置沿着法向量方向移动一定的距离 43 gl_Position = uMVPMatrix * vec4(aPosition+aNormal*uFatFactor,1); 44 vec4 ambientTemp, diffuseTemp, specularTemp;//环境光、散射光、镜面反射光的临时变量 45 pointLight(normalize(aNormal),ambientTemp,diffuseTemp,specularTemp,uLightLocation, vec4(0.15,0.15,0.15,1.0),vec4(0.9,0.9,0.9,1.0),vec4(0.4,0.4,0.4,1.0)); 46 ambient=ambientTemp; //将环境光最终强度传给片元着色器 47 diffuse=diffuseTemp; //将散射光最终强度传给片元着色器 48 specular=specularTemp; //将镜面光最终强度传给片元着色器 49 vTextureCoord = aTexCoor; //将接收的纹理坐标传递给片元着色器 50 }
提示
从上述顶点着色器的程序中可以看出,大部分都是与前面案例相同的,如计算定位光光照等。最能体现本节案例特点的就是第43行的代码,其在计算顶点经过变换后的最终位置时不是直接针对顶点的坐标计算的。而是首先将顶点坐标沿着顶点的法向量方向移动一定的距离(移动距离的大小由接收的吹气膨胀系数uFatFactor来确定),然后再与变换矩阵相乘。