webgl基础:顶点到片元的联动

继前期分享了 初入webgl 的一些内容之后,相信大家已经对webgl有了一个初步的认识,今天再来分享一些基础内容,已加深大家对于它的认知。

众所周知,3d 的内容是比较多的,也很容易让人看的很迷惑。所以,此系列文章的分享理念就一个 -- 说透彻,说清楚,说明白。

好了,话不多说,进入今天的分享内容。

1. 前言:

分享之前先来回顾一下两个内容:

  • webgl 中有几种类型的变量?
  • 如何使用缓冲区对象向着色器传递数据?

也希望大家带着这两个问题来阅读本篇文章。

2. varying变量的使用

前边的文章中介绍了 webgl 中的数据类型,现在我们来重温一下:

  • attribute:影响单个顶点
  • uniform:影响全部顶点
  • varying:由顶点向片元传递数据

在之前文章的实例中也使用了 attribute、uniform 这两个变量。相信对于这两个变量的使用都有了一定程度的认识。但是对于最后一个 varying 变量,之前的文章中从未使用过。

由介绍可得知,varying 变量的主要作用就是从顶点着色器向片元着色器传值。具体怎么使用呢?莫慌,一步步来看下面的实现:

2.1 绘制一个点,(老代码,回顾一下。无新知识)

const VERTEX_SHADER_SOURCE = '' +
      'attribute vec4 apos;' +
      'void main () {' +
      ' gl_Position = apos;' +
      ' gl_PointSize = 10.0;' +
      '}'

const FRAGMENT_SHADER_SOURCE = '' +
      'precision lowp float;' +
      'varying vec4 vColor;' +
      'void main() {' +
      ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);' +
      '}' +
      ''
const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);

const buffer = gl.createBuffer()
const data = new Float32Array([
  -1.0, -1.0,
  1.0, -1.0,
  0.0, 1.0,
])

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

const apos = gl.getAttribLocation(program, 'apos');
gl.vertexAttribPointer(apos, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(apos)

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

这段代码相信大家已经非常熟悉,在画布中绘制一个红色的三角形,对于其中用到的方法也可以查下之前的文章。这里就不赘述了。

2.2 使用 varying 传递数据

使用 varying 传递数据需要分以下几步:

  • 顶点着色器中创建一个变量,用于获取数据。可暂时先用 apos。将位置信息作为颜色传入到片元着色器中

  • 顶点着色器中创建 varying变量

  • 将数据 apos 赋值给 varying 变量

  • 片元着色器中指定精度

  • 片元着色器中创建同名varying 变量

  • 使用 varying 变量

注意:

varying 变量的类型与 attribute 的类型相同。也是四分量的浮点数 -- vec4

了解了以上步骤之后,来看下代码怎么写:

栗子:

const VERTEX_SHADER_SOURCE = '' +
      'attribute vec4 apos;' + // 用于获取数据的 apos 变量
      'varying vec4 vColor;' + // 用于传输数据的 varying 变量
      'void main () {' +
      ' gl_Position = apos;' +
      ' gl_PointSize = 10.0;' +
      ' vColor = apos;' +    // 将位置数据传输到片元着色器中
      '}'

const FRAGMENT_SHADER_SOURCE = '' +
      'precision lowp float;' +  // 指定浮点数精度为低精度
      'varying vec4 vColor;' +  // 创建同名的 varying 变量
      'void main() {' +
      ' gl_FragColor = vColor;' + // 使用 varying 变量
      '}' +
      ''

再次运行后可以看到一个彩色的三角形。这里我们就将位置信息当做颜色信息传入到了片元着色器中。记得一定要指定精度。否则会出现渲染错误。

2.3 传入指定的颜色数据

在第二步中,通过传入位置信息来显示对应位置的颜色。接下来来看看,如何通过 varying 变量传入指定的颜色信息。

1. 首先修改着色器源代码。

const VERTEX_SHADER_SOURCE = '' +
      'attribute vec4 apos;' +
      'attribute vec4 aColor;' + // 新增颜色信息变量
      'varying vec4 vColor;' +
      'void main () {' +
      ' gl_Position = apos;' +
      ' gl_PointSize = 10.0;' +
      ' vColor = aColor;' + // 将颜色信息传入到 varying 变量中
      '}'

const FRAGMENT_SHADER_SOURCE = '' +
      'precision lowp float;' +
      'varying vec4 vColor;' +
      'void main() {' +
      ' gl_FragColor = vColor;' +
      '}' +
      ''

2. 修改缓冲区数据。

// 在每个位置后新增颜色信息。vec4 的数据。
const data = new Float32Array([
  -1.0, -1.0, 1.0, 1.0, 0.0, 1.0,
  1.0, -1.0, 0.0, 1.0, 1.0, 1.0,
  0.0, 1.0, 1.0, 0.0, 1.0, 1.0,
])

3. 修改数据传入

绘制三角形的时候只有一个 apos 变量。现在新增了一个,所以需要修改数据的传入方式。修改后的代码如下

const apos = gl.getAttribLocation(program, 'apos');
gl.vertexAttribPointer(apos, 2, gl.FLOAT, false, data.BYTES_PER_ELEMENT * 6, 0)
gl.enableVertexAttribArray(apos)

const aColor = gl.getAttribLocation(program, 'aColor');
gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, data.BYTES_PER_ELEMENT * 6, data.BYTES_PER_ELEMENT * 2);
gl.enableVertexAttribArray(aColor)

这里可以这样理解:

  • data 缓冲区中,每 6 个元素表示一个顶点信息。
  • apos 需要两个,从每个顶点的第1个数据开始查找。
  • aColor 需要四个,从每个顶点的第 3 个数据开始查找。

同时对于代码中所涉及到的新知识点 gl.vertexAttribPointer、data.BYTES_PER_ELEMENT两个内容,也来详细介绍下。

1.对于 gl.vertexAttribPointer 之前的文章里有过介绍,可能介绍的不太详细。再来详细介绍下:

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

  • location: 指定修改的着色器变量的位置
  • size: 每次绘制需要几个顶点,定义的数据如何分配。
  • type:顶点的数据类型。与类型化数组的类型相同。
    • gl.BYTE:带符号的8位整数
    • gl.SHORT:带符号的16位整数
    • gl.UNSIGNED_BYTE:无符号8位整数
    • gl.UNSIGNED_SHORT:无符号16位整数
    • gl.FLOAT:32位浮点数
  • normalized:是否将浮点数归一化到 [0, 1] 或 [-1, 1] 之间
  • stride可简单理解为一个点需要多少个数据。
  • offset:偏移几个顶点开始绘制

这里也会注意到,最后的 stride 和 offset 两个参数都做了修改。而且是通过 data.BYTES_PER_ELEMENT 来实现的。

2. data.BYTES_PER_ELEMENT 表示数组中每个元素所占的字节数。主要有以下几个分类:
数组类型:           占的字节数
Int8Array;         // 1
Uint8Array;        // 1
Uint8ClampedArray; // 1
Int16Array;        // 2
Uint16Array;       // 2
Int32Array;        // 4
Uint32Array;       // 4
Float32Array;      // 4
Float64Array;      // 8

不出意外的话,经过这些操作,你会得到一个色彩鲜艳的彩色三角形。

3. 总结

接下来回顾一下文章开始提出的问题:

3.1 webgl 中有几种类型的变量?

有三种。

  • attribute:影响单个顶点
  • uniform:影响全部顶点
  • varying:由顶点向片元传递数据

3.2 如何使用缓冲区对象向着色器传递数据?

  1. 创建 buffer : gl.createBuffer()
  2. 创建缓冲区数据:new Float32Array()
  3. 绑定缓冲区: gl.bindBuffer
  4. 传入缓冲区数据: gl.bufferData
  5. 获取变量:const 获取到的变量 = gl.getAttribLocation(program, '变量名称')
  6. 给变量赋值:gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
  7. 激活变量:gl.enableVertexAttribArray(获取到的变量)

4. 完整代码

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>顶点着色器向片元着色器传递数据</title>
</head>
<body>
  <div>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid #666666;"></canvas>
  </div>
  <script>
    const canvas = document.getElementById('canvas')
    const gl = canvas.getContext('webgl')

    const VERTEX_SHADER_SOURCE = '' +
      'attribute vec4 apos;' +
      'attribute vec4 aColor;' +
      'varying vec4 vColor;' +
      'void main () {' +
      ' gl_Position = apos;' +
      ' gl_PointSize = 10.0;' +
      ' vColor = aColor;' +
      '}'

    const FRAGMENT_SHADER_SOURCE = '' +
      'precision lowp float;' +
      'varying vec4 vColor;' +
      'void main() {' +
      ' gl_FragColor = vColor;' +
      '}' +
      ''

    const vertexShader = gl.createShader(gl.VERTEX_SHADER)
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)

    gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE)
    gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE)

    gl.compileShader(vertexShader)
    gl.compileShader(fragmentShader)

    const program = gl.createProgram()
    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)

    gl.linkProgram(program)
    gl.useProgram(program)

    const buffer = gl.createBuffer()
    const data = new Float32Array([
      -1.0, -1.0, 1.0, 1.0, 0.0, 1.0,
      1.0, -1.0, 0.0, 1.0, 1.0, 1.0,
      0.0, 1.0, 1.0, 0.0, 1.0, 1.0,
    ])

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

    const apos = gl.getAttribLocation(program, 'apos');
    gl.vertexAttribPointer(apos, 2, gl.FLOAT, false, data.BYTES_PER_ELEMENT * 6, 0)
    gl.enableVertexAttribArray(apos)

    /*
      Int8Array;         // 1
      Uint8Array;        // 1
      Uint8ClampedArray; // 1
      Int16Array;        // 2
      Uint16Array;       // 2
      Int32Array;        // 4
      Uint32Array;       // 4
      Float32Array;      // 4
      Float64Array;      // 8
    */
    const aColor = gl.getAttribLocation(program, 'aColor');
    gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, data.BYTES_PER_ELEMENT * 6, data.BYTES_PER_ELEMENT * 2);
    gl.enableVertexAttribArray(aColor)

    gl.drawArrays(gl.TRIANGLES, 0, 3)
  </script>
</body>
</html>
原文地址:https://www.cnblogs.com/yancyCathy/p/14465593.html