On Github jpdurham / WebGL-As-Easy-As-One-Two-Three.js-Slides
Josh Durham
In here a vertex is a set of attributes such as its location in space, as well as its color, normal, texture coordinates, amongst others. The inputs for this stage are the individual vertices attributes. Some of the operations performed by the fixed functionality at this stage are:
The inputs for this stage are the transformed vertices, as well as connectivity information. This latter piece of data tells the pipeline how the vertices connect to form a primitive. It is in here that primitives are assembled.
This stage is also responsible for clipping operations against the view frustum, and back face culling.
Rasterization determines the fragments, and pixel positions of the primitive. A fragment in this context is a piece of data that will be used to update a pixel in the frame buffer at a specific location. A fragment contains not only color, but also normals and texture coordinates, amongst other possible attributes, that are used to compute the new pixel's color. The output of this stage is twofold:
Interpolated fragment information is the input of this stage. A color has already been computed in the previous stage through interpolation, and in here it can be combined with a texel (texture element) for example. Texture coordinates have also been interpolated in the previous stage. Fog is also applied at this stage. The common end result of this stage per fragment is a color value and a depth for the fragment.
The inputs of this stage are:
The last stage of the pipeline performs a series of tests on the fragment, namely:
If successful the fragment information is then used to update the pixel's value according to the current blend mode. Notice that blending occurs only at this stage because the Fragment Texturing and Coloring stage has no access to the frame buffer. The frame buffer is only accessible at this stage.
For more details, see openglinsights.csom.
function webGLStart() { var canvas = document.getElementById("lesson04-canvas"); initGL(canvas); initShaders() initBuffers(); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.DEPTH_TEST); tick(); }
var gl; function initGL(canvas) { try { gl = canvas.getContext("experimental-webgl"); gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; } catch (e) { } if (!gl) { alert("Could not initialise WebGL, sorry :-("); } }
function getShader(gl, id) { var shaderScript = document.getElementById(id); if (!shaderScript) { return null; } var str = ""; var k = shaderScript.firstChild; while (k) { if (k.nodeType == 3) { str += k.textContent; } k = k.nextSibling; } var shader; if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); } else { return null; } gl.shaderSource(shader, str); gl.compileShader(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(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Could not initialise shaders"); } gl.useProgram(shaderProgram); shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); }
var mvMatrix = mat4.create(); var mvMatrixStack = []; var pMatrix = mat4.create(); function mvPushMatrix() { var copy = mat4.create(); mat4.set(mvMatrix, copy); mvMatrixStack.push(copy); } function mvPopMatrix() { if (mvMatrixStack.length == 0) { throw "Invalid popMatrix!"; } mvMatrix = mvMatrixStack.pop(); } function setMatrixUniforms() { gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix); gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix); } function degToRad(degrees) { return degrees * Math.PI / 180; }
var pyramidVertexPositionBuffer; var pyramidVertexColorBuffer; var cubeVertexPositionBuffer; var cubeVertexColorBuffer; var cubeVertexIndexBuffer;
function initBuffers() { pyramidVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer); var vertices = [ // Front face 0.0, 1.0, 0.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // Right face 0.0, 1.0, 0.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, // Back face 0.0, 1.0, 0.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, // Left face 0.0, 1.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); pyramidVertexPositionBuffer.itemSize = 3; pyramidVertexPositionBuffer.numItems = 12;
pyramidVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer); var colors = [ // Front face 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // Right face 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, // Back face 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // Left face 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); pyramidVertexColorBuffer.itemSize = 4; pyramidVertexColorBuffer.numItems = 12;
cubeVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); vertices = [ // Front face -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, // Back face -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // Top face -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, // Bottom face -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // Right face 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, // Left face -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.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); cubeVertexPositionBuffer.itemSize = 3; cubeVertexPositionBuffer.numItems = 24;
cubeVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer); colors = [ [1.0, 0.0, 0.0, 1.0], // Front face [1.0, 1.0, 0.0, 1.0], // Back face [0.0, 1.0, 0.0, 1.0], // Top face [1.0, 0.5, 0.5, 1.0], // Bottom face [1.0, 0.0, 1.0, 1.0], // Right face [0.0, 0.0, 1.0, 1.0] // Left face ]; var unpackedColors = []; for (var i in colors) { var color = colors[i]; for (var j=0; j < 4; j++) { unpackedColors = unpackedColors.concat(color); } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(unpackedColors), gl.STATIC_DRAW); cubeVertexColorBuffer.itemSize = 4; cubeVertexColorBuffer.numItems = 24;
cubeVertexIndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); var cubeVertexIndices = [ 0, 1, 2, 0, 2, 3, // Front face 4, 5, 6, 4, 6, 7, // Back face 8, 9, 10, 8, 10, 11, // Top face 12, 13, 14, 12, 14, 15, // Bottom face 16, 17, 18, 16, 18, 19, // Right face 20, 21, 22, 20, 22, 23 // Left face ]; gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW); cubeVertexIndexBuffer.itemSize = 1; cubeVertexIndexBuffer.numItems = 36; }
var rPyramid = 0; var rCube = 0;
function drawScene() { gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix); mat4.identity(mvMatrix); mat4.translate(mvMatrix, [-1.5, 0.0, -8.0]); mvPushMatrix(); mat4.rotate(mvMatrix, degToRad(rPyramid), [0, 1, 0]); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, pyramidVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, pyramidVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms(); gl.drawArrays(gl.TRIANGLES, 0, pyramidVertexPositionBuffer.numItems); mvPopMatrix(); mat4.translate(mvMatrix, [3.0, 0.0, 0.0]); mvPushMatrix(); mat4.rotate(mvMatrix, degToRad(rCube), [1, 1, 1]); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); mvPopMatrix(); }
var lastTime = 0; function animate() { var timeNow = new Date().getTime(); if (lastTime != 0) { var elapsed = timeNow - lastTime; rPyramid += (90 * elapsed) / 1000.0; rCube -= (75 * elapsed) / 1000.0; } lastTime = timeNow; } function tick() { requestAnimFrame(tick); drawScene(); animate(); }
//vertex shader attribute vec3 aVertexPosition; attribute vec4 aVertexColor; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; varying vec4 vColor; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vColor = aVertexColor; }
//fragment shader precision mediump float; varying vec4 vColor; void main(void) { gl_FragColor = vColor; }
There has to be a better way...
3D Javascript Library
Renderers: WebGL, <canvas>, <svg>, CSS3D / DOM, and more
Scenes, Cameras, Geometry, 3D Model Loaders, Lights, Materials,Shaders, Particles, Animation, Math Utilities
<html> <head> <title>Basic Three.js App</title> <style> html, body { margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <script src="js/three.min.js"></script> <script> // Javascript will go here. </script> </body> </html>
<script> var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 ); var renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); var geometry = new THREE.BoxGeometry( 1, 1, 1 ); // var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); var material = new THREE.MeshNormalMaterial() var cube = new THREE.Mesh( geometry, material ); scene.add( cube ); camera.position.z = 5; var render = function () { requestAnimationFrame( render ); cube.rotation.x += 0.1; cube.rotation.y += 0.1; renderer.render(scene, camera); }; render(); </script>
mesh.position.x = 0
mesh.position.x = -100
mesh.scale.set(2,2,2)
mesh.rotation.y = Math.PI / 4
mesh.rotation.y = Math.PI * 5 / 4
mesh.rotation.y = THREE.Math.degToRad(45);
mesh.position.x = Math.cos( time ); mesh.position.y = Math.sin( time );
cam = new THREE.PerspectiveCamera( fov, aspect, near, far )
cam = new THREE.PerspectiveCamera( fov, aspect, near, far )
cam = new THREE.PerspectiveCamera( fov, aspect, near, far )
camera.fov = 15
camera.fov = 60
camera.far = 1000
camera.far = 3000
camera = new THREE.OrthographicCamera( left, right, top, bottom, near, far );
/three.js/examples/js/controls/OrbitControls.js
<script src="path/to/OrbitControls.js"></script> controls = new THREE.OrbitControls( camera ); function render() { requestAnimationFrame( render ); controls.update(); renderer.render( scene, camera ); }
controls.enablePan = false; controls.enableZoom = false; controls.enableRotate = false; controls.minDistance controls.maxDistance controls.minPolarAngle controls.maxPolarAngle
var geo = new THREE.BoxGeometry( width, height, depth );
var geo = new THREE.SphereGeometry( 60, 24, 16 );
var geo = new THREE.CylinderGeometry( ... );
var geo = new THREE.TorusGeometry( ... );
var material = new THREE.MeshBasicMaterial({ ... });
var material = new THREE.MeshLambertMaterial({ ... });
var material = new THREE.MeshPhongMaterial({ ... });
var material = new THREE.MeshNormalMaterial({ ... });
var material = new THREE.MeshNormalMaterial({ ... });
shading: THREE.SmoothShading
shading: THREE.FlatShading
shading: THREE.FlatShading // face normals
shading: THREE.FlatShading // face normals
shading: THREE.SmoothShading // vertex normals
color: 0xaaaaaa
color: 0x3794cf
shininess: 40
shininess: 80
wireframe: true
transparent: true, opacity: 0.5
var loader = new THREE.TextureLoader(); var texture = loader.load("color-map.jpg");
map: texture
normalMap: texture
specularMap: texture
map: colorMap, specularMap: specMap, normalMap: normalMap
var material = new THREE.MeshPhongMaterial({ color: 0xaaaaaa, specular: 0x333333, shininess: 15, map: colorMap, specularMap: specMap, normalMap: normalMap });
light = new THREE.DirectionalLight( 0xdddddd, 0.8 );
light.position.set( -80, 80, 80 );
light.position.x = 80;
light.target.position = 160;
light.position.x = -80;
light = new THREE.DirectionalLight( 0xdddddd, 0.8 );
light = new THREE.DirectionalLight( 0xb4e7f2, 0.8 );
light = new THREE.DirectionalLight( 0xb4e7f2, 0.2 );
light = new THREE.DirectionalLight( 0xb4e7f2, 1.5 );
light = new THREE.DirectionalLight( 0xb4e7f2, 0.8 );
light = new THREE.PointLight( 0xb4e7f2, 0.8 );
light = new THREE.PointLight( 0xb4e7f2, 0.8 );
light = new THREE.SpotLight( 0xb4e7f2, 0.8 );
light.angle = Math.PI / 9;
light.angle = Math.PI / 5;
light.penumbra = 0.1;
light.penumbra = 0;
light.penumbra = 0.2;
light = new THREE.AmbientLight( 0x444444 );
light = new THREE.AmbientLight( 0x000000 );
light = new THREE.AmbientLight( 0x444444 );
OBJ to JSON converter python tool /three.js/utils/converters/obj/convert_obj_three.py
python convert_obj_three.py -i teapot.obj -o teapot.js
var loader = new THREE.JSONLoader(); loader.load("teapot.js", function( geometry, materials ) { material = new THREE.MultiMaterial( materials ); mesh = new THREE.Mesh( geometry, material ); scene.add( mesh ); });
Physijs makes physics simulations just as easy to run as three.js. In fact, there are just five easy steps that must be taken to make a 3D scene come alive
Physijs is built on top of ammo.js (although there is also a cannon.js branch) and runs the physics simulation in a separate thread (via web worker) to avoid impacting in your application's performance and taking up your 3D rendering time
Slides and code available at https://jpdurham.github.io/presentations
Josh Durham