【WebGL系列-03】获取shader变量地址及赋值

发布时间 2023-07-24 13:45:47作者: 李煎饼_GISer

获取shader变量地址及赋值

上一节创建了WebGL程序对象,创建好program对象后,对象中包含顶点着色器和片元着色器,着色器中含有变量,我们需要对其进行赋值后才能够进行绘制。

着色器代码如下:

const VSHADER_SOURCE = /* glsl */`
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
    gl_PointSize = 10.0;
  }
`;

const FSHADER_SOURCE = /* glsl */`
  void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }
`;

其中,顶点着色代码VSHADER_SOURCE中定义了一个attribute变量,a_Position,在main函数中将其赋值给GLSL ES的内置变量gl_Position,表示这个顶点所在的位置。

GLSL ES语言中,传入的变量有两种,attributeuniform。其中,attribute变量只能定义在顶点着色器中,传输的是与顶点有关的数据,而uniform变量可以定义在顶点着色器和片元着色器中,传输的是与顶点无关或者说所有顶点共同使用的值。还有一类变量,varying变量,是用于顶点着色器和片元着色器之间传输数据的。定义varying变量时,需要在顶点着色器和片元着色器中各定义一个名称、类型都相同的变量,并指明为varying变量。

只传入一个数据(不使用缓冲区)

给attribute变量赋值

以示例的shader代码为例,如果我们只打算在指定的位置绘制一个点,那么我们需要做的事就是:找到变量所在的地址,然后给它赋值即可。所以,给一个变量赋值步骤如下:

  1. 找到变量所在的地址
  2. 给变量赋值

示例代码

const a_Position = gl.getAttribLocation(shaderProgram, "a_Position");
if (a_Position < 0) {
  console.log("变量a_Position地址查找失败!");
  return;
}
gl.vertexAttrib3f(a_position, 0.0, 0.0, 0.0);

接口

GLint WebGLRenderingContext.getAttribLocation(program, name)

获取attribute变量的地址

  • program: WebGLProgram,webgl程序

  • name: string,变量名,与shader中的变量名一致

void WebGLRenderingContext.vertexAttrib3f(index, v0, v1, v2)

给顶点着色器中一个attribute变量赋值

index: 变量的地址

v0,v1,v2: 要赋值的值

vertexAttrib3f还有很多同族函数,针对不同类型的变量

  • vertexAttrib1f(index, v0)

  • vertexAttrib2f(index, v0, v1)

  • vertexAttrib3f(index, v0, v1, v2)

  • vertexAttrib4f(index, v0, v1, v2, v3)

  • vertexAttrib1fv(index, value)

  • vertexAttrib2fv(index, value)

  • vertexAttrib3fv(index, value)

  • vertexAttrib4fv(index, value)

其中,以f结尾的函数表示以浮点数的形式对变量进行赋值,1234表示分量的个数
fv结尾的函数表示使用一个向量进行赋值,其值为一个Float32Array类型的值,1234表示这个结构化数组中的元素个数。

对变量进行赋值的时候,我们既可以通过浮点数分别对每个分量进行赋值,也可以使用结构化数组对整体赋值。

const a_foobar = gl.getAttribLocation(shaderProgram, "foobar");
// 对每个分量进行赋值
gl.vertexAttrib3f(a_foobar, 10.0, 5.0, 2.0);

// 使用结构化数组进行赋值
const floatArray = new Float32Array([10.0. 5.0, 2.0]);
gl.vertexAttrib3fv(a_foobar, floatArray);

对于矩阵,实际上是由多个列向量构成的,在计算其位置的时候,可以先获取到矩阵的起始位置,该位置+0,为第一列向量的位置,位置+1,为第二列向量的位置,以此类推,分别对每个列向量进行赋值,完成对整个矩阵的赋值。

/**
 * 想赋值的3x3的矩阵为:
 * 0 1 2
 * 3 4 5
 * 6 7 8
 */
const matrix3x3Location = gl.getAttribLocation(shaderProgram, "matrix3x3");
gl.vertexAttrib3f(matrix3x3Location, 0, 3, 6);
gl.vertexAttrib3f(matrix3x3Location + 1, 1, 4, 7);
gl.vertexAttrib3f(matrix3x3Location + 2, 2, 5, 8);

对uniform变量的赋值

uniform变量又称为一致变量,一般定义为与顶点无关的变量,即所有顶点保持一致的变量。

首先需要获取uniform变量的位置:

GLIint WebGLRenderingContext.getUniformLocation(program, name)

获取uniform变量的地址

  • program: WebGLProgram, webgl程序对象

  • name: string,变量名


之后,就可以对这些变量进行赋值

  • uniform1f(location, v0)

  • uniform2f(location, v0, v1)

  • uniform3f(location, v0, v1, v2)

  • uniform4f(location, v0, v1, v2, v3)

  • uniform1fv(location, value)

  • uniform2fv(location, value)

  • uniform3fv(location, value)

  • uniform4fv(location, value)

  • uniform1i(location, v0)

  • uniform1i(location, v0, v1)

  • uniform1i(location, v0, v1, v2)

  • uniform1i(location, v0, v1, v2, v3)

  • uniform1iv(location, value)

  • uniform2iv(location, value)

  • uniform3iv(location, value)

  • uniform4iv(location, value)

参数的含义与vertexAttrib[1234][fv]相同,不同的是,uniform变量可以设置整数类型的变量,即将其中的f换成i便代表整数类型,传入的数值类型也为整数。

对于uniform矩阵,有以下三个接口可以使用

  • uniformMatrix2fv(location, transpose, value)

  • uniformMatrix3fv(location, transpose, value)

  • uniformMatrix4fv(location, transpose, value)

其中,location表示uniform变量的地址,transpose为一个GLboolean类型的值,表示是否转置,默认为falsevalue是一个Float32Array类型的结构化数组,以列主序的方式指定矩阵的值

还有一个函数,用于获取指定位置的uniform变量的值

WebGLRenderingContext.getUniform(program, location)

获取uniform变量的值

  • program: WebGLProgram,webgl程序对象

  • location: GLint,变量位置,可以由getUniformLocation获取

该函数的返回值跟据变量类型的不同而改变,对应类型如下表:

Uniform类型 返回类型
Uniform type Returned type
WebGL 1 only
boolean GLBoolean
int GLint
float GLfloat
vec2 Float32Array (with 2 elements)
ivec2 Int32Array (with 2 elements)
bvec2 Array of GLBoolean (with 2 elements)
vec3 Float32Array (with 3 elements)
ivec3 Int32Array (with 3 elements)
bvec3 Array of GLBoolean (with 3 elements)
vec4 Float32Array (with 4 elements)
ivec4 Int32Array (with 4 elements)
bvec4 Array of GLBoolean (with 4 elements)
mat2 Float32Array (with 4 elements)
mat3 Float32Array (with 9 elements)
mat4 Float32Array (with 16 elements)
sampler2D GLint
samplerCube GLint
Additionally available in WebGL 2
uint GLuint
uvec2 Uint32Array (with 2 elements)
uvec3 Uint32Array (with 3 elements)
uvec4 Uint32Array (with 4 elements)
mat2x3 Float32Array (with 6 elements)
mat2x4 Float32Array (with 8 elements)
mat3x2 Float32Array (with 6 elements)
mat3x4 Float32Array (with 12 elements)
mat4x2 Float32Array (with 8 elements)
mat4x3 Float32Array (with 12 elements)
any sampler type GLint

赋值多个数据(使用缓冲区)

一般情况下,一个图形程序中有很多要绘制的对象,每个对象的位置、颜色等信息都不相同,但是用的是同一份shader源码,此时,如果使用上面的方法给shader中的变量赋值,那么最终渲染的结果会发现所有的东西都是一模一样的。此时,就需要使用缓冲区对每个渲染对象赋值不同的值。

缓冲区可以这么理解,就是开辟一个内存空间,把所有对象的位置信息、颜色信息等各种数据存储起来,当执行到这个对象对应的shader时,从内存中找到这个对象对应的数据,然后对变量进行赋值。这样,每次渲染的时候,每个对象都可以绘制成不同的样子。

使用缓冲区给变量赋值的过程分为如下几步:

  • 创建缓冲区对象
  • 将缓冲区对象绑定到目标
  • 向缓冲区中写入数据
  • 找到变量的位置
  • 将缓冲区对象分配给变量
  • 链接变量与缓冲区对象

示例代码

// 定义顶点数据
let vertices = new Float32Array([
    0.0, 0.5, -0.5, -0.5, 0.5, -0.5
]);
 
// 创建顶点缓冲区
let vertexBuffer = gl.createBuffer();
if (!vertexBuffer) {
    alert("缓冲区对象创建失败!");
}

// 绑定顶点缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 绑定顶点数据,并定义绘制方式
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
 
// 获取attribute变量地址
let aVertexPosition = gl.getAttribLocation(shaderProgram, "aVertexPosition");
if (aVertexPosition < 0) {
    alert('查找变量aVertexPosition失败!');
}

// 将缓冲区对象分配给变量
gl.vertexAttribPointer(aVertexPosition, 2, gl.FLOAT, false, 0, 0);
// 链接变量与缓冲区对象
gl.enableVertexAttribArray(aVertexPosition);

接口

WebGLBuffer WebGLRenderingContext.createBuffer()

创建一个buffer对象


void WebGLRenderingContext.bindBuffer(target, buffer)

将一个Buffer对象绑定到目标

  • target:GLenum,可以是以下几种:

    • gl.ARRAY_BUFFER,包含顶点属性的buffer,如顶点坐标、纹理坐标数据或顶点颜色数据
    • gl.ELEMENT_ARRAY_BUFFER:用于元素索引的buffer
    • 在webgl2,还可以使用以下值:
      • gl.COPY_READ_BUFFER:从一个buffer对象复制到另一个buffer对象
      • gl.COPY_WRITE_BUFFER:从一个buffer对象复制到另一个buffer对象
      • gl.TRANSFORM_FEEDBACK_BUFFER:buffer for transform feedback operations
      • gl.UNIFORM_BUFFER:用于存储统一块的buffer
      • gl.PIXEL_PACK_BUFFER:用于像素传输操作的buffer
      • gl.PIXEL_UNPACK_BUFFER:用于像素传输操作的buffer
  • buffer:要绑定的buffer


webgl1中

void WebGLRenderingContext.bufferData(target, size, usage)

void WebGLRenderingContext.bufferData(target, ArrayBuffer? srcData, usage)

void WebGLRenderingContext.bufferData(target, ArrayBufferView srcData, usage)

webgl2中

void WebGLRenderingContext.bufferData(target, ArrayBufferView srcData, usage, srcOffset, length)

创建并初始化buffer对象的数据存储区

  • target: GLenum,指定绑定目标,可以时以下值:

    • gl.ARRAY_BUFFER,包含顶点属性的buffer,如顶点坐标、纹理坐标数据或顶点颜色数据
    • gl.ELEMENT_ARRAY_BUFFER:用于元素索引的buffer
    • 在webgl2,还可以使用以下值:
      • gl.COPY_READ_BUFFER:从一个buffer对象复制到另一个buffer对象
      • gl.COPY_WRITE_BUFFER:从一个buffer对象复制到另一个buffer对象
      • gl.TRANSFORM_FEEDBACK_BUFFER:buffer for transform feedback operations
      • gl.UNIFORM_BUFFER:用于存储统一块的buffer
      • gl.PIXEL_PACK_BUFFER:用于像素传输操作的buffer
      • gl.PIXEL_UNPACK_BUFFER:用于像素传输操作的buffer
  • size:GLsizeiptr:设定buffer对象的数据存储区大小

  • srcData(optional):一个ArrayBuffer、SharedArrayBuffer或者ArrayBufferView类型的数组对象,将被复制到buffer的数据存储区,如果为null,数据存储区会被创建,但是不会进行初始化和定义

  • usage:GLenum,指定数据存储区的使用方法,可以是以下值:

    • gl.STATIC_DRAW:缓冲区的内容可能经常使用,而不会经常修改,内容被写入缓冲区,但不被读取
    • gl.DYNAMIC_DRAW:缓冲区的内容可能经常被使用,并且经常更改,内容被写入缓冲区,但不被读取
    • gl.STREAM_DRAW:缓冲区的内容可能不会经常使用,内容被写入缓冲区,但不被读取
    • 使用webgl2时,还可以使用以下值:
      • gl.STATIC_READ:缓冲区的内容可能经常使用,而不会经常修改,内容从缓冲区读取,但不写入
      • gl.DYNAMIC_READ:缓冲区的内容可能经常被使用,并且经常更改,内容从缓冲区读取,但不写入
      • gl.STREAM_READ:缓冲区的内容可能不会经常使用,内容从缓冲区读取,但不写入
      • gl.STATIC_COPY:缓冲区的内容可能经常使用,而不会经常修改,内容既不从缓冲区读取,也不写入
      • gl.DYNAMIC_COPY:缓冲区的内容可能经常被使用,并且经常更改,内容既不从缓冲区读取,也不写入
      • gl.STREAM_COPY:缓冲区的内容可能不会经常使用,内容既不从缓冲区读取,也不写入
  • srcOffset:GLuint,指定读取缓冲时的初始元素索引偏移量

  • length(optional):GLuint,默认为0


void WebGLRenderingContext.vertexAttribPointer(index, size, type, normalized, stride, offset)

告诉显卡从当前绑定的缓冲区中读取顶点数据。

  • index:GLuint,指定要修改的顶点的索引

  • size:GLint,指定每个顶点属性的组成数量,必须是1,2,3,4之一

  • type:GLenum,指定数组中每个元素的数据类型,可以是以下值:

    • gl.BYTE:有符号8位整数,范围[-128, 127]
    • gl.SHORT:有符号16位整数,范围[-32768, 32767]
    • gl.UNSIGNED_BYTE:无符号8位整数,范围[0, 255]
    • gl.UNSIGNED_SHORT:无符号16位整数,范围[0, 65535]
    • gl.FLOAT:32位IEEE标准的浮点数
    • gl.HALF_FLOAT(webgl2):16位IEEE标准的浮点数
  • normalized:GLboolean,当转换为浮点数时是否应该将整数数值归一化到特定的范围

    • 对于BYTE和SHORT,将归一化到[-1, 1]
    • 对于UNSIGNED_BYTE和UNSIGNED_SHORT,将归一化到[0, 1]
    • 对于FLOAT和HALF_FLOAT,此参数无效
  • stride:GLsizei,以字节为单位指定连续顶点属性开始之间的偏移量

  • offset:GLintptr,指定顶点属性数组中第一部分的字节偏移量,必须是类型的字节长度的倍数


void WebGLRenderingContext.enableVertexAttribArray(index)

打开属性数组列表中指定索引处的通用点点属性数组

  • index:GLuint,指向要激活的顶点属性

如果要关闭顶点属性数组,可以使用以下接口

void WebGLRenderingContext.disableVertexAttribArray(index)

关闭属性数组列表中指定索引处的通用点属性数组

  • index:GLuint,指定要关闭的顶点属性