WebGL 之添加光照

前期准备

先使用之前所学的知识渲染一个纯色立方体如下:

由于各个面都是同样的灰色,所以想要看出它是个立方体,需要一定的想象力的加持。在 c4d 等构建三维物体的软件里,我们是可以添加灯光对象的,从而让物体表面呈现出明暗对比,让人一眼看出物体的形状:

在 webgl 中,我们也可以给物体添加光照,并且请注意,在现实世界中,我们之所以能看到物体,是因为物体被光线照射后反射了一部分光进入了人的眼睛。所以我们要给目标物体添加光照的效果,要考虑 2 大部分 ------ 光源和反射。

光源

先来分析光源,根据光源发出的光线方向的不同,我们可以将光源分为 2 种:

平行光(directional light)

光线互相平行,射向同一个方向,如太阳光。定义时只需要光线的方向和光的颜色,至于光源的位置,可以认为是特别远的地方,无需定义。

点光源(point light)

本案例中要使用的就是点光源,它的光线从一点向周围放射,比如灯泡。定义点光源时,需要定义光源的颜色、位置和光线的方向。我们可以在顶点着色器源码的 main 函数中,定义一个类型为 vec3 的变量 pointLightColor 代表点光源的颜色;另外定义一个类型为 vec3 的变量 pointLightPosition 代表点光源的位置:

javascript 复制代码
// 代码片段 1.1
const vsSource = `
  void main() {
    // 定义点光源的颜色 - 红色
    vec3 pointLightColor = vec3(1.0, 0.0, 0.0);
    // 定义点光源的位置
    vec3 pointLightPosition = vec3(10.0, 10.0, 10.0);
  }
`

至于点光源的光线方向 lightDirect,则需要根据光源和物体的位置进行计算,其实就是做个向量的减法:

注意,这里说的光线的方向其实是光源入射方向的反方向,是用光源的位置 pointLightPosition 减去物体的顶点坐标 vPosition,然后用 GLSL 的内置函数 normalize 进行归一化处理得到的:

javascript 复制代码
// 代码片段 1.2
const vsSource = `
  void main() {
    // 点的坐标
    vec4 vPosition = uMatrix * aPosition;
    // 光线方向
    vec3 lightDirect = normalize(pointLightPosition - vec3(vPosition));
  }
`

环境光(ambient light)

除了平行光和点光源,还有一种间接光,是光源发出的光线照射到环境中的各个物体后产生的反射。环境光的光线会均匀地照射到目标对象表面,其强度差距很小,无需精确计算光线强度,只需定义光的颜色即可:

javascript 复制代码
// 代码片段 1.3
const vsSource = `
  void main() {
    // 定义环境光的颜色
    vec3 ambientLightColor = vec3(0.2, 0.0, 0.0);
  }
`

反射

现在来分析光的反射,反射可以大致分为以下 3 类:

环境反射(environment reflection)

先是最容易定义其反射颜色的环境反射。物体对环境光的反射就是环境反射,反射的方向为入射光的反方向。环境反射的颜色 = 环境光的颜色 * 物体表面的颜色:

javascript 复制代码
// 代码片段 2.1
const vsSource = `
  void main() {
    // 物体表面的颜色 - 灰色
    vec4 aColor = vec4(0.8, 0.8, 0.8, 1.0);
    // 环境反射的颜色
    vec3 ambientColor = ambientLightColor * vec3(aColor);
  }
`

漫反射(diffuse reflection)

接着是现实生活中最常见的漫反射。因为大多数物体的表面都是粗糙不平的,所以在接收到光源的入射光后,反射的光线会均匀地射向四周,称为漫反射。漫反射的反射光的颜色 =入射光的颜色 * 物体表面的颜色 * cosα。

javascript 复制代码
// 代码片段 2.2
const vsSource = `
  void main() {
    // 漫反射光的颜色
    vec3 diffuseColor = pointLightColor * vec3(aColor) * deg;
  }
`

入射角

代码片段 2.2 中的 deg 为入射光与物体表面的法向量形成的入射角 α 的余弦值:

由下图易知,在同样的光源下,在 0 -90°的区间内,入射角越小,则物体表面接收到的光线数量就越大:

所以在求漫反射的反射光颜色的公式中,就用入射角的余弦值 cosα 来表示物体的受光程度。 因为 l · n = |l| * |n| * cosαln 为单位向量,则 |l||n| 均为 1,所以 cosα = l · n。注意,向量的点积是符合乘法交换律的:

javascript 复制代码
// 代码片段 2.3
const vsSource = `
  attribute vec4 aNormal;
  void main() {
    // 入射角
    float deg = dot(lightDirect, vec3(aNormal));
  }
`

lightDirect 为代码片段 1.2 中定义的光线方向,aNormal 则为各个顶点的法向量。

法向量

aNormal 为 attribute 变量,我们可以定义好立方体 6 个面的法向量,赋值给变量 normals 中,然后将 normals 存入缓冲区对象aNormal 去读取:

javascript 复制代码
// 代码片段 2.4
const aNormal = gl.getAttribLocation(program, 'aNormal')
// 法向量
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, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.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, 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, -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)

注意,因为在创建顶点数据 points 时每个面都有 4 个顶点数据,所以 normals 中每个面也需要对应的建立 4 次法线数据。

镜面反射(specular reflection)

最后一种是镜面反射。物体接收到光源的入射光后,会将光线以与物体表面法线对称的方向反射出去。

物体的最终颜色

当同时存在漫反射和环境反射时,物体的最终颜色就应该是漫反射光颜色 + 环境反射光颜色:

javascript 复制代码
// 代码片段 3.1
const vsSource = `
  vColor = vec4(ambientColor + diffuseColor, aColor.a);
`

注意,代码片段 2.1 获取的 ambientColor 和代码片段 2.2 获取的 diffuseColor 都是 vec3 类型的,而最终赋给 gl_FragColor 的值需要是 vec4 类型的,所以使用了矢量构造函数 vec4(),代表透明度的参数则传入物体表面原本的颜色 aColora 分量。

最终效果如下,可以看到添加了红光后物体,有了明暗效果,一眼看出是个立方体:

相关推荐
长风清留扬9 分钟前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
web1478621072322 分钟前
C# .Net Web 路由相关配置
前端·c#·.net
m0_7482478023 分钟前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖26 分钟前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案134 分钟前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_7482548839 分钟前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl
ZJ_.1 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营1 小时前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端1 小时前
0基础学前端-----CSS DAY9
前端·css