Skip to content

Instantly share code, notes, and snippets.

@veeenu
Last active August 29, 2015 14:13
Show Gist options
  • Save veeenu/c4f030fc9dfd637323e4 to your computer and use it in GitHub Desktop.
Save veeenu/c4f030fc9dfd637323e4 to your computer and use it in GitHub Desktop.
Isosurface polygonization with chunked voxels
precision highp float;
varying vec3 fnormal, flight, fpos;
varying vec2 ftextureCoordinate;
varying vec3 fogv;
uniform sampler2D textureSampler;
void main(void) {
vec3 ldir = normalize(flight);
float lweight = 0.3 + 0.7 * max(dot(normalize(fnormal), ldir), 0.0);
vec4 texColor = texture2D(textureSampler, ftextureCoordinate);
float fog = length(fogv);
float dfo = clamp(0.6 + 64.0 / exp2(fog - 6.0), 0.0, 1.0);
float fogval = clamp(exp2(fog - 6.0) / 2.0, 0.0, 1.0);
gl_FragColor = mix(
vec4(0.0, 0.0, 0.0, 1.0),
vec4(texColor.rgb * lweight, texColor.a),
dfo
);
/* gl_FragColor = mix(
gl_FragColor,
vec4(127.0/255.0, 192.0/255.0, 1.0, 1.0),
fogval
);*/
}
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#canvas {
background: rgb(127, 192, 255);
}
#texture {
display: none;
}
</style>
</head>
<body>
<canvas id='canvas'></canvas>
<canvas width='64' height='64' id='texture'></canvas>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.1.0/gl-matrix-min.js'></script>
<script src='isosurface-voxels.js'></script>
</body>
</html>
/*
* Procedurally generated Minecraft dirt texture.
*/
(function() {
var ctx, imgd;
ctx = document.getElementById('texture').getContext('2d');
imgd = ctx.createImageData(64, 64);
for(var x = 0; x < 48; x++) for(var y = 0; y < 16; y++) {
var color, br = Math.random() * 0.5 + 0.5;
if(x < 16) { // Grass on the leftmost tile
color = 0x51D651;
}
else if(x < 32) { // Side on the center tile
// Behave randomly around the 7th vertical row of pixels,
// full grass and full dirt before and after that
if(y > br * 7)
color = 0x633402;
else
color = 0x51D651;
} else { // Dirt on the rightmost tile
color = 0x633402;
}
var idx = 4 * (y * 64 + x);
imgd.data[idx] = br * ((color >> 16) & 0xFF);
imgd.data[idx + 1] = br * ((color >> 8) & 0xFF);
imgd.data[idx + 2] = br * ((color) & 0xFF);
imgd.data[idx + 3] = 0xFF;
}
ctx.putImageData(imgd, 0, 0);
}());
(function() {
var canvas, gl, prg, aspectRatio,
vertexAttrib, texcAttrib, normAttrib, projUniform, viewUniform, texUniform,
texture, viewMatrix, ortho,
Chunk, Voxel, voxels, easeMap, quintFn;
/*
* Initialize context, compile shaders.
*/
canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
aspectRatio = canvas.clientWidth / canvas.clientHeight;
gl = canvas.getContext('webgl');
gl.enable(gl.DEPTH_TEST);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, canvas.clientWidth, canvas.clientHeight);
prg = (function(vert, frag) {
var prg = gl.createProgram();
gl.attachShader(prg, vert);
gl.attachShader(prg, frag);
gl.linkProgram(prg);
gl.useProgram(prg);
return prg;
}.apply(null, ['vertex', 'fragment']
.map(function(f) {
var xhr = new XMLHttpRequest();
xhr.open('GET', f + '.glsl', false);
xhr.send();
return { type: f, src: xhr.responseText };
})
.map(function(s) {
var sh = gl.createShader(s.type === 'vertex' ?
gl.VERTEX_SHADER : gl.FRAGMENT_SHADER);
gl.shaderSource(sh, s.src);
gl.compileShader(sh);
return sh;
})));
Chunk = function(pos) {
this.vertices = gl.createBuffer();
this.normals = gl.createBuffer();
this.uv = gl.createBuffer();
this.pos = pos;
}
Chunk.prototype.buildMesh = function() {
var vertices = [],
normals = [],
uvs = [],
hp = 1/256,
x, y, z, pos,
sideUvs = [
.25 + hp, .75 + hp, .5 - hp, .75 + hp, .25 + hp, 1 - hp,
.5 - hp, .75 + hp, .25 + hp, 1 - hp, .5 - hp, 1 - hp
],
topUvs = [
hp, .75 + hp, .25 - hp, .75 + hp, hp, 1 - hp,
.25 - hp, .75 + hp, hp, 1 - hp, .25 - hp, 1 - hp
],
bottomUvs = [
.5 + hp, .75 + hp, .75 - hp, .75 + hp, .5 + hp, 1 - hp,
.75 - hp, .75 + hp, .5 + hp, 1 - hp, .75 - hp, 1 - hp
],
transformFn = function(p, i) {
return (p + 2 * pos[i % 3]) / 20;
},
xDisp = 16 * this.pos[0],
yDisp = 8 * this.pos[1],
zDisp = 16 * this.pos[2],
xMin = -16 + xDisp, xMax = 16 + xDisp,
yMin = -8 + yDisp, yMax = 8 + yDisp,
zMin = -16 + zDisp, zMax = 16 + zDisp;
for(x = xMin; x < xMax; x++)
for(y = yMin; y < yMax; y++)
for(z = zMin; z < zMax; z++) {
if(this.density(x, y, z) === 0) continue;
pos = [x, y, z];
if(x === xMax - 1 || this.density(x + 1, y, z) === 0) {
vertices.push.apply(vertices, [
1, -1, -1, 1, -1, 1, 1, 1, -1, // right
1, -1, 1, 1, 1, -1, 1, 1, 1
].map(transformFn));
normals.push.apply(normals, [
1, 0, 0, 1, 0, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 0
]);
uvs.push.apply(uvs, sideUvs);
}
if(x === xMin || this.density(x - 1, y, z) === 0) {
vertices.push.apply(vertices, [
-1, -1, -1, -1, -1, 1, -1, 1, -1, // left
-1, -1, 1, -1, 1, -1, -1, 1, 1
].map(transformFn));
normals.push.apply(normals, [
-1, 0, 0, -1, 0, 0, -1, 0, 0,
-1, 0, 0, -1, 0, 0, -1, 0, 0
]);
uvs.push.apply(uvs, sideUvs);
}
if(z === zMax - 1 || this.density(x, y, z + 1) === 0) {
vertices.push.apply(vertices, [
-1, -1, 1, 1, -1, 1, -1, 1, 1, // back
1, -1, 1, -1, 1, 1, 1, 1, 1
].map(transformFn));
normals.push.apply(normals, [
0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1
]);
uvs.push.apply(uvs, sideUvs);
}
if(z === zMin || this.density(x, y, z - 1) === 0) {
vertices.push.apply(vertices, [
-1, -1, -1, 1, -1, -1, -1, 1, -1, // front
1, -1, -1, -1, 1, -1, 1, 1, -1
].map(transformFn));
normals.push.apply(normals, [
0, 0, -1, 0, 0, -1, 0, 0, -1,
0, 0, -1, 0, 0, -1, 0, 0, -1
]);
uvs.push.apply(uvs, sideUvs);
}
if(y === yMax - 1 || this.density(x, y + 1, z) === 0) {
vertices.push.apply(vertices, [
-1, 1, 1, 1, 1, 1, -1, 1, -1, // top
1, 1, 1, -1, 1, -1, 1, 1, -1
].map(transformFn));
normals.push.apply(normals, [
0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0
]);
uvs.push.apply(uvs, topUvs);
}
if(y === yMin || this.density(x, y - 1, z) === 0) {
vertices.push.apply(vertices, [
-1, -1, 1, 1, -1, 1, -1, -1, -1, // bottom
1, -1, 1, -1, -1, -1, 1, -1, -1
].map(transformFn));
normals.push.apply(normals, [
0, -1, 0, 0, -1, 0, 0, -1, 0,
0, -1, 0, 0, -1, 0, 0, -1, 0
]);
uvs.push.apply(uvs, bottomUvs);
}
}
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertices);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this.normals);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this.uv);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvs), gl.STATIC_DRAW);
this.count = vertices.length / 3;
}
Chunk.prototype.draw = function() {
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertices);
gl.vertexAttribPointer(vertexAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vertexAttrib);
gl.bindBuffer(gl.ARRAY_BUFFER, this.normals);
gl.vertexAttribPointer(normAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(normAttrib);
gl.bindBuffer(gl.ARRAY_BUFFER, this.uv);
gl.vertexAttribPointer(texcAttrib, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texcAttrib);
gl.drawArrays(gl.TRIANGLES, 0, this.count);
}
// Initialize orthographic view matrix.
ortho = mat4.create();
mat4.ortho(ortho, -aspectRatio, aspectRatio, -1, 1, -10, 100);
mat4.perspective(ortho, Math.PI / 3, aspectRatio, 0.1, 100);
// Incremental quintic easing function - we don't map to [0, 1], we map to
// increments such that their sum is always 1 and we can rotate the matrix
// from the step before instead of reinitializing it to identity every time
quintFn = function(i) {
return Math.pow(i / 64, 5) - Math.pow((i - 1) / 64, 5);
}
// Create an easing map so we don't need to compute the rotation angle every
// time, just look up the table.
// 0 - 64 -> quintic ease out. 64 - 96 -> stay put
easeMap = [];
for(var i = 64; i < 96; i++)
easeMap[i] = 0;
for(var i = 1; i <= 64; i++)
easeMap[i - 1] = 0.5 * Math.PI * (quintFn(65 - i));
vertexAttrib = gl.getAttribLocation(prg, 'vertex');
texcAttrib = gl.getAttribLocation(prg, 'textureCoordinate');
normAttrib = gl.getAttribLocation(prg, 'normal');
projUniform = gl.getUniformLocation(prg, 'projection');
viewUniform = gl.getUniformLocation(prg, 'view');
texUniform = gl.getUniformLocation(prg, 'textureSampler');
gl.uniformMatrix4fv(projUniform, false, ortho);
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,
document.getElementById('texture'));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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);
gl.uniform1i(texUniform, 0);
viewMatrix = mat4.create();
var counter = 0;
mat4.translate(viewMatrix, viewMatrix, [0, -2, -12]);
mat4.rotate(viewMatrix, viewMatrix, Math.PI / 24, [1, 0, 0]);
mat4.rotate(viewMatrix, viewMatrix, Math.PI / 6, [0, 1, 0]);
var dens = function(x, y, z) {
return Math.abs(Math.sin(Math.PI * x / 32) *
Math.sin(Math.PI * z / 32) * 8 - y) <= 1 ? 1 : 0;
}
var cnks = [];
//console.profile('Chunks');
for(var _x = -4; _x < 4; _x++)
for(var _z = -4; _z < 4; _z++) {
var cnk = new Chunk([_x, 0, _z]);
cnk.density = dens;
cnk.buildMesh();
cnks.push(cnk);
}
//console.profileEnd();
var loop = function() {
// Rotate the view matrix and step the counter
//mat4.rotate(viewMatrix, viewMatrix, easeMap[counter], [0, 1, 0]);
mat4.rotate(viewMatrix, viewMatrix, Math.PI / 1024, [0, 1, 0]);
counter++;
counter %= 95;
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(viewUniform, false, viewMatrix);
for(var i = 0; i < cnks.length; i++)
cnks[i].draw();
requestAnimationFrame(loop);
}
loop();
}());
uniform mat4 projection, view;
attribute vec3 vertex, normal;
attribute vec2 textureCoordinate;
varying highp vec3 fnormal, flight, fpos;
varying highp vec2 ftextureCoordinate;
varying highp vec3 fogv;
void main(void) {
gl_Position = projection * view * vec4(vertex, 1.0);
fnormal = normal;
flight = vec3(0.0, 0.5, 0.5);
fpos = vertex;
fogv = gl_Position.xyz;
ftextureCoordinate = textureCoordinate;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment