☕从二维到三维:探索WebGL绘图技术

🔍前言

视觉盛宴 WebGL

用WebGL制作的小米SU7 3D互动网站

WebGL招聘需求

以上两点让我学起了WebGL。

  • 我亦无他,惟手熟尔

📣

☕正文

什么是WebGL

一句话概括:在浏览器中开发渲染交互式的3D图形。

知识点

坐标系 :默认为右手坐标系。

正方向最大值为1,负方向最大值为-1。

绘制区域: 用canvas。做2D还是3D,是由上下文对象是2还是3。

2: 用getContext('2d')。

3: 用getContext('webgl')。

类型化数组

举个例子,下面贴个画三角形的类数组的定义,如下:

这种new Float32Array去定义几何图形的顶点的,定义如下:

javascript 复制代码
const vertices = new Float32Array([
    0.0, 0.5, // 第一个顶点坐标
    -0.5, -0.5, // 第二顶点坐标
    0.5, -0.5, // 第三顶点坐标
    ... // 再有就是以此类推
])

着色器

类型分两种:1、顶点 ;2、片段

  • 第一种写法:

举例说明:

js 复制代码
// 顶点
const VSHADER_SOURCE =
  `void main() {
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0); // 顶点位置
    gl_PointSize = 10.0; // 点的大小
  }`

// 片段
const FSHADER_SOURCE =
  `void main() {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); // 点的颜色
  }`
  • 第二种写法:

设置自定义类型的script标签。

顶点: vertex-shader

片段: fragment-shader


数据类型:

  • 基本: float int bool
  • 向量:
    • 浮点: vec2 vec3 vec4
    • 整数: ivec2 ivec3 ivec4
    • 布尔: ivec2 bvec2 bvec2
  • 矩阵: mat2 mat3 mat4
  • 采样器(纹理):sampler2D samplerCube

变量修饰符

顶点:从js中传参给顶点着色器。

  • attribute:传输的是 那些与顶点相关的数据。
  • uniform:传输的是 那些对于所有顶点都相同(或者与顶点无关)的数据。

内置

  • 顶点:gl_Positiongl_PointSize
  • 片元: gl_FragColor

用于顶点和片段传参的。

  • varying

例子:

js 复制代码
// 顶点着色器代码
const VSHADER_SOURCE =
  `
  attribute vec4 a_Position;
  attribute vec4 a_Color;
  varying vec4 v_Color; // 声明v_Color,用来传颜色给片元的。

  void main() {
    gl_Position = a_Position;
    v_Color = a_Color; // 定义v_Color为a_Color
  }
  `;

// 片元着色器代码
const FSHADER_SOURCE =
  `
  precision mediump float;
  varying vec4 v_Color; // 声明来接收顶点传过来的颜色的。

  void main() {
    gl_FragColor = v_Color; // 片元颜色用顶点传过来的这个值。
  }
  `;

精度

  • precision:默认精度声明。
    • high:用于 位置、复杂的物理效果等。
    • mediump:用于 纹理坐标等。
    • lowp:用于颜色值等。

用例:
precision lowp float;

入口main()

先看效果,再看码。

// index.html文件

html 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Clear canvas</title>
  </head>
  <body onload="main()">
    // 定义一个canvas 宽高都是400px的。
    <canvas id="webgl" width="400" height="400">
      Please use the browser supporting "canvas"
    </canvas>
    
    <script src="./point.js"></script>
  </body>
</html>

// index.js

js 复制代码
// 顶点着色器
const VSHADER_SOURCE =
  `void main() {
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0); // 顶点位置
    gl_PointSize = 10.0; // 点大小
  }`

// 片元着色器
const FSHADER_SOURCE =
  `void main() {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); // 图像中的像素(片元)的颜色为绿
  }`

// 主函数main
function main() {
  // 获取canvas
  const canvas = document.getElementById('webgl')

  // 获取WebGL上下文
  const gl = canvas.getContext('webgl') // 创WebGL上下文gl

  if (!gl) {
    console.log('Failed to get the rendering context for WebGL')
    return
  }

  const vertexShader = gl.createShader(gl.VERTEX_SHADER) // 创建顶点着色器对象
  gl.shaderSource(vertexShader, VSHADER_SOURCE) // 将顶点着色器源码VSHADER_SOURCE分配给顶点着色器对象
  gl.compileShader(vertexShader) // 编译顶点着色器

  // 片元也是如此步骤:创、分、编译
  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
  gl.shaderSource(fragmentShader, FSHADER_SOURCE)
  gl.compileShader(fragmentShader)

  const program = gl.createProgram() // 创建WebGL程序对象
  gl.attachShader(program, vertexShader) // 将顶点着色器附加到程序对象
  gl.attachShader(program, fragmentShader) // 将片元着色器附加到程序对象
  gl.linkProgram(program) // 将顶点和片元连接成一个可执行的WebGL程序
  gl.useProgram(program) // 告诉WebGL使用特定的程序对象

  // 清空canvas = 设置canvas的背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)

  // 清空canvas
  gl.clear(gl.COLOR_BUFFER_BIT)

  // 绘制一个点
  gl.drawArrays(gl.POINTS, 0, 1)
}

注:可以借助一些封装好的WebGL工具库做。

三角形

先看效果,再看码。

// html部分和上面一样,换js文件就行

// index.js

1、定义着色器

js 复制代码
// 顶点着色器
// 用到了接收js给的顶点数据进行处理
const VSHADER_SOURCE = `
  attribute vec4 a_Position; // 声明一个attribute属性a_Position。用来接收顶点位置信息。
  void main() { // 顶点着色器的主函数main
    gl_Position = a_Position; // 将传入的顶点位置赋值给内置变量
    gl_PointSize = 10.0; // 设置点的大小
  }
`

// 片元着色器
const FSHADER_SOURCE = `
  void main() { // 片元着色器的主函数main
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置片元的颜色为红
  }
`

// 主函数

js 复制代码
function main() {
  // 获取canvas元素
  const canvas = document.getElementById('webgl')
  // 获取WebGL上下文
  const gl = canvas.getContext('webgl')

  // 初始化着色器
  const vertexShader = gl.createShader(gl.VERTEX_SHADER) // 顶点着色器对象
  gl.shaderSource(vertexShader, VSHADER_SOURCE) // 将顶点着色器源码 `VSHADER_SOURCE` 分配给顶点着色器对象。 
  gl.compileShader(vertexShader) // 编译顶点着色器。

  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
  gl.shaderSource(fragmentShader, FSHADER_SOURCE)
  gl.compileShader(fragmentShader)

  // 创建程序对象并链接着色器
  const program = gl.createProgram()
  gl.attachShader(program, vertexShader)
  gl.attachShader(program, fragmentShader)
  gl.linkProgram(program)
  gl.useProgram(program)

  // 设置顶点位置
  const n = initVertexBuffers(gl, program);
  if (n < 0) {
    console.log('Failed to set the positions of the vertices');
    return
  }

  // 设置<canvas>背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)

  gl.clear(gl.COLOR_BUFFER_BIT)

  console.log(n)
  // 绘制三个点
  gl.drawArrays(gl.TRIANGLES, 0, n) // 绘制三角形,从顶点数组的第一个点开始,绘制 `n` 个顶点。
}

// 初始化顶点缓冲区函数

js 复制代码
function initVertexBuffers(gl, program) {
  // 定义顶点数据 - 3个点,构成三角形
  const vertices = new Float32Array([
    0.0, 0.5,
    -0.5, -0.5,
    0.5, -0.5
  ])

  const n = 3;
  
  // 创建WebGL缓冲区对象
  const vertexBuffer = gl.createBuffer()
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object')
    return -1
  }

  // 将缓冲区对象绑定到目标
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

  // 向缓冲区对象中写入数据
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
  
  // 获取并设置顶点着色器中的属性
  const a_Position = gl.getAttribLocation(program, 'a_Position')
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return
  }
    
  // 将顶点数据传入顶点着色器
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  // 启用点点属性函数
  gl.enableVertexAttribArray(a_Position);

  return n;
}

矩形

先看效果,再看码:

绘制矩形,WebGL不能直接绘制矩形,但可以用两个三角形去组成一个矩形。

两个三角形:

  • (v0, v1, v2)
  • (v2, v1, v3)

gl.TRIANGLESgl.TRANGLES_STRIPgl.TRIANGLES_FAN去做。

跟上面那个三角形八九差不离。

1、加个点

js 复制代码
// 共4个顶点
const vertices = new Float32Array([
    -0.5, 0.5,
    -0.5, -0.5,
    0.5, 0.5,
    0.5, -0.5
])

顶点数从3改成3 const n = 4

2、改函数

gl.drawArrays(gl.TRIANGLE_STRIP, 0, n)

图形变化

平移

比如说,平移一个三角形。

步骤流程是,需要对顶点坐标的每个分量(x和y),加上三角形在对应轴平移的距离。

概念:将平移距离Tx、Ty、Tz的值传入顶点着色器。然后,分别加在顶点坐标的对应值上。再赋值给gl_Positiion。

旋转

先看效果,再看码:

  • 旋转轴(图形将围绕旋转轴)
  • 旋转方向(方向:顺时针或者逆时针)
  • 旋转角度(图形旋转角度)

用z轴逆时针旋转角度去解释:

// 旋转的顶点着色器代码

js 复制代码
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  uniform float u_CosB, u_SinB;
  void main() {
    gl_Position.x = a_Position.x * u_CosB - a_Position.y * u_SinB; // 根据传入的旋转角度 `u_CosB` 和 `u_SinB` 对顶点进行 x 轴方向的线性变换。
    gl_Position.y = a_Position.x * u_SinB - a_Position.y * u_CosB; // 根据传入的旋转角度 `u_CosB` 和 `u_SinB` 对顶点进行 y 轴方向的线性变换。
    gl_Position.z = a_Position.z; // 保持原始的 z 轴坐标不变。
    gl_Position.w = 1.0; // 设置顶点的齐次坐标 w 为1.0,表示一个点。
  }
`

// 用于计算旋转角度的余弦和正弦值。 // 并将它们作为 uniform 变量传递给 WebGL 程序的顶点着色器。

js 复制代码
// 将角度值转换为弧度值
const radian = Math.PI * ANGLE / 180 
const cosB = Math.cos(radian)
const sinB = Math.sin(radian)

const u_CosB = gl.getUniformLocation(program, 'u_CosB')
const u_SinB = gl.getUniformLocation(program, 'u_SinB')

gl.uniform1f(u_CosB, cosB)
gl.uniform1f(u_SinB, sinB)

缩放

乘以某值,进行缩放:

js 复制代码
x' = Sx * X
y' = Sy * Y
z' = Sz * Z

比如说,把之前那个三角形在垂直方向拉伸1.5倍,代码如下:

js 复制代码
const Sx = 1.0, Sy = 1.5, Sz = 1.0

const xformMatrix = new Float32Array([
    Sx, 0.0, 0.0, 0.0,
    0.0, Sy, 0.0, 0.0,
    0.0, 0.0, Sz, 0.0,
    0.0, 0.0, 0.0, 1.0,
])

图形变化这几个罗列了平移、旋转和缩放。

👉就是在js中把数据(点的处理)处理完成之后传值给顶点着色器。

🥤后记

☎️ 希望对大家有所帮助,本文有些地方可能考虑不够周到,有些纰漏,就当抛砖引玉,还望您海涵,如有错误,望不吝赐教,欢迎评论区留言互相学习。感谢阅读,祝您开发有乐趣。

搞WebGL,还要学WebGpu、Cesium、threejs、babylonjs等等。

相关推荐
前端爆冲1 分钟前
项目中无用export的检测方案
前端
热爱编程的小曾29 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin40 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox