<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width" />
<title>WebGL thing</title>
<style type="text/css">
* {
margin: 0; padding: 0; border: 0;
background: #000 url("https://farm9.staticflickr.com/8692/16140033894_1edcf94d33_o.png");
}
canvas { display: block; }
</style>
</head>
<body><canvas></canvas></body>
<script id="fragment" type="x-shader/fragment">
precision mediump float;
varying vec3 Color;
/* texture mapping happens here */
void main () {
// gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
gl_FragColor = vec4(Color, 1);
}
</script><script id="vertex" type="x-shader/vertex">
uniform mat4 projection;
attribute vec3 coord;
attribute vec3 color;
varying vec3 Color;
uniform vec3 mRotate, mTranslate;
uniform vec3 vRotate, vTranslate;
// model = scale * rotate * translate
uniform float mScale;
uniform float vScale;
uniform float invert;
mat4 rotation_matrix (vec3 rotate) {
return mat4( // around X axis
1.0, 0.0, 0.0, 0.0,
0.0, cos(rotate.x), -sin(rotate.x), 0.0,
0.0, sin(rotate.x), cos(rotate.x), 0.0,
0.0, 0.0, 0.0, 1.0
) * mat4( // around Y axis
cos(rotate.y), 0.0, sin(rotate.y), 0.0,
0.0, 1.0, 0.0, 0.0,
-sin(rotate.y), 0.0, cos(rotate.y), 0.0,
0.0, 0.0, 0.0, 1.0
) * mat4( // around Z axis
cos(rotate.z), sin(rotate.z), 0.0, 0.0,
-sin(rotate.z), cos(rotate.z), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
mat4 scale_matrix (float scale) {
return mat4(
scale, 0.0, 0.0, 0.0,
0.0, scale, 0.0, 0.0,
0.0, 0.0, scale, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
/* OpenGL matrices appear to be flipped along the x-w diagonal.
1.0, 0.0, 0.0, 0.0, <-.
0.0, 1.0, 0.0, 0.0, <-+- projection
0.0, 0.0, 1.0, 0.0, <-'
0.0, 0.0, 0.0, 1.0
^ ^ ^
`----+----'
|
translate
*/
mat4 translation_matrix (vec3 translate) {
return mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
translate.x, translate.y, translate.z, 1.0
);
}
/* coordinate transformations happen here */
void main () {
Color = invert - color * (invert * 2.0 - 1.0);
/* that line is the mathematical expression of this block
* of code, only without any branches, because GPU.
if (0.0 == invert) Color = vec3(1.0) - color;
else Color = color;
*/
//gl_PointSize = 1.1;
/*
mat4 view
= translation_matrix(vTranslate)
* scale_matrix(vScale)
* rotation_matrix(vRotate);
*/
mat4 view
= rotation_matrix(vRotate)
* translation_matrix(vTranslate)
* scale_matrix(vScale);
mat4 model
= translation_matrix(mTranslate)
* scale_matrix(mScale)
* rotation_matrix(mRotate);
gl_Position = projection * view * model * vec4(coord, 1.0);
}
</script><script type="text/javascript">
var canvas = document.getElementsByTagName("canvas")[0];
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl) {
alert("sorry, you need webgl for this.");
//history.back();
}
/*
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
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);
*/
gl.enable(gl.DEPTH_TEST);
/*
gl.depthFunc(gl.LESS);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
*/
gl.lineWidth(2);
function compile_shader (type, id) {
var shader = gl.createShader(type);
gl.shaderSource(shader, document.getElementById(id).innerHTML);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(shader));
return shader;
}
var shader = gl.createProgram();
gl.attachShader(shader, compile_shader(gl.FRAGMENT_SHADER, "fragment"));
gl.attachShader(shader, compile_shader(gl.VERTEX_SHADER, "vertex"));
gl.linkProgram(shader);
if (!gl.getProgramParameter(shader, gl.LINK_STATUS))
console.log(gl.getProgramInfoLog(shader));
gl.useProgram(shader);
function load_buffer (data, type) {
var vbo = gl.createBuffer();
gl.bindBuffer(type, vbo);
gl.bufferData(type, data, gl.STATIC_DRAW);
return vbo;
}
function cached_uniform1f (pgm, dest) {
var u = gl.getUniformLocation(pgm, dest);
return function (v) {
return gl.uniform1f(u, v);
}
}
var hyp = Math.sqrt(2) / 2;
var cube = {
invert: cached_uniform1f(shader, "invert"),
attributes: {
coord: gl.getAttribLocation(shader, "coord"),
color: gl.getAttribLocation(shader, "color")
},
vertices: load_buffer(new Float32Array([
// x, y, z, r, g, b
0, hyp,-1, 1, 0, 0,
0, hyp, 1, 0, 1, 0,
-1,-hyp, 0, 0, 0, 1,
1,-hyp, 0, 1, 1, 1,
]), gl.ARRAY_BUFFER),
elements: {
length: 6,
buffer: load_buffer(new Uint16Array([
0, 1, 2, 3, 0, 1, 3, 0, 2
]), gl.ELEMENT_ARRAY_BUFFER)
}
};
// GL_FLOAT = 4 bytes. 4 * 3 = 12 (color offset), 4 * 6 = 24 (stride)
gl.bindBuffer(gl.ARRAY_BUFFER, cube.vertices);
gl.enableVertexAttribArray(cube.attributes.coord);
gl.vertexAttribPointer(
cube.attributes.coord, 3, gl.FLOAT, gl.FALSE, 24, 0
);
gl.enableVertexAttribArray(cube.attributes.color);
gl.vertexAttribPointer(
cube.attributes.color, 3, gl.FLOAT, gl.FALSE, 24, 12
);
function cached_static_uniform3fv (pgm, dest) {
var u = gl.getUniformLocation(pgm, dest);
function upload () {
return gl.uniform3fv(u, this);
}
function reinit (d) {
var c = new Float32Array(d);
c.upload = upload.bind(c);
c.reinit = reinit.bind(c);
if (c.length) c.upload();
return c;
}
return reinit([]);
}
function cached_static_uniform_mat4fv (pgm, dest) {
var u = gl.getUniformLocation(pgm, dest);
function upload () {
return gl.uniformMatrix4fv(u, false, this);
}
function reinit (d) {
var c = new Float32Array(d);
c.upload = upload.bind(c);
c.reinit = reinit.bind(c);
if (c.length) c.upload();
return c;
}
return reinit([]);
}
var matrix = {
/* identity matrix does nothing
identity: function () { return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])},
*/
// view matrix moves the camera.
view: {
scale: cached_uniform1f(shader, "vScale"),
translate: cached_static_uniform3fv(shader, "vTranslate").reinit([
0, 0, 2
]),
rotate: cached_static_uniform3fv(shader, "vRotate").reinit([
0, 0, 0
])
},
// model matrix moves the object.
// model matrix = scale * translate * rotate
model: {
scale: cached_uniform1f(shader, "mScale"),
translate: cached_static_uniform3fv(shader, "mTranslate").reinit([
0, 0, 1
]),
rotate: cached_static_uniform3fv(shader, "mRotate").reinit([
0, 0, 0
])
},
// projection matrix defines the view frustum
projection: cached_static_uniform_mat4fv(shader, "projection")
};
matrix.view.scale(1);
gl.clearColor(0, 0, 0, 0);
Math.TAU = 2 * Math.PI;
var theta = 0;
var lastframe;
var spinspeed = 1 / 20000;
function doFrame (time) {
if (time - lastframe < 1 / 60) return;
if (fullscreen.active()) {
requestAnimationFrame(doFrame);
if (lastframe) {
theta = theta + (time - lastframe) * spinspeed;
if (Math.TAU <= theta) theta = 0;
}
lastframe = time;
matrix.model.rotate[0] = 3 * theta;
matrix.model.rotate[1] = 5 * theta;
matrix.model.rotate[2] = 7 * theta;
matrix.model.rotate.upload();
}
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
cube.invert(0);
matrix.model.scale(1.0);
gl.drawElements(gl.TRIANGLE_STRIP, cube.elements.length, gl.UNSIGNED_SHORT, 0);
cube.invert(1);
matrix.model.scale(1.1);
gl.drawElements(gl.LINE_LOOP, cube.elements.length + 3, gl.UNSIGNED_SHORT, 0);
}
var scrollspeed = 1 / 16;
document.onwheel = function (e) {
// this function shamelessly stolen from
// https://stackoverflow.com/users/3225372/martijn
function sign(x) {
return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;
}
matrix.view.translate[2] += sign(e.deltaY) * scrollspeed;
console.log(matrix.view.translate[2]);
matrix.view.translate.upload();
if (!fullscreen.active()) requestAnimationFrame(doFrame);
}
document.onmousemove = function (e) {
if (fullscreen.active()) return;
matrix.model.rotate[0] = 2 * Math.PI * (e.clientY / (canvas.height - 1) - .5);
matrix.model.rotate[1] = 2 * Math.PI * (e.clientX / (canvas.width - 1) - .5);
matrix.model.rotate[2] = 0;
matrix.model.rotate.upload();
requestAnimationFrame(doFrame);
}
var fullscreen = (function () {
var el = document.documentElement,
fs_off = document.exitFullscreen
|| document.webkitExitFullscreen
|| document.mozCancelFullScreen
|| document.msExitFullscreen,
fs_on = el.requestFullScreen
|| el.webkitRequestFullScreen
|| el.mozRequestFullScreen
|| el.msRequestFullscreen;
return {
off: fs_off.bind(document),
on: fs_on.bind(el),
active: function () {
return (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
};
})()
onresize = function () {
gl.viewport(0, 0, innerWidth, innerHeight);
canvas.height = innerHeight;
canvas.width = innerWidth;
// for the projection matrix, we must first normalize
// the screen dimensions to somewhere near 1.
var hyp
= Math.sqrt(canvas.width * canvas.width
+ canvas.height * canvas.height);
/* for matrix
a, b, c, d,
e, f, g, h,
i, j, k, l,
m, n, o, p
x = a*x + e*y + i*z + m
y = b*x + f*y + j*z + n
z = c*x + g*y + k*z + o
*/
// 50mm lens = 48.8° diagonal FOV.
// var fov = Math.tan(Math.PI * 45 / 180 / 2);
var fov = Math.tan(Math.PI * 0.125);
/* orthographic
var zf = -100;
var zn = -0;
var xx = canvas.height / hyp;
var yy = canvas.width / hyp;
var zz = -1 / (zf - zn);
// if zz is 0, no z-ordering can be done.
matrix.projection = matrix.projection.reinit([
xx, 0, 0, 0,
0,yy, 0, 0,
0, 0,zz, 0,
0, 0, 0, 1
]);
//*/
// projection
var zf = -1.1;
var zn = -1;
var xx = canvas.height / hyp;
var yy = canvas.width / hyp;
var zt = zf - zn;
var zz = zt / zf;
var zw = fov;
matrix.projection = matrix.projection.reinit([
xx, 0, 0, 0,
0,yy, 0, 0,
0, 0,zz,zw,
0, 0,zt, 0
]);
//*/
/* ??
var fov = 1 / Math.tan(Math.PI * 45 / 180 / 2);
var ar = canvas.width / canvas.height;
matrix.projection = matrix.projection.reinit([
fov / ar, 0, 0, 0,
0, fov, 0, 0,
0, 0, 1, 2, // still have no idea why this isn't zero
0, 0,-1, 0
]);
//*/
if (!fullscreen.active()) requestAnimationFrame(doFrame);
}
onresize();
document.onclick = function () {
if (fullscreen.active()) {
fullscreen.off();
lastframe = undefined;
} else {
fullscreen.on();
requestAnimationFrame(doFrame);
}
}
</script></body>
</html>