之前我们介绍了反射效果、折射效果、色散效果的模拟,本部分我们将反射效果、折射效果综合到一起的菲涅尔(Fresnel)效果(在BRDF模型中,有专门的涅菲尔项计算方法,此处方法与之不同,但效果类似)。
基本原理
本部分场景基本上与前面一节的相同,所不同的是场景中的玻璃球同时具有折射与反射效果。玻璃球正对摄像机的部分主要是折射效果,而边缘主要是反射效果,这就是前面提到的菲涅尔效果。
造成菲涅尔效果的原因是,当光线到达两种材质的接触面时,一部分光线被反射,另一部分光线被折射。大致的规律是当入射角较小时主要发生折射,当入射角较大时主要发生反射。
这与大家平时在湖边或池塘边的感觉一样:当目光相对于水面基本垂直时,主要看到的是水面下的内容;而当目光相对于水面入射角很大时,主要看到的是湖面反射的内容,而看不到水面下的内容,下图简单地说明了这个问题。
了解了菲涅尔效果的基本原理后还有一个重要的问题需要解决,那就是在给定情况下反射和折射各自所占的比例为多少。这个问题的精确计算比较复杂,需要用到专门为菲涅尔效果建立的复杂数学模型,基于这些模型的计算难以满足游戏实时性的需要。
实际开发中我们可以采用简化的数学模型,也就是将折反射比例分成 3 种情况进行计算,具体情况如下所列。
- 若入射角小于一定的值,只计算折射效果。
- 若入射角大于一定的值,只计算反射效果。
- 若入射角在一定的范围内,则首先单独计算折射效果与反射效果,再将两种效果的计算结果按一定的比例进行融合
二、开发步骤
我们首先把设置两个临界值,当入射光线的反向与法线的余弦值为0.7及0.2时作为变化条件,这样我们可以将球体分为三个部分:
划分好区域后,我们便需要对不同部分进行折射反射等采样融合操作:
//菲涅尔效果
vec4 FresnelEffect(in float coefficient){
vec3 cI = normalize (inPos);
vec4 finalColorZS; //若是折射的采样结果
vec4 finalColorFS; //若是反射的采样结果
vec4 finalColor; //最终颜色
const float maxH=0.7; //入射角余弦值若大于此值则仅计算折射
const float minH=0.2; //入射角余弦值若小于此值则仅计算反射
float sizeH=maxH-minH; //混合时余弦值的跨度
float testValue=abs(dot(-cI,inNormal)); //计算视线向量与法向量的余弦值
if(testValue>maxH)
{//余弦值大于maxH仅折射
vec3 cR = refract(cI,inNormal,coefficient );//折射
cR = vec3(inInvModelView * vec4(cR, 0.0));
cR.x *= -1.0;
finalColor = texture(samplerColor, cR, inLodBias);
//finalColor=vec4(0,0,1,0);
}
else if(testValue<=maxH && testValue>=minH)
{//余弦值在minH~maxH范围内反射、折射融合
vec3 cR = reflect(cI, normalize(inNormal));//反射
cR = vec3(inInvModelView * vec4(cR, 0.0));
cR.x *= -1.0;
finalColorFS = texture(samplerColor, cR, inLodBias);
vec3 cRZS = refract(cI,inNormal,coefficient);//折射
cRZS = vec3(inInvModelView * vec4(cRZS, 0.0));
cRZS.x *= -1.0;
finalColorZS = texture(samplerColor, cRZS, inLodBias);
float ratio=(testValue-minH)/sizeH; //融合比例
finalColor=finalColorZS*ratio+(1.0-ratio)*finalColorFS; //折反射结果线性融合
//finalColor=vec4(0,1,0,0);
}
else
{//余弦值小于minH仅反射
vec3 cR = reflect (cI, normalize(inNormal));//反射
cR = vec3(inInvModelView * vec4(cR, 0.0));
cR.x *= -1.0;
finalColor = texture(samplerColor, cR, inLodBias);
//finalColor=vec4(1,0,0,0);
}
return finalColor; //返回最终结果
}
最后在main中调用:
void main()
{
vec4 color = vec4(0,0,0,0);
color.r = FresnelEffect(0.95).r;
color.g = FresnelEffect(0.945).g;
color.b = FresnelEffect(0.94).b;
...
}
生成可见开头效果。