webgl(四)画基本形状

1,053 阅读5分钟

在实际应用中,肯定不能只绘制一个点😭,需要绘制到各种各样的图形,三角形四边形五边形等等,但是所有多边形都可以看做是由1到n个三角形△拼接而成。

1.了解gl.drawArrays

WebGL方法 gl.drawArrays ,通过第一个参数指定不同的值,可以以多种不同的方式来绘制图形。在之前的例子中,绘制点都了解过了,这边我们单独拉出来看看。对比下绘制图形与绘制点之间的区别。

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

从上面的代码片段看到gl.drawArrays(mode, first, count);有三个参数,分别是modefirstcount

mode:显而易见,是模式,区别于绘制一个点(gl.POINT),还是一个三角形(gl.trangle)等等,点和三角形用的最多。下面将所有模式列举出来,需要的时候不至于陌生。

  • gl.POINTS: 绘制一系列点。
  • gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。
  • gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
  • gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
  • gl.TRIANGLE_STRIP:绘制一个三角带
  • gl.TRIANGLE_FAN:绘制一个三角扇
  • gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。

first:指定从哪个点开始绘制。

count:指定绘制需要使用到多少个点。即有多少个顶点参与绘制,多余的会被忽略。

再回过来看这行代码gl.drawArrays(gl.POINTS,0,1),表明从最开始的一个点绘制,绘制一个点(多了也没有😭,意思就算有多个,这里的count写了1,也只绘制一个)。

2. 绘制一个三角形

显而易见,我们会使用gl.TRIANGLES来绘制三角形,三角形需要三个顶点信息(有点废话)。 这里我们设置三个顶点坐标A(-0.5,0.0),B(0.5,0.0),C(0.0,0.8)。

let vertices = [
    -0.5, 0.0,
    0.5, 0.0,
    0.0, 0.8
]
vertices = new Float32Array(vertices)

采用WebGLBuffer对象来往WebGL中传递数据。这个buffer就相当于是中间人,WebGLBuffer与这个 Float32Array之间不互通,需要buffer来翻译一下。这个翻译分为5个步骤(看代码中的注释)

// 1 创建缓冲区对象
let buffer = gl.createBuffer()
// 2 将缓冲区对象绑定到目标 
gl.bindBuffer(gl.ARRAY_BUFFER,buffer)
// 3 向缓冲区对象中写入数据 
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW)
// 4 把带有数据的buffer赋值给attribute,也就是分配给a_position变量
let a_position = gl.getAttribLocation(gl.program,'a_position')
gl.vertexAttribPointer(
    a_position,
    2,
    gl.FLOAT,
    false,
    0,
    0
)
// 5:确认把带有数据的buffer赋值给attribute(起连接的作用)
gl.enableVertexAttribArray(a_position)

详细讲下第四步骤中带6个变量的那行,该函数告诉显卡如何从当前绑定的缓冲区(bindBuffer() 指定的缓冲区)中读取顶点数据。

gl.vertexAttribPointer(location, size, type, normalized, stride, offset);

  • location:location: vertex Shader里面attribute变量的location
  • size:指定每个顶点属性的组成数量,必须是 1,2,3 或 4。attribute变量的长度(vec2)
  • type:指定数组中每个元素的数据类型:gl.BYTE,gl.SHORT,gl.UNSIGNED_BYTE,gl.UNSIGNED_SHORT,gl.FLOAT。
  • normalized:当转换为浮点数时是否应该将整数数值归一化到特定的范围(对于类型gl.FLOATgl.HALF_FLOAT,此参数无效)。正交化,true,false, [1, 2] => [1/根号5, 2/根号5]
  • stride:顶点之间的偏移量,每个点的信息所占的BYTES
  • offset:顶点属性数组中第一部分的字节偏移量,每个点的信息,从第几个BYTES开始数

在上述的代码中,是将a_position这个顶点的位置属性告诉了webgl,进行了形状的绘制。绘制效果如下所示:

image.png

在上述的案例中,顶点坐标只包含了位置信息,其实每个店都可以包含它的颜色信息,这里我们更改下顶点坐标:

let vertices = [
    // x    y    r    g   b
    -0.5, 0.0, 1.0, 0.0, 0.0,  // 第一个点
    0.5, 0.0, 0.0, 1.0, 0.0,   // 第二个点
    0.0, 0.8, 0.0, 0.0, 1.0,   // 第三个点
]
vertices = new Float32Array(vertices)

第一个点坐标(-0.5,0,0),颜色为红色;第二个点坐标(0.5,0.0),颜色为绿色;第三个点坐标(0.0,0.8),颜色为蓝色。 要获取到顶点信息中的字节偏移量

let FSIZE = vertices.BYTES_PER_ELEMENT

则原来用来解析顶点数据的函数应改为:

gl.vertexAttribPointer(
    a_position,  
    2,          
    gl.FLOAT,   
    false,     
    5 * FSIZE,   //区别在这里
    0           
)

如若要增加颜色的赋值,需要增加如下的解析。

let a_color = gl.getAttribLocation(gl.program, 'a_color')
gl.vertexAttribPointer(
    a_color,
    3,
    gl.FLOAT,
    false,
    5 * FSIZE,
    2 * FSIZE
)
gl.enableVertexAttribArray(a_color)

最后,看下完整的js代码和效果

import initShaders from '../initShaders.js'

let canvas = document.getElementById('webgl')
let gl = canvas.getContext('webgl')

// vertex shader
let vertexShader = `
attribute vec2 a_position;
attribute vec3 a_color;
varying vec3 v_color;
void main() {
    v_color = a_color;
    gl_Position = vec4(a_position, 0.0, 1.0);
}
`

// fragment shader
let fragmentShader = `
precision mediump float;
varying vec3 v_color;
void main() {
    gl_FragColor = vec4(v_color, 1.0);
}
`
initShaders(gl,vertexShader,fragmentShader)

// 清空画布
gl.clearColor(0.0,0.0,0.0,1.0)
gl.clear(gl.COLOR_BUFFER_BIT)

let vertices = [
    // x    y     r    g   b
    -0.5,0.0,1.0,0.0,0.0,  // 第一个点
    0.5,0.0,0.0,1.0,0.0,   // 第二个点
    0.0,0.8,0.0,0.0,1.0,   // 第三个点
]
vertices = new Float32Array(vertices)
let FSIZE = vertices.BYTES_PER_ELEMENT

let buffer = gl.createBuffer()

gl.bindBuffer(gl.ARRAY_BUFFER,buffer)
gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW)

let a_position = gl.getAttribLocation(gl.program,'a_position')
let a_color = gl.getAttribLocation(gl.program,'a_color')
gl.vertexAttribPointer(
    a_position,  //location: vertex Shader里面attribute变量的location
    2,           //size: attribute变量的长度(vec2)
    gl.FLOAT,    //type: buffer里面数据的类型
    false,       //normalized: 正交化,true,false, [1, 2] => [1/根号5, 2/根号5]
    5 * FSIZE,   //stride:每个点的信息所占的BYTES
    0            //offset: 每个点的信息,从第几个BYTES开始数
)
gl.vertexAttribPointer(
    a_color,
    3,
    gl.FLOAT,
    false,
    5 * FSIZE,
    2 * FSIZE
)
gl.enableVertexAttribArray(a_position)
gl.enableVertexAttribArray(a_color)

gl.drawArrays(gl.TRIANGLES,0,3)

image.png

3.用三角形绘制“圆”

在开头写到所有的多边形都可以看成是n个三角形组成,圆也是。这里我们造出一圈点,点的颜色随机生成,然后组成圆形。

// 随机生成 n 个点
let n = 20
let R = 0.8 // 圆的半径
let vertices = []

for (let i = 0; i < n; i++) {
    let deg = 2 * Math.PI / n * i
    let x = Math.cos(deg) * R
    let y = Math.sin(deg) * R

    let r = (Math.random() - 0.5) * 2 + 0.8
    let g = (Math.random() - 0.5) * 2
    let b = (Math.random() - 0.5) * 2
    vertices.push(x, y, r, g, b)
}

vertices = new Float32Array(vertices)

Math.random()生成0到1之间的数字。这里,n越大,越接近圆。

在开头gl.drawArrays(mode, first, count);里面提到的mode,我们这里一一试下,看效果都是啥样的,直观看。

  • gl.drawArrays(gl.POINTS,0,n)
  • gl.drawArrays(gl.LINES, 0, n)
  • gl.drawArrays(gl.LINE_STRIP, 0, n)
  • gl.drawArrays(gl.LINE_LOOP, 0, n)
  • gl.drawArrays(gl.TRIANGLES, 0, n)
  • gl.drawArrays(gl.TRIANGLE_STRIP, 0, n)
  • gl.drawArrays(gl.TRIANGLE_FAN,0,n)

image.png

参考代码来自 webgl-tutorial-github