Skip to content

Using mat4 attribute in WebGL2

I am trying to pass a 4×4 matrix as an attribute to WebGL2 vertex shader. After following closely this SO answer, I am stuck with wrapping my head around why I can’t successfully render and get a glDrawArrays: attempt to access out of range vertices in attribute 0 error in my console.

It is my understanding that mat4 is represented as 4 times vec4 in WebGL. When I query a_modelMatrix position on the GPU I can see it occupies location from 0 to 3, so this seems okay. I break up the assignments into 4 parts like this:

for (let i = 0; i < 4; ++i) {
  gl.enableVertexAttribArray(a_modelMatrix + i)
  gl.vertexAttribPointer(a_modelMatrix + i, 4, gl.FLOAT, false, 64, i * 16)
}

But still get an error.

Here is my code:

console.clear()

const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl')

document.body.appendChild(canvas)
canvas.width = 500
canvas.height = 500

gl.viewport(0, 0, 500, 500)

const vertexShaderSrc = `
  attribute mat4 a_modelMatrix;
  attribute vec4 a_pos;

  void main () {
    gl_Position = a_modelMatrix * a_pos;
  }
`

const fragmentShaderSrc = `
  precision highp float;

  void main () {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
`

const vShader = makeShader(gl.VERTEX_SHADER, vertexShaderSrc)
const fShader = makeShader(gl.FRAGMENT_SHADER, fragmentShaderSrc)
const program = makeProgram(vShader, fShader)
gl.useProgram(program)


const modelMatrix = mat4.create()
const scale = vec3.create()
vec3.set(scale, 2, 2, 2)
mat4.scale(modelMatrix, modelMatrix, scale)

const a_pos = gl.getAttribLocation(program, 'a_pos')
const a_modelMatrix = gl.getAttribLocation(program, 'a_modelMatrix')

const posBuffer = gl.createBuffer()
const modelMatrixBuffer = gl.createBuffer()

gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -0.1, 0.1, 0.1, 0.1, 0.0, 0.0 ]), gl.STATIC_DRAW)
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_pos)

gl.bindBuffer(gl.ARRAY_BUFFER, modelMatrixBuffer)
gl.bufferData(gl.ARRAY_BUFFER, modelMatrix, gl.STATIC_DRAW)

for (let i = 0; i < 4; ++i) {
  gl.enableVertexAttribArray(a_modelMatrix + i)
  gl.vertexAttribPointer(a_modelMatrix + i, 4, gl.FLOAT, false, 64, i * 16)
}

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

function makeShader (type, src) {
  const shader = gl.createShader(type)
  gl.shaderSource(shader, src)
  gl.compileShader(shader)
  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    return shader
  }
  console.log(gl.getShaderInfoLog(shader))
}

function makeProgram (vShader, fShader) {
  const program = gl.createProgram()
  gl.attachShader(program, vShader)
  gl.attachShader(program, fShader)
  gl.linkProgram(program)
  if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
    return program
  }
  console.log(gl.getProgramInfoLog(shader))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>

Answer

You must specify 1 attribute for each vertex coordinate. The vertices cannot share 1 matrix attribute. Each vertex coordinate must have its own model matrix attribute:

at_array = new Float32Array(16*no_of_vertices)
for (let i = 0; i < no_of_vertices; i ++) 
    mat_array.set(modelMatrix, 16*i)
gl.bufferData(gl.ARRAY_BUFFER, mat_array, gl.STATIC_DRAW)

console.clear()

const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl')

document.body.appendChild(canvas)
canvas.width = 300
canvas.height = 300

gl.viewport(0, 0, 300, 300)

const vertexShaderSrc = `
  attribute mat4 a_modelMatrix;
  attribute vec4 a_pos;

  void main () {
    gl_Position = a_modelMatrix * a_pos;
  }
`

const fragmentShaderSrc = `
  precision highp float;

  void main () {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
`

const vShader = makeShader(gl.VERTEX_SHADER, vertexShaderSrc)
const fShader = makeShader(gl.FRAGMENT_SHADER, fragmentShaderSrc)
const program = makeProgram(vShader, fShader)
gl.useProgram(program)


const modelMatrix = mat4.create()
const scale = vec3.create()
vec3.set(scale, 2, 2, 2)
mat4.scale(modelMatrix, modelMatrix, scale)

const a_pos = gl.getAttribLocation(program, 'a_pos')
const a_modelMatrix = gl.getAttribLocation(program, 'a_modelMatrix')

const posBuffer = gl.createBuffer()
const modelMatrixBuffer = gl.createBuffer()

gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -0.3, 0.3, 0.3, 0.3, 0.0, 0.0 ]), gl.STATIC_DRAW)
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_pos)

gl.bindBuffer(gl.ARRAY_BUFFER, modelMatrixBuffer)
let mat_array = new Float32Array(16*3)
for (let i = 0; i < 3; i ++) mat_array.set(modelMatrix, 16*i)
gl.bufferData(gl.ARRAY_BUFFER, mat_array, gl.STATIC_DRAW)

for (let i = 0; i < 4; ++i) {
  gl.enableVertexAttribArray(a_modelMatrix + i)
  gl.vertexAttribPointer(a_modelMatrix + i, 4, gl.FLOAT, false, 64, i * 16)
}

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

function makeShader (type, src) {
  const shader = gl.createShader(type)
  gl.shaderSource(shader, src)
  gl.compileShader(shader)
  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    return shader
  }
  console.log(gl.getShaderInfoLog(shader))
}

function makeProgram (vShader, fShader) {
  const program = gl.createProgram()
  gl.attachShader(program, vShader)
  gl.attachShader(program, fShader)
  gl.linkProgram(program)
  if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
    return program
  }
  console.log(gl.getProgramInfoLog(shader))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>

If you use WebGL 1.0, I recommend to use a Uniform variable instead of the matrix attribute. A uniform is a global Shader variable:

attribute vec4 a_pos;
uniform mat4 u_modelMatrix;

void main () {
    gl_Position = u_modelMatrix * a_pos;
} 
const u_modelMatrix = gl.getUniformLocation(program, 'u_modelMatrix')

// [...]

gl.uniformMatrix4fv(u_modelMatrix, false, modelMatrix)

console.clear()

const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl')

document.body.appendChild(canvas)
canvas.width = 300
canvas.height = 300

gl.viewport(0, 0, 300, 300)

const vertexShaderSrc = `
  attribute vec4 a_pos;
  uniform mat4 u_modelMatrix;

  void main () {
    gl_Position = u_modelMatrix * a_pos;
  }
`

const fragmentShaderSrc = `
  precision highp float;

  void main () {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
`

const vShader = makeShader(gl.VERTEX_SHADER, vertexShaderSrc)
const fShader = makeShader(gl.FRAGMENT_SHADER, fragmentShaderSrc)
const program = makeProgram(vShader, fShader)
gl.useProgram(program)


const modelMatrix = mat4.create()
const scale = vec3.create()
vec3.set(scale, 2, 2, 2)
mat4.scale(modelMatrix, modelMatrix, scale)

const a_pos = gl.getAttribLocation(program, 'a_pos')
const u_modelMatrix = gl.getUniformLocation(program, 'u_modelMatrix')

const posBuffer = gl.createBuffer()
const modelMatrixBuffer = gl.createBuffer()

gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -0.3, 0.3, 0.3, 0.3, 0.0, 0.0 ]), gl.STATIC_DRAW)
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_pos)

gl.uniformMatrix4fv(u_modelMatrix, false, modelMatrix)

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

function makeShader (type, src) {
  const shader = gl.createShader(type)
  gl.shaderSource(shader, src)
  gl.compileShader(shader)
  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    return shader
  }
  console.log(gl.getShaderInfoLog(shader))
}

function makeProgram (vShader, fShader) {
  const program = gl.createProgram()
  gl.attachShader(program, vShader)
  gl.attachShader(program, fShader)
  gl.linkProgram(program)
  if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
    return program
  }
  console.log(gl.getProgramInfoLog(shader))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>

However, If you create a WebGL 2.0 context:

const gl = canvas.getContext('webgl')

const gl = canvas.getContext('webgl2')

and a Vertex Array Object:

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

You can use Instancing. The matix attribute is an instance attribute:

gl.bindBuffer(gl.ARRAY_BUFFER, modelMatrixBuffer)
gl.bufferData(gl.ARRAY_BUFFER, modelMatrix, gl.STATIC_DRAW)

for (let i = 0; i < 4; ++i) {
  gl.enableVertexAttribArray(a_modelMatrix + i)
  gl.vertexAttribPointer(a_modelMatrix + i, 4, gl.FLOAT, false, 64, i * 16)
  gl.vertexAttribDivisor(a_modelMatrix + i, 1)
}

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

gl.drawArraysInstanced(gl.LINE_LOOP, 0, 3, 1)

console.clear()

const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl2')

document.body.appendChild(canvas)
canvas.width = 300
canvas.height = 300

gl.viewport(0, 0, 300, 300)

const vertexShaderSrc = `
  attribute mat4 a_modelMatrix;
  attribute vec4 a_pos;

  void main () {
    gl_Position = a_modelMatrix * a_pos;
  }
`

const fragmentShaderSrc = `
  precision highp float;

  void main () {
    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
`

const vShader = makeShader(gl.VERTEX_SHADER, vertexShaderSrc)
const fShader = makeShader(gl.FRAGMENT_SHADER, fragmentShaderSrc)
const program = makeProgram(vShader, fShader)
gl.useProgram(program)


const modelMatrix = mat4.create()
const scale = vec3.create()
vec3.set(scale, 2, 2, 2)
mat4.scale(modelMatrix, modelMatrix, scale)

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

const a_pos = gl.getAttribLocation(program, 'a_pos')
const a_modelMatrix = gl.getAttribLocation(program, 'a_modelMatrix')

const posBuffer = gl.createBuffer()
const modelMatrixBuffer = gl.createBuffer()

gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -0.3, 0.3, 0.3, 0.3, 0.0, 0.0 ]), gl.STATIC_DRAW)
gl.vertexAttribPointer(a_pos, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_pos)

gl.bindBuffer(gl.ARRAY_BUFFER, modelMatrixBuffer)
gl.bufferData(gl.ARRAY_BUFFER, modelMatrix, gl.STATIC_DRAW)

for (let i = 0; i < 4; ++i) {
  gl.enableVertexAttribArray(a_modelMatrix + i)
  gl.vertexAttribPointer(a_modelMatrix + i, 4, gl.FLOAT, false, 64, i * 16)
  gl.vertexAttribDivisor(a_modelMatrix + i, 1)
}

//gl.drawArrays(gl.LINE_LOOP, 0, 3)
gl.drawArraysInstanced(gl.LINE_LOOP, 0, 3, 1)

function makeShader (type, src) {
  const shader = gl.createShader(type)
  gl.shaderSource(shader, src)
  gl.compileShader(shader)
  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    return shader
  }
  console.log(gl.getShaderInfoLog(shader))
}

function makeProgram (vShader, fShader) {
  const program = gl.createProgram()
  gl.attachShader(program, vShader)
  gl.attachShader(program, fShader)
  gl.linkProgram(program)
  if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
    return program
  }
  console.log(gl.getProgramInfoLog(shader))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>