Building 3D Worlds in the Browser
James Milner
@JamesLMilner
jmilner@esriuk.com
loxodrome.io
The web was very different growing up...
The Web has Matured
The standards and technologies have changed and we build such experiences differently
-
Open GL ES 2.0 for the web
-
Allows us to create 2D & 3D graphics
-
JavaScript API
-
Integrates into HTML page via canvas
-
No file formats etc.
-
No markup
Increasingly a common choice is WebGL
Why WebGL?
*NPAPI now disabled in Chrome 45
More Plugins = More Attack Vectors
Plugins are Frustrating
Credit: xkcd.com
Some are Platform Specific
... but many of them were very valuable and served their purpose
A Plugin Free World
Source: Microsoft
HTML5 vs ActionScript
- WebGL is a separate spec from HTML5 but requires canvas support
- WebGL also depends Typed Arrays (ECMAScript 2016) : an array-like view of an underlying binary data buffer
Transitioning to a WebGL World
What About Support?
Sources: caniuse.com/webgl and webglstats.com
"WebGL is already a standard"
Ricardo Cabello (Three.js)
WebGL is a Low Level API
"low-level means that WebGL commands are expressed in terms that map relatively directly to how a GPU actually works" - Robert Nyman
Constructing Things in WebGL
Source: University of New Mexico
Moving Data Around: Buffers
// Vertices for a square
gl.bufferData( gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]),
gl.STATIC_DRAW
);
// Some few lines of code later
gl.drawArrays(gl.TRIANGLES, 0, squareVertexPositionBuffer.numItems);
-
Buffer - a block of memory allocated to storing a specific data
-
Pass vertices using Typed Arrays
-
Stateful and implicit; uses last buffer referenced
Alongside our WebGL JavaScript API we require shaders
Shaders?
-
Shader - "a program that tells a computer how to draw something in a specific and unique way"
-
Vertex Shader - generates scene coordinates
-
Fragment Shader - provide colour for current pixel
Using Shaders
Vertex and Fragment Shaders
- Create Shader
- Set Source
- Compile
- Create Program
- Attach Shaders
- Link Program
- Use Program
A Simplified Pipeline
Source: Mozilla
Introducing GLSL ES
Graphics Library Shader Language
<!-- Drawing a green square -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
// Storage qualifer - type - variable name
void main() {
gl_Position = vec4(a_position, 0, 1); // X, Y, Z, W
// gl_Position is a built in variable
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(0, 1, 0, 1); // R, G, B, A (Green)
// gl_FragColor is a built in variable
}
</script>
Vertex Shaders : Sorry What?
Source: webglfundamentals.org
(Buffer)
(Screen)
Source: webglfundamentals.org
Fragment Shaders: Again Please?
-
Storage qualifiers e.g. attributes, uniforms
-
Stored in a string, a script tag, or separate file
-
Typed, C like language
-
Has vectors and matrices as primitives
-
gl_ prefix dictates built in variables
GLSL? What is this sorcery in my web page?
Hurry up and show me some JavaScript!
<html>
<head>
<title>WebGL - Black and White Square</title>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<script type="text/javascript" src="glMatrix-0.9.5.min.js"></script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
// more info on these matrices at:
http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
</script>
<script type="text/javascript">
var gl;
function initGL(canvas) {
// Initialise the WebGL context on the canvas element
try {
var gl = canvas.getContext("experimental-webgl");
if (!gl) {
gl = canvas.getContext("webgl");
}
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch (e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}
function getShader(gl, id) {
// Get the shader from the script tag
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null; // No shader script with that id
}
var str = ""; //concatenate the whole shader together
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) { //text will have nodeType property will return 3.
str += k.textContent;
}
k = k.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER); // if fragment
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER); // if vertex
} else {
return null;
}
gl.shaderSource(shader, str); // Link type and source
gl.compileShader(shader); // Compile shader
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
shaderProgram = gl.createProgram(); // create shader program
gl.attachShader(shaderProgram, vertexShader); // add vertex shader
gl.attachShader(shaderProgram, fragmentShader); // add vertex shader
gl.linkProgram(shaderProgram); // Link the two to program
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaderProgram); // Use the shader program
// Explicitly say which variable is the vertex position in vertex shader
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); // Enable it
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}
var mvMatrix = mat4.create(); // glMatrix functions
var pMatrix = mat4.create();
function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}
var triangleVertexPositionBuffer;
var squareVertexPositionBuffer;
function initBuffers() {
//Triangle
triangleVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
var vertices = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.1,
1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
triangleVertexPositionBuffer.itemSize = 3;
triangleVertexPositionBuffer.numItems = 3;
//Square
squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
}
function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Clears the color and depth buffers
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
// Field of View = 45 , width-to-height ratio of canvas, min view distance, max view distance
mat4.identity(mvMatrix);
// DRAW THE TRIANGLE
mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
// DRAW THE SQUARE
mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}
function webGLStart() {
var canvas = document.getElementById("lesson01-canvas"); //Get Canvas
initGL(canvas);
initShaders();
initBuffers();
gl.clearColor(0.0, 0.0, 0.0, 1.0); //Black
gl.enable(gl.DEPTH_TEST);
drawScene();
}
</script>
</head>
<body onload="webGLStart();">
<a href="http://learningwebgl.com/blog/?p=28"><< Back to Lesson 1</a><br />
<canvas id="lesson01-canvas" style="border: none;" width="500" height="500"></canvas>
</body>
<!-- Adapted from: http://learningwebgl.com/blog/?p=28 -->
</html>
This is pretty verbose... we have to be explicit so many things!
Frameworks Help us Move Faster
Things We're (Generally) Immediately Concerned With
Pure WebGL
Three.js, Babylon.js, etc.
Vertices
Buffers
Shaders
Dot Products
Cross Products
Camera
Scene
Objects
Lighting
Matrices
Clipspace Coordinates
Pixel Coordinates
Raw WebGL
TWGL
PhiloGL
Three.js
Babylon.js
Scene.js
OSG.js
GLGE
CopperLicht
WebGL Studio
Increasing Abstraction
Enter JavaScript Libraries
"It shouldn't be underestimated how much WebGL adoption has been driven by Three.js."
Let's see some examples
Obviously these are fairly trivial...
It's not just fantasy worlds benefiting from WebGL...
WebGL Powered Vector Map Tiles
WebGL is currently the weapon of choice for 3D Mapping on the web
Why 3D?
Queen Flight Visualisation
3D Edition!
Flight Simulator
Physics Intergration
- Plugins are dying
- WebGL + HTML5 are powerful replacements
- ... but WebGL is low level and verbose
- Frameworks can help (Three.js, Babylon.js)
- We can now build immersive 3D apps
- They are not just limited to video games
- WebGL 2 is in the works...
Thanks!
@JamesLMilner
jmilner@esriuk.com
WebGL Links
Content Links
Building 3D Worlds in the Browser
By James Milner
Building 3D Worlds in the Browser
A talk on building 3D worlds in the browser with WebGL (or more specifically abstraction libraries for WebGL!)
- 1,927