Skip to content
Go back

Webgl的概念和示例

Updated:  at  03:26 PM

文章目录

Canvas是什么

canvas是透明的,2d坐标系统的x轴(正方向朝右)y轴(正方向朝下)

<canvas id="canvas2d" class="m-auto" width="720" height="100"> </canvas>

<script>
  var canvas = document.getElementById("canvas2d") as HTMLCanvasElement;
  if (!canvas) {
    console.log("Failed to retrieve the <canvas> element");
  } else {
    // 获取canvas上下文
    var ctx = canvas.getContext("2d");
    if (ctx) {
      // 设置填充颜色
      ctx.fillStyle = "oklch(0.769 0.188 70.08)";
      // 绘制矩形
      ctx.fillRect(0, 0, 720, 100);
    }
  }
</script>

绘制第一个3d图形

绘制三维图形与二维类似,也遵循类似的步骤,获取canvas元素、获取绘图上下文、开始绘图。

<canvas id="canvas3d" class="m-auto" width="720" height="400"> </canvas>

<script type="module">
  function canvas3D() {
    var canvas = document.getElementById("canvas3d");
    if (!canvas) {
      return console.log("Failed to retrieve the <canvas> element");
    }
    console.dir(canvas);

    // 获取webgl绘图上下文
    var gl = getWebGLContext(canvas);

    if (!gl) {
      console.log("Failed to get the redering context for WebGL");
      return;
    }

    // 指定清空canvas的颜色
    gl.clearColor(0.0, 0.5, 0.5, 1.0);

    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);
  }
  canvas3D();
</script>

gl.clearColor(red, green, blue, alpha),指定绘图区域的背景色。

定义名称解释
参数red指定红色值(从0.0到1.0)
green指定绿色值(从0.0到1.0)
blue指定蓝色值(从0.0到1.0)
alpha指定透明度(从0.0到1.0)

如果任何一个分量小于0.0或者大于1.0,那么就会分别截断为0.0或1.0

一旦指定了背景色之后,背景色就会驻存在WebGL系统(WebGL System)中,在下一次调用gl.clearColor()方法前不会改变。如果将来什么时候你还想用同一个颜色再清空一次绘图区,没必要再指定一次背景色,你可以调用gl.clear()函数,用之前指定的背景色清空(即用背景色填充,擦除已经绘制的内容)绘图区域。

清空Canvas

gl.clearColor(gl.COLOR_BUFFER_BIT)清空颜色缓冲区。因为webgl的gl.clearColor继承opengl,基于多基本缓冲区模型,gl.COLOR_BUFFER_BIT代表的是颜色缓冲区。webgl还有深度缓冲区和模板缓冲区。

绘制一个点

const VSHADER_SOURCE = `
  void main() {
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);   // 设置坐标
    gl_PointSize = 5.0;                     // 设置点的大小
  }
`;
const FSHADER_SOURCE = `
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色
  }
`;

let canvas = document.getElementById('drawPoint');

// @ts-ignore
var gl = window.getWebGLContext(canvas);

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

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  console.log('Failed to initialize shaders.');
  return;
}

gl.clearColor(0.0, 0.0, 0.0, 1.0);  // 设置清屏颜色为黑色
gl.clear(gl.COLOR_BUFFER_BIT);      // 清除颜色缓冲区
gl.drawArrays(gl.POINTS, 0, 1);     // 设置绘制模式为点,顶点索引从0开始,共绘制1个点

顶点着色器

顶点着色器控制点的位置和大小,它用来描述顶点特性(如位置,颜色等)的程序。顶点是指二维和三维空间的一个点,比如二维或三维图形的端点或交点。

片元着色器

进行逐片元处理如光照的程序。片元是一个图形学术语,可理解为像素(图像的单元,包括这个像素的位置,颜色和其他信息)。

绘制

gl.drawArrays可用于绘制各种图形,简单介绍如下

gl.drawArrays(mode,frst,count)执行顶点着色器,按照 mode 参数指定的方式绘制图形。

参数说明类型
mode指定绘制的方式,可接收以下常量符号: gl.POINTSgl.LINES
gl.LINE_STRIPgl.LINE_LOOPgl.TRIANGLESgl.TRIANGLE_STRIPgl.TRIANGLE_FAN
枚举
first指定从哪个顶点开始绘制整型数
count指定绘制需要用到多少个顶点整型数

当程序调用g1.drawArrays()时,顶点着色器将被执行count次,每次处理一个顶点。

WebGL坐标系统

attribute变量和uniform变量

attribute变量传输的是那些与顶点相关的数据,而uniform变量传输的是那些对于所有顶点都相同(或与顶点无关)的数据。

attribute变量是一种 GLSL ES变量,被用来从外部向顶点着色器内传输数据,只有顶点着色器能使用它。为了使用 attribute 变量,需要包含以下步骤

  1. 在顶点着色器中,声明attribute变量;
  2. 将attribute 变量赋值给gl_Position变量;
  3. 向 attribute 变量传输数据。

使用js设置attribute变量画一个点

这里随意点击即可绘制一个点

const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;   // 设置坐标
    gl_PointSize = 10.0;                     // 设置点的大小
  }
`;

// ... 中间代码与上一版相同

// gl.getAttribLocation函数用于获取a_Position变量指定的attribute变量的存储地址
// 简单理解:让gl帮忙创建一个变量
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');

// ...

// 将数据(0.0, 0.0, 0.0)传给由a_Position参数指定的attribute 变量。
// 简单理解:让gl帮忙给变量赋值
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);   // 设置顶点位置

gl.clearColor(0.0, 0.0, 0.0, 1.0);  // 设置清屏颜色为黑色
gl.clear(gl.COLOR_BUFFER_BIT);      // 清除颜色缓冲区
gl.drawArrays(gl.POINTS, 0, 1);     // 设置绘制模式为点,顶点索引从0开始,共绘制1个点

attribute vec4 a_Position ,attribute被称为存储限定符

attribute变量都以a_前缀开始 uniform变量都以u_前缀开始 设置vec4变量时,当省略分量时,默认为1.0

gl.vertexAttrib3fgl.vertexAttrib3fv区别在于参数的接收,3f逐个接收三个参数,3fv接受一个长度为3的数组

随机设置点的颜色

uniform变量将色值传递给片元着色器。

  1. 在片元着色器中声明一个uniform变量
  2. 在JavaScript中获取该变量的存储地址
  3. 将颜色值传递给该变量
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;   // 设置坐标
    gl_PointSize = 10.0;                     // 设置点的大小
  }
`;

const FSHADER_SOURCE = `
  precision mediump float; // 设置精度
  uniform vec4 u_FragColor; // 声明uniform变量
  void main() {
    gl_FragColor = u_FragColor; // 设置片元颜色
  }
`;

function main() {
  const canvas = document.getElementById('webgl');

  // 获取webgl上下文
  const gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  // 获取a_Position变量的存储地址
  const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return;
  }

  // 获取u_FragColor变量的存储地址
  const u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
  if (!u_FragColor) {
    console.log('Failed to get the storage location of u_FragColor');
    return;
  }

  // 根据点击位置设置a_Position和u_FragColor
  canvas.onmousedown = function(ev){ click(ev, gl, canvas, a_Position, u_FragColor) };


  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

const g_points = [];  // 顶点位置数组
const g_colors = [];  // 顶点颜色数组
function click(ev, gl, canvas, a_Position, u_FragColor) {
  const x = ev.clientX; // 设置x坐标
  const y = ev.clientY; // 设置y坐标
  const rect = ev.target.getBoundingClientRect();

  x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
  y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);

  // 存储顶点位置
  g_points.push([x, y]);
  // 根据所在象限设置顶点颜色
  if (x >= 0.0 && y >= 0.0) {
    g_colors.push([1.0, 0.0, 0.0, 1.0]);  // Red
  } else if (x < 0.0 && y < 0.0) {
    g_colors.push([0.0, 1.0, 0.0, 1.0]);  // Green
  } else {
    g_colors.push([1.0, 1.0, 1.0, 1.0]);  // White
  }

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

  // 循环设置顶点
  const len = g_points.length;
  for(let i = 0; i < len; i++) {
    const xy = g_points[i];
    const rgba = g_colors[i];

    // 设置当前顶点的a_Position
    gl.vertexAttrib3f(a_Position, xy[0], xy[1], 0.0);
    // 设置当前顶点的u_FragColor
    gl.uniform4f(u_FragColor, rgba[0], rgba[1], rgba[2], rgba[3]);
    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1);
  }
}

gl.getUniformLocation(program, name)执行顶点着色器,按照 mode 参数指定的方式绘制图形。

参数说明类型
program指定包含了着色器程序的对象对象
name指定要获得存储位置的变量名字符串

绘制和变换三角形

绘制多个点

想要一次性绘制多个点,可使用webgl提供的缓冲区对象,它可以一次性的向着色器传入多个顶点的数据。

绘制过程 获取webgl绘图上下文 -> 初始化着色器 -> 设置点的坐标 -> 设置<canvas>背景色 -> 清空canvas -> 绘制

const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;   // 设置坐标
    gl_PointSize = 10.0;        // 设置点的大小
  }
`;
const FSHADER_SOURCE = `
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置点的颜色
  }
`;

function main() {
  const canvas = document.getElementById('webgl');

  // 获取webgl上下文
  const gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

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

  // 设置清屏颜色
  gl.clearColor(0, 0, 0, 1);

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

  // 绘制三个点
  gl.drawArrays(gl.POINTS, 0, n);
}

function initVertexBuffers(gl) {
  const vertices = new Float32Array([
    0.0, 0.5,   -0.5, -0.5,   0.5, -0.5
  ]);
  const n = 3; // 点的个数

  // 创建缓冲区对象
  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(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }

  // 将缓冲区对象分配给a_Position
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  // Enable the assignment to a_Position variable
  gl.enableVertexAttribArray(a_Position);

  return n;
}

gl.createBuffer()创建缓冲区对象,gl.deleteBuffer()删除缓冲区对象。

gl.bindBuffer(target, buffer)将buffer表示的缓冲区对象绑定target表示的目标上。

参数说明类型
target指定包含了着色器程序的对象gl.ARRAY_BUFFER() 表示缓冲区中包含的顶点数据
gl.ELEMENT_ARRAY_BUFFER 表示缓冲区中包含的顶点索引
buffer指定之前由gl.createBuffer()返回的待绑定的缓冲区对象
如果指定为null,则禁用对target的绑定
对象

gl.bufferData(target, data, usage)开辟存储空间,向绑定在target上的缓冲区对象中写入数据data

参数说明类型
target指定包含了着色器程序的对象gl.ARRAY_BUFFER() 表示缓冲区中包含的顶点数据
gl.ELEMENT_ARRAY_BUFFER 表示缓冲区中包含的顶点索引
data写入缓冲区对象的数据对象
usage表示程序将如何使用存储在缓冲区对象中的数据。gl.STATIC DRAW 只会向缓冲区对象中写入一次数据,但需要绘制很多次
gl.STREAM DRAW 只会向缓冲区对象中写入一次数据,然后绘制若干次
gl.DYNAMIC DRAW 会向缓冲区对象中多次写入数据,并绘制很多次


Next Post
Cocos Creator学习之旅