webgl中的光照和反射变换

优雅的学习webgl(5)—webgl中的光照和反射变换

在前一章中我们介绍了投影模型,至此我们知道了在三维场景下观测一个物体必须要有视觉模型和投影模型,然而光有这两个模型好不够,跟现实生活中还是有差异的,举例来说,我们前面绘制的正方体没给面的颜色是不同的,因此我们可以区别出来不同的面,如果正方体的每个面的颜色都是相同的,我们只用视觉模型和投影模型是区别不出来的。然而现实生活中,即使是一个纯色的正方体,我们也能够区别出不同的平面。那么是为什么呢,这就是光照的影响

  • 光照
  • 光源类型和反射类型
  • 方向光和漫反射
  • 点光源和漫反射

这个系列的源码地址为:源码的地址为: https://github.com/forthealllight/webgl-demo

一、光照

我们以一个简单的例子来引出光照对于渲染结果的影响,同样的绘制一个正方体,这次我们绘制的正方体的8个面的颜色是相同的,比如都是白色的。那么绘制结果如下图所示:

上述的绘制结果,其实我们无法区别是每一个面,然而在现实生活中,即使是一个纯色的正方体,我们用肉眼也是可以区别出不同平面的,那么是为什么呢?

因为正方体每个平面处的光照不同,因此成像在人的视网膜中的每个平面的颜色就不会完全相同。

简单来说,是光照导致了纯色正方体成像的差异,因为光照通常是随位置变化的。影响光照的因素就主要是光源类型和反射类型,不同的光源类型和反射类型也可以排列组合,因此光照的影响表现出多样性。

二、光源类型和反射类型

1.光源类型

下面我们来看光源类型,在你本文中主要讨论平行光、点光源和环境光。在本文中不会去考虑光强的影响,在本文中的光源默认照射到的地方,光强是相同的。

  • 平行光: 典型的就是太阳光,其方向是恒定不变的
  • 点光源: 典型的就是手电或者是一个蜡烛,其光的方向是从光源指向物体的表面
  • 环境光:典型的就是狭小空间的日光灯,其方向是四面八方的,物体的一个表面的一个点可能接收到任意方向的几束光,也可以说环境光是无方向的,环境光我们只需要考虑其光照颜色。

这里举例太阳光,手电等其实是不准确的,具体要根据光源的类型实际分析,比如单个手电可以被认为是点光源,但是如果是10个手电筒从同一个方向照射,可能被认为是平行光,如果这10个手电筒分别从10个方向照射一个物体,那么可能被认为是环境光。

我们用表格来表示不同光源类型之前的区别:

光源类型 方向 实例
平行光/方向光 固定的一个方向,物体表面光的方向都是相同的 太阳光
点光源 方向从光源指向物体的表面 蜡烛,手电等
环境光 方向是四面八方的,或者说是无方向性,方向无关性 日光灯

2.反射类型

光源照在物体表面最后成像在画布上,不仅与光源以及光的类型有关,还与物体表面的反射类型有关,典型的例子就是金属和石头,太阳光照在金属上成像在人眼中是晶莹透亮很有光泽,而同样是太阳光照在石头上,成像则没有金属的质感。导致这种成像差异的就是:反射类型。

在物理中我们研究过镜面反射、漫反射以及环境反射。镜面反射较为特殊,比如镜子等很光滑的物体表面, 不管光线从什么方向入射,都是被反射到同一个方向 ,对于镜面反射我们不做讨论,现实生活中发生镜面反射的场景也很有限,本文主要考虑的是漫反射和环境反射。

  • 镜面反射(本文不会详细介绍):任意方向的入射光,从同一个方向反射出来
  • 漫反射:当光照射到不光滑的物体上,入射光与法线的夹角等于反射光与法线的夹角
  • 环境反射:入射光与反射光的方向完全相反

本文主要研究漫反射和环境反射这两种在实际场景中比较常见的反射类型。

漫反射

上述是漫反射的示意图,漫反射后反射光的颜色跟入射角,表面基地的颜色以及入射光的颜色有关。

漫反射光最后的颜色定义公式为:

<漫反射光的颜色>= <入射光的颜色> x <表面基底的颜色> x 入射角的余弦

环境反射

上述是环境反射的示意图,根据环境反射的原理我们可以知道,环境反射跟入射角没有关系,只跟物体表面基底的颜色以及入射光的颜色有关,环境反射光最后的颜色定义公式为:

<环境反射光的颜色>= <入射光的颜色> x <表面基底的颜色>

如果一个物体在光照下既发生了漫反射,又发生了环境反射,那么它最后反射光的颜色为:

<反射光的颜色> = <漫反射光的颜色> + <环境反射光的颜色>

三、方向光和漫反射

下面我们组合光源和反射类型,来实际的看看,在光照和投影下物体的成像。首先来看不考虑光照下的正方体的渲染结果:

接着我们来看如何在光照下渲染改正方体。

const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec4 aVertexColor;
    //法向量的变量
    ***attribute vec3 a_normal;*** (1)

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying lowp vec4 vColor;
    //将法向量从顶点着色器传递到片元着色器的变量
    varying lowp vec3 v_normal;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vColor = aVertexColor;
      ***v_normal = mat3(uModelViewMatrix) * a_normal;*** (2)
    }
  `;

上述是顶点着色器中的代码,添加光照的渲染的代码在(1)和(2)处,(1)处我们增加了个变量a_normal用来接收物体的法向量。而随着物体的旋转,其法向量也是会跟着旋转的,因此我们在(2)处将法向量同样的根据世界变换矩阵来进行旋转。

接着来看片元着色器,

const fsSource = `
    precision mediump float;
    varying lowp vec4 vColor;
    ***varying lowp vec3 v_normal;*** (3)
    
    //方向光的方向
    uniform lowp vec3 u_light;
    
    void main(void) {
      vec3 normal = normalize(v_normal);
      ***float light = dot(normal,u_light);***(4)
      gl_FragColor = vColor;
      gl_FragColor.rgb *= light;
    }
  `;

在片元着色器中的代码,接收从顶点着色器中传递过来的法向量变量v_normal,然后根据方向光和漫反射的类型来计算最后的反射光,(4)处用于计算两个向量的点乘(包含了余弦值)。

这里可能有一个疑问,就是为什么向量的点乘就等于两个向量的余弦值呢?

向量a,b的点乘 = |a||b|cos

如果a,b都是归一化后的向量,归一化后向量的模为1,因此:

向量a,b的点乘 = cos

这里提示我们必须保证两个向量都归一化后才能用点乘来计算余弦值。

最后的成像结果如下图所示:

在这里我们并没有指定光源光的颜色,默认为白色光。

这里因为每个面的颜色是不同的,我们看到的在光照下的成像效果,没有很明显,如果我们将正方体每个面的颜色都修改为纯色的,那么在白光下的渲染结果为:

上述纯白色的正方体在白色平行光的照射下的成像模型很复合我们人的肉眼观测结果。

本例的代码见: https://github.com/forthealllight/webgl-demo/tree/master/demo10

四、点光源和漫反射

在上一节我们讲了平行光或者说方向光和漫反射的组合下的成像规律,本章我们来介绍一下点光源和漫反射下的成像规律。

我们同样以白色的光源为例,与方向光不同,点光源的光的方向是从光源点指向物体表面的,所以我们要根据物理表面的每个一点,以及光源的位置来重新计算光照的方向。

首先来看顶点着色器:

const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec4 aVertexColor;
    attribute vec3 a_normal;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;
    ***uniform vec3 u_lightWorldPosition;*** (1)

    varying lowp vec4 vColor;
    varying lowp vec3 v_normal;
    varying lowp vec3 v_surfaceToLight;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vColor = aVertexColor;
      v_normal = mat3(uModelViewMatrix) * a_normal;
      ***vec3 surfaceWorldPosition = (uModelViewMatrix * aVertexPosition).xyz;***(2)
      ***v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;***(3)
    }
  `;

跟方向光不同,我们在(1)处传入了光源在世界坐标系中的位置坐标,同时在(2)处拿到模型变换后,被观测的物体的表面每一个点的坐标。最后根据这两个坐标我们在(3)中就算出了每一个点的光照的方向,即:

从光源指向物体表面某个点的向量

从顶点着色器中我们通过计算拿到了光照的方向,在片元着色器中,我们只要根据光照方向,重新计算一下颜色即可。

varying lowp vec4 vColor;
    varying lowp vec3 v_normal;
    varying lowp vec3 v_surfaceToLight;
    
    void main(void) {
      vec3 normal = normalize(v_normal);
      vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
      float light = dot(normal,surfaceToLightDirection);
      gl_FragColor = vColor;
      gl_FragColor.rgb *= light;
    }

片元着色器中的代码仅仅是将传入的光照的方向归一化后,与法线向量一起计算最后的颜色。

最后的渲染结果如下,同时也对比了方向光下的渲染结果:

前图表示白色方向光下的渲染结果,后图表示白色点光源下的方向光的渲染结果。很明显电光源下的渲染结果较暗,这也复合我们的肉眼实际成像,点光源照射下的物体没有太阳光等平行光照射下的物体那样明亮。

本例的代码见: https://github.com/forthealllight/webgl-demo/tree/master/demo11

本文没有讨论环境光的详情,环境光更简单,只需要将环境光颜色与入射光颜色相乘即可。没有方向性。这里就不具体讨论.