Skip to content

Instantly share code, notes, and snippets.

@dptole
Last active August 24, 2020 09:45
Show Gist options
  • Save dptole/4579eadc840885874674b6ec11cb1b5c to your computer and use it in GitHub Desktop.
Save dptole/4579eadc840885874674b6ec11cb1b5c to your computer and use it in GitHub Desktop.
WebGL standalone example: 3D cube (js) https://jsfiddle.net/4bdwavo0/
<!-- https://jsfiddle.net/4bdwavo0/ -->
<!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 vec4 a_position;
uniform mat4 u_transform;
attribute vec4 a_color;
varying vec4 v_color;
void main() {
gl_Position = u_transform * a_position;
v_color = gl_Position;
v_color = a_color;
}
</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 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="-500" max="300" value="150">
</div>
<div>TRANSLATE Y: <span id="translate_y_range_value"></span></div>
<div>
<input type="range" id="translate_y_range" min="-500" max="300" value="150">
</div>
<div>TRANSLATE Z: <span id="translate_z_range_value"></span></div>
<div>
<input type="range" id="translate_z_range" min="-500" max="300" value="0">
</div>
</td>
<td style="width: 33%; border: 2px solid cyan; vertical-align: top">
<div>ROTATE X: <span id="rotate_x_range_value"></span>&deg;</div>
<div>
<input type="range" id="rotate_x_range" min="-360" max="360" value="30">
</div>
<div>ROTATE Y: <span id="rotate_y_range_value"></span>&deg;</div>
<div>
<input type="range" id="rotate_y_range" min="-360" max="360" value="30">
</div>
<div>ROTATE Z: <span id="rotate_z_range_value"></span>&deg;</div>
<div>
<input type="range" id="rotate_z_range" min="-360" 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="0">
</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="0">
</div>
<div>PIVOT Z: <span id="rotate_pivot_z_range_value"></span></div>
<div>
<input type="range" id="rotate_pivot_z_range" min="-100" max="100" value="0">
</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="0.5">
</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="0.5">
</div>
<div>SCALE Z: <span id="scale_z_range_value"></span></div>
<div>
<input type="range" id="scale_z_range" min="-3" max="3" step="0.1" value="0.5">
</div>
</td>
</tr>
</tbody>
</table>
</td>
<td rowspan="4" style="vertical-align: top; border: 2px solid green">
<div id="info1block">
<div>Coords:</div>
<pre id="info1"></pre>
</div>
<div id="info2block">
<div>Transformation matrix:</div>
<div>(VERTEX: u_transform)</div>
<pre id="info2"></pre>
</div>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center; border: 2px solid green" id="c"></td>
</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 mat4 = {
translation: function(tx, ty, tz) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1,
]
},
xRotation: function(deg) {
var _1_rad = Math.PI / 180
var c = Math.cos(deg * _1_rad)
var s = Math.sin(deg * _1_rad)
return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1,
]
},
yRotation: function(deg) {
var _1_rad = Math.PI / 180
var c = Math.cos(deg * _1_rad)
var s = Math.sin(deg * _1_rad)
return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1,
]
},
zRotation: function(deg) {
var _1_rad = Math.PI / 180
var c = Math.cos(deg * _1_rad)
var s = Math.sin(deg * _1_rad)
return [
c, s, 0, 0,
-s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]
},
scaling: function(sx, sy, sz) {
return [
sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, sz, 0,
0, 0, 0, 1,
]
},
projection: function(w, h, d) {
return [
2 / w, 0, 0, 0,
0, -2 / h, 0, 0,
0, 0, 2 / d, 0,
-1, 1, 0, 1,
]
},
orthographic: function(left, right, bottom, top, near, far) {
return [
2 / (right - left), 0, 0, 0,
0, 2 / (top - bottom), 0, 0,
0, 0, 2 / (near - far), 0,
(left + right) / (left - right), (bottom + top) / (bottom - top), (near + far) / (near - far), 1,
];
},
multiply: function(a, b) {
var b00 = b[0 * 4 + 0]
var b01 = b[0 * 4 + 1]
var b02 = b[0 * 4 + 2]
var b03 = b[0 * 4 + 3]
var b10 = b[1 * 4 + 0]
var b11 = b[1 * 4 + 1]
var b12 = b[1 * 4 + 2]
var b13 = b[1 * 4 + 3]
var b20 = b[2 * 4 + 0]
var b21 = b[2 * 4 + 1]
var b22 = b[2 * 4 + 2]
var b23 = b[2 * 4 + 3]
var b30 = b[3 * 4 + 0]
var b31 = b[3 * 4 + 1]
var b32 = b[3 * 4 + 2]
var b33 = b[3 * 4 + 3]
var a00 = a[0 * 4 + 0]
var a01 = a[0 * 4 + 1]
var a02 = a[0 * 4 + 2]
var a03 = a[0 * 4 + 3]
var a10 = a[1 * 4 + 0]
var a11 = a[1 * 4 + 1]
var a12 = a[1 * 4 + 2]
var a13 = a[1 * 4 + 3]
var a20 = a[2 * 4 + 0]
var a21 = a[2 * 4 + 1]
var a22 = a[2 * 4 + 2]
var a23 = a[2 * 4 + 3]
var a30 = a[3 * 4 + 0]
var a31 = a[3 * 4 + 1]
var a32 = a[3 * 4 + 2]
var a33 = a[3 * 4 + 3]
return [
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
]
},
multiplyArray: function(mats) {
return mats.slice(1).reduce(function(r, m) {
return mat4.multiply(r, m)
}, mats[0])
},
inspect: function(m4) {
var mm = m4.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] + ' ' + mm[ 9] + ' ' + mm[10] + ' ' + mm[11],
mm[12] + ' ' + mm[13] + ' ' + mm[14] + ' ' + mm[15],
].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.fragment_shader = fragment_shader
program.vertex_shader = vertex_shader
gl.program = program
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 setupEvents(draw) {
function rangeSelector(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)
requestAnimationFrame(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]))
}
}
rangeSelector('#translate_x_range', '#translate_x_range_value')
rangeSelector('#translate_y_range', '#translate_y_range_value')
rangeSelector('#translate_z_range', '#translate_z_range_value')
rangeSelector('#rotate_x_range', '#rotate_x_range_value')
rangeSelector('#rotate_y_range', '#rotate_y_range_value')
rangeSelector('#rotate_z_range', '#rotate_z_range_value')
rangeSelector('#rotate_pivot_x_range', '#rotate_pivot_x_range_value')
rangeSelector('#rotate_pivot_y_range', '#rotate_pivot_y_range_value')
rangeSelector('#rotate_pivot_z_range', '#rotate_pivot_z_range_value')
rangeSelector('#scale_x_range', '#scale_x_range_value')
rangeSelector('#scale_y_range', '#scale_y_range_value')
rangeSelector('#scale_z_range', '#scale_z_range_value')
}
function main() {
return new Promise(function(resolve, reject) {
function draw() {
// Add colors
gl.enableVertexAttribArray(a_color_ref)
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer)
// Tell the attribute how to get data out of color_buffer (ARRAY_BUFFER)
var size = 3 // 3 components per iteration
var type = gl.UNSIGNED_BYTE // the data is 8bit unsigned values
var normalize = true // normalize the data (convert from 0-255 to 0-1)
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_color_ref, size, type, normalize, stride, offset
)
// Add coords
gl.enableVertexAttribArray(a_position_ref)
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer)
// Tell the attribute how to get data out of position_buffer (ARRAY_BUFFER)
var size = 3 // 3 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
)
// Add uniforms
var left = 0
var right = gl.canvas.clientWidth
var bottom = gl.canvas.clientHeight
var top = 0
var near = -400
var far = 400
var ortmat4 = mat4.orthographic(left, right, bottom, top, near, far)
var tramat4 = mat4.translation(
parseFloat(document.querySelector('#translate_x_range').value),
parseFloat(document.querySelector('#translate_y_range').value),
parseFloat(document.querySelector('#translate_z_range').value)
)
var xrotmat4 = mat4.xRotation(
parseFloat(document.querySelector('#rotate_x_range').value)
)
var yrotmat4 = mat4.yRotation(
parseFloat(document.querySelector('#rotate_y_range').value)
)
var zrotmat4 = mat4.zRotation(
parseFloat(document.querySelector('#rotate_z_range').value)
)
var pivmat4 = mat4.translation(
parseFloat(document.querySelector('#rotate_pivot_x_range').value),
parseFloat(document.querySelector('#rotate_pivot_y_range').value),
parseFloat(document.querySelector('#rotate_pivot_z_range').value)
)
var scamat4 = mat4.scaling(
parseFloat(document.querySelector('#scale_x_range').value),
parseFloat(document.querySelector('#scale_y_range').value),
parseFloat(document.querySelector('#scale_z_range').value)
)
var m4 = mat4.multiplyArray([
ortmat4,
tramat4,
xrotmat4,
yrotmat4,
zrotmat4,
pivmat4,
scamat4,
])
var square_coords = [
'X = ' + (tramat4[12] + x).toFixed(2),
'Y = ' + (tramat4[13] + y).toFixed(2),
'Z = ' + (tramat4[14] + z).toFixed(2),
'Width = ' + (scamat4[0] * width).toFixed(2),
'Height = ' + (scamat4[5] * height).toFixed(2),
'Depth = ' + (scamat4[10] * depth).toFixed(2),
]
document.querySelector('#info1').textContent = square_coords.join('\n')
document.querySelector('#info2').textContent = [
'Orthographic',
mat4.inspect(ortmat4),
'Translation',
mat4.inspect(tramat4),
'Rotation X',
mat4.inspect(xrotmat4),
'Rotation Y',
mat4.inspect(yrotmat4),
'Rotation Z',
mat4.inspect(zrotmat4),
'Rotation pivot',
mat4.inspect(pivmat4),
'Scaling',
mat4.inspect(scamat4),
'uniform u_transform',
mat4.inspect(m4)
].join('\n')
gl.uniformMatrix4fv(u_transform_ref, false, m4)
gl.drawArrays(gl.TRIANGLES, 0, 36)
}
var shader = document.querySelector('#shader').textContent
var vertex = document.querySelector('#vertex').textContent
var can = document.createElement('canvas')
can.width = 300
can.height = 300
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 reject()
}
var vertex_shader_result = createShader(gl, gl.VERTEX_SHADER, vertex)
if(!vertex_shader_result.success) {
stderr.textContent = vertex_shader_result.data
return reject()
}
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 reject()
}
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 reject()
}
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.DEPTH_BUFFER_BIT)
gl.enable(gl.CULL_FACE)
gl.enable(gl.DEPTH_TEST)
gl.useProgram(program)
// #################################################################
// References
// Attributes
var a_position_ref = gl.getAttribLocation(program, 'a_position')
var a_color_ref = gl.getAttribLocation(program, 'a_color')
// Uniforms
var u_transform_ref = gl.getUniformLocation(program, 'u_transform')
// #################################################################
// Color buffer
var color_buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer)
var back_color = [
255, 0, 0,
255, 0, 0,
255, 0, 0,
]
var front_color = [
0, 255, 0,
0, 255, 0,
0, 255, 0,
]
var left_color = [
0, 0, 255,
0, 0, 255,
0, 0, 255,
]
var right_color = [
255, 0, 255,
255, 0, 255,
255, 0, 255,
]
var top_color = [
255, 255, 0,
255, 255, 0,
255, 255, 0,
]
var bottom_color = [
0, 255, 255,
0, 255, 255,
0, 255, 255,
]
var colors = [].concat(
back_color,
back_color,
front_color,
front_color,
left_color,
left_color,
right_color,
right_color,
top_color,
top_color,
bottom_color,
bottom_color,
)
gl.bufferData(
gl.ARRAY_BUFFER,
new Uint8Array(colors),
gl.STATIC_DRAW
)
// #################################################################
// Position buffer
var position_buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer)
var x = 0
var y = 0
var z = 0
var width = 100
var height = 100
var depth = 100
var back_tri_1 = [
100, 100, 100,
-100, 100, 100,
100, -100, 100,
]
var back_tri_2 = [
100, -100, 100,
-100, 100, 100,
-100, -100, 100,
]
var front_tri_1 = [
100, 100, -100,
100, -100, -100,
-100, 100, -100,
]
var front_tri_2 = [
-100, 100, -100,
100, -100, -100,
-100, -100, -100,
]
var left_tri_1 = [
-100, 100, 100,
-100, 100, -100,
-100, -100, 100,
]
var left_tri_2 = [
-100, -100, 100,
-100, 100, -100,
-100, -100, -100,
]
var right_tri_1 = [
100, 100, 100,
100, -100, 100,
100, 100, -100,
]
var right_tri_2 = [
100, 100, -100,
100, -100, 100,
100, -100, -100,
]
var top_tri_1 = [
100, 100, 100,
100, 100, -100,
-100, 100, 100,
]
var top_tri_2 = [
-100, 100, 100,
100, 100, -100,
-100, 100, -100,
]
var bottom_tri_1 = [
100, -100, 100,
-100, -100, 100,
100, -100, -100,
]
var bottom_tri_2 = [
100, -100, -100,
-100, -100, 100,
-100, -100, -100,
]
var coords = [].concat(
back_tri_1,
back_tri_2,
front_tri_1,
front_tri_2,
left_tri_1,
left_tri_2,
right_tri_1,
right_tri_2,
top_tri_1,
top_tri_2,
bottom_tri_1,
bottom_tri_2,
)
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(coords),
gl.STATIC_DRAW
)
// #################################################################
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)
draw()
resolve(window.gl = gl)
})
}
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