Drawing many shapes in WebGL
webgl
Solution
In pseudo code
At init time
- Get a context (gl) from the canvas element.
- for each shader
- create shader
- look up attribute and uniform locations
- for each shape
- initialize buffers with the shape
- for each texture
- create textures and/or fill them with data.
At draw time
- for each shape
- if the last shader used is different than the shader needed for this shape call `gl.useProgram`
- For each attribute needed by shader
- call `gl.enableVertexAttribArray`, `gl.bindBuffer` and `gl.vertexAttribPointer` for each attribute needed by shape with the attribute locations for the current shader.
- For each uniform needed by shader
- call `gl.uniformXXX` with the desired values using the locations for the current shader
- call `gl.drawArrays` or if the data is indexed called `gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferOfIndicesForCurrentShape)` followed by `gl.drawElements`
Common Optimizations
1) Often you don't need to set every uniform. For example if you are drawing 10 shapes with the same shader and that shader takes a viewMatrix or cameraMatrix it's likely that viewMatrix uniform or cameraMatrix uniform is the same for every shape so just set it once.
2) You can often move the calls to `gl.enableVertexAttribArray` to initialization time.
Problem
I was reading tutorials from here. ``` <script class = "WebGL"> var gl; function initGL() { // Get A WebGL context var canvas = document.getElementById("canvas"); gl = getWebGLContext(canvas); if (!gl) { return; } } var positionLocation; var resolutionLocation; var colorLocation; var translationLocation; var rotationLocation; var translation = [50,50]; var rotation = [0, 1]; var angle = 0; function initShaders() { // setup GLSL program vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader"); fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader"); program = createProgram(gl, [vertexShader, fragmentShader]); gl.useProgram(program); // look up where the vertex data needs to go. positionLocation = gl.getAttribLocation(program, "a_position"); // lookup uniforms resolutionLocation = gl.getUniformLocation(program, "u_resolution"); colorLocation = gl.getUniformLocation(program, "u_color"); translationLocation = gl.getUniformLocation(program, "u_translation"); rotationLocation = gl.getUniformLocation(program, "u_rotation"); // set the resolution gl.uniform2f(resolutionLocation, canvas.width, canvas.height); } function initBuffers() { // Create a buffer. var buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // Set Geometry. setGeometry(gl); } function setColor(red, green, blue) { gl.uniform4f(colorLocation, red, green, blue, 1); } // Draw the scene. function drawScene() { // Clear the canvas. gl.clear(gl.COLOR_BUFFER_BIT); // Set the translation. gl.uniform2fv(translationLocation, translation); // Set the rotation. gl.uniform2fv(rotationLocation, rotation); // Draw the geometry. gl.drawArrays(gl.TRIANGLES, 0, 6); } // Fill the buffer with the values that define a letter 'F'. function setGeometry(gl) { /*Assume size1 is declared*/ var vertices = [ -size1/2, -size1/2, -size1/2, size1/2, size1/2, size1/2, size1/2, size1/2, size1/2, -size1/2, -size1/2, -size1/2 ]; gl.bufferData( gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); } function animate() { translation[0] += 0.01; translation[1] += 0.01; angle += 0.01; rotation[0] = Math.cos(angle); rotation[1] = Math.sin(angle); } function tick() { requestAnimFrame(tick); drawScene(); animate(); } function start() { initGL(); initShaders(); initBuffers(); setColor(0.2, 0.5, 0.5); tick(); } </script> <!-- vertex shader --> <script id="2d-vertex-shader" type="x-shader/x-vertex"> attribute vec2 a_position; uniform vec2 u_resolution; uniform vec2 u_translation; uniform vec2 u_rotation; void main() { vec2 rotatedPosition = vec2( a_position.x * u_rotation.y + a_position.y * u_rotation.x, a_position.y * u_rotation.y - a_position.x * u_rotation.x); // Add in the translation. vec2 position = rotatedPosition + u_translation; // convert the position from pixels to 0.0 to 1.0 vec2 zeroToOne = position / u_resolution; // convert from 0->1 to 0->2 vec2 zeroToTwo = zeroToOne * 2.0; // convert from 0->2 to -1->+1 (clipspace) vec2 clipSpace = zeroToTwo - 1.0; gl_Position = vec4(clipSpace, 0, 1); } </script> <!-- fragment shader --> <script id="2d-fragment-shader" type="x-shader/x-fragment"> precision mediump float; uniform vec4 u_color; void main() { gl_FragColor = u_color; } </script> ``` My WebGL program for 1 shape works something like this: - Get a context (gl) from the canvas element. - initialize buffers with the shape of my object - `drawScene()` : a call to `gl.drawArrays()` - If there is animation, an update function, which updates my shape's angles, positions, and then `drawScene()` both in `tick()`, so that it gets called repeatedly. Now when I need more than 1 shape, should I fill the single buffer at once with many objects and then use it to later call `drawScene()` drawing all the objects at once [OR] should I repeated call the `initBuffer` and `drawScene()` from `requestAnimFrame()`.