Last active
July 7, 2016 00:11
-
-
Save agmcleod/3068fc05eb349a433d04a9f6c14ad127 to your computer and use it in GitHub Desktop.
webgl gallery steps
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Start with: | |
* 1. Explain game loop | |
* 2. Explain asset loader | |
* 3. Explain input manager briefly | |
* 4. Go over gallery file quickly (show clip coordinates system) | |
*/ | |
//------ webgl.js | |
export default { | |
initialize(canvas) { | |
this.canvas = canvas; | |
this.gl = canvas.getContext('experimental-webgl'); | |
} | |
}; | |
//------ run.js | |
import Webgl from './webgl'; | |
AssetLoader.loadAtlas(function(atlas) { | |
atlas.buildRegions(); | |
const canvas = document.getElementById('canvas'); | |
AssetLoader.loadShaderCode(function(vert, frag) { | |
// add this line: | |
Webgl.initialize(canvas); | |
InputManager.bind(); | |
const gallery = new Gallery(atlas, canvas.clientWidth, canvas.clientHeight); | |
gallery.buildGalleryImages(); | |
time = Date.now(); | |
requestAnimationFrame(update); | |
}); | |
}); | |
//------ webgl.js | |
// DEPTH_TEST enables the depth buffer, so it will actually draw based on z coordinate. | |
// instead of painters algorithm | |
setupGL() { | |
const gl = this.gl; | |
gl.enable(gl.DEPTH_TEST); | |
gl.enable(gl.BLEND); | |
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); | |
} | |
draw() { | |
const gl = this.gl; | |
gl.viewport(0, 0, this.canvas.clientWidth, this.canvas.clientHeight); | |
gl.clearColor(0, 0, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
} | |
//------- run.js | |
Webgl.initialize(canvas); | |
Webgl.setupGL(); | |
Webgl.draw(); // Before RAF call in update() | |
//------- vert.glsl | |
attribute vec3 aVertexPosition; | |
attribute vec2 aTextureCoord; | |
uniform mat4 uMatrix; | |
varying vec2 vTextureCoord; | |
void main() { | |
gl_Position = uMatrix * vec4(aVertexPosition, 1); | |
vTextureCoord = aTextureCoord; | |
} | |
//--------- frag.glsl | |
precision mediump float; | |
varying vec2 vTextureCoord; | |
uniform sampler2D texture; | |
uniform vec3 color; | |
void main(void) { | |
gl_FragColor = texture2D(texture, vec2(vTextureCoord.s, vTextureCoord.t)) * vec4(color, 1.0); | |
} | |
// show texture coordinates diagram | |
//--------- shader.js | |
// OPEN shader.js | |
// explain file | |
// explain compilation of each text | |
// attaching the shader | |
// checking for errors | |
// use gl.getShaderInfoLog(shaderProgram); to get errors | |
//-------- webgl.js | |
import Shader from './shader'; | |
compileShaders(vert, frag) { | |
const gl = this.gl; | |
this.shader = new Shader(gl, vert, frag); | |
const shader = this.shader; | |
shader.vertexPositionAttribute = gl.getAttribLocation(shader.shaderProgram, "aVertexPosition"); | |
gl.enableVertexAttribArray(shader.vertexPositionAttribute); | |
shader.textureCoordAttribute = gl.getAttribLocation(shader.shaderProgram, "aTextureCoord"); | |
gl.enableVertexAttribArray(shader.textureCoordAttribute); | |
shader.textureUniform = gl.getUniformLocation(shader.shaderProgram, "texture"); | |
shader.matrixUniform = gl.getUniformLocation(shader.shaderProgram, "uMatrix"); | |
shader.colorUniform = gl.getUniformLocation(shader.shaderProgram, "color"); | |
gl.useProgram(shader.shaderProgram); | |
}, | |
//---------- run.js | |
// before setupGL() | |
Webgl.compileShaders(vert, frag); | |
//---------- webgl.js | |
// setup buffers | |
createBuffers() { | |
this.positionBuffer = this.gl.createBuffer(); | |
this.textureBuffer = this.gl.createBuffer(); | |
this.indexBuffer = this.gl.createBuffer(); | |
}, | |
//---------- run.js | |
// call it before setupGL() | |
Webgl.createBuffers(); | |
//---------- webgl.js | |
const MAX_QUADS_PER_BATCH = 100; // arbitrary for now | |
const VERTS_PER_QUAD = 4; | |
const INDICES_PER_QUAD = 6; | |
const FLOATS_PER_VERT = 3; | |
const FLOATS_PER_TEXTURE_VERT = 2; | |
// show vertice count, indexes, etc. | |
initArrays() { | |
this.verts = new Float32Array(MAX_QUADS_PER_BATCH * VERTS_PER_QUAD * FLOATS_PER_VERT); | |
this.textureCoords = new Float32Array(MAX_QUADS_PER_BATCH * VERTS_PER_QUAD * FLOATS_PER_TEXTURE_VERT); | |
this.indices = new Uint16Array(MAX_QUADS_PER_BATCH * INDICES_PER_QUAD); | |
}, | |
resetQuadCount() { | |
this.quadCount = 0; | |
}, | |
// add both at top of setupGL() | |
setupGL(radius, atlas) { | |
this.initArrays(); | |
this.resetQuadCount(); | |
// ... | |
} | |
// setup addRect | |
getVertexIndex() { | |
return this.quadCount * VERTS_PER_QUAD * FLOATS_PER_VERT; | |
}, | |
getTextureIndex() { | |
return this.quadCount * VERTS_PER_QUAD * FLOATS_PER_TEXTURE_VERT; | |
}, | |
addPositionalData(vertexOffset, x1, y1, x2, y2, z) { | |
this.verts[vertexOffset] = x1; | |
this.verts[vertexOffset + 1] = y1; | |
this.verts[vertexOffset + 2] = z; | |
this.verts[vertexOffset + 3] = x2; | |
this.verts[vertexOffset + 4] = y1; | |
this.verts[vertexOffset + 5] = z; | |
this.verts[vertexOffset + 6] = x1; | |
this.verts[vertexOffset + 7] = y2; | |
this.verts[vertexOffset + 8] = z; | |
this.verts[vertexOffset + 9] = x2; | |
this.verts[vertexOffset + 10] = y2; | |
this.verts[vertexOffset + 11] = z; | |
const indiceOffset = this.quadCount * INDICES_PER_QUAD; | |
const vertexIndex = vertexOffset / FLOATS_PER_VERT; | |
this.indices[indiceOffset] = vertexIndex; | |
this.indices[indiceOffset + 1] = vertexIndex + 1; | |
this.indices[indiceOffset + 2] = vertexIndex + 2; | |
this.indices[indiceOffset + 3] = vertexIndex + 2; | |
this.indices[indiceOffset + 4] = vertexIndex + 3; | |
this.indices[indiceOffset + 5] = vertexIndex + 1; | |
}, | |
addRect(x1, y1, x2, y2) { | |
const vertexOffset = this.getVertexIndex(); | |
// z value changes later | |
this.addPositionalData(vertexOffset, x1, y1, x2, y2, -0.05); | |
const tx1 = 0; | |
const ty1 = 0; | |
const tx2 = 1; | |
const ty2 = 1; | |
const textureOffset = this.getTextureIndex(); | |
this.textureCoords[textureOffset] = tx1; | |
this.textureCoords[textureOffset + 1] = ty1; | |
this.textureCoords[textureOffset + 2] = tx2; | |
this.textureCoords[textureOffset + 3] = ty1; | |
this.textureCoords[textureOffset + 4] = tx1; | |
this.textureCoords[textureOffset + 5] = ty2; | |
this.textureCoords[textureOffset + 6] = tx2; | |
this.textureCoords[textureOffset + 7] = ty2; | |
this.quadCount++; | |
}, | |
draw() { | |
// ... | |
this.addRect(-1, -1, 1, 1); | |
this.flushQuads(); | |
} | |
/** | |
STREAM | |
The data store contents will be modified once and used at most a few times. | |
STATIC | |
The data store contents will be modified once and used many times. | |
DYNAMIC | |
The data store contents will be modified repeatedly and used many times. | |
*/ | |
flushQuads() { | |
const gl = this.gl; | |
const shader = this.shader; | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.verts, gl.STATIC_DRAW); | |
gl.vertexAttribPointer(shader.vertexPositionAttribute, FLOATS_PER_VERT, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, this.textureCoords, gl.STATIC_DRAW); | |
gl.vertexAttribPointer(shader.textureCoordAttribute, FLOATS_PER_TEXTURE_VERT, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); | |
gl.drawElements(gl.TRIANGLES, INDICES_PER_QUAD * MAX_QUADS_PER_BATCH, gl.UNSIGNED_SHORT, 0); | |
this.initArrays(); | |
this.resetQuadCount(); | |
}, | |
// glTexImage2D( GLenum target, | |
// GLint level, | |
// GLint internalformat, | |
// GLsizei width, | |
// GLsizei height, | |
// GLint border, | |
// GLenum format, | |
// GLenum type, | |
// const GLvoid * data); | |
// http://docs.gl/es2/glTexParameter | |
initColorTexture() { | |
const gl = this.gl; | |
this.colorTexture = gl.createTexture(); | |
gl.bindTexture(gl.TEXTURE_2D, this.colorTexture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255])); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.bindTexture(gl.TEXTURE_2D, null); | |
}, | |
// TEXTURE_MAG_FILTER are properties for filtering. Blow up image. | |
// https://open.gl/textures | |
// add to bottom of setupGL() | |
this.initColorTexture(); | |
// add before addRect | |
gl.bindTexture(gl.TEXTURE_2D, this.colorTexture); | |
gl.uniform3f(this.shader.colorUniform, 1.0, 1.0, 1.0); | |
// add to setupGL | |
setupGL() { | |
this.mvMatrix = mat4.create(); | |
} | |
// add to draw | |
draw() { | |
mat4.identity(this.mvMatrix); | |
const shader = this.shader; | |
gl.uniformMatrix4fv(shader.matrixUniform, false, this.mvMatrix); | |
} | |
// add to setup | |
this.pMatrix = mat4.create(); | |
// add after clear in draw() | |
draw() { | |
mat4.identity(this.pMatrix); | |
mat4.perspective(this.pMatrix, 45 * Math.PI / 180, this.canvas.clientWidth / this.canvas.clientHeight, 1, 100); | |
mat4.multiply(this.mvMatrix, this.pMatrix, this.mvMatrix); | |
} | |
// put at top | |
const eye = vec3.create(); | |
const center = vec3.create(); | |
const up = vec3.clone([0, 1, 0]); | |
// add after matrix defs | |
setupGL(radius) { | |
this.lookatMatrix = mat4.create(); | |
eye[2] = radius; | |
mat4.lookAt(this.lookatMatrix, eye, center, up); | |
} | |
//-------- run.js | |
const RADIUS = 3; | |
Webgl.setupGL(RADIUS); | |
//-------- webgl.js | |
draw() { | |
// before pMatrix multiply | |
mat4.multiply(this.mvMatrix, this.mvMatrix, this.lookatMatrix); | |
} | |
//-------- run.js | |
Webgl.setupGL(RADIUS, atlas); | |
Webgl.gallery = gallery; | |
//-------- webgl.js | |
setupGL(radius, atlas) { | |
this.atlas = atlas; | |
atlas.texture = this.buildTexture(atlas.image); | |
} | |
buildTexture(image) { | |
const gl = this.gl; | |
const texture = gl.createTexture(); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.bindTexture(gl.TEXTURE_2D, null); | |
return texture; | |
}, | |
// then add | |
addImage(x1, y1, x2, y2, atlas, region) { | |
const vertexOffset = this.getVertexIndex(); | |
this.addPositionalData(vertexOffset, x1, y1, x2, y2, 0); | |
const sx = region.x; | |
const sy = region.y; | |
const sw = region.w; | |
const sh = region.h; | |
const tx1 = sx / atlas.image.width; | |
const ty1 = sy / atlas.image.height; | |
const tx2 = ((sx + sw) / atlas.image.width); | |
const ty2 = ((sy + sh) / atlas.image.height); | |
const textureOffset = this.getTextureIndex(); | |
this.textureCoords[textureOffset] = tx1; | |
this.textureCoords[textureOffset + 1] = ty1; | |
this.textureCoords[textureOffset + 2] = tx2; | |
this.textureCoords[textureOffset + 3] = ty1; | |
this.textureCoords[textureOffset + 4] = tx1; | |
this.textureCoords[textureOffset + 5] = ty2; | |
this.textureCoords[textureOffset + 6] = tx2; | |
this.textureCoords[textureOffset + 7] = ty2; | |
this.quadCount++; | |
}, | |
draw() { | |
const atlas = this.atlas; | |
gl.bindTexture(gl.TEXTURE_2D, atlas.texture); | |
gl.uniform3f(this.shader.colorUniform, 1.0, 1.0, 1.0); | |
for (let i = 0; i < this.gallery.galleryImages.length; i++) { | |
const galleryImage = this.gallery.galleryImages[i]; | |
const x1 = galleryImage.position.x; | |
const y1 = galleryImage.position.y; | |
const region = atlas.regions[galleryImage.regionName]; | |
this.addImage(x1, y1, x1 + galleryImage.width, y1 - galleryImage.height, atlas, region); | |
} | |
this.flushQuads(); | |
} | |
// add after images | |
gl.bindTexture(gl.TEXTURE_2D, this.colorTexture); | |
gl.uniform3f(this.shader.colorUniform, 0.47, 0.84, 0.96); | |
for (let i = 0; i < this.gallery.galleryImages.length; i++) { | |
const galleryImage = this.gallery.galleryImages[i]; | |
const x1 = galleryImage.position.x - 0.02; | |
const y1 = galleryImage.position.y + 0.02; | |
const width = galleryImage.width + 0.04; | |
const height = galleryImage.height + 0.04; | |
this.addRect(x1, y1, x1 + width, y1 - height); | |
} | |
//-------- run.js | |
const SPEED = 0.001; | |
function update() { | |
// ... | |
const cameraOffset = Webgl.cameraOffset; | |
if (InputManager.isKeyPressed(KEYS.W)) { | |
cameraOffset[1] += SPEED * delta; | |
} | |
if (InputManager.isKeyPressed(KEYS.A)) { | |
cameraOffset[0] -= SPEED * delta; | |
} | |
if (InputManager.isKeyPressed(KEYS.S)) { | |
cameraOffset[1] -= SPEED * delta; | |
} | |
if (InputManager.isKeyPressed(KEYS.D)) { | |
cameraOffset[0] += SPEED * delta; | |
} | |
if (InputManager.isKeyPressed(KEYS.Q)) { | |
cameraOffset[2] += SPEED * delta; | |
} | |
if (InputManager.isKeyPressed(KEYS.E)) { | |
cameraOffset[2] -= SPEED * delta; | |
} | |
} | |
// ------- webgl.js | |
setupGL() { | |
this.cameraOffset = vec3.clone([0, 0, 0]); | |
} | |
draw() { | |
// before lookat mul | |
mat4.translate(this.mvMatrix, this.mvMatrix, this.cameraOffset); | |
} | |
// ROTATION | |
// ---------- run.js | |
const ROTATE_SPEED = 0.005; | |
let cameraAngle = 0; | |
update() { | |
if (InputManager.isKeyPressed(KEYS.Z)) { | |
cameraAngle += ROTATE_SPEED * delta; | |
} | |
if (InputManager.isKeyPressed(KEYS.C)) { | |
cameraAngle -= ROTATE_SPEED * delta; | |
} | |
if (InputManager.isKeyPressed(KEYS.Z) || InputManager.isKeyPressed(KEYS.C)) { | |
const x = RADIUS * Math.sin(cameraAngle); | |
const z = RADIUS * Math.cos(cameraAngle); | |
Webgl.orbitCamera(x, z); | |
} | |
} | |
// ------- webgl.js | |
orbitCamera(x, z) { | |
eye[0] = x; | |
eye[2] = z; | |
this.changeLookat = true; | |
}, | |
draw() { | |
// put before mvMatrix stuff | |
if (this.changeLookat) { | |
this.changeLookat = false; | |
mat4.lookAt(this.lookatMatrix, eye, center, up); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment