Last active
August 24, 2020 09:45
-
-
Save dptole/b27defe86ed213d9d2a25aa272f87380 to your computer and use it in GitHub Desktop.
WebGL standalone example: 2D square (js) https://jsfiddle.net/jLo12nr5/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- https://jsfiddle.net/jLo12nr5/ --> | |
| <!doctype html> | |
| <html> | |
| <head> | |
| <!-- https://webglfundamentals.org/ --> | |
| <style> | |
| input[type="range"] { | |
| width: 100%; | |
| } | |
| </style> | |
| <!-- VERTEX --> | |
| <script id="vertex" type="text/x-webgl-vertex-shader"> | |
| attribute vec2 a_position; | |
| uniform mat3 u_transform; | |
| varying vec4 v_color; | |
| void main() { | |
| gl_Position = vec4((u_transform * vec3(a_position, 1)).xy, 0, 1); | |
| v_color = gl_Position * 0.5 + 0.5; | |
| } | |
| </script> | |
| <!-- SHADER --> | |
| <script id="shader" type="text/x-webgl-fragment-shader"> | |
| precision mediump float; | |
| varying vec4 v_color; | |
| void main() { | |
| gl_FragColor = v_color; | |
| } | |
| </script> | |
| <!-- TABLE CONTENT --> | |
| <script id="table_content" type="text/html"> | |
| <tbody> | |
| <tr> | |
| <td style="text-align: center; border: 2px solid green" id="c"></td> | |
| <td style="vertical-align: top; border: 2px solid green"> | |
| <div id="info1block"> | |
| <div>Coords:</div> | |
| <pre id="info1"></pre> | |
| </div> | |
| <div id="info2block"> | |
| <div>Transformation matrices:</div> | |
| <pre id="info2"></pre> | |
| </div> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td colspan="2" style="padding: 0; margin: 0"> | |
| <table style="width: 100%"> | |
| <tbody> | |
| <tr> | |
| <td style="width: 33%; border: 2px solid brown; vertical-align: top"> | |
| <div>TRANSLATE X: <span id="translate_x_range_value"></span></div> | |
| <div> | |
| <input type="range" id="translate_x_range" min="-100" max="300" value="100"> | |
| </div> | |
| <div>TRANSLATE Y: <span id="translate_y_range_value"></span></div> | |
| <div> | |
| <input type="range" id="translate_y_range" min="-100" max="300" value="100"> | |
| </div> | |
| </td> | |
| <td style="width: 33%; border: 2px solid cyan; vertical-align: top"> | |
| <div>ROTATE: <span id="rotate_range_value"></span>°</div> | |
| <div> | |
| <input type="range" id="rotate_range" min="0" max="360" value="0"> | |
| </div> | |
| <div>PIVOT X: <span id="rotate_pivot_x_range_value"></span></div> | |
| <div> | |
| <input type="range" id="rotate_pivot_x_range" min="-100" max="100" value="-50"> | |
| </div> | |
| <div>PIVOT Y: <span id="rotate_pivot_y_range_value"></span></div> | |
| <div> | |
| <input type="range" id="rotate_pivot_y_range" min="-100" max="100" value="-50"> | |
| </div> | |
| </td> | |
| <td style="width: 33%; border: 2px solid pink; vertical-align: top"> | |
| <div>SCALE X: <span id="scale_x_range_value"></span></div> | |
| <div> | |
| <input type="range" id="scale_x_range" min="-3" max="3" step="0.1" value="1"> | |
| </div> | |
| <div>SCALE Y: <span id="scale_y_range_value"></span></div> | |
| <div> | |
| <input type="range" id="scale_y_range" min="-3" max="3" step="0.1" value="1"> | |
| </div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </tr> | |
| <tr> | |
| <td style="text-align: center; border: 2px solid blue">VERTEX</td> | |
| <td style="text-align: center; border: 2px solid red">SHADER</td> | |
| </tr> | |
| <tr> | |
| <td style="vertical-align: top; border: 2px solid blue" id="v"></td> | |
| <td style="vertical-align: top; border: 2px solid red" id="s"></td> | |
| </tr> | |
| </tbody> | |
| </script> | |
| <script> | |
| var mat3 = { | |
| translation: function(tx, ty) { | |
| return [ | |
| 1, 0, 0, | |
| 0, 1, 0, | |
| tx, ty, 1, | |
| ] | |
| }, | |
| rotation: function(deg) { | |
| var one_rad = Math.PI / 180 | |
| var c = Math.cos(deg * one_rad) | |
| var s = Math.sin(deg * one_rad) | |
| return [ | |
| c, -s, 0, | |
| s, c, 0, | |
| 0, 0, 1, | |
| ] | |
| }, | |
| scaling: function(sx, sy) { | |
| return [ | |
| sx, 0, 0, | |
| 0, sy, 0, | |
| 0, 0, 1, | |
| ] | |
| }, | |
| projection: function(w, h) { | |
| return [ | |
| 2 / w, 0, 0, | |
| 0, -2 / h, 0, | |
| -1, 1, 1 | |
| ]; | |
| }, | |
| multiply: function(a, b) { | |
| var a00 = a[0 * 3 + 0] | |
| var a01 = a[0 * 3 + 1] | |
| var a02 = a[0 * 3 + 2] | |
| var a10 = a[1 * 3 + 0] | |
| var a11 = a[1 * 3 + 1] | |
| var a12 = a[1 * 3 + 2] | |
| var a20 = a[2 * 3 + 0] | |
| var a21 = a[2 * 3 + 1] | |
| var a22 = a[2 * 3 + 2] | |
| var b00 = b[0 * 3 + 0] | |
| var b01 = b[0 * 3 + 1] | |
| var b02 = b[0 * 3 + 2] | |
| var b10 = b[1 * 3 + 0] | |
| var b11 = b[1 * 3 + 1] | |
| var b12 = b[1 * 3 + 2] | |
| var b20 = b[2 * 3 + 0] | |
| var b21 = b[2 * 3 + 1] | |
| var b22 = b[2 * 3 + 2] | |
| return [ | |
| b00 * a00 + b01 * a10 + b02 * a20, | |
| b00 * a01 + b01 * a11 + b02 * a21, | |
| b00 * a02 + b01 * a12 + b02 * a22, | |
| b10 * a00 + b11 * a10 + b12 * a20, | |
| b10 * a01 + b11 * a11 + b12 * a21, | |
| b10 * a02 + b11 * a12 + b12 * a22, | |
| b20 * a00 + b21 * a10 + b22 * a20, | |
| b20 * a01 + b21 * a11 + b22 * a21, | |
| b20 * a02 + b21 * a12 + b22 * a22, | |
| ] | |
| }, | |
| multiplyArray: function(mats) { | |
| return mats.slice(1).reduce(function(r, m) { | |
| return mat3.multiply(r, m) | |
| }, mats[0]) | |
| }, | |
| inspect: function(m3) { | |
| var mm = m3.map(function(a) { | |
| return a.toFixed(2).padStart(6, ' ') | |
| }) | |
| return [ | |
| mm[0] + ' ' + mm[1] + ' ' + mm[2], | |
| mm[3] + ' ' + mm[4] + ' ' + mm[5], | |
| mm[6] + ' ' + mm[7] + ' ' + mm[8], | |
| ].join('\n') | |
| } | |
| } | |
| function createShader(gl, type, source) { | |
| var shader = gl.createShader(type) | |
| gl.shaderSource(shader, source) | |
| gl.compileShader(shader) | |
| var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS) | |
| if(success) { | |
| return { | |
| success: true, | |
| data: shader | |
| } | |
| } | |
| var source_lines = source.split(/\n/) | |
| var source_code = source_lines.map(function(line, i) { | |
| return (i + 1).toString().padStart(source_lines.length.toString().length, '0') + ' ' + line | |
| }) | |
| var output = gl.getShaderInfoLog(shader).replace('\0', '') | |
| gl.deleteShader(shader) | |
| var shader_type = gl.VERTEX_SHADER === type ? 'VERTEX SHADER' : 'FRAGMENT SHADER' | |
| var header_error = `Error creating shader (${shader_type})` | |
| var line = '-'.repeat(50) | |
| return { | |
| success: false, | |
| data: [header_error].concat(line, output, line, source_code).join('\n') | |
| } | |
| } | |
| function createProgram(gl, vertex_shader, fragment_shader) { | |
| var program = gl.createProgram() | |
| gl.attachShader(program, fragment_shader) | |
| gl.attachShader(program, vertex_shader) | |
| gl.linkProgram(program) | |
| var success = gl.getProgramParameter(program, gl.LINK_STATUS) | |
| if(success) { | |
| program.shader = fragment_shader | |
| program.vertex = vertex_shader | |
| return { | |
| success: true, | |
| data: program | |
| } | |
| } | |
| var output = gl.getProgramInfoLog(program).replace('\0', '') | |
| gl.deleteProgram(program) | |
| return { | |
| success: false, | |
| data: ['Error compiling program'].concat(output).join('\n') | |
| } | |
| } | |
| function main() { | |
| function draw() { | |
| var x = 0 | |
| var y = 0 | |
| var width = 100 | |
| var height = 100 | |
| var p1x = x | |
| var p1y = y | |
| var p2x = x + width | |
| var p2y = y | |
| var p3x = x | |
| var p3y = y + height | |
| var p4x = x + width | |
| var p4y = y + height | |
| var coords = [ | |
| // first triangle | |
| p1x, p1y, | |
| p2x, p2y, | |
| p3x, p3y, | |
| // second triangle | |
| p2x, p2y, | |
| p3x, p3y, | |
| p4x, p4y, | |
| ] | |
| gl.bufferData( // a_position | |
| gl.ARRAY_BUFFER, | |
| new Float32Array(coords), | |
| gl.STATIC_DRAW | |
| ) | |
| var promat3 = mat3.projection( | |
| gl.canvas.width, | |
| gl.canvas.height | |
| ) | |
| var tramat3 = mat3.translation( | |
| parseFloat(document.querySelector('#translate_x_range').value), | |
| parseFloat(document.querySelector('#translate_y_range').value) | |
| ) | |
| var rotmat3 = mat3.rotation( | |
| parseFloat(document.querySelector('#rotate_range').value) | |
| ) | |
| var pivmat3 = mat3.translation( | |
| parseFloat(document.querySelector('#rotate_pivot_x_range').value), | |
| parseFloat(document.querySelector('#rotate_pivot_y_range').value) | |
| ) | |
| var scamat3 = mat3.scaling( | |
| parseFloat(document.querySelector('#scale_x_range').value), | |
| parseFloat(document.querySelector('#scale_y_range').value) | |
| ) | |
| var m3 = mat3.multiplyArray([ | |
| promat3, | |
| tramat3, | |
| rotmat3, | |
| pivmat3, | |
| scamat3, | |
| ]) | |
| var square_coords = [ | |
| 'X = ' + (tramat3[6] + x).toFixed(2), | |
| 'Y = ' + (tramat3[7] + y).toFixed(2), | |
| 'Width = ' + (scamat3[0] * width).toFixed(2), | |
| 'Height = ' + (scamat3[4] * height).toFixed(2), | |
| ] | |
| document.querySelector('#info1').textContent = square_coords.join('\n') | |
| document.querySelector('#info2').textContent = [ | |
| 'Projection', | |
| mat3.inspect(promat3), | |
| 'Translation', | |
| mat3.inspect(tramat3), | |
| 'Rotation', | |
| mat3.inspect(rotmat3), | |
| 'Rotation pivot', | |
| mat3.inspect(pivmat3), | |
| 'Scaling', | |
| mat3.inspect(scamat3), | |
| 'uniform u_transform', | |
| mat3.inspect(m3) | |
| ].join('\n') | |
| gl.uniformMatrix3fv(u_transform_ref, false, m3) | |
| gl.drawArrays(gl.TRIANGLES, 0, 6) | |
| } | |
| function setupEvents() { | |
| setupEvents.attachEventToRangeSelector('#translate_x_range', '#translate_x_range_value') | |
| setupEvents.attachEventToRangeSelector('#translate_y_range', '#translate_y_range_value') | |
| setupEvents.attachEventToRangeSelector('#rotate_range', '#rotate_range_value') | |
| setupEvents.attachEventToRangeSelector('#rotate_pivot_x_range', '#rotate_pivot_x_range_value') | |
| setupEvents.attachEventToRangeSelector('#rotate_pivot_y_range', '#rotate_pivot_y_range_value') | |
| setupEvents.attachEventToRangeSelector('#scale_x_range', '#scale_x_range_value') | |
| setupEvents.attachEventToRangeSelector('#scale_y_range', '#scale_y_range_value') | |
| } | |
| setupEvents.attachEventToRangeSelector = function(range_selector, output_selector) { | |
| var evs = ['wheel', 'change', 'mousemove'] | |
| function wrap(f) { | |
| return function(e) { | |
| if(e.type === 'wheel' && e.isTrusted) { | |
| e.preventDefault() | |
| var direction = e.deltaY > 0 ? -1 : 1 | |
| var step = parseFloat(this.step) || 1 | |
| this.value = parseFloat(this.value) + direction * step | |
| } | |
| f.apply(this, arguments) | |
| draw() | |
| } | |
| } | |
| function f(e) { | |
| var s = document.querySelector(output_selector) | |
| if(s && e && e.target) s.textContent = e.target.value | |
| } | |
| var el = document.querySelector(range_selector) | |
| if(el) { | |
| for(var i = 0; i < evs.length; i++) | |
| el.addEventListener(evs[i], wrap(f), {passive: evs[i] !== 'wheel'}) | |
| evs.length && el.dispatchEvent(new Event(evs[0])) | |
| } | |
| } | |
| var shader = document.querySelector('#shader').textContent | |
| var vertex = document.querySelector('#vertex').textContent | |
| var canvas_css_class = 'canvastmp' | |
| var canvas_css_selector = 'canvas.' + canvas_css_class | |
| var can = document.querySelector(canvas_css_selector) | |
| if(can) { | |
| if(can.gl) { | |
| var gl = can.gl | |
| gl.bindBuffer(gl.ARRAY_BUFFER, null) | |
| gl.getExtension('WEBGL_lose_context').loseContext() | |
| if(can.program) { | |
| gl.deleteProgram(can.program) | |
| } | |
| } | |
| can.remove() | |
| } | |
| var can = document.createElement('canvas') | |
| can.width = 300 | |
| can.height = 300 | |
| can.className = canvas_css_class | |
| can.style.backgroundColor = 'white' | |
| can.style.border = '1px solid red' | |
| can.style.width = can.width + 'px' | |
| can.style.height = can.height + 'px' | |
| var gl = can.getContext('webgl') | |
| if(!gl) { | |
| stderr.textContent = 'No WebGL context' | |
| return false | |
| } | |
| var vertex_shader_result = createShader(gl, gl.VERTEX_SHADER, vertex) | |
| if(!vertex_shader_result.success) { | |
| stderr.textContent = vertex_shader_result.data | |
| return false | |
| } | |
| var vertex_shader = vertex_shader_result.data | |
| var fragment_shader_result = createShader(gl, gl.FRAGMENT_SHADER, shader) | |
| if(!fragment_shader_result.success) { | |
| stderr.textContent = fragment_shader_result.data | |
| return false | |
| } | |
| var fragment_shader = fragment_shader_result.data | |
| var program_result = createProgram(gl, vertex_shader, fragment_shader) | |
| if(!program_result.success) { | |
| stderr.textContent = program_result.data | |
| return false | |
| } | |
| var program = program_result.data | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height) | |
| gl.clearColor(0, 0, 0, 0) | |
| gl.clear(gl.COLOR_BUFFER_BIT) | |
| gl.useProgram(program) | |
| // Setting up u_transform | |
| var u_transform_ref = gl.getUniformLocation(program, 'u_transform') | |
| // Setting up a_position | |
| var a_position_ref = gl.getAttribLocation(program, 'a_position') | |
| gl.enableVertexAttribArray(a_position_ref) | |
| gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()) | |
| // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) | |
| var size = 2 // 2 components per iteration | |
| var type = gl.FLOAT // the data is 32bit floats | |
| var normalize = false // don't normalize the data | |
| var stride = 0 // 0 = move forward size * sizeof(type) each iteration to get the next position | |
| var offset = 0 // start at the beginning of the buffer | |
| gl.vertexAttribPointer( | |
| a_position_ref, size, type, normalize, stride, offset | |
| ) | |
| window.program = program | |
| window.gl = gl | |
| can.gl = gl | |
| can.program = program | |
| var t = document.createElement('table') | |
| t.innerHTML = document.querySelector('#table_content').innerHTML | |
| t.style.width = '100%' | |
| document.body.appendChild(t) | |
| t.querySelector('#c').appendChild(can) | |
| var v = document.createElement('pre') | |
| v.style.whiteSpace = 'pre-wrap' | |
| document.querySelector('#v').appendChild(v) | |
| v.textContent = vertex | |
| var s = document.createElement('pre') | |
| s.style.whiteSpace = 'pre-wrap' | |
| document.querySelector('#s').appendChild(s) | |
| s.textContent = shader | |
| setupEvents() | |
| draw() | |
| } | |
| window.addEventListener('DOMContentLoaded', main, {passive: true}) | |
| </script> | |
| </head> | |
| <body> | |
| <pre style="white-space: pre-wrap" id="stderr"></pre> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment