Skip to content

Instantly share code, notes, and snippets.

@dptole
Last active August 24, 2020 09:45
Show Gist options
  • Select an option

  • Save dptole/b27defe86ed213d9d2a25aa272f87380 to your computer and use it in GitHub Desktop.

Select an option

Save dptole/b27defe86ed213d9d2a25aa272f87380 to your computer and use it in GitHub Desktop.
WebGL standalone example: 2D square (js) https://jsfiddle.net/jLo12nr5/
<!-- 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>&deg;</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