| // Inspired by https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_shaders_to_apply_color_in_WebGL |
| |
| function getCanvasDataURL(canvas) { |
| main(canvas); |
| return canvas.toDataURL(); |
| } |
| |
| function createMatrix() { |
| let out = new Float32Array(16); |
| out[0] = 1; |
| out[5] = 1; |
| out[10] = 1; |
| out[15] = 1; |
| return out; |
| } |
| |
| |
| function drawScene(gl, programInfo, buffers) { |
| gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque |
| gl.clearDepth(1.0); // Clear everything |
| gl.enable(gl.DEPTH_TEST); // Enable depth testing |
| gl.depthFunc(gl.LEQUAL); // Near things obscure far things |
| |
| // Clear the canvas before we start drawing on it. |
| |
| gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| |
| // Create a perspective matrix, a special matrix that is |
| // used to simulate the distortion of perspective in a camera. |
| // Our field of view is 45 degrees, with a width/height |
| // ratio that matches the display size of the canvas |
| // and we only want to see objects between 0.1 units |
| // and 100 units away from the camera. |
| |
| const fieldOfView = (45 * Math.PI) / 180; // in radians |
| const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; |
| const zNear = 0.1; |
| const zFar = 100.0; |
| const projectionMatrix = (() => { |
| let out = createMatrix(); |
| const f = 1.0 / Math.tan(fieldOfView / 2); |
| out[0] = f / aspect; |
| out[1] = 0; |
| out[2] = 0; |
| out[3] = 0; |
| out[4] = 0; |
| out[5] = f; |
| out[6] = 0; |
| out[7] = 0; |
| out[8] = 0; |
| out[9] = 0; |
| out[11] = -1; |
| out[12] = 0; |
| out[13] = 0; |
| out[15] = 0; |
| if (zFar != null && zFar !== Infinity) { |
| const nf = 1 / (zNear - zFar); |
| out[10] = (zFar + zNear) * nf; |
| out[14] = 2 * zFar * zNear * nf; |
| } else { |
| out[10] = -1; |
| out[14] = -2 * zNear; |
| } |
| return out; |
| })(); |
| |
| // Set the drawing position to the "identity" point, which is |
| // the center of the scene. |
| // Now move the drawing position a bit to where we want to |
| // start drawing the square. |
| // Inspired by https://github.com/toji/gl-matrix/blob/master/src/mat4.js |
| const modelViewMatrix = (() => { |
| let x = -0.0; |
| let y = 0.0; |
| let z = -6.0; |
| let out = createMatrix(); |
| |
| out[12] = out[0] * x + out[4] * y + out[8] * z + out[12]; |
| out[13] = out[1] * x + out[5] * y + out[9] * z + out[13]; |
| out[14] = out[2] * x + out[6] * y + out[10] * z + out[14]; |
| out[15] = out[3] * x + out[7] * y + out[11] * z + out[15]; |
| return out; |
| })(); |
| |
| // Tell WebGL how to pull out the positions from the position |
| // buffer into the vertexPosition attribute. |
| setPositionAttribute(gl, buffers, programInfo); |
| setColorAttribute(gl, buffers, programInfo); |
| |
| // Tell WebGL to use our program when drawing |
| gl.useProgram(programInfo.program); |
| |
| // Set the shader uniforms |
| gl.uniformMatrix4fv( |
| programInfo.uniformLocations.projectionMatrix, |
| false, |
| projectionMatrix |
| ); |
| gl.uniformMatrix4fv( |
| programInfo.uniformLocations.modelViewMatrix, |
| false, |
| modelViewMatrix |
| ); |
| |
| { |
| const offset = 0; |
| const vertexCount = 4; |
| gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount); |
| } |
| } |
| |
| // Tell WebGL how to pull out the positions from the position |
| // buffer into the vertexPosition attribute. |
| function setPositionAttribute(gl, buffers, programInfo) { |
| const numComponents = 2; // pull out 2 values per iteration |
| const type = gl.FLOAT; // the data in the buffer is 32bit floats |
| const normalize = false; // don't normalize |
| const stride = 0; // how many bytes to get from one set of values to the next |
| // 0 = use type and numComponents above |
| const offset = 0; // how many bytes inside the buffer to start from gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); |
| gl.vertexAttribPointer( |
| programInfo.attribLocations.vertexPosition, |
| numComponents, |
| type, |
| normalize, |
| stride, |
| offset |
| ); |
| gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition); |
| } |
| |
| // Tell WebGL how to pull out the colors from the color buffer |
| // into the vertexColor attribute. |
| function setColorAttribute(gl, buffers, programInfo) { |
| const numComponents = 4; |
| const type = gl.FLOAT; |
| const normalize = false; |
| const stride = 0; |
| const offset = 0; |
| gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color); |
| gl.vertexAttribPointer( |
| programInfo.attribLocations.vertexColor, |
| numComponents, |
| type, |
| normalize, |
| stride, |
| offset |
| ); |
| gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor); |
| } |
| |
| function initBuffers(gl) { |
| const positionBuffer = initPositionBuffer(gl); |
| const colorBuffer = initColorBuffer(gl); |
| |
| return { |
| position: positionBuffer, |
| color: colorBuffer, |
| }; |
| } |
| |
| function initPositionBuffer(gl) { |
| // Create a buffer for the square's positions. |
| const positionBuffer = gl.createBuffer(); |
| |
| // Select the positionBuffer as the one to apply buffer |
| // operations to from here out. |
| gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); |
| |
| // Now create an array of positions for the square. |
| const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; |
| |
| // Now pass the list of positions into WebGL to build the |
| // shape. We do this by creating a Float32Array from the |
| // JavaScript array, then use it to fill the current buffer. |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); |
| |
| return positionBuffer; |
| } |
| |
| function initColorBuffer(gl) { |
| const colors = [ |
| 1.0, |
| 1.0, |
| 1.0, |
| 1.0, // white |
| 1.0, |
| 0.0, |
| 0.0, |
| 1.0, // red |
| 0.0, |
| 1.0, |
| 0.0, |
| 1.0, // green |
| 0.0, |
| 0.0, |
| 1.0, |
| 1.0, // blue |
| ]; |
| |
| const colorBuffer = gl.createBuffer(); |
| gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); |
| |
| return colorBuffer; |
| } |
| |
| // |
| // Initialize a shader program, so WebGL knows how to draw our data |
| // |
| function initShaderProgram(gl, vsSource, fsSource) { |
| const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); |
| const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); |
| |
| // Create the shader program |
| |
| const shaderProgram = gl.createProgram(); |
| gl.attachShader(shaderProgram, vertexShader); |
| gl.attachShader(shaderProgram, fragmentShader); |
| gl.linkProgram(shaderProgram); |
| |
| // If creating the shader program failed, alert |
| |
| if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { |
| alert( |
| `Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}` |
| ); |
| return null; |
| } |
| |
| return shaderProgram; |
| } |
| |
| // |
| // creates a shader of the given type, uploads the source and |
| // compiles it. |
| // |
| function loadShader(gl, type, source) { |
| const shader = gl.createShader(type); |
| |
| // Send the source to the shader object |
| |
| gl.shaderSource(shader, source); |
| |
| // Compile the shader program |
| |
| gl.compileShader(shader); |
| |
| // See if it compiled successfully |
| |
| if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { |
| alert(`An error occurred compiling the shaders: ${gl.getShaderInfoLog(shader)}`); |
| gl.deleteShader(shader); |
| return null; |
| } |
| |
| return shader; |
| } |
| |
| function main(canvas) { |
| // Initialize the GL context |
| const gl = canvas.getContext("webgl"); |
| |
| // Only continue if WebGL is available and working |
| if (gl === null) { |
| alert( |
| "Unable to initialize WebGL. Your browser or machine may not support it." |
| ); |
| return; |
| } |
| |
| // Set clear color to black, fully opaque |
| gl.clearColor(0.0, 0.0, 0.0, 1.0); |
| // Clear the color buffer with specified clear color |
| gl.clear(gl.COLOR_BUFFER_BIT); |
| |
| // Vertex shader program |
| const vsSource = ` |
| attribute vec4 aVertexPosition; |
| attribute vec4 aVertexColor; |
| uniform mat4 uModelViewMatrix; |
| uniform mat4 uProjectionMatrix; |
| varying lowp vec4 vColor; |
| void main() { |
| gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; |
| vColor = aVertexColor; |
| } |
| `; |
| |
| const fsSource = ` |
| varying lowp vec4 vColor; |
| void main() { |
| gl_FragColor = vColor; |
| } |
| `; |
| |
| // Initialize a shader program; this is where all the lighting |
| // for the vertices and so forth is established. |
| const shaderProgram = initShaderProgram(gl, vsSource, fsSource); |
| |
| // Collect all the info needed to use the shader program. |
| // Look up which attribute our shader program is using |
| // for aVertexPosition and look up uniform locations. |
| const programInfo = { |
| program: shaderProgram, |
| attribLocations: { |
| vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"), |
| vertexColor: gl.getAttribLocation(shaderProgram, "aVertexColor"), |
| }, |
| uniformLocations: { |
| projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"), |
| modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"), |
| }, |
| }; |
| |
| |
| |
| // Here's where we call the routine that builds all the |
| // objects we'll be drawing. |
| const buffers = initBuffers(gl); |
| |
| // Draw the scene |
| drawScene(gl, programInfo, buffers); |
| } |