☕从二维到三维:探索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等等。

相关推荐
Watermelo61712 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489414 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_7482356125 分钟前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript