WebGL+Three.js—第六章 WebGL光照

494 阅读6分钟

6.1 光是如何使用的

6.1.1 光的作用

        在现实生活中,我们很少见到一个完全纯色的物体,因为在光照的作用下,物体也会产生出不同的明暗的反应。

image.png

        在这个立方体可以体现出,正对着光的平面会显得明亮一些,背对着光的平面就会显得暗淡一些。即使是一个完全纯色的物体也会在光照的作用下产生不同的反应,让我们看到物体真实的样子。

6.1.2 光的类型

    1、点光源

        (1)一个点向周围发出的光,如灯泡、火焰等。

        (2)定义一个点光源光需要光源的位置、光线方向以及颜色

        (3)根据照射点的位置不同,光线的方向也不同。

image.png

            目前立方体在光源的左下方,因此光线的方向也是往左下方的,如果立方体在光源的右下方,那么光线的方向也随之而改变。

    2、平行光

        (1)可以看成是无限远处的光源发出的光,如太阳光。

        (2)因为离光源的位置特别远,所以到达被照物体时可以认为光线是平行的。

        (3)只需要光照方向和光照颜色

image.png

    3、环境光

        (1)也就是间接光,是指光源发出后,经过其他物体各种发射,然后照到物体表面上的光线。

        (2)环境光的强度差距非常小,没有必要精确计算光线强度。

        (3)环境光是均匀照射到物体表面的,只需要定义光照颜色

image.png

6.1.3 反射

    有了光有时候也不一定就能看到物体,因为有的物体是不发射光线的,但有些物体它反射光线非常强烈。反射包含以下两种类型:

    1、环境反射

        (1)环境反射是针对环境光而言的,在环境反射中,环境光照射物体是各方面均匀、强度相等的,反射的方向就是入射光的反方向。

        (2)最终物体的颜色只跟入射光颜色和基底色有关。

        (3)环境反射光颜色 = 入射光颜色 * 基底色

image.png

        环境反射在反射的时候,因为物体表面是均匀的,所以反射出去之后也是均匀的。

    2、漫反射

        (1)在现实中大多数物体表面都是粗糙的,导致每一条光线的入射光的角度和反射角度都不一样

image.png

        (2)漫反射中反射光的颜色除了取决于入射光的颜色、表面的基底色,还有入射光与物体表面的法向量形成的入射角

        (3)令入射角为α,漫反射光的颜色可以根据下式计算:

            漫反射光颜色 = 入射光颜色 * 表面基底色 * cos(α)

        (4)入射角α可以通过光线方向和法线方向的点积来计算:

            cos(α) = 光线方向 * 法线方向

        (5)最终漫反射光颜色的公式如下:

            漫反射光颜色 = 入射光颜色 * 表面基底色 * (光线方向 * 法线方向)

        (6)"光线方向"指的是入射方向的反方向,即从入射点指向光源方向

image.png

        (7)当漫反射和环境反射同时存在时,将两者加起来,就会得到物体最终被观察到的颜色:

            表面的反射光颜色 = 漫反射光颜色 + 环境反射光颜色

6.2 给场景添加光源

    我们了解了光源最主要的组成是漫反射光颜色和环境反射光颜色,那我们只需要根据它们所需要的条件设置即可。

6.2.1 设置环境反射光颜色

    环境反射光的颜色,是由入射光颜色和基底色组成的。入射光颜色也就是环境光颜色,基底色也就是物体表面的颜色。

    环境反射光颜色 = 入射光颜色 * 基底色

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
  attribute vec4 aPosition;
  attribute vec4 aNormal;
  varying vec4 vColor;

  uniform mat4 mat;
  void main() {
    // 环境光
    vec3 uAmbientLightColor = vec3(0.2,0.2,0.2);

    // 物体表面的颜色
    vec4 aColor = vec4(1.0,0.0,0.0,1.0);

    // 环境反射光颜色
    vec3 ambient = uAmbientLightColor * vec3(aColor);
  }
`; // 顶点着色器

6.2.2 设置漫反射光颜色

    漫反射光的颜色,是由入射光颜色、表面基底色、光线方向、法线方向4者组成的。

    漫反射光颜色 = 入射光颜色 * 表面基底色 * cos(α)(入射光与物体表面的法向量形成的入射角)

                           = 入射光颜色 * 表面基底色* (光线方向 * 法线方向)

    1、设置法线方向(法向量)

        以矩形为例子,它一共有6个面,因此我们要给它设置6个面的法向量,所谓法向量的意思是垂直于当前平面的方向,当然这个方向有两个,一个是向内,一个是向外,在这里是设置光源的反射光,那我们设置的是向外的法向量。

        它们的法向量分别是:前(0.0,0.0,1.0)、后(0.0,0.0,-1.0)、左(-1.0,0.0,0.0)、右(1.0,0.0,0.0)、上(0.0,1.0,0.0)、下(0.0,-1.0,0.0)

        注意:在设置法向量的时候,要注意每个方向的数量要跟每个面的点的数量一致,例如前面的法向量为(0.0,0.0,1.0),但因为一个面是由两个三角形(4个点)组成的,因此在设置的时候要写成0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0。

// 法向量
const normals = new Float32Array([
  0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,                // 前面
  0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,            // 后面
  -1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,            // 左面
  1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,                // 右面
  0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,                // 上面
  0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,            // 下面
])
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aNormal)

    2、设置漫反射光颜色

        现在法向量已经准备就绪,它可以通过变量传入到着色器源码里。其余3个入射光颜色、表面基底色和光线方向分别给它们定义即可。

        其中光线方向是从入射点指向光源方向,这个得先定义好光源的位置和点坐标的位置,通过光源位置与坐标位置的向量差可以得到光线方向。

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
  attribute vec4 aPosition;
  attribute vec4 aNormal;
  varying vec4 vColor;

  uniform mat4 mat;
  void main() {
    // 定义点光源的颜色
    vec3 uPointLightColor = vec3(1.0,1.0,0.0);

    // 物体表面的颜色
    vec4 aColor = vec4(1.0,0.0,0.0,1.0);

    // 点光源的位置
    vec3 uPointLightPosition = vec3(-5.0,6.0,10.0);
    
    // 顶点的世界坐标
    vec4 vertexPosition = mat * aPosition;

    // 点光源的方向
    vec3 lightDirection = normalize(uPointLightPosition - vec3(vertexPosition));

    // 计算入射角 光线方向和法线方向的点积
    float dotDeg = dot(lightDirection, vec3(aNormal));

    // 漫反射光的颜色
    vec3 diffuseColor = uPointLightColor * vec3(aColor) * dotDeg;
  }
`;

// 法向量
const normals = new Float32Array([
  0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,                // 前面
  0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,            // 后面
  -1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,            // 左面
  1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,                // 右面
  0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,                // 上面
  0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,            // 下面
])
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aNormal)

6.2.3 设置光源

    至此已经获取环境反射光和漫反射光的颜色,最后把它们俩相加即可得到最终的光源。

// 创建着色器源码
const VERTEX_SHADER_SOURCE = `
  attribute vec4 aPosition;
  attribute vec4 aNormal;
  varying vec4 vColor;

  uniform mat4 mat;
  void main() {
    // 环境光
    vec3 uAmbientLightColor = vec3(0.2,0.2,0.2);

    // 物体表面的颜色
    vec4 aColor = vec4(1.0,0.0,0.0,1.0);

    // 环境反射光颜色
    vec3 ambient = uAmbientLightColor * vec3(aColor);

    // 定义点光源的颜色
    vec3 uPointLightColor = vec3(1.0,1.0,0.0);

    // 点光源的位置
    vec3 uPointLightPosition = vec3(-5.0,6.0,10.0);

    // 顶点的世界坐标
    vec4 vertexPosition = mat * aPosition;

    // 点光源的方向
    vec3 lightDirection = normalize(uPointLightPosition - vec3(vertexPosition));

    // 计算入射角 光线方向和法线方向的点积
    float dotDeg = dot(lightDirection, vec3(aNormal));

    // 漫反射光的颜色
    vec3 diffuseColor = uPointLightColor * vec3(aColor) * dotDeg;

    gl_Position = vertexPosition;
    vColor = vec4(ambient + diffuseColor, aColor.a);
  }
`;
// 法向量
const normals = new Float32Array([
  0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,                // 前面
  0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,            // 后面
  -1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,            // 左面
  1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,                // 右面
  0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,0.0,1.0,0.0,                // 上面
  0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,0.0,-1.0,0.0,            // 下面
])
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(aNormal)

6.2.4 代码示例