How WebGL re-introduced me to JavaScript...
linking the possibilities of WebGL with a peek into some of the darker corners of JavaScript
Not intertwined with Graham, in any way
By day
By night
Basically
Caveat: I approach WebGL from a mathematical, progamming perspective rather than a web design perspective
† the verb form is the only correct use of “math” all other forms are an abomination before Man and God
Never promise
Never deliver
!important
!!important
¡important!
CPU bad (slower) (click to zoom) | GPU good (faster) (mouse-wheel to zoom) |
Refresh page if panel(s) blank |
“WebGL is a royalty-free, cross-platform API that brings OpenGL ES 2.0 to the web as a 3D drawing context within HTML, exposed as low-level Document Object Model interfaces.
“It uses the OpenGL shading language, GLSL ES, and can be cleanly combined with other web content that is layered on top or underneath the 3D content.
“It is ideally suited for dynamic 3D web applications in the JavaScript programming language, and will be fully integrated in leading web browsers.”
Khronos.org
Web standard | |
Cross-device | |
Cross-platform | |
Combination of JavaScript API | |
and GLSL language |
Canvas based | |
GPU access from browser | |
Control via shader programs | |
Rasteriser - 2D and 3D graphics and more |
JavaScriptCreate Context/Canvas Compile GLSL Draw commands Data control GLSLShader program (compiled) Vertex shader transform vertices Fragment (pixel) shader transform pixels |
Your html page will look something this...
<html>
<head>
<!-- place holders for glsl code -->
<script id="vertex" type="x-shader">
</script>
<script id="fragment" type="x-shader">
</script>
</head>
<body>
<!-- canvas to hold webgl context -->
<canvas id="glCanvas">
</body>
</html>
"x-shader" arbitrary name, no special meaning
<script> convenient place to put GLSL source
Obvs, JavaScript can go in separate .js file
Shaders can be loaded with XHR
Simple vertex shader
Simple fragment shader
Get a WebGL context
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
for (var i = 0; i < names.length; ++i) {
try {
gl = canvas.getContext(names[i]);
}
catch (e) { }
if (gl) break;
}
Error check
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(vs));
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(fs));
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
console.log(gl.getProgramInfoLog(program));
gl.useProgram(shaderProgram);
Get source, build, compile, link and use the shader
var v = document.getElementById("vertex").firstChild.nodeValue;
var f = document.getElementById("fragment").firstChild.nodeValue;
var vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, v);
gl.compileShader(vs);
var fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, f);
gl.compileShader(fs);
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vs);
gl.attachShader(shaderProgram, fs);
gl.linkProgram(shaderProgram);
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(vs));
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(fs));
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
console.log(gl.getProgramInfoLog(shaderProgram));
gl.useProgram(shaderProgram);
Get data location
shaderProgram.uColor = gl.getUniformLocation(shaderProgram, "uColor");
shaderProgram.aVertexPosition = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram. aVertexPosition);
Define Geometry
var vertices = new Float32Array([-0.5, 0.5,
0.5, -0.5,
-0.5, -0.5]);
cubeVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
cubeVertexPositionBuffer.itemSize = 2;
cubeVertexPositionBuffer.numItems = vertices.length / cubeVertexPositionBuffer.itemSize;
Send data to GPU and draw
gl.uniform4fv(shaderProgram.uColor, [0.0, 1.0, 0.0, 1.0]);
gl.vertexAttribPointer(shaderProgram.aVertexPosition,
cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.clearColor(0, 0.5, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, cubeVertexPositionBuffer.numItems);
Ta da!
Adding methods / members to object
var obj = { a: 0 };
obj.b = 6;
"Down to" "operator"
var x = 10;
while (x --> 0) // x goes to 0
{
console.log(x);
}
Just spacing
var x = 10;
while ((x--) > 0) // x goes to 0
{
console.log(x);
}
.bind and .call
var imgProcessed = document.createElement("img");
imgProcessed.src = imgMissingSrc;
img.src = imgMissingSrc;
handleImgLoaded.call(img, radius, offset);
imgProcessed.onload = function (drillColour, count, missingData) {
// do stuff
}.bind(imgProcessed, radius, offset);
C-like
Strongly typed
Optimised for geometry
Native support of vectors and matrices (no quaternions)
Built-in geometry functions e.g. cos, sin, dot, cross, reflect
Swizzle:
vec3 v1, v2;
v1[0] = v2.r;
v1.xyz = v2.rgb;
v1.zyx = v2.bbb;
Textures via sampler2D and texture2D (no 1D or 3D textures)
uniform
varying
attributes
Variables initialised
Out of bounds
tamapolis : Javi Agenjo's personal blog
TWGL: A Tiny WebGL Helper Library
WebGL is
• long winded
• complicated
BUT
• scales well
Still, APIs
• hide complexity
• simplifies
Oh so very many...
Two of the best known
Both have plenty of support, big communities and on-line editors of varying utility
Three.js is a bit... unstable
function init() {
var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
var scene = new THREE.Scene();
var texture = new THREE.TextureLoader().load('crate.jpg', function(texture) {
texture.needsUpdate = true
});
var geometry = new THREE.BoxGeometry(200, 200, 200);
var material = new THREE.MeshBasicMaterial({ map: texture });
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var render = function () {
requestAnimationFrame(render);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
};
render();
}
function init() {
var canvas = document.getElementById('renderCanvas');
var engine = new BABYLON.Engine(canvas, true);
var scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0, 0, 0.0, 0.0);
var camera = new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 1, -400), scene);
camera.attachControl(canvas, false);
var light = new BABYLON.HemisphericLight("hemi", new BABYLON.Vector3(0, 1, 0), scene);
light.groundColor = new BABYLON.Color3(0.4, 0.4, 0.4);
var mesh = BABYLON.Mesh.CreateBox("mesh", 110, scene);
var material = new BABYLON.StandardMaterial("texture", scene);
material.diffuseTexture = new BABYLON.Texture("crate.jpg", scene);
mesh.material = material;
var render = function () {
requestAnimationFrame(render);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
scene.render();
};
render();
}
SIMD
void main() {
vUv = uv;
float mag = max(length(position), uRadius);
if(length(position) > uRadius) {
vec3 t = normalize(position);
t *= uRadius;
gl_Position = projectionMatrix * modelViewMatrix * vec4( t, 1.0 );
} else {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
}
Avoid if's that use internal state
void main() {
vUv = uv;
float mag = max(length(position), uRadius);
vec3 t = max(normalize(position) * uRadius, position);
gl_Position = projectionMatrix * modelViewMatrix
* vec4( mag * normalize(position), 1.0 );
}
Built in to FireFox
CSS
body{
margin: 0px;
background-color: #000;
}
#sun {
width-segments: 64;
height-segments: 64;
vertex-shader: url(../../shaders/sun.vs);
fragment-shader: url(../../shaders/sun.fs);
shader-uniforms: time f 0
texture1 t url(../../images/cloud.png)
texture2 t url(../../images/lavatile.jpg);
}
@-webkit-keyframes kfRotateY {
from {
-webkit-transform: rotateY(0deg);
}
to {
-webkit-transform: rotateY(360deg);
}
}
.animRotateY {
-webkit-animation-duration: 90s;
-webkit-animation-name: kfRotateY;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function:linear;
}
JavaScript
var theSun = null;
var startTime = Date.now();
window.addEventListener('load', function(){
glam.ready();
theSun = document.getElementById("sun");
run();
},
false);
function run() {
requestAnimationFrame(run);
if (theSun.material) {
var now = Date.now();
var elapsed = (now - startTime) / 1000;
theSun.material.uniforms.time.value = elapsed;
}
}
HTML
HTML
WebGL 2
• experimental
• 3D texutres
• profiling and debugging
WebVR (Mozilla)
• expose VR devices, e.g. Oculus Rift, to web apps
Aframe (Mozilla)
• open-source framework for 3D and VR on the web
[email protected] | |
@CarlBateman | |
meetup.com/WebGL-Workshop-London | |
linkedin.com/in/dcbateman |