var gl = document.querySelector("canvas").getContext("webgl");
var ext = gl.getExtension("OES_texture_float");
if (!ext) {
alert("need OES_texture_float extension cause I'm lazy");
//return;
}
if (gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) < 2) {
alert("need to be able to access textures from vertex shaders");
//return;
}
var m4 = twgl.m4;
var v3 = twgl.v3;
var program = twgl.createProgramFromScripts(gl, ["vshader", "fshader"]);
var positionIndexLoc = gl.getAttribLocation(program, "a_positionIndex");
var normalIndexLoc = gl.getAttribLocation(program, "a_normalIndex");
var colorLoc = gl.getUniformLocation(program, "u_color");
var mvpMatrixLoc = gl.getUniformLocation(program, "u_mvpMatrix");
var mvMatrixLoc = gl.getUniformLocation(program, "u_mvMatrix");
var lightDirLoc = gl.getUniformLocation(
program, "u_lightDirection");
var u_positionsLoc = gl.getUniformLocation(
program, "u_positions");
var u_normalsLoc = gl.getUniformLocation(
program, "u_normals");
gl.uniform1i(u_positionsLoc, 0); // use texture unit 0 for positions
gl.uniform1i(u_normalsLoc, 1); // use texture unit 1 for normals
// Cube data
var positions = [
-1, -1, -1, // 0 lbb
+1, -1, -1, // 1 rbb 2---3
-1, +1, -1, // 2 ltb /| /|
+1, +1, -1, // 3 rtb 6---7 |
-1, -1, +1, // 4 lbf | | | |
+1, -1, +1, // 5 rbf | 0-|-1
-1, +1, +1, // 6 ltf |/ |/
+1, +1, +1, // 7 rtf 4---5
];
var positionIndices = [
3, 7, 5, 3, 5, 1, // right
6, 2, 0, 6, 0, 4, // left
6, 7, 3, 6, 3, 2, // top
0, 1, 5, 0, 5, 4, // bottom
7, 6, 4, 7, 4, 5, // front
2, 3, 1, 2, 1, 0, // back
];
var normals = [
+1, 0, 0,
-1, 0, 0,
0, +1, 0,
0, -1, 0,
0, 0, +1,
0, 0, -1,
]
var normalIndices = [
0, 0, 0, 0, 0, 0, // right
1, 1, 1, 1, 1, 1, // left
2, 2, 2, 2, 2, 2, // top
3, 3, 3, 3, 3, 3, // bottom
4, 4, 4, 4, 4, 4, // front
5, 5, 5, 5, 5, 5, // back
];
function degToRad(deg) {
return deg * Math.PI / 180;
}
function uploadIndices(loc, data, indices) {
// scale indices into texture coordinates
var scaledIndices = new Float32Array(indices.length);
// to index the value in the texture we need to
// compute a texture coordinate that will access
// the correct texel. To do that we need access from
// the middle of the first texel to the middle of the
// last texel.
//
// In other words if we had 3 values (and therefore
// 3 texels) we'd have something like this
//
// ------3x1 ----- texels ----------
// [ ][ ][ ]
// 0.0 |<----------------------------->| 1.0
//
// If we just did index / numValues we'd get
//
// [ ][ ][ ]
// | | |
// 0.0 0.333 0.666
//
// Which is right between texels so we add a
// a halfTexel to get this
//
// [ ][ ][ ]
// | | |
// 0.167 0.5 0.833
var size = data.length / 3;
var texel = 1 / size;
var halfTexel = texel / 2;
for (var ii = 0; ii < indices.length; ++ii) {
scaledIndices[ii] = indices[ii] / size + halfTexel;
}
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER,
scaledIndices,
gl.STATIC_DRAW);
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 1, gl.FLOAT, false, 0, 0);
}
function uploadTexture(unit, data) {
gl.activeTexture(gl.TEXTURE0 + unit);
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB,
data.length / 3, 1, 0,
gl.RGB, gl.FLOAT, new Float32Array(data));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER,
gl.NEAREST);
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);
}
uploadIndices(positionIndexLoc, positions, positionIndices);
uploadIndices(normalIndexLoc, normals, normalIndices);
uploadTexture(0, positions);
uploadTexture(1, normals);
var xRot = 30;
var yRot = 20;
var zRot = 0;
var lightDir = v3.normalize([-0.2, -0.1, 0.5]);
function draw() {
xRot += 0;
yRot += 1;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
var projection = m4.perspective(
degToRad(45),
gl.canvas.clientWidth / gl.canvas.clientHeight,
0.1, 100.0);
var world = m4.identity();
world = m4.translate(world, [0.0, 0.0, -5.0]);
world = m4.rotateX(world, degToRad(xRot));
world = m4.rotateY(world, degToRad(yRot));
world = m4.rotateZ(world, degToRad(zRot));
var mvp = m4.multiply(projection, world);
gl.uniformMatrix4fv(mvMatrixLoc, false, world);
gl.uniformMatrix4fv(mvpMatrixLoc, false, mvp);
gl.uniform4f(colorLoc, 0.5, 0.8, 1, 1);
gl.uniform3fv(lightDirLoc, lightDir);
gl.drawArrays(gl.TRIANGLES, 0, 36);
requestAnimationFrame(draw);
}
draw();
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<script src="//twgljs.org/dist/2.x/twgl-full.min.js"></script>
<script id="vshader" type="whatever">
attribute float a_positionIndex;
attribute float a_normalIndex;
attribute vec4 a_pos;
uniform sampler2D u_positions;
uniform sampler2D u_normals;
uniform mat4 u_mvpMatrix;
uniform mat4 u_mvMatrix;
varying vec3 v_normal;
void main() {
vec3 position = texture2D(
u_positions, vec2(a_positionIndex, 0.5)).rgb;
vec3 normal = texture2D(
u_normals, vec2(a_normalIndex, 0.5)).rgb;
gl_Position = u_mvpMatrix * vec4(position, 1);
v_normal = (u_mvMatrix * vec4(normal, 0)).xyz;
}
</script>
<script id="fshader" type="whatever">
precision mediump float;
uniform vec4 u_color;
uniform vec3 u_lightDirection;
varying vec3 v_normal;
void main() {
float light = dot(
normalize(v_normal), u_lightDirection) * 0.5 + 0.5;
gl_FragColor = vec4(u_color.rgb * light, u_color.a);
}
</script>
<canvas></canvas>