如何在绑定顶点数组对象和缓冲区数据以在渲染时动态绘制时编写通用的 webgl 渲染循环?

How to write a generic webgl render loop while binding vertex array objects and buffer data for dynamic draw on render time?

我想使用this article中提到的方法显示文本。同时我关心代码是否通用。

文章中提到手动创建缓冲区信息,我称之为第一种方法:

// Maunally create a bufferInfo
var textBufferInfo = {
  attribs: {
    a_position: { buffer: gl.createBuffer(), numComponents: 2, },
    a_texcoord: { buffer: gl.createBuffer(), numComponents: 2, },
  },
  numElements: 0,
};
var textVAO = twgl.createVAOFromBufferInfo(
    gl, textProgramInfo, textBufferInfo);

并设置渲染使用:

// update the buffers
textBufferInfo.attribs.a_position.numComponents = 2;
gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_position.buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.position, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_texcoord.buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.texcoord, gl.DYNAMIC_DRAW);

反对第二种方法:

// Create data for 'F'
var fBufferInfo = twgl.primitives.create3DFBufferInfo(gl);
var fVAO = twgl.createVAOFromBufferInfo(
    gl, fProgramInfo, fBufferInfo);

和设置渲染:

// setup the attributes and buffers for the F
gl.bindVertexArray(fVAO);

所以我认为这意味着,在初始化时,我可以像这样设置一个 VAO:

const makeVao = (bufferInfos) => {
  let vao = gl.createVertexArray();
  gl.bindVertexArray(vao);

  bufferInfos.forEach(({
    array,
    size,
    index
  }) => {

    let buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(array), gl.STATIC_DRAW);

    gl.enableVertexAttribArray(index);
    gl.vertexAttribPointer(index,
                           size,
                           gl.FLOAT,
                           false,
                           0,
                           0);

  });

  gl.bindVertexArray(null);

  return vao;
};

bufferInfos 用法:

let bufferInfos = [{
  array: [vertices],
  size: 2,
  index: gl.getAttribLocation(program, name) 
}];

这将设置属性并给我一个我可以在渲染时使用的 VAO,例如:

gl.bindVertexArray(vao);

那我就完了

除此之外,我想要第一种方法,我可以在其中设置每个渲染器的顶点属性。那么我该如何设置通用代码才能在渲染时设置着色器属性呢?

由于您使用的是顶点数组对象,因此您只需在初始化时设置属性。属性保留指向调用 vertexAttribPointer 时当前缓冲区的指针。参见 this article on attribute state or or this one

换句话说,如果你这样做

// assume positionLoc = 0, normalLoc = 1, texcoordLoc = 2

gl.bindVertexArray(someVAO);

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLoc, ...);
gl.enableVertexAttribArray(positionLoc);

gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.vertexAttribPointer(normalLoc, ...);
gl.enableVertexAttribArray(texcoordLoc);

gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.vertexAttribPointer(texcoordLoc, ...);
gl.enableVertexAttribArray(texcoordLoc);

然后someVAO保持以下状态

// pseudo code
someVAO = {
  attributes: [
    { enabled: true, buffer: positionBuffer, ... },  // loc = 0
    { enabled: true, buffer: normalBuffer, ... },    // loc = 1
    { enabled: true, buffer: texcoordBuffer, ... },  // loc = 2
    { enabled: false, ... },                         // loc = 3
    ...
  ]
  elementArrayBuffer: null,  
}

所以只要你想更新一个缓冲区就

gl.bindBuffer(gl.ARRAY_BUFFER, bufferToUpdate);
gl.bindData(gl.ARRAY_BUFFER, newData, gl.???_DRAW);

gl.bindBuffer(gl.ARRAY_BUFFER, bufferToUpdate);
gl.bindSubData(gl.ARRAY_BUFFER, offset, newData);

任何时候你想呈现你只是

gl.useProgram(someProgram);
gl.bindVertexArray(someVAO)
gl.uniform... // for each uniform
gl.drawXXX

唯一的问题是,如果您尝试对 2 个或更多程序使用相同的顶点数组,则需要确保属性位置与两个程序匹配。您可以通过在顶点着色器 GLSL

中手动分配位置来做到这一点
#version 300 es
layout(location = 0) in vec4 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 texcoord;

或者调用gl.linkProgram之前,你可以像

那样调用gl.bindAttribLocation
gl.bindAttribLocation(someProgram, 0, "position");
gl.bindAttribLocation(someProgram, 1, "normal");
gl.bindAttribLocation(someProgram, 2, "texcoord");
gl.linkProgram(someProgram);

我更喜欢第二种方法,因为它更 D.R.Y. 但第一种方法更常见 我只是猜测,因为 D.R.Y。风格编程也不如非D.R.Y.

常见

如果您使用 twgl 来编译您的程序,您可以传入位置,以便它为您调用 bindAttribLocation

const programOptions = {
  attribLocations: {
    'position': 0,
    'normal':   1,
    'texcoord': 2,
    'color':    3,
  },
};
const programInfo1 = twgl.createProgramInfo(gl, [vs1, fs1], programOptions);
const programInfo2 = twgl.createProgramInfo(gl, [vs2, fs2], programOptions);

至于你的代码,我能看到你的 makeVAO 函数的唯一问题是你没有为任何地方的每个属性存储 buffer 所以你没有简单的方法来调用 gl.bindBuffer(gl.ARRAY_BUFFER, theBufferToUpdate) 当您想尝试更新缓冲区时。否则,乍一看,您的 makeVAO 函数看起来不错。

例如,您可以这样做

const makeVao = (bufferInfos) => {
  let vao = gl.createVertexArray();
  gl.bindVertexArray(vao);

  bufferInfos.forEach((bufferInfo) => {
    const {
        array,
        size,
        index
      } = bufferInfo;
    const buffer = gl.createBuffer();
    bufferInfo.buffer = buffer;         // remember the buffer
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(array), gl.STATIC_DRAW);

    gl.enableVertexAttribArray(index);
    gl.vertexAttribPointer(index,
                           size,
                           gl.FLOAT,
                           false,
                           0,
                           0);

  });

  gl.bindVertexArray(null);

  return vao;
};

现在您可以使用

gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfos[0].buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, offset, newData);

更新第一个缓冲区。