Created
August 26, 2019 22:35
-
-
Save eguneys/feca9e56280f960e82d24451d2c931e9 to your computer and use it in GitHub Desktop.
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
"use strict"; | |
var fVertexShaderSource = `#version 300 es | |
// an attribute is an input (in) to a vertex shader. | |
// It will receive data from a buffer | |
in vec4 a_position; | |
in vec4 a_color; | |
// A matrix to transform the positions by | |
uniform mat4 u_matrix; | |
// a varying the color to the fragment shader | |
out vec4 v_color; | |
// all shaders have a main function | |
void main() { | |
// Multiply the position by the matrix. | |
gl_Position = u_matrix * a_position; | |
// Pass the color to the fragment shader. | |
v_color = a_color; | |
} | |
`; | |
var fFragmentShaderSource = `#version 300 es | |
precision mediump float; | |
// the varied color passed from the vertex shader | |
in vec4 v_color; | |
// we need to declare an output for the fragment shader | |
out vec4 outColor; | |
void main() { | |
outColor = v_color; | |
} | |
`; | |
var textVertexShaderSource = `#version 300 es | |
in vec4 a_position; | |
in vec2 a_texcoord; | |
uniform mat4 u_matrix; | |
out vec2 v_texcoord; | |
void main() { | |
// Multiply the position by the matrix. | |
gl_Position = u_matrix * a_position; | |
// Pass the texcoord to the fragment shader. | |
v_texcoord = a_texcoord; | |
} | |
`; | |
var textFragmentShaderSource = `#version 300 es | |
precision mediump float; | |
// Passed in from the vertex shader. | |
in vec2 v_texcoord; | |
uniform sampler2D u_texture; | |
out vec4 outColor; | |
void main() { | |
outColor = texture(u_texture, v_texcoord); | |
} | |
`; | |
var fontInfo = { | |
letterHeight: 8, | |
spaceWidth: 8, | |
spacing: -1, | |
textureWidth: 64, | |
textureHeight: 40, | |
glyphInfos: { | |
'a': { x: 0, y: 0, width: 8, }, | |
'b': { x: 8, y: 0, width: 8, }, | |
'c': { x: 16, y: 0, width: 8, }, | |
'd': { x: 24, y: 0, width: 8, }, | |
'e': { x: 32, y: 0, width: 8, }, | |
'f': { x: 40, y: 0, width: 8, }, | |
'g': { x: 48, y: 0, width: 8, }, | |
'h': { x: 56, y: 0, width: 8, }, | |
'i': { x: 0, y: 8, width: 8, }, | |
'j': { x: 8, y: 8, width: 8, }, | |
'k': { x: 16, y: 8, width: 8, }, | |
'l': { x: 24, y: 8, width: 8, }, | |
'm': { x: 32, y: 8, width: 8, }, | |
'n': { x: 40, y: 8, width: 8, }, | |
'o': { x: 48, y: 8, width: 8, }, | |
'p': { x: 56, y: 8, width: 8, }, | |
'q': { x: 0, y: 16, width: 8, }, | |
'r': { x: 8, y: 16, width: 8, }, | |
's': { x: 16, y: 16, width: 8, }, | |
't': { x: 24, y: 16, width: 8, }, | |
'u': { x: 32, y: 16, width: 8, }, | |
'v': { x: 40, y: 16, width: 8, }, | |
'w': { x: 48, y: 16, width: 8, }, | |
'x': { x: 56, y: 16, width: 8, }, | |
'y': { x: 0, y: 24, width: 8, }, | |
'z': { x: 8, y: 24, width: 8, }, | |
'0': { x: 16, y: 24, width: 8, }, | |
'1': { x: 24, y: 24, width: 8, }, | |
'2': { x: 32, y: 24, width: 8, }, | |
'3': { x: 40, y: 24, width: 8, }, | |
'4': { x: 48, y: 24, width: 8, }, | |
'5': { x: 56, y: 24, width: 8, }, | |
'6': { x: 0, y: 32, width: 8, }, | |
'7': { x: 8, y: 32, width: 8, }, | |
'8': { x: 16, y: 32, width: 8, }, | |
'9': { x: 24, y: 32, width: 8, }, | |
'-': { x: 32, y: 32, width: 8, }, | |
'*': { x: 40, y: 32, width: 8, }, | |
'!': { x: 48, y: 32, width: 8, }, | |
'?': { x: 56, y: 32, width: 8, }, | |
}, | |
}; | |
function makeVerticesForString(fontInfo, s) { | |
var len = s.length; | |
var numVertices = len * 6; | |
var positions = new Float32Array(numVertices * 2); | |
var texcoords = new Float32Array(numVertices * 2); | |
var offset = 0; | |
var x = 0; | |
var maxX = fontInfo.textureWidth; | |
var maxY = fontInfo.textureHeight; | |
for (var ii = 0; ii < len; ++ii) { | |
var letter = s[ii]; | |
var glyphInfo = fontInfo.glyphInfos[letter]; | |
if (glyphInfo) { | |
var x2 = x + glyphInfo.width; | |
var u1 = glyphInfo.x / maxX; | |
var v1 = (glyphInfo.y + fontInfo.letterHeight - 1) / maxY; | |
var u2 = (glyphInfo.x + glyphInfo.width - 1) / maxX; | |
var v2 = glyphInfo.y / maxY; | |
// 6 vertices per letter | |
positions[offset + 0] = x; | |
positions[offset + 1] = 0; | |
texcoords[offset + 0] = u1; | |
texcoords[offset + 1] = v1; | |
positions[offset + 2] = x2; | |
positions[offset + 3] = 0; | |
texcoords[offset + 2] = u2; | |
texcoords[offset + 3] = v1; | |
positions[offset + 4] = x; | |
positions[offset + 5] = fontInfo.letterHeight; | |
texcoords[offset + 4] = u1; | |
texcoords[offset + 5] = v2; | |
positions[offset + 6] = x; | |
positions[offset + 7] = fontInfo.letterHeight; | |
texcoords[offset + 6] = u1; | |
texcoords[offset + 7] = v2; | |
positions[offset + 8] = x2; | |
positions[offset + 9] = 0; | |
texcoords[offset + 8] = u2; | |
texcoords[offset + 9] = v1; | |
positions[offset + 10] = x2; | |
positions[offset + 11] = fontInfo.letterHeight; | |
texcoords[offset + 10] = u2; | |
texcoords[offset + 11] = v2; | |
x += glyphInfo.width + fontInfo.spacing; | |
offset += 12; | |
} else { | |
// we don't have this character so just advance | |
x += fontInfo.spaceWidth; | |
} | |
} | |
// return ArrayBufferViews for the portion of the TypedArrays | |
// that were actually used. | |
return { | |
arrays: { | |
position: new Float32Array(positions.buffer, 0, offset), | |
texcoord: new Float32Array(texcoords.buffer, 0, offset), | |
}, | |
numVertices: offset / 2, | |
}; | |
} | |
function main() { | |
// Get A WebGL context | |
/** @type {HTMLCanvasElement} */ | |
var canvas = document.getElementById("canvas"); | |
var gl = canvas.getContext("webgl2"); | |
if (!gl) { | |
return; | |
} | |
// tell twgl to match program to a_program and | |
// normal to a_normal etc... | |
twgl.setAttributePrefix("a_"); | |
// setup GLSL programs | |
var fProgramInfo = twgl.createProgramInfo( | |
gl, [fVertexShaderSource, fFragmentShaderSource]); | |
var textProgramInfo = twgl.createProgramInfo( | |
gl, [textVertexShaderSource, textFragmentShaderSource]); | |
// Create data for 'F' | |
var fBufferInfo = twgl.primitives.create3DFBufferInfo(gl); | |
var fVAO = twgl.createVAOFromBufferInfo( | |
gl, fProgramInfo, fBufferInfo); | |
// Maunally create a bufferInfo | |
var textBufferInfo = { | |
attribs: { | |
a_position: { buffer: gl.createBuffer(), numComponents: 2, }, | |
a_texcoord: { buffer: gl.createBuffer(), numComponents: 2, }, | |
}, | |
numElements: 0, | |
}; | |
var textVAO = twgl.createVAOFromBufferInfo( | |
gl, textProgramInfo, textBufferInfo); | |
// Create a texture. | |
var glyphTex = gl.createTexture(); | |
gl.bindTexture(gl.TEXTURE_2D, glyphTex); | |
// Fill the texture with a 1x1 blue pixel. | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, | |
new Uint8Array([0, 0, 255, 255])); | |
// Asynchronously load an image | |
var image = new Image(); | |
image.src = "https://webgl2fundamentals.org/webgl/resources/8x8-font.png"; | |
image.addEventListener('load', function() { | |
// Now that the image has loaded make copy it to the texture. | |
gl.bindTexture(gl.TEXTURE_2D, glyphTex); | |
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
}); | |
var names = [ | |
"anna", // 0 | |
"colin", // 1 | |
"james", // 2 | |
"danny", // 3 | |
"kalin", // 4 | |
"hiro", // 5 | |
"eddie", // 6 | |
"shu", // 7 | |
"brian", // 8 | |
"tami", // 9 | |
"rick", // 10 | |
"gene", // 11 | |
"natalie",// 12, | |
"evan", // 13, | |
"sakura", // 14, | |
"kai", // 15, | |
]; | |
var fUniforms = { | |
u_matrix: m4.identity(), | |
}; | |
var textUniforms = { | |
u_matrix: m4.identity(), | |
u_texture: glyphTex, | |
u_color: [0, 0, 0, 1], // black | |
}; | |
function degToRad(d) { | |
return d * Math.PI / 180; | |
} | |
var translation = [0, 30, 0]; | |
var rotation = [degToRad(190), degToRad(0), degToRad(0)]; | |
var scale = [1, 1, 1]; | |
var fieldOfViewRadians = degToRad(60); | |
var rotationSpeed = 1.2; | |
var then = 0; | |
requestAnimationFrame(drawScene); | |
function drawScene(time) { | |
// Convert to seconds | |
var now = time * 0.001; | |
// Subtract the previous time from the current time | |
var deltaTime = now - then; | |
// Remember the current time for the next frame. | |
then = now; | |
twgl.resizeCanvasToDisplaySize(gl.canvas); | |
// Every frame increase the rotation a little. | |
rotation[1] += rotationSpeed * deltaTime; | |
// Tell WebGL how to convert from clip space to pixels | |
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
// Clear the canvas | |
gl.clearColor(0, 0, 0, 0); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
// turn on depth testing | |
gl.enable(gl.DEPTH_TEST); | |
// tell webgl to cull faces | |
gl.enable(gl.CULL_FACE); | |
gl.disable(gl.BLEND); | |
gl.depthMask(true); | |
// Compute the matrix | |
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; | |
var zNear = 1; | |
var zFar = 2000; | |
var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar); | |
// Compute the camera's matrix using look at. | |
var cameraRadius = 360; | |
var cameraPosition = [Math.cos(now) * cameraRadius, 0, Math.sin(now) * cameraRadius]; | |
var target = [0, 0, 0]; | |
var up = [0, 1, 0]; | |
var cameraMatrix = m4.lookAt(cameraPosition, target, up); | |
var viewMatrix = m4.inverse(cameraMatrix); | |
var textPositions = []; | |
var spread = 170; | |
for (var yy = -1; yy <= 1; ++yy) { | |
for (var xx = -2; xx <= 2; ++xx) { | |
var fViewMatrix = m4.translate(viewMatrix, | |
translation[0] + xx * spread, translation[1] + yy * spread, translation[2]); | |
fViewMatrix = m4.xRotate(fViewMatrix, rotation[0]); | |
fViewMatrix = m4.yRotate(fViewMatrix, rotation[1] + yy * xx * 0.2); | |
fViewMatrix = m4.zRotate(fViewMatrix, rotation[2] + now + (yy * 3 + xx) * 0.1); | |
fViewMatrix = m4.scale(fViewMatrix, scale[0], scale[1], scale[2]); | |
fViewMatrix = m4.translate(fViewMatrix, -50, -75, 0); | |
// remember the position for the text | |
textPositions.push([fViewMatrix[12], fViewMatrix[13], fViewMatrix[14]]); | |
// setup to draw the 'F' | |
gl.useProgram(fProgramInfo.program); | |
// setup the attributes and buffers for the F | |
gl.bindVertexArray(fVAO); | |
fUniforms.u_matrix = m4.multiply(projectionMatrix, fViewMatrix); | |
twgl.setUniforms(fProgramInfo, fUniforms); | |
twgl.drawBufferInfo(gl, fBufferInfo); | |
} | |
} | |
gl.enable(gl.BLEND); | |
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); | |
gl.depthMask(false); | |
// setup to draw the text. | |
// Because every letter uses the same attributes and the same progarm | |
// we only need to do this once before drawing all the letters | |
gl.useProgram(textProgramInfo.program); | |
gl.bindVertexArray(textVAO); | |
textPositions.forEach(function(pos, ndx) { | |
var name = names[ndx]; | |
var s = name + ":" + pos[0].toFixed(0) + "," + pos[1].toFixed(0) + "," + pos[2].toFixed(0); | |
var vertices = makeVerticesForString(fontInfo, s); | |
// update the buffers | |
textBufferInfo.attribs.a_position.numComponents = 2; | |
gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_position.buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.position, gl.DYNAMIC_DRAW); | |
gl.bindBuffer(gl.ARRAY_BUFFER, textBufferInfo.attribs.a_texcoord.buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, vertices.arrays.texcoord, gl.DYNAMIC_DRAW); | |
// use just the view position of the 'F' for the text | |
// because pos is in view space that means it's a vector from the eye to | |
// some position. So translate along that vector back toward the eye some distance | |
var fromEye = m4.normalize(pos); | |
var amountToMoveTowardEye = 150; // because the F is 150 units long | |
var viewX = pos[0] - fromEye[0] * amountToMoveTowardEye; | |
var viewY = pos[1] - fromEye[1] * amountToMoveTowardEye; | |
var viewZ = pos[2] - fromEye[2] * amountToMoveTowardEye; | |
var desiredTextScale = -1 / gl.canvas.height * 2; // 1x1 pixels | |
var scale = viewZ * desiredTextScale; | |
var textMatrix = m4.translate(projectionMatrix, viewX, viewY, viewZ); | |
textMatrix = m4.scale(textMatrix, scale, scale, 1); | |
// setup to draw the text. | |
gl.useProgram(textProgramInfo.program); | |
gl.bindVertexArray(textVAO); | |
m4.copy(textMatrix, textUniforms.u_matrix); | |
twgl.setUniforms(textProgramInfo, textUniforms); | |
// Draw the text. | |
gl.drawArrays(gl.TRIANGLES, 0, vertices.numVertices); | |
}); | |
requestAnimationFrame(drawScene); | |
} | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment