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>
Advertisement
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>