site infoHacknerd | Tech Blog
blog cover

🎊 [WebGL编程指南] 8. 光照原理

WebGL

光照原理

现实世界物体被光线照射时,会反射一部分光。当只有反射光线进入眼镜时,才能辨认出它的颜色。比如白盒子反射白光,眼睛看到盒子是白色的

光照射到物体上,发生了两个重要现象

  • 根据光源和光线的方向,物体不同表面的明暗程度不一样
  • 根据光源和光线方向,物体向地面投下影子
  • 三维图形学属于着色(shading)的含义是,根据光照条件重新构建“物体个表面明暗不一的效果”。物体向地面投下影子,又被称为阴影。

    光源类型

  • 平行光,类似于自然中的太阳光,光线是相互平行的,因为太阳离地球很远,光线到达地球可以认为是平行的。
  • 点光源,从一个点向周围的所有方向发出的光,可以用来表示灯泡,火焰等。需要指定点光源的位置和颜色,光照方向根据点光源的位置和被照射之处计算出来。
  • 环境光,光线经光源发出后,被墙壁等物体多次反射,然后照射到物体表面上的光。环境光从各个角度照射物体,其强度是一致的
  • 聚光灯光,模拟电筒、车前灯
  • 反射类型

  • 漫反射。
  • 针对平行光或者点光源而言。漫反射的反射光在各个方向上均匀的。现实中大多数材质,比如纸张、岩石、塑料等,表面是粗糙的,反射光会以不固定角度反射出去,漫反射是这一种情况的理想模型。

    image

    漫反射的反射光,取决于入射光的颜色、表面的基地色、入射光与表面形成的入射角。

    image
  • 环境反射
  • 针对环境光而言。反射光的方向可以任务是入射光的反方向。由于环境光照是个方向平均、相等的,所以反射光也是各方向平均,相等的。

    image

    当漫反射和环境反射同时存在时,将两者颜色相加得到最终颜色

    image

    计算入射角

    可以通过计算两个矢量的点积,来计算这两个矢量的夹角的余弦值

    image

    点积的运算:

    image
    image
    image

    则反射光的颜色等于:

    image

    注意:

    法向量:

    image

    平行光下的漫反射

    javascriptCopy
    // 顶点着色器
    const VSHADER_SOURCE =
    	"attribute vec4 a_Position;\n" +
    	"attribute vec4 a_Color;\n" + 
    	"attribute vec4 a_Nromal;\n" + // 法向量
    	"uniform vec4 u_MvpMatrix;\n" +
    	"uniform vec3 u_LightColor;\n" + // 光线颜色
    	"uniform vec3 u_LightDirection;\n" + // 归一化的世界坐标
    	"varying vec4 v_Color;\n" +
      "void main() {\n" +
      "  gl_Position = u_MvpMatrix * a_Position;\n" + 
      // 法向量归一化
      "  vec3 normal = normalize(vec3(a_normal));\n" + 
      // 计算光线方向和法向量点积 
      "  float nDotL = max(dot(u_LightDirection, normal), 0.0);\n" +
      // 计算漫反射光的颜色 
      "  vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n" + 
      "  v_Color = vec4(diffuse , a_Color.a);\n" +
      "}\n";
      
      // ...
      const u_LightColor = gl.getUniformLocation(gl.program. 'u_LightColor')
      const u_LightDirection= gl.getUniformLocation(gl.program. 'u_LightDirection')
      
      // ...
      // 设置光线颜色
      gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0)
      
      // 设置光线方向(三方库)
      const lightDirection = new Vector3([0.5, 3.0, 4.0])
      // 归一化
      lightDirection.normalize()
      gl.uniform3fv(u_LightDirection, lightDirection.elements)
      
      // ...
      
      // 法向量
      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,
    	  1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
    	  // ... 设置其他顶点的法向量
      ])
      
      // 创建buffer
      // 写入数据

  • 当dot(u_LightDirection, normal) 点积小于0时,说明光线照射在物体背面,不考虑物体透明度的情况下,点积取值为0.
  • 环境光下的漫反射

    真实世界的物体,是被环境光和平行光一起照射的。上述程序只有平行光,会导致没有被照射的面呈现黑色

    image

    环境光由墙壁等其他物体反射产生,所以环境光通常比较弱,假设环境光是较弱的白光(0.2, 0.2, 0.2) 物体表面是红色(1.0 ,1.0 ,1.0) 则反射光的颜色就是暗红色(0.2, 0.0, 0.0)

    javascriptCopy
    // 顶点着色器
    const VSHADER_SOURCE =
    	"attribute vec4 a_Position;\n" +
    	"attribute vec4 a_Color;\n" + 
    	"attribute vec4 a_Nromal;\n" +
    	"uniform vec4 u_MvpMatrix;\n" +
    	"uniform vec3 u_LightColor;\n" +
    	"uniform vec3 u_LightDirection;\n" +
    	"uniform vec3 u_AmbientLight;\n" + // 环境光颜色
    	"varying vec4 v_Color;\n" +
      "void main() {\n" +
      "  gl_Position = u_MvpMatrix * a_Position;\n" + 
      // 法向量归一化
      "  vec3 normal = normalize(vec3(a_normal));\n" + 
      // 计算光线方向和法向量点积 
      "  float nDotL = max(dot(u_LightDirection, normal), 0.0);\n" +
      // 计算漫反射光的颜色 
      "  vec3 diffuse = u_LightColor *  a_Color.rgb * nDotL;\n" + 
      // 计算环境光颜色
      "  vec3 ambient = u_AmbientLight * a_Color.rgb * nDotL;\n" + 
      "  v_Color = vec4(diffuse + ambient , a_Color.a);\n" +
      "}\n";
      
    // ...
    const u_AmbientLight = gl.getUniformLocation(gl.program. 'u_AmbientLight')
    gl.unifrom3f(u_AmbientLight , 0.2, 0.2, 0.2)

    运动物体的光照

    物体运动时,每个表面的法向量也会随之变化。此时需要将法向量乘以逆转置矩阵。逆转置矩阵是逆矩阵的转置。

    逆矩阵的含义是,如果矩阵M的逆矩阵是R,那么R * M 或M*R的结果都是单位矩阵。

    求逆转置矩阵的步骤:

  • 1.求原矩阵的逆矩阵
  • 2.求上一步逆矩阵的转置
  • TODO

    javascriptCopy
    // 顶点着色器
    const VSHADER_SOURCE =
    	"attribute vec4 a_Position;\n" +
    	"attribute vec4 a_Color;\n" + 
    	"attribute vec4 a_Nromal;\n" +
    	"uniform vec4 u_NormalMatrix;\n" + // 用于变换法向量的矩阵
    	"uniform vec4 u_MvpMatrix;\n" +
    	"uniform vec3 u_LightColor;\n" +
    	"uniform vec3 u_LightDirection;\n" +
    	"uniform vec3 u_AmbientLight;\n" +
    	"varying vec4 v_Color;\n" +
      "void main() {\n" +
      "  gl_Position = u_MvpMatrix * a_Position;\n" + 
      // 法向量归一化
      "  vec3 normal = normalize(vec3(a_normal * u_NormalMatrix));\n" + 
      // 计算光线方向和法向量点积 
      "  float nDotL = max(dot(u_LightDirection, normal), 0.0);\n" +
      // 计算漫反射光的颜色 
      "  vec3 diffuse = u_LightColor *  a_Color.rgb * nDotL;\n" + 
      // 计算环境光颜色
      "  vec3 ambient = u_AmbientLight * a_Color.rgb * nDotL;\n" + 
      "  v_Color = vec4(diffuse + ambient , a_Color.a);\n" +
      "}\n";

    点光源

    点光源对物体进行着色,需要计算每个入射点光源所处的方向。

    image
    javascriptCopy
    // 顶点着色器
    const VSHADER_SOURCE =
    	"attribute vec4 a_Position;\n" +
    	"attribute vec4 a_Color;\n" + 
    	"attribute vec4 a_Nromal;\n" +
    	"uniform vec4 u_ModelMatrix;\n" + // 模型矩阵
    	"uniform vec4 u_NormalMatrix;\n" +
    	"uniform vec4 u_MvpMatrix;\n" +
    	"uniform vec3 u_LightColor;\n" +
    	"uniform vec3 u_LightPosition;\n" + // 光源位置(世界坐标系)
    	"uniform vec3 u_AmbientLight;\n" +
    	"varying vec4 v_Color;\n" +
      "void main() {\n" +
      "  gl_Position = u_MvpMatrix * a_Position;\n" + 
      // 法向量归一化
      "  vec3 normal = normalize(vec3(a_normal * u_NormalMatrix));\n" + 
      // 计算顶点的世界坐标系
      "  vec4 vertexPostion = normalize(vec3(u_ModelMatrix * a_normal));\n" + 
      ;// 计算光线方向并且归一化
      "  vec3 lightDirection = normalize(vec3(u_LightPosition - vec3(vertexPostion)));\n" + 
      // 计算光线方向和法向量点积 
      "  float nDotL = max(dot(lightDirection , normal), 0.0);\n" +
      // 计算漫反射光的颜色 
      "  vec3 diffuse = u_LightColor *  a_Color.rgb * nDotL;\n" + 
      // 计算环境光颜色
      "  vec3 ambient = u_AmbientLight * a_Color.rgb * nDotL;\n" + 
      "  v_Color = vec4(diffuse + ambient , a_Color.a);\n" +
      "}\n";
      
      // ...
      // 模型矩阵(三方库)
      const modelMatrix = new Matrix4()
      
      modelMatrix.setRotate(...)
      
      const u_ModelMatrix = gl.getUniformLocation(gl.program. 'u_ModelMatrix ')
      gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements)
      
      // ...

  • 使用四个顶点计算光照颜色,会出现物体表面颜色不自然的情况。
  • 因为webGL在片元着色器中会内插颜色
  • image

    逐片元光照

    javascriptCopy
    // 顶点着色器
    const FSHADER_SOURCE =
    	"attribute vec4 a_Position;\n" +
    	"attribute vec4 a_Nromal;\n" +
    	"attribute vec4 a_Color;\n" + 
    	// ...
    	"uniform vec4 u_ModelMatrix;\n" + // 模型矩阵
    	"uniform vec4 u_NormalMatrix;\n" +
    	"varying vec4 v_Normal;\n" +
    	"varying vec4 v_Color;\n" +
    	"varying vec4 v_Color;\n" +
      "void main() {\n" +
      "  gl_Position = u_MvpMatrix * a_Position;\n" + 
      "  v_Position = vec3(u_ModelMatrix * a_Position);\n" + 
      "  v_Color = a_Color;\n" + 
      "}\n";
    
    // 片元着色器
    const FSHADER_SOURCE =
    	// ...
    	"uniform vec3 u_MvpMatrix;\n" +
    	"uniform vec3 u_LightColor;\n" +
    	"uniform vec3 u_LightPosition;\n" + // 光源位置
    	"uniform vec3 u_AmbientLight;\n" +
    	"varying vec3 v_Normal;\n" +
    	"varying vec3 v_Position;\n" +
    	"varying vec4 v_Color;\n" +
      "void main() {\n" +
      // 归一化(内插之后长度不一定为1.-)
      "  vec3 normal = normalize(v_Normal);\n" + 
      ;// 计算光线方向并且归一化
      "  vec3 lightDirection = normalize(u_LightPosition - v_Position);\n" + 
      // 计算光线方向和法向量点积 
      "  float nDotL = max(dot(lightDirection , normal), 0.0);\n" +
      // 计算漫反射光的颜色 
      "  vec3 diffuse = u_LightColor *  a_Color.rgb * nDotL;\n" + 
      // 计算环境光颜色
      "  vec3 ambient = u_AmbientLight * v_Color.rgb * nDotL;\n" + 
      "  gl_FragColor = vec4(diffuse + ambient , v_Color.a);\n" +
      "}\n";

    Contents

    • 光照原理
    • 光源类型
    • 反射类型
    • 计算入射角
    • 平行光下的漫反射
    • 环境光下的漫反射
    • 运动物体的光照
    • 点光源
    • 逐片元光照

    2024/10/15 12:53