3.2 折射环境模拟

现实世界中很多场合都会有这样的情况,透过透明物体观察周围的环境时会有折射的现象。平时使用的很多光学设备利用的就是光线的折射,例如:放大镜、眼镜、望远镜、显微镜等。本节将通过一个案例Sample3_2向读者介绍如何基于OpenGL ES 2.0来开发折射的效果。

3.2.1 案例效果与基本原理

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

▲图3-3 反射环境纹理案例运行效果图

从运行效果图中可以看出,本节案例在自然场景中放置了一个透明的玻璃球,透过玻璃球观察后面的场景时会有放大镜的效果。自然场景是用前面章节介绍过的天空盒技术实现的,玻璃球折射出周围环境的内容是通过立方图纹理技术实现的。

提示

本案例运行时,当手指在屏幕上水平滑动时摄像机会绕场景转动,当手指在屏幕上垂直滑动时摄像机会随手指的移动而升降。

本节案例与上一节反射的案例均采用立方体纹理技术,所不同的是本节中的采样向量是视线经玻璃球表面折射后的向量,而不再是被表面反射后的向量。立方体纹理技术本身已经在上一节详细介绍过,因此本节仅介绍采用立方图纹理技术实现折射效果的原理,如图3-4所示。

▲图3-4 立方体纹理技术实现折射效果的原理

从图3-4中可以看出,本案例中为了实现玻璃球折射出周围环境的内容也有两项关键工作要做,具体如下所列。

● 根据摄像机位置及被观察点位置计算出观察方向(视线)向量,并参照被观察点的法向量计算出视线折射方向向量。

● 根据视线折射方向向量确定采样点,实施立方图纹理采样。这与现实世界中人眼观察透明物体,其折射出观察的环境内容是完全一致的。

提示

上述两项工作中的第一项需要自己编程在着色器中实现,第二项只需要拿着采样向量调用textureCube函数即可完成。

折射方向的计算遵循斯涅尔定律,具体情况如图3-5所示。

▲图3-5 斯涅尔定律原理

从图3-5中可以看出,入射向量与折射向量共面,且入射角α、折射角β与两种材质的折射率满足如下公式。

A×sin(α) = B×sin(β)

提示

了解了折射的基本原理与计算公式后,下面就可以进行实际的开发了,下一小节将对这些内容进行介绍。

3.2.2 开发步骤

了解了案例的运行效果及基本原理后,就可以进行案例的开发了,具体步骤如下。

(1)首先用3ds Max生成一个球,并导出成obj文件放入项目的assets目录中待用。

(2)开发出搭建场景的基本代码,包括:天空盒、立方图纹理、加载物体、摆放物体等。这些代码与前面章节的许多案例基本套路完全一致,因此这里不再赘述。

(3)完成了Java代码之后就可以开发着色器了。本案例中有两套着色器:一套是普通贴纹理的,用来绘制天空盒的各个面;另一套是负责玻璃球立方图纹理实施的。第一套与原来案例中的完全一样,这里不再赘述。下面着重介绍用来绘制玻璃球折射效果的、立方图纹理相关的着色器,首先是顶点着色器,其代码如下。

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

      1    uniform mat4 uMVPMatrix;        //总变换矩阵
      2    uniform mat4 uMMatrix;          //变换矩阵
      3    uniform vec3 uCamera;           //摄像机位置
      4    attribute vec3 aPosition;       //顶点位置
      5    attribute vec3 aNormal;         //顶点法向量
      6    varying vec3 eyeVary;           //用于传递给片元着色器的视线向量
      7    varying vec3 newNormalVary;    //用于传递给片元着色器的变换后法向量
      8    void main(){
      9      gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
      10      //计算变换后的法向量并规格化
      11     vec3 normalTarget=aPosition+aNormal;
      12     vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
      13     newNormal=normalize(newNormal);
      14     newNormalVary=newNormal;//将变换后的法向量传递给片元着色器
      15     //计算从观察点到摄像机的向量(视线向量)
      16     vec3 eye = normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);
      17     eyeVary=eye;//将视线向量传递给片元着色器
      18   }

说明

上述顶点着色器的主要功能为将视线向量、法向量通过易变变量传递给片元着色器,供片元着色器在折射计算中使用。

(4)完成了顶点着色器的开发后,就可以开发对应的片元着色器了,其代码如下。

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

      1    precision mediump float;
      2    uniform samplerCube sTexture;  //纹理内容数据
      3    varying vec3 eyeVary;           //接收从顶点着色器过来的视线向量
      4    varying vec3 newNormalVary;    //接收从顶点着色器过来的变换后法向量
      5    vec4 zs(                      //根据法向量、视线向量及斯涅尔定律计算立方图纹理采样的方法
      6      in float zsl                  //折射系数
      7  ){
      8      vec3 vTextureCoord=refract(-eyeVary,newNormalVary,zsl);
                                            //根据斯涅尔定律计算折射后的视线向量
      9      vec4 finalColor=textureCube(sTexture, vTextureCoord);   //进行立方图纹理采样
      10     return finalColor;
      11   }
      12   void main(){
      13      gl_FragColor=zs(0.94);       //以折射系数0.94调用zs方法完成片元颜色的计算
      14   }

● 第8行调用本套书第一卷中第4章介绍过的折射函数refract计算出折射后的视线向量。

● 第9行根据计算出的折射后视线向量调用textureCube函数进行立方图纹理采样。

提示

从上述代码中可以看出,由于OpenGL ES 2.0着色语言内置函数的良好支持,很多工作都被大大地简化了。笔者强烈建议读者花些时间熟悉本套书第一卷中第4章介绍的函数,避免“重复发明车轮”,这样在开发中就可以事半功倍了。

到这里为止如何采用立方图纹理技术实现折射环境的模拟就介绍完了,在以后的具体项目开发中读者若能做到灵活运用,将可以开发出更加逼真的场景。