OpenGL ES 2.0游戏开发(下卷)
上QQ阅读APP看书,第一时间看更新

3.4 菲涅尔效果的模拟

前面几节单独地介绍了反射、折射效果的模拟,本节将通过一个案例Sample3_4介绍将反射效果、折射效果综合到一起的菲涅尔(Fresnel)效果。

3.4.1 案例效果与基本原理

介绍本案例的具体开发步骤之前首先需要了解一下案例的运行效果与基本原理,其运行效果如图3-8所示。

▲图3-8 菲涅尔效果案例运行效果图

从运行效果图中可以看出,本案例场景基本上与前面一节的相同,所不同的就是场景中的玻璃球同时具有折射与反射效果。玻璃球正对摄像机的部分主要是折射效果,而边缘主要是反射效果,这就是前面提到的菲涅尔效果。

提示

由于本书是黑白灰度印刷,因此通过上面的图3-8可能看不清菲涅尔效果,这时请读者自行运行本节的案例或参看本书前面的彩页。

造成菲涅尔效果的原因是,当光线到达两种材质的接触面时,一部分光线被反射,另一部分光线被折射。大致的规律是当入射角较小时主要发生折射,当入射角较大时主要发生反射。

这与大家平时在湖边或池塘边的感觉一样:当目光相对于水面基本垂直时,主要看到的是水面下的内容;而当目光相对于水面入射角很大时,主要看到的是湖面反射的内容,而看不到水面下的内容,图3-9简单地说明了这个问题。

了解了菲涅尔效果的基本原理后还有一个重要的问题需要解决,那就是在给定情况下反射和折射各自所占的比例为多少。这个问题的精确计算比较复杂,需要用到专门为菲涅尔效果建立的复杂数学模型,基于这些模型的计算难以满足游戏实时性的需要。

▲图3-9 菲涅尔效果

实际开发中笔者建议采用简化的数学模型,也就是将折反射比例分成3种情况进行计算,具体情况如下所列。

● 若入射角小于一定的值,只计算折射效果。

● 若入射角大于一定的值,只计算反射效果。

● 若入射角在一定的范围内,则首先单独计算折射效果与反射效果,再将两种效果的计算结果按一定的比例进行融合。

提示

了解了菲涅尔效果的基本原理与案例的运行效果后,下面就可以进行实际的开发了,下一小节将对这些内容进行介绍。

3.4.2 菲涅尔效果开发步骤

上一小节介绍了菲涅尔效果的基本原理及案例的运行效果,本小节将介绍如何进行本节案例的开发。由于本节案例与上一节案例的大部分代码相同,仅仅在片元着色器中有所不同。因此,这里仅给出改动后片元着色器的代码,具体内容如下。

代码位置:见随书光盘中源代码/第3章/Sample3_4/assets目录下的frag_tex_cube.sh。

      1    precision mediump float;
      2    uniform samplerCube sTexture;        //纹理内容数据
      3    varying vec3 eyeVary;                //接收从顶点着色器过来的视线向量
      4    varying vec3 newNormalVary;          //接收从顶点着色器过来的变换后法向量
      5    vec4 zfs(//计算折反射纹理采样颜色的方法
      6      in float zsl//折射率
      7  ){
      8      vec3 vTextureCoord;                 //用于进行立方图纹理采样的向量
      9      vec4 finalColorZS;                  //若是折射的采样结果
      10     vec4 finalColorFS;                  //若是反射的采样结果
      11     vec4 finalColor;                    //最终颜色
      12     const float maxH=0.7;              //入射角余弦值若大于此值则仅计算折射
      13     const float minH=0.2;              //入射角余弦值若小于此值则仅计算反射
      14     float sizeH=maxH-minH;             //混合时余弦值的跨度
      15     float testValue=abs(dot(eyeVary,newNormalVary));   //计算视线向量与法向量的余弦值
      16     if(testValue>maxH){                                //余弦值大于maxH仅折射
      17        vTextureCoord=refract(-eyeVary,newNormalVary,zsl);
      18        finalColor=textureCube(sTexture, vTextureCoord);
      19     }  else if(testValue<=maxH&&testValue>=minH){//余弦值在minH~maxH范围内反射、折射融合
      20        vTextureCoord=reflect(-eyeVary,newNormalVary);
      21        finalColorFS=textureCube(sTexture, vTextureCoord);   //反射的计算结果
      22        vTextureCoord=refract(-eyeVary,newNormalVary,zsl);
      23        finalColorZS=textureCube(sTexture, vTextureCoord);   //折射的计算结果
      24        float ratio=(testValue-minH)/sizeH;                   //融合比例
      25        finalColor=finalColorZS*ratio+(1.0-ratio)*finalColorFS;   //折反射结果线性融合
      26     }  else   {                                                //余弦值小于minH仅反射
      27        vTextureCoord=reflect(-eyeVary,newNormalVary);
      28        finalColor=textureCube(sTexture, vTextureCoord);
      29     }
      30     return finalColor;                              //返回最终结果
      31   }
      32   void main(){
      33      vec4 finalColor=vec4(0.0,0.0,0.0,0.0);
      34      //由于有色散RGB 3个色彩通道单独计算折射
      35      finalColor.r=zfs(0.97).r;                      //计算红色通道
      36      finalColor.g=zfs(0.955).g;                     //计算绿色通道
      37      finalColor.b=zfs(0.94).b;                      //计算蓝色通道
      38      gl_FragColor=finalColor;                       //将最终的片元颜色传递给管线
      39   }

提示

从上述代码中可以看出,主要的变化在于将原来计算折射的zs函数替换为综合计算折反射效果的zfs函数。此函数中根据入射角度的大小分为3种情况计算纹理采样的结果,实现了上一小节提出的简化数学模型。

到这里为止如何实现菲涅尔效果的模拟就介绍完了,在以后的具体项目开发中读者若能做到灵活运用,将可以开发出更加逼真的场景。