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

问题描述

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

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

// 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);

然后我就完成了。

除了,我想要第一种方法,我可以在每个渲染上设置顶点属性。那么如何设置通用代码以便能够在渲染时设置着色器属性呢?

标签: javascripthtmlcssnode.jswebgl

解决方案


由于您使用的是顶点数组对象,因此您只需要在初始化时设置属性。属性保留一个指向当前vertexAttribPointer被调用时的缓冲区的指针。请参阅有关属性状态此问题此问题的这篇文章

换句话说,如果你这样做

// 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);

我更喜欢第二种方法,因为它更DRY,但第一种方法更常见我只是猜测,因为 DRY 风格的编程也比非 DRY 少

如果你使用 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);

更新第一个缓冲区。


推荐阅读