从 WebGL 到 Three.js: 强大而简化的3D图形编程

avatar
FE @字节跳动

GPU

一幅图像是由成千上万的像素点组成,我们每次处理一个像素都是一个任务。如果这些处理都让 CPU 来处理就有点浪费,所以 GPU 诞生~

GPU 是由大量的小型处理单元构成的,数量众多,能够保证同时处理所有的像素点。

  • 顶点缓冲区( VBO :存储顶点的位置、颜色、法向量等数据(createBuffer()创建)

  • 顶点数组对象( VAO :存储顶点数据的格式以及顶点数据所需的VBO对象的引用,保存了所有顶点数据属性的状态结合

  • 纹理缓冲区:存储纹理贴图像素数据

  • 深度缓冲区

    • 存储每个像素点的深度,即z坐标轴值(又称z缓冲区)
    • 用来判断像素颜色能否存入颜色缓冲区,同一xy坐标舍弃z值大的颜色
  • 颜色缓冲区

    • 存储像素数据,即片元的颜色值
    • 16位:RGB565,红色5位,绿色6位,蓝色5位,不保存透明通道(之所以绿色多1位是因为人眼对绿色更加敏感)
    • 24位:RGB888,红色8位,绿色8位,蓝色8位,不保存透明通道
    • 32位:RGBA8888,红色8位,绿色8位,蓝色8位,透明通道8位
    • 如果使用了多渲染目标(Multiple Render Targets)技术,那么颜色附着的数量可能会大于一
  • 模板缓冲区

    • 控制颜色缓存某个位置的写入操作(阴影绘制需要使用)

OpenGL图形渲染管线、VBO、VAO、EBO概念及用例

WebGL 工作原理

WebGL 的发展历程

OpenGL 就是个开放图形库,是用于渲染 2D,3D 矢量图形的跨平台,跨语言的应用程序编程接口。

WebGL API 基于 OpenGL ES,而 OpenGL ES 又是 OpenGL 的针对嵌入式设备图形开发的子集。 因此,它的 API 可以让 Web 开发者通过 js 代码来操作本地 OpenGL(OpenGL ES---移动设备) 的部分接口,和显卡进行通信实现页面的图形的渲染。

GLSL

GLSL,全称 OpenGL Shading Language,也就是 OpenGL 着色器语言。主要用于开发着色器程序,可以通过编程控制 GPU 渲染过程中的某些部分。它是 C 语言的一个超集,增加了一些数据类型和数学函数。

WebGL、OpenGL 与 GLSL

渲染管线

绿色:可编程状态

紫色:不可编程状态

黄色:CPU,其余 GPU

以下内容均是基于 webgl 1

  1. 顶点着色器(Vertex Shader)

功能:操作顶点缓冲区中的顶点数据(顶点的坐标转换,转化为相机坐标系),此阶段为可编程状态。

顶点着色器处理的是逐顶点处理顶点数据

  • Attribute 属性

    • 从缓冲中获取
    • 常见的信息有uv、position、normal、indices、color
  • Uniform 全局变量

    • 顶点变换矩阵、光方向、光源位置、光颜色
  • Textures 纹理:从像素或纹理元素中获取的数据

// attribute 从缓冲区获取的数据
attribute vec4 position;
uniform mat4 matrix;
void main() {
    // position 即顶点坐标,3D图形,顶点坐标需转换成屏幕坐标
    gl_Position = position * matrix; 
}

  1. 图元装配

目的:通过顶点着色器的处理,我们得到了我们想要的顶点,由顶点生成一个个图元(三角形)。

  • 点:gl.POINTS
  • 线:gl.LINES
  • 三角形:gl.TRIANGLES
  • 顶点的像素尺寸:顶点着色器中的内置变量gl_PointSize

  1. 光栅化

光栅化就是把图元转化为片元,canvas 画布上图像的每一个像素都对应一个片元。(

  1. 判断像素是否在三角网格内,计算每条边上的像素坐标
  2. 对非顶点数据插值处理(赋予其他信息,因为“图元”信息不止有颜色)

gl_FragCoord:相对canvas 画布这个窗口坐标系统的值 内置变量

  • 片元位置(canvas 画布):gl_FragCoord.xy(vec2类型)
  • 片元的深度值:gl_FragCoord.z (标识片元距离人眼睛的距离)

gl_PointCoord:点域图元光栅化后的图元 内置变量

  • gl_PointSize 定义的区域内的片元坐标
  • 是个相对值,点域图元与其对应片元在canvas画布上的相对位置,区域[0, 1]

varying

  • 顶点着色器中的 gl_Position 顶点数据
  • 光栅化阶段进行插值
  1. 片元着色器(Fragment Shader)

片元着色器是逐片元处理片元数据,完成模型颜色,质地,光照效果,阴影

  • gl_FragColor:给其赋值即是给片元上色 内置变量

    • 可以是一个确定的RGBA值
    • 可以是一个和片元位置相关的值
    • 可以是插值后的顶点颜色
  • dicard:将片元丢弃,并移除帧缓冲区

/* 确定颜色值 */
 //红色
 gl_FragColor = vec4(1.0,0.0,0.0,1.0);
 
/* 颜色值关联片元位置 */
 //canvasWidth、canvasHeight表示画布的宽高尺寸
 gl_FragColor = vec4(gl_FragCoord.x/canvasWidth,gl_FragCoord.y/canvasHeight,0.0,1.0);

/* 顶点颜色插值 */
 varying vec4 v_Color;//插值后顶点颜色数据
 gl_FragColor = v_Color;
 
/* 纹理缓冲区 */
 varying vec2 v_TexCoord;//插值后纹理坐标数据
 uniform sampler2D u_Sampler;//插值处理后纹理贴图像素值数据
 //texture2D方法拾取纹理坐标对应的像素值
 gl_FragColor = texture2D(u_Sampler,v_TexCoord);

  1. 裁切测试

      对用户指定的矩形区域进行测试,剔除窗口之外的像素

    • 主要解决视口比屏幕窗口小造成的渲染浪费
    • 开启:glEnable(GL_SCISSOR_TEST),指定区域:glScissor()
  1. 模板测试

      根据Stencil或 Depth Buffer Test结果分条件更新 Stencil Buffer

    • 开启:glEnable(GL_STENCIL_TEST)
    • 利用模板测试:实现平面镜效果、平面阴影、物体轮廓等
  1. 深度测试

比较(x,y)坐标相同片元的深度值Z,默认的情况下深度值是gl_FragCoord.z(大的在后,小的在前)

  • 抛弃掉位置靠后的像素值
  • 开启:glEnable(GL_DEPTH_TEST),指定比较运算符:glDepthFunc()
  • 渲染半透明物体,需要开启深度测试(关闭深度写入)
  • alpha为0时,会进行深度测试
  1. alpha混合
    • 主要用于产生半透明效果,可根据片元的 alpha 值混合
    • 开启:glEnable(GL_BLEND)
    • 通过 glBlendFuncSeparate()、glBlendFunc() 和 glBlendEquation() 来设置各种混合效果(GL_ZERO、GL_ONE、GL_SRC_ALPHA、GL_FUNC_ADD等)

      混合方程透明度混合(Alpha blending)

    C¯result=C¯source∗Fsource+C¯destination∗Fdestination
    
    /*
    C¯source:源颜色向量。这是来自纹理的本来的颜色向量。
    C¯destination:目标颜色向量。这是储存在颜色缓冲中当前位置的颜色向量。
    Fsource:源因子。设置了对源颜色的alpha值影响。 
    Fdestination:目标因子。设置了对目标颜色的alpha影响。 */
    
  1. 抖动处理
    • 开启:glEnable(GL_DITHER),默认开启

    • 针对颜色较少的系统,牺牲分辨率通过颜色值的抖动增加可用颜色数量

WebGL 的一些限制

  • Line:一些机器的一些浏览器上线框永远是1,fix:可以用三角形模拟线条

  • 应用于摄像机的任何后期处理效果都会禁用内置的抗锯齿功能 (webgl 1)

  • HDR 与抗锯齿功能不兼容 (webgl 1)

  • 不同的多重采样级别(2x、4x 等)在 WebGL 中没有任何影响(webgl 1)

WebGL 2 与 WebGL 1 差异

兼容性

  • WebGL2 几乎 100% 兼容 WebGL1
  • WebGL2并非所有的浏览器都支持,所以判断不存在其上下文,则回退使用WebGL1

差异点

  • 扩展功能:在WebGL1需要先加载扩展再调用,WebGL2中很多可以直接使用(比如VAO,OES_vertex_array_object扩展)
  • WebGL 2 需要 显示指定着色器语言版本
// version 必须在第一行且不能有任何空格
var vsSource = `#version 300 es 
`; 
  • GLSL 300 es vs GLSL 100 es

    • 使用 in 代替 attribute
    •   // 顶点数据的变量,使用 in 代替 attribute
        // 在GLSL 100中,使用attribute关键词
        attribute vec4 aPosition;
        attribute vec2 aTexcoord;
        attribute vec3 aNormal;
      
        // 在GLSL 300 es中,使用in关键词
        in vec4 aPosition;
        in vec2 aTexcoord;
        in vec3 aNormal;
      
    • varying 被in/out替代
    •   // 在GLSL 100varying关键词声明变量
         varying vec2 vTexcoord;
         varying vec3 vNormal;
         
         // 在GLSL 300 es中,varying改为out声明
          out vec2 vTexcoord;
          out vec3 vNormal;
      
    • 取消内置变量 gl_FragColor
    •   // GLSL 100 中,给内置变量gl_FragColor赋值来设置片元的输出颜色
        gl_FragColor = vec4(1,1,1, 1);  // white
      
        // GLSL 300 es中,需要自己定义一个输出颜色的变量
        out vec4 myOutputColor;......
        void main() {
           myOutputColor = vec4(1, 1, 1, 1);  // white
        }
      
    • texture代替 texture2D、textureCube

Three.js 工作原理

工作流程

黄色:JS 部分 绿色:opengl es 部分

顶点处理
坐标变换

将一个立方体模型绕 y 轴顺时针旋转 Math.PI/3,相机位置 y 为 60

顶点变换流程如下:

  • 模型矩阵:modelMatrix,记录模型旋转、平移以及缩放等(使用 JS 计算位置性能较低,使用矩阵记录)

  • 视图矩阵:viewMatrix,设置相机对象的位置属性和lookAt方法本质就是改变其视图矩阵属性

  • 投影矩阵:projectMatrix 根据不同的相机其投影不一,且参数变化其也需要改变

    • 通过 perspectiveCamera.xxx 修改属性,调用updateProjectionMatrix(不然不生效)
    • 在更新渲染区域相关参数时,也要手动更新矩阵
  • 编写顶点着色器

顶点着色器

CPU JS 矩阵变换流程

Three.js本地矩阵.materix和世界矩阵.matrixWorld

GPU opengl es

gl_Position = position * modelMatrix * viewMatrix * projectionMatrix;
片元着色器

CPU JS 创建一个 MeshBasicMaterial 材质,增加灯光,以及场景雾化效果

GPU opengl es

vec3 vLighting = LightColor * directional;
gl_FragColor = vec4(color.rgb * vLighting, color.a);

绘制流程

  • 整体流程

  • 画一个立方体

绘图页面中必备且唯一的要素:camera、scene、renderer

  • Scene:场景(三维空间),是物体、光源等元素的容器

  • Camera:相机(观察者),控制视角位置、范围和焦点位置

  • Object3D:物体(三维模型),包括二维的点线面、三维的粒子等

  • Light:光源,包括全局光、平行光、点光源等

  • Renderer:渲染器(画布),用于渲染场景及其物体

  • Control:控制器(相机控件),可通过键盘、鼠标控制相机

Three.js vs WebGL

  • WebGL

    • 学习和开发成本较高,需要数学以及图形学知识基础
    • JS 中可以直接使用其API绘制3D图形
  • Three.js

    • 辅助生成材质,配置灯光,导出模型数据

    • 自动生成了各种矩阵,用来达到旋转、平移以及缩放效果

    • 生成了顶点着色器、根据材质生成片元着色器

    • 将webGL基于光栅化的2D API,封装成了 3D API

参考文章

WebGL概述--原理篇 - 掘金

WebGL 工作原理

Three.js 教程

Three.js 学习笔记(1)--坐标体系和旋转