twgl.js: How to use a packed vertex array via BufferInfo

417 Views Asked by At

I am using twgl.js (http://twgljs.org/) in a webgl project, it's very nice to use, but I want to optimize my code to use packed vertex arrays. However I can't seem to figure out how that works in twgl, I can see there are some options for the stride and offset in it, but no further information how exactly they will behave. I've read the code, but from that I get the feeling that it will not allow me to use a single call to createBufferInfoFromArrays, but require alot more by hand plumbing. Is it possible to setup packed arrays with twgl.js? How?

1

There are 1 best solutions below

7
On BEST ANSWER

The short answer is "Yes, it will require a lot more plumbing" or rather it's up to you to pack the data. You can easily make a your own BufferInfo

var packedBuffer = twgl.createBufferFromTypedArray(
    gl, typedArrayWithPackedData);

var indexBuffer = twgl.createBufferFromTypedArray(
    gl, typedArrayWithIndices, gl.ELEMENT_ARRAY_BUFFER);

var stride = 36; // position + normal + texcoord + rgba8

var handMadeBufferInfo = {
  numElements: 123, // or whatever
  indices: indexBuffer,            // remove if no indices
  elementType: gl.UNSIGNED_SHORT,  // remove if no indices
  attribs: {
    position: { buffer: packedBuffer, numComponents: 3, type: gl.FLOAT, stride: stride, offset: 0, },
    normal:   { buffer: packedBuffer, numComponents: 3, type: gl.FLOAT, stride: stride, offset: 12, },
    texcoord: { buffer: packedBuffer, numComponents: 2, type: gl.FLOAT, stride: stride, offset: 24, },
    vcolor:   { buffer: packedBuffer, numComponents: 4, type: gl.UNSIGNED_BYTE, stride: stride, offset: 32, normalize: true },
  },
};

var gl = document.getElementById("c").getContext("webgl");
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

// there's no easy way to interleave the data unless we use pure binary so 
// lets do it manually.
var position = [
  -1, -1, 0, 
   1, -1, 0,
  -1,  1, 0,
   1,  1, 0,
];
var texcoord = [
   0,  0,
   1,  0,
   0,  1,
   1,  1,
]
var color = [
   255, 0, 0, 255,
   0, 255, 0, 255,
   0, 0, 255, 255,
   255, 255, 255, 255,
];

var stride = 24; // position + texcoord + rgba8

var typedArrayWithPackedData = new ArrayBuffer(stride * 4);
var floatView = new Float32Array(typedArrayWithPackedData);
var uint8View = new Uint8Array(typedArrayWithPackedData);
var floatAdd = 1;
var uint8Add = stride - 4;
var floatOffset = 0;
var uint8Offset = stride - 4;
var positionOffset = 0;
var texcoordOffset = 0;
var colorOffset = 0;
for (var ii = 0; ii < 4; ++ii) {
  floatView[floatOffset++] = position[positionOffset++];
  floatView[floatOffset++] = position[positionOffset++];
  floatView[floatOffset++] = position[positionOffset++];
  floatView[floatOffset++] = texcoord[texcoordOffset++];
  floatView[floatOffset++] = texcoord[texcoordOffset++];
  floatOffset += floatAdd;
  uint8View[uint8Offset++] = color[colorOffset++];
  uint8View[uint8Offset++] = color[colorOffset++];
  uint8View[uint8Offset++] = color[colorOffset++];
  uint8View[uint8Offset++] = color[colorOffset++];
  uint8Offset += uint8Add;
}

var packedBuffer = twgl.createBufferFromTypedArray(
    gl, typedArrayWithPackedData);

var typedArrayWithIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
var indexBuffer = twgl.createBufferFromTypedArray(
    gl, typedArrayWithIndices, gl.ELEMENT_ARRAY_BUFFER);

var handMadeBufferInfo = {
  numElements: 6, // or whatever
  indices: indexBuffer,            // remove if no indices
  elementType: gl.UNSIGNED_SHORT,  // remove if no indices
  attribs: {
    position: { buffer: packedBuffer, numComponents: 3, type: gl.FLOAT, stride: stride, offset: 0, },
    texcoord: { buffer: packedBuffer, numComponents: 2, type: gl.FLOAT, stride: stride, offset: 12, },
    color:    { buffer: packedBuffer, numComponents: 4, type: gl.UNSIGNED_BYTE, stride: stride, offset: 20, normalize: true, },
  },
};

gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

// make a texture with a circle in it
var canvas2d = document.createElement("canvas");
canvas2d.width = 64;
canvas2d.height = 64;
var ctx = canvas2d.getContext("2d");
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2, false);
ctx.fill();


var uniforms = {
  u_texture: twgl.createTexture(gl, { src: canvas2d }),
};

gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, handMadeBufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, handMadeBufferInfo);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<script id="vs" type="notjs">
attribute vec4 position;
attribute vec2 texcoord;
attribute vec4 color;

varying vec2 v_texcoord;
varying vec4 v_color;

void main() {
  v_texcoord = texcoord;
  v_color = color;
  gl_Position = position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texcoord;
varying vec4 v_color;

void main() {
  gl_FragColor = texture2D(u_texture, v_texcoord) * v_color;
}
</script>
<canvas id="c"></canvas>

Note: twgl.createBufferFromTypedArray was only recently exposed in version 0.0.37

It's not really clear how it could be any easier. If you want twgl to pack the data for you it's not clear what the point is. Is it really that much faster to render? Maybe twgl should support Vertex Array Objects instead?

If you packed it yourself you'd have to give it all the same information above anyway just to have twgl transform it into a BufferInfo which doesn't seem like it would save you much time.