OpenGL ES for Android(投光物)

·  阅读 501

简介

在之前的章节学习的光源都是一个点,虽然效果不错,和现实世界的光源还有一定的差距。将光投射(Cast)到物体的光源叫做投光物(Light Caster),这里我们学习几种比较常见的光源:定向光(Directional Light),点光源(Point Light)还有聚光(Spotlight)。

定向光

当光源非常远时,来自光源的每条光线就会近似于互相平行,而且光源强度相同。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光。定向光非常好的一个例子就是太阳。太阳距离我们并不是无限远,但它已经远到在光照计算中可以把它视为无限远了。所以来自太阳的所有光线将被模拟为平行光线,我们可以在下图看到:

因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的。由于光的位置向量保持一致,场景中每个物体的光照计算将会是类似的。

我们可以定义一个光线方向向量而不是位置向量来模拟一个定向光。着色器的计算基本保持不变,但这次我们将直接使用光的direction向量而不是通过position来计算lightDir向量。我们修改着色器代码:

  struct Light {
      // vec3 position; // 使用定向光就不再需要位置了
      vec3 direction;
 
      vec3 ambient;
      vec3 diffuse;
      vec3 specular;
  };
  ...
  void main()
  {
    vec3 lightDir = normalize(-(aLightMatrix *light.direction));
    ...
  }
复制代码

首先我们对光的向量在View空间计算,得到最终的lightDir向量,随后再用它来计算环境光和镜面光。这里我们创建多个箱子并对它们进行不同的缩放和位移,来展示光照的效果。直接来看效果图


源码地址github.com/jklwan/Open…

点光源

在前面的章节中,我们用的光源都是点光源,但是我们看到被光照的地方亮度都是相同的,而实际上光是会随着距离变大而变弱的,而且超过最大距离后就没有光照效果了,这通常叫做衰减。我们需要一个公式来减少光的强度,不用担心已经有前辈解决了这个问题,

在这里d代表了片段距光源的距离。接下来为了计算衰减值,我们定义3个(可配置的)项:常数项、一次项和二次项。

  • 常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
  • 一次项会与距离值相乘,以线性的方式减少强度。
  • 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。* 二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。

由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:

怎么选择合适的值呢,可以参考文档wiki.ogre3d.org/tiki-index.…,我们选择32到100距离之内的值都可以,这里我们选择50距离的值:1.0, 0.09, 0.032,我们给着色器代码的光源结构体添加这三个常数项参数,并计算衰减,计算衰减后再与三个光照效果相乘,最后得到结果,关键代码如下:

  ……
  struct Light {
 
      ……
      float constant;
      float linear;
      float quadratic;
  };
  ……
  void main() {
      ……
      // 衰减
      float distance = length(light.position - fragPos);
      float attenuation = 1.0 / (light.constant + light.linear * distance + 
  light.quadratic * (distance * distance));
      ambient *= attenuation;
      diffuse *= attenuation;
      specular *= attenuation;
      // 结果
      vec3 result = ambient + diffuse + specular;
 
      gl_FragColor = vec4(result, 1.0);
  }
复制代码

我们查看效果图,可以看到离光源远的地方光照效果更弱

源码地址github.com/jklwan/Open…

聚光

聚光也很常见,例如手电筒,有灯罩的路灯等等,这种光之后朝一个特定方向照射,在光照范围外就没有光照效果。先看下图聚光灯的工作:

我们先要计算得到顶点上与光源的向量:LightDir,然后 计算其与光源方向(SoptDir)的夹角θ,如果这个角度大于聚光的角度ϕ则该顶点可以被照射到,反之则不可以。所以我们要定义这三个参数,光源位置,光源照射方向,还有光源的照射范围:

 // 定义光源结构体
  struct Light {
      vec3 position;
      vec3 direction;
      float cutOff;
  };
复制代码

其中cutOff为切光角,是用角度值计算得到的余弦值,我们需要计算LightDir和SpotDir向量的点积,然后和cutOff对比。主要代码如下:

  ……
  void main() {
      vec3 lightDir = normalize(light.position - fragPos);
 
      // 检查当前点与光源的连线是否在聚光灯内
      float theta = dot(lightDir, normalize(-(aLightMatrix * 
  light.direction)));
 
      if(theta > light.cutOff){
          // 被照射到
          ……
      } else {
          // 没有被照射到
          gl_FragColor = vec4(light.ambient * 
  texture2D(material.diffuse, TextCoord).rgb, 1.0);
      }
 
  }
复制代码

我们传入各项值后查看效果:

我们看到聚光灯内的光照比较亮,所以我们需要一个逐渐变化的效果。我们定义一个内圆锥和一个外圆锥:大于外圆锥不显示光照效果;小于内圆锥显示最大亮度;而在外圆锥和内圆锥内的区域光照效果逐渐变化。具体公式如下,

这里ϵ(Epsilon)是内(ϕ)和外圆锥(γ)之间的余弦值差(ϵ=ϕ−γ)。最终的I值就是在当前片段聚光的强度。值若小于0时置为0,这时我们就不用再使用if else了,主要代码如下:

  float theta = dot(lightDir, normalize(-light.direction));
  float epsilon = light.cutOff - light.outerCutOff;
  float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 
  1.0);    
  ……
  diffuse  *= intensity;
  specular *= intensity;
  ……
复制代码

此时我们看到聚光的效果就比较平滑了





分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改