Skip to main content
  1. Blogs/
  2. 前端开发/
  3. 可视化/
  4. WebGL/

WebGL编程指南03:绘制和变化三角形

·812 words·4 mins
Table of Contents

前言
#

首先,构成三维模型的基本单位是三角形,那意味着一张图会会充斥着许许多多的顶点。

在上一篇张我们只能绘制一个点,对于 WebGL 提供一种 缓冲区机制(buffer object) 来向着色器一次性设置多个顶点,达到多点绘制的能力。

缓冲区的使用过程
#

我们通过 示例:一次性绘制多个点 例子来认识缓冲区的使用:

效果图如下:

|300

  1. 分别定义顶点着色去位置和大小,及片元着色器颜色:
// Vertex shader program
var VSHADER_SOURCE = `
	attribute vec4 a_Position;
	void main() {
		gl_Position = a_Position;
		gl_PointSize = 10.0;
	}`;

// Fragment shader program
var FSHADER_SOURCE = `
	void main() {
		gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
	}`;
  1. 初始化着色器程序,以及缓存区
function main() {
  // 初始化 gl
  var canvas = document.getElementById("webgl");
  var gl = getWebGLContext(canvas);

  // 初始化着色器程序
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  }

  // 初始化缓存区
  var n = initVertexBuffers(gl);

  // 绘制
}
  1. 缓冲区的逻辑(创建缓存区,绑定缓存区,写入数据,分配变量,开启变量):
function initVertexBuffers(gl) {
  var vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);
  var n = 3; // The number of vertices

  /**
   * 1. 创建缓存区
   * 如果返回 null,表示创建缓存区失败
   * gl.deleteBuffer(buffer) 方法用于删除缓存区对象。
   */
  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log("Failed to create the buffer object");
    return -1;
  }

  /**
   * 2. 将缓存区绑定到目标
   * gl.bindBuffer(target, buffer) 方法接受两个参数:
   * target:绑定目标,可以是 gl.ARRAY_BUFFER 或 gl.ELEMENT_ARRAY_BUFFER。
   *    gl.ARRAY_BUFFER:顶点属性数据缓存区(本例子)。
   *    gl.ELEMENT_ARRAY_BUFFER:顶点索引缓存区。
   * buffer:缓存区对象。
   */
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

  /**
   * 3. 向缓存区写入数据
   * gl.bufferData(target, data, usage) 方法接受三个参数:
   * target:缓存区目标,可以是 gl.ARRAY_BUFFER 或 gl.ELEMENT_ARRAY_BUFFER。
   * data:数据
   * usage:使用方法,可以是 gl.STATIC_DRAW、gl.DYNAMIC_DRAW 或 gl.STREAM_DRAW。
   *    gl.STATIC_DRAW:只会向缓冲区写入一次,但需要绘制多次(本例子)。
   *    gl.DYNAMIC_DRAW:只会向缓冲区写入一次,但需要绘制若干次。
   *    gl.STREAM_DRAW:会向缓存区多次写入数据,并绘制多次。
   */
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  var a_Position = gl.getAttribLocation(gl.program, "a_Position");

  /**
   * 4. 将缓存区数据分配给 a_Position 变量
   * gl.vertexAttribPointer(location, size, type, normalized, stride, offset) 方法接受多个参数:
   * location:attribute 变量的存储位置,即 gl.getAttribLocation 返回的变量。
   * size:顶点属性的大小。(本例为2,表示每个顶点属性有 2 个值 x,y。)
   * type:数据类型。
   *      gl.FLOAT:32 位浮点数。
   *      gl.BYTE:8 位有符号整数,范围是 [-128, 127]。
   *      gl.UNSIGNED_BYTE:8 位无符号整数,范围是 [0, 255]。
   *      gl.SHORT:16 位有符号整数,范围是 [-32768, 32767]。
   *      gl.UNSIGNED_SHORT:16 位无符号整数,范围是 [0, 65535]。
   *      gl.INT:32 位有符号整数,范围是 [-2147483648, 2147483647]。
   *      gl.UNSIGNED_INT:32 位无符号整数,范围是 [0, 4294967295]。
   * normalized:是否归一化,WebGL 默认是 false。
   * stride:相邻顶点属性之间的字节数。
   * offset:顶点属性的起始位置(偏移量)。
   */
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  /**
   * 5. 链接 a_Position 变量和缓存区
   * gl.enableVertexAttribArray(index) 方法接受一个参数:
   * index:顶点属性索引,即 gl.getAttribLocation 返回的变量。
   */
  gl.enableVertexAttribArray(a_Position);

  return n;
}
  1. 绘制图形
var n = initVertexBuffers(gl);

gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, n);

WebGL 的基本图形
#

我们在绘制方法 gl.drawArrays 中,我们将 gl.POINTS 改为 gl.TRIANGLES:

gl.drawArrays(gl.POINTS, 0, n);

可以看到这样的效果:

|300

WebGL 的基本图形有这些:

mode描述
gl.POINTS绘制点
gl.LINES绘制线段,按 (v0,v1), (v2,v3),(v4,v5) 顺序绘制
gl.LINE_STRIP绘制线段,并连接所有点。按 (v0,v1), (v1,v2), (v2,v3) 顺序绘制
gl.LINE_LOOP绘制线段,并连接第一个和最后一个点。按 (v0,v1), (v1,v2), (v2,v0) 顺序绘制
gl.TRIANGLES绘制(多个独立的)三角形。按 (v0,v1,v2), (v3,v4,v5) 顺序绘制
gl.TRIANGLE_STRIP绘制三角形,并连接所有点(三角形共用一条边)。按 (v0,v1,v2), (v1,v2,v3), (v2,v3,v4) 顺序绘制
gl.TRIANGLE_FAN绘制三角形,共享起点。按 (v0,v1,v2), (v0,v2,v3), (v0,v3,v4) 顺序绘制

效果如下:

gl.LINES(线段)

var vertices = new Float32Array([0, 0.5, -0.5, -0.5, 0.2, -0.5, 0.7, 0.5]);
var n = 4; // The number of vertices

|300

gl.LINE_STRIP(线条)

var vertices = new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5, 1, 0.5]);
var n = 4; // The number of vertices

|300

gl.LINE_LOOP(回路)

var vertices = new Float32Array([-0.5, 0.5, -1, -0.5, 0, -0.5, 0.5, 0.5, -0.5, 0.5, -1, -0.5]);
var n = 6; // The number of vertices

|300

gl.TRIANGLE_STRIP

var vertices = new Float32Array([-0.5, 0.5, -1, -0.5, 0, -0.5, 0.5, 0]);
var n = 4; // The number of vertices

|300

gl.TRIANGLE_FAN(三角扇)

var vertices = new Float32Array([-0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5]);
var n = 4; // The number of vertices

|300

变化(移动,旋转,缩放)
#

平移
#

现在展示尝试将 gl.TRIANGLE 例子中的三角形如何移动到屏幕的右上角。这里将修改三角形的顶点数据,这样的操作成为逐顶点操作(per-vertex operation)。

|300

  1. 修改顶点着色器,添加平移变量 u_Translation
var VSHADER_SOURCE = `attribute vec4 a_Position;
  uniform vec4 u_Translation;
  void main() {
    gl_Position = a_Position + u_Translation;
  }`;
  1. 获取平移变量存储位置
var n = initVertexBuffers(gl);
var u_Translation = gl.getUniformLocation(gl.program, "u_Translation");
  1. 给平移变量赋值(赋予其次坐标值)
var Tx = 0.5,
  Ty = 0.5,
  Tz = 0.0;
gl.uniform4f(u_Translation, Tx, Ty, Tz, 0.0);
// 最终我们新三角形的位置将是 gl_Position = a_Position + u_Translation;

旋转
#

现在示意旋转效果图:

|300

  1. 通过 三角函数 换算,设置顶点位置坐标:
var 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;
    gl_Position.y = a_Position.x * u_SinB + a_Position.y * u_CosB;
    gl_Position.z = a_Position.z;
    gl_Position.w = 1.0;
  }`;
  1. 通过 弧度制 计算三角函数值
var n = initVertexBuffers(gl);

// Pass the data required to rotate the shape to the vertex shader
var radian = (Math.PI * ANGLE) / 180.0; // Convert to radians
var cosB = Math.cos(radian);
var sinB = Math.sin(radian);
  1. 赋值给将三角函数值传递给顶点着色器:
var u_CosB = gl.getUniformLocation(gl.program, "u_CosB");
var u_SinB = gl.getUniformLocation(gl.program, "u_SinB");

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

// draw...

矩阵旋转
#

  1. 引用矩阵工具函数:
<script src="../lib/cuon-matrix.js"></script>
  1. 在顶点着色器中,使用矩阵旋转来替换三角函数:
var VSHADER_SOURCE = `
  attribute vec4 a_Position;
  uniform mat4 u_xformMatrix;
  void main() {
    gl_Position = u_xformMatrix * a_Position;
  }`;
  1. 定义变换矩阵:
var n = initVertexBuffers(gl);
var radian = (Math.PI * ANGLE) / 180.0; // Convert to radians
var cosB = Math.cos(radian),
  sinB = Math.sin(radian);

var xformMatrix = new Float32Array([cosB, sinB, 0.0, 0.0, -sinB, cosB, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]);
  1. 在绘制之前,将变换矩阵传递给顶点着色器:
var u_xformMatrix = gl.getUniformLocation(gl.program, "u_xformMatrix");\

/**
 * gl.uniformMatrix4fv(location, transpose, value)
 * location: uniform变量的位置
 * transpose: 是否转置矩阵,在 webgl 中为 false
 * value: 4x4矩阵,即 xformMatrix 的值
 */
gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);

矩阵平移
#

类似,我们可以通过矩阵平移来改变三角形的位置:

var Tx = 0.5,
  Ty = 0.5,
  Tz = 0.0;
var xformMatrix = new Float32Array([
  1.0, 0.0, 0.0, 0.0, // x轴缩放
  0.0, 1.0, 0.0, 0.0, // y轴缩放
  0.0, 0.0, 1.0, 0.0, // z轴缩放
  Tx, Ty, Tz, 1.0// 平移
]);

矩阵缩放
#

类似,我们可以通过矩阵缩放来改变三角形的大小:

var Sx = 1.0,
  Sy = 1.5,
  Sz = 1.0;

var xformMatrix = new Float32Array([
  Sx, 0.0, 0.0, 0.0, // x轴缩放
  0.0, Sy, 0.0, 0.0, // y轴缩放
  0.0, 0.0, Sz, 0.0, // z轴缩放
  0.0, 0.0, 0.0, 1.0// 平移
]);

|300