diff --git a/index.html b/index.html index 37d1caf..b5c108b 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,4 @@ - WebGL test application @@ -13,16 +12,15 @@ + -
- + It appears that your browser does not support WebGL or doesn't have it enabled.
- \ No newline at end of file diff --git a/main.js b/main.js index 17e5eaf..38765dc 100644 --- a/main.js +++ b/main.js @@ -25,6 +25,8 @@ /* default fragment and vertex shaders, if we were clever we would import these * from a file, rather than just hard-coding them here */ + +/* Texture Shader */ var fragmentShaderSource = "precision mediump float; \ uniform sampler2D sampler; \ @@ -46,18 +48,17 @@ vUV = uv; \ }"; - var camera; - var shaderProgram; - var projectionMatrix; - var viewMatrix; - var squareVertexPositionBuffer; - var squareTexture; - var gl; +var camera; +var shaderProgram; +var projectionMatrix; +var viewMatrix; +var terrain; +var gl; - /* compile the provided shaderCode and return a shader which we can then - * include in the shader program */ - function compileShader(gl, type, shaderCode) - { +/* compile the provided shaderCode and return a shader which we can then + * include in the shader program */ +function compileShader(gl, type, shaderCode) +{ var shader = gl.createShader(type); gl.shaderSource(shader, shaderCode); gl.compileShader(shader); @@ -67,15 +68,15 @@ return null; } return shader; - } +} - /* compile a shader program from the provided shader source - * @param vertexShaderCode vertex shader source code to compile - * @param fragmentShaderCode fragment shader source code to compile - * @returns shaderProgram object, or null if failed. - */ - function createShaderProgram(gl, vertexShaderCode, fragmentShaderCode) - { +/* compile a shader program from the provided shader source + * @param vertexShaderCode vertex shader source code to compile + * @param fragmentShaderCode fragment shader source code to compile + * @returns shaderProgram object, or null if failed. + */ +function createShaderProgram(gl, vertexShaderCode, fragmentShaderCode) +{ var fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderCode); var vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderCode); var shader = gl.createProgram(); @@ -111,144 +112,85 @@ } return shader; - } +} - function initGL(canvas) - { - var _gl; - try { - _gl = canvas.getContext("experimental-webgl") || canvas.getConext("webgl"); - } - catch(e) {} +function initGL(canvas) +{ + var _gl; + try { + _gl = canvas.getContext("experimental-webgl") || canvas.getConext("webgl"); + } + catch(e) {} - if (!_gl) { - alert("Could not initialise WebGL. Your browser may not support it"); + if (!_gl) { + alert("Could not initialise WebGL. Your browser may not support it"); return null; } - _gl.viewport(0,0, canvas.width, canvas.height); - //gl.clearDepth(1.0); - //gl.cullFace(gl.FRONT); - _gl.clearColor(0.0, 0.0, 0.0, 1.0); - _gl.enable(_gl.DEPTH_TEST); + _gl.viewport(0,0, canvas.width, canvas.height); + _gl.clearDepth(1.0); + _gl.cullFace(_gl.FRONT); + _gl.clearColor(0.0, 0.0, 0.0, 1.0); + _gl.enable(_gl.DEPTH_TEST); - return _gl; - } + return _gl; +} - function initBuffers(gl) - { - var vertices = [ - /* x, y, z, u, v */ - 2.0, 2.0, 0.0, 0.0, 0.0, - -2.0, 2.0, 0.0, 1.0, 0.0, - 2.0, -2.0, 0.0, 0.0, 1.0, - -2.0, -2.0, 0.0, 1.0, 1.0, - ]; +function loadImage(gl, fileURL) +{ + var image = new Image(); + image.src = fileURL; + image.texture = null; - squareVertexPositionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); - } - - /* - function initTerrainMesh(gl, heightMap) - { - var vertices = [] - - for (var x = 0; x < 512; x++) { - for (var z = 0; z < 512; z++) { - var vertex = []; - vertex[0] = x; - vertex[1] = heightMap[x,z]; - vertex[2] = z; - vertex[3] = 0; - vertex[4] = 0; - vertices.push(vertex); - } - } - squareVertexPositionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); - } - - - function loadTextures(gl) - { - //squareTexture = loadImage(gl, "terrain.png"); - - } - - function loadImage(gl, fileURL) - { - var image = new Image(); - image.src = fileURL; - image.texture = null; - - image.onload = function(e) { - var texture = gl.createTexture(); - // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - - 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.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.bindTexture(gl.TEXTURE_2D, null); - image.texture = texture; - } - return image; - } - */ - - function createTextureFromArray(gl, data, width, height) - { - var image = new Image(); - var texture = gl.createTexture(); - - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array(data)); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.bindTexture(gl.TEXTURE_2D, null); - image.texture = texture; - - return image; - } + image.onload = function(e) { + var 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.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.bindTexture(gl.TEXTURE_2D, null); + image.texture = texture; + } + return image; +} + +/* Given R,G,B data in an array convert this to a WebGL texture */ +function createTextureFromArray(gl, data, width, height) +{ + var texture = gl.createTexture(); + + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array(data)); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.bindTexture(gl.TEXTURE_2D, null); + + return texture; +} - function drawFrame(gl) - { - viewMatrix = camera.view; - +function drawFrame(gl) +{ + viewMatrix = camera.view; + gl.useProgram(shaderProgram); - + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.uniformMatrix4fv(shaderProgram.uniform.uPMatrix, false, projectionMatrix); - gl.uniformMatrix4fv(shaderProgram.uniform.uMVMatrix, false, viewMatrix); + gl.uniformMatrix4fv(shaderProgram.uniform.uMVMatrix, false, viewMatrix); gl.uniform1i(shaderProgram.uniform.sampler, 0); - if (squareTexture.texture) { - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D,squareTexture.texture); - } - - gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); - - gl.enableVertexAttribArray(shaderProgram.attribute.position); - gl.enableVertexAttribArray(shaderProgram.attribute.uv); - - gl.vertexAttribPointer(shaderProgram.attribute.position, 3, gl.FLOAT, false, 4*(3+2), 0); - gl.vertexAttribPointer(shaderProgram.attribute.uv, 2, gl.FLOAT, false, 4*(3+2), 3*4); - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - } + + terrain.render(gl); +} - function tick() - { +function tick() +{ requestAnimFrame(tick); drawFrame(gl); - } +} - function main() - { +function main() +{ var canvas = document.getElementById("viewport"); gl = initGL(canvas); @@ -258,19 +200,17 @@ mat4.perspective(45, canvas.width / canvas.height, 0.1, 100.0, projectionMatrix); shaderProgram = createShaderProgram(gl, vertexShaderSource, fragmentShaderSource); - - initBuffers(gl); - //loadTextures(gl); - + camera = Object.create(Camera).init(canvas); camera.position = [0.0, 0.0, 7.0]; - var heightMap = Object.create(DiamondSquare).init(513, 0).image; - squareTexture = createTextureFromArray(gl, heightMap, 512,512); - //initTerrainMesh(gl, heightMap); + var heightMap = Object.create(DiamondSquare).init(129, 0); + terrain = Object.create(Terrain).init(gl, heightMap.data, 129, 129); + terrain.texture = createTextureFromArray(gl, heightMap.image, 128, 128); + terrain.shaderProgram = shaderProgram; requestAnimFrame(tick); - } - +} + /* once the page has finished loading, kick off the main() function */ window.addEventListener("load", main); \ No newline at end of file diff --git a/terrain.js b/terrain.js new file mode 100644 index 0000000..bb99c86 --- /dev/null +++ b/terrain.js @@ -0,0 +1,174 @@ +/* + * Copyright 2014, Chris Dewbery + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgement in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + */ + +var Terrain = Object.create(Object, { + + _gl : { + writable: true, + value : null, + }, + _verticesBuffer: { + writable: true, + value: null, + }, + _indicesBuffer : { + writable: true, + value: null, + }, + _texture : { + writable: true, + value: null, + }, + width : { + value: 0, + }, + length : { + value: 0, + }, + scale : { + value : 10, + }, + + generateVerticesFromHeightMap: { + value: function (heightMap, width, length) { + + var vertices = []; + var halfWidth = width / 2.0; + var halfLength = length / 2.0; + var index = 0; + + for (var z = 0; z < length; z++) { + for (var x = 0; x < width; x++) { + vertices[index++] = x - halfWidth; // x + vertices[index++] = heightMap[x][z] / this.scale; // y + vertices[index++] = z - halfLength; // z + vertices[index++] = x / (width - 1); // u + vertices[index++] = z / (length - 1); // v + } + } + return vertices; + } + }, + + generateTriangleStripIndices: { + value: function(width, length) { + + var indices = []; + var index = 0; + + for (var z = 0; z < length - 1; z++) { + if (z % 2 == 0) { + // even row, move from left to right + for (var x = 0; x < width; x++) { + indices[index++] = x + (z * width); + indices[index++] = x + (z * width) + width; + } + // insert degenerate vertex if this isn't the last row + if (z != length - 2) { + indices[index++] = --x + (z * width); + } + } + else { + + // Odd row, move from right to left + for ( var x = width - 1; x >= 0; x-- ) { + indices[index++] = x + (z * width); + indices[index++] = x + (z * width) + width; + } + // Insert degenerate vertex if this isn't the last row + if (z != length - 2) { + index[index++] = ++x + (z * width); + } + } + } + return indices; + } + }, + + texture : { + get : function() { + return this._texture; + }, + set : function(texture) { + this._texture = texture; + }, + }, + + shaderProgram : { + get : function() { + return this._shaderProgram; + }, + set : function(shaderProgram) { + this._shaderProgram = shaderProgram; + }, + }, + + /* generate terrain mesh from the provided height map */ + init : { + value : function(gl, heightMap, width, length) { + var vertices = this.generateVerticesFromHeightMap(heightMap, width, length); + var indices = this.generateTriangleStripIndices(width, length); + + this._verticesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + this._verticesBuffer.numIndices = vertices.length; + + this._indicesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); + this._indicesBuffer.numIndices = indices.length; + + this._gl = gl; + this.width = width; + this.length = length; + + return this; + } + }, + + /* render terrain mesh */ + render : { + value : function (gl) { + + if (this._texture) { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this._texture); + } + + gl.bindBuffer(gl.ARRAY_BUFFER, this._verticesBuffer); + + gl.enableVertexAttribArray(this._shaderProgram.attribute.position); + gl.enableVertexAttribArray(this._shaderProgram.attribute.uv); + + /* vertices are in the format (x,y,z) (u,v) */ + gl.vertexAttribPointer(this._shaderProgram.attribute.position, 3, gl.FLOAT, false, 20, 0); + gl.vertexAttribPointer(this._shaderProgram.attribute.uv, 2, gl.FLOAT, false, 20, 12); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indicesBuffer); + //gl.drawElements(gl.TRIANGLE_STRIP, this._indicesBuffer.numIndices, gl.UNSIGNED_SHORT, 0); + gl.drawElements(gl.TRIANGLE_STRIP, this._indicesBuffer.numIndices, gl.UNSIGNED_SHORT, 0); + } + }, + + }); \ No newline at end of file