The code included below uses HTML5’s experimental WebGL functionality to draw a 3D scene containing a few textured rectangular boxes with some simple lighting.
To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Or, for an online version, visit http://thiscouldbebetter.neocities.org/webgl.html. You can move the camera around with the keys “Q, W, E, A, S, D, X, C, V, R, F”. You’ll figure it out.
This code was originally inspired by similar demonstrations found at “http://learningwebgl.com“. It’s mutated pretty profoundly since then, though. Some of the matrices in it are a little sketchy at this point. Specifically, I don’t think that the orientation transformation is working quite right, but it works for this particular scene, and hopefully I’ll figure out how to repair it later.
UPDATE 2014/10/29 – I have cleaned this code up somewhat, with a mind to reducing the number of new objects that have to be allocated every time the scene is rendered. I also worked with the matrices a little. They’re still kind of “magical” at the moment, but it’s a kind of magic that I feel I understand better.
UPDATE 2017/02/08 – I have cleaned up the code further, in preparation for hopefully adding shadow mapping in the future. Notably, I have incorporated a lot of mesh data into the Face object, rather than having several different arrays containing disparate information for each face. I also moved the code for the WebGL vertex and fragment shaders into HTML elements rather than building them up as giant strings from a bunch of single-line strings.
<html> <body> <script type="text/javascript"> // main function WebGLTest() { this.main = function() { var imageTextGL = ImageHelper.buildImageFromStrings ( "ImageTextGL", 8, // scaleMultiplier [ "RRRRRRRRRRRRRRRR", "RRcccccRcRRRRRcR", "RRcRRRRRcRRRRRcR", "RRcRRRRRcRRRRRcR", "RRcRcccRcRRRRRcR", "RRcRRRcRcRRRRRRR", "RRcccccRcccccRcR", "RRRRRRRRRRRRRRRR", ] ); Globals.Instance.mediaHelper.loadImages ( [ imageTextGL ], this.main2 ); } this.main2 = function(event) { var mediaHelper = Globals.Instance.mediaHelper; var displaySize = new Coords(320, 240, 2000); var scene = new DemoData().scene(mediaHelper, displaySize); var display = new Display(displaySize); Globals.Instance.initialize ( display, scene ); } } // extensions function ArrayExtensions() { // extension class } { Array.prototype.addLookups = function(keyName) { for (var i = 0; i < this.length; i++) { var element = this[i]; var key = element[keyName]; this[key] = element; } return this; } Array.prototype.append = function(other) { for (var i = 0; i < other.length; i++) { var element = other[i]; this.push(element); } return this; } } // classes function Body(name, defn, pos, orientation) { this.name = name; this.defn = defn; this.pos = pos; this.orientation = orientation; } { Body.prototype.drawToWebGLContext = function(webGLContext, scene) { this.defn.mesh.drawToWebGLContext(webGLContext, scene); } } function BodyDefn(name, mesh) { this.name = name; this.mesh = mesh; } function Camera(viewSize, focalLength, pos, orientation) { this.viewSize = viewSize; this.focalLength = focalLength; this.pos = pos; this.orientation = orientation; } { Camera.prototype.toString = function() { var returnValue = "<Camera " + "pos='" + this.pos.toString() + "' " + ">" + this.orientation.toString() + "</Camera>"; return returnValue; } } function Color(name, codeChar, systemColor, componentsRGBA) { this.name = name; this.codeChar = codeChar; this.systemColor = systemColor; this.componentsRGBA = componentsRGBA; } { // constants Color.NumberOfComponentsRGBA = 4; // instances function Color_Instances() { this.Transparent = new Color("Transparent", ".", "rgba(0, 0, 0, 0)", [0, 0, 0, 0]); this.Black = new Color("Black", "k", "Black", [0, 0, 0, 1]); this.Blue = new Color("Blue", "b", "Blue", [0, 0, 1, 1]); this.Cyan = new Color("Cyan", "c", "Cyan", [0, 1, 1, 1]); this.Gray = new Color("Gray", "a", "Gray", [.5, .5, .5, 1]); this.Green = new Color("Green", "g", "Green", [0, 1, 0, 1]); this.GreenDark = new Color("GreenDark", "G", "#008000", [0, .5, 0, 1]); this.Orange = new Color("Orange", "o", "Orange", [1, .5, 0, 1]); this.Red = new Color("Red", "r", "Red", [1, 0, 0, 1]); this.RedDark = new Color("RedDark", "R", "#800000", [.5, 0, 0, 1]); this.Violet = new Color("Violet", "v", "Violet", [1, 0, 1, 1]); this.White = new Color("White", "w", "White", [1, 1, 1, 1]); this.Yellow = new Color("Yellow", "y", "Yellow", [1, 1, 0, 1]); this._All = [ this.Transparent, this.Blue, this.Black, this.Cyan, this.Gray, this.Green, this.GreenDark, this.Orange, this.Red, this.RedDark, this.Violet, this.White, this.Yellow, ].addLookups("codeChar"); } Color.Instances = new Color_Instances(); } function Constants() {} { Constants.DegreesPerCircle = 360; Constants.RadiansPerCircle = 2 * Math.PI; Constants.RadiansPerDegree = Constants.RadiansPerCircle / Constants.DegreesPerCircle; } function Coords(x, y, z) { this.x = x; this.y = y; this.z = z; } { // constants Coords.NumberOfDimensions = 3; // instances Coords.Instances = new Coords_Instances(); function Coords_Instances() { this.OnesNegative = new Coords(-1, -1, -1); } // instance methods Coords.prototype.add = function(other) { this.x += other.x; this.y += other.y; this.z += other.z; return this; } Coords.prototype.clone = function() { return new Coords(this.x, this.y, this.z); } Coords.prototype.crossProduct = function(other) { return this.overwriteWithXYZ ( this.y * other.z - this.z * other.y, this.z * other.x - this.x * other.z, this.x * other.y - this.y * other.x ); } Coords.prototype.dimensionValues = function() { return [ this.x, this.y, this.z ]; } Coords.prototype.divide = function(other) { this.x /= other.x; this.y /= other.y; this.z /= other.z; return this; } Coords.prototype.divideScalar = function(scalar) { this.x /= scalar; this.y /= scalar; this.z /= scalar; return this; } Coords.prototype.dotProduct = function(other) { var returnValue = this.x * other.x + this.y * other.y + this.z * other.z; return returnValue; } Coords.prototype.magnitude = function() { var returnValue = Math.sqrt ( this.x * this.x + this.y * this.y + this.z * this.z ); return returnValue; } Coords.prototype.multiplyScalar = function(scalar) { this.x *= scalar; this.y *= scalar; this.z *= scalar; return this; } Coords.prototype.normalize = function() { return this.divideScalar(this.magnitude()); } Coords.prototype.overwriteWith = function(other) { this.x = other.x; this.y = other.y; this.z = other.z; return this; } Coords.prototype.overwriteWithXYZ = function(x, y, z) { this.x = x; this.y = y; this.z = z; return this; } Coords.prototype.subtract = function(other) { this.x -= other.x; this.y -= other.y; this.z -= other.z; return this; } Coords.prototype.toString = function() { var returnValue = "(" + this.x + "," + this.y + "," + this.z + ")"; return returnValue; } Coords.prototype.toWebGLArray = function() { var returnValues = new Float32Array(this.dimensionValues()); return returnValues; } } function Display(size) { this.size = size; } { Display.prototype.initialize = function() { var canvas = document.createElement("canvas"); canvas.width = this.size.x; canvas.height = this.size.y; document.body.appendChild(canvas); this.webGLContext = new WebGLContext(canvas); } } function Face(vertexIndices, material, textureUVs, vertexNormals) { this.vertexIndices = vertexIndices; this.material = material; this.textureUVs = textureUVs; this.vertexNormals = vertexNormals; } { // constants Face.VertexIndexIndicesForQuad = [ [ 0, 1, 2 ], [ 0, 2, 3 ] ]; Face.VertexIndexIndicesForTriangle = [ [ 0, 1, 2 ] ]; // methods Face.prototype.plane = function(mesh) { var returnValue = new Plane(this.vertices(mesh)); return returnValue; } Face.prototype.vertices = function(mesh) { var returnValues = []; for (var i = 0; i < this.vertexIndices.length; i++) { var vertexIndex = this.vertexIndices[i]; var vertexPosition = mesh.vertexPositions[vertexIndex]; returnValues.push(vertexPosition); } return returnValues; } // WebGL Face.prototype.drawToWebGLContext = function ( webGLContext, scene, mesh, vertexPositionsAsFloatArray, vertexColorsAsFloatArray, vertexNormalsAsFloatArray, vertexTextureUVsAsFloatArray, numberOfTrianglesSoFarWrapped ) { var face = this; var facePlane = face.plane(mesh); var faceNormal = facePlane.normal; var faceMaterial = face.material; var faceVertexNormals = face.vertexNormals; var numberOfVerticesInFace = face.vertexIndices.length; var vertexIndexIndicesForChildTriangles = (numberOfVerticesInFace == 4) ? Face.VertexIndexIndicesForQuad : Face.VertexIndexIndicesForTriangle for (var t = 0; t < vertexIndexIndicesForChildTriangles.length; t++) { var vertexIndexIndicesForChildTriangle = vertexIndexIndicesForChildTriangles[t]; for (var vii = 0; vii < vertexIndexIndicesForChildTriangle.length; vii++) { var vertexIndexIndex = vertexIndexIndicesForChildTriangle[vii]; var vertexIndex = face.vertexIndices[vertexIndexIndex]; var vertexPosition = mesh.vertexPositions[vertexIndex]; vertexPositionsAsFloatArray.append ( vertexPosition.dimensionValues() ); vertexColorsAsFloatArray.append ( faceMaterial.color.componentsRGBA ); var vertexNormal = ( faceVertexNormals == null ? faceNormal : faceVertexNormals[vertexIndex] ); vertexNormalsAsFloatArray.append ( vertexNormal.dimensionValues() ); var vertexTextureUV = ( face.textureUVs == null ? Coords.Instances.OnesNegative : face.textureUVs[vertexIndexIndex] ); vertexTextureUVsAsFloatArray.append ( [ vertexTextureUV.x, vertexTextureUV.y ] ); } } numberOfTrianglesSoFarWrapped.value += vertexIndexIndicesForChildTriangles.length; } } function Globals() { this.mediaHelper = new MediaHelper(); } { Globals.Instance = new Globals(); Globals.prototype.initialize = function(display, scene) { this.display = display; this.display.initialize(); this.inputHelper = new InputHelper(); this.inputHelper.initialize(); this.scene = scene; this.scene.initialize(this.display.webGLContext); this.scene.drawToWebGLContext(this.display.webGLContext); } } function Image(name, systemImage) { this.name = name; this.systemImage = systemImage; this.filePath = this.systemImage.src; } { } function ImageHelper() {} { // static methods ImageHelper.buildImageFromStrings = function ( name, scaleMultiplier, stringsForPixels ) { var sizeInPixels = new Coords ( stringsForPixels[0].length, stringsForPixels.length ); var canvas = document.createElement("canvas"); canvas.width = sizeInPixels.x * scaleMultiplier; canvas.height = sizeInPixels.y * scaleMultiplier; var graphics = canvas.getContext("2d"); var pixelPos = new Coords(0, 0); var colorForPixel = Color.Instances.Transparent; for (var y = 0; y < sizeInPixels.y; y++) { var stringForPixelRow = stringsForPixels[y]; pixelPos.y = y * scaleMultiplier; for (var x = 0; x < sizeInPixels.x; x++) { var charForPixel = stringForPixelRow[x]; pixelPos.x = x * scaleMultiplier; colorForPixel = Color.Instances._All[charForPixel]; graphics.fillStyle = colorForPixel.systemColor; graphics.fillRect ( pixelPos.x, pixelPos.y, scaleMultiplier, scaleMultiplier ); } } var imageFromCanvasURL = canvas.toDataURL("image/png"); var htmlImageFromCanvas = document.createElement("img"); htmlImageFromCanvas.width = canvas.width; htmlImageFromCanvas.height = canvas.height; htmlImageFromCanvas.isLoaded = false; htmlImageFromCanvas.onload = function(event) { event.target.isLoaded = true; } htmlImageFromCanvas.src = imageFromCanvasURL; var returnValue = new Image(name, htmlImageFromCanvas); // hack // WebGL doesn't support images from DataURLs? returnValue.canvas = canvas; return returnValue; } } function InputHelper() { this.tempCoords = new Coords(); } { InputHelper.prototype.initialize = function() { document.body.onkeydown = this.processKeyDownEvent.bind(this); } InputHelper.prototype.processKeyDownEvent = function(event) { var scene = Globals.Instance.scene var camera = scene.camera; var key = event.key.toLowerCase(); var distanceToMove = camera.focalLength; var amountToTurn = .1; var cameraOrientation = camera.orientation; if (key == "a") { // move left camera.pos.subtract ( this.tempCoords.overwriteWith ( cameraOrientation.right ).multiplyScalar ( distanceToMove ) ); } else if (key == "d") { // move right camera.pos.add ( this.tempCoords.overwriteWith ( cameraOrientation.right ).multiplyScalar ( distanceToMove ) ); } else if (key == "e") { // roll right cameraOrientation.overwriteWithForwardDown ( cameraOrientation.forward, cameraOrientation.down.add ( cameraOrientation.right.multiplyScalar ( amountToTurn ) ) ); } else if (key == "f") { // fall camera.pos.subtract ( this.tempCoords.overwriteWith ( cameraOrientation.down ).multiplyScalar ( distanceToMove ) ); } else if (key == "q") { // roll left cameraOrientation.overwriteWithForwardDown ( cameraOrientation.forward, cameraOrientation.down.subtract ( cameraOrientation.right.multiplyScalar ( amountToTurn ) ) ); } else if (key == "r") { // rise camera.pos.add ( this.tempCoords.overwriteWith ( cameraOrientation.down ).multiplyScalar ( distanceToMove ) ); } else if (key == "s") { // move back camera.pos.subtract ( this.tempCoords.overwriteWith ( cameraOrientation.forward ).multiplyScalar ( distanceToMove ) ); } else if (key == "w") { // move forward camera.pos.add ( this.tempCoords.overwriteWith ( cameraOrientation.forward ).multiplyScalar ( distanceToMove ) ); } else if (key == "z") { // turn left cameraOrientation.overwriteWithForwardDown ( cameraOrientation.forward.subtract ( cameraOrientation.right.multiplyScalar ( amountToTurn ) ), cameraOrientation.down ); } else if (key == "c") { // turn right cameraOrientation.overwriteWithForwardDown ( cameraOrientation.forward.add ( cameraOrientation.right.multiplyScalar ( amountToTurn ) ), cameraOrientation.down ); } else if (key == "x") { // cancel roll cameraOrientation.overwriteWithForwardDown ( cameraOrientation.forward, new Coords(0, 0, 1) ); } scene.draw(); } } function Lighting(ambientIntensity, direction, directionalIntensity) { this.ambientIntensity = ambientIntensity; this.direction = direction.clone().normalize(); this.directionalIntensity = directionalIntensity; } function Material(name, color) { this.name = name; this.color = color; } { function Material_Instances() { var colors = Color.Instances; this.Blue = new Material("Blue", colors.Blue); this.Cyan = new Material("Cyan", colors.Cyan); this.Green = new Material("Green", colors.Green); this.GreenDark = new Material("GreenDark", colors.GreenDark); this.Orange = new Material("Orange", colors.Orange); this.Red = new Material("Red", colors.Red); this.Violet = new Material("Violet", colors.Violet); this.Yellow = new Material("Yellow", colors.Yellow); this.White = new Material("White", colors.White); } Material.Instances = new Material_Instances(); } function Matrix(values) { this.values = values; } { // static methods Matrix.buildZeroes = function() { var returnValue = new Matrix ([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]); return returnValue; } // instance methods Matrix.prototype.clone = function() { var valuesCloned = []; for (var i = 0; i < this.values.length; i++) { valuesCloned[i] = this.values[i]; } var returnValue = new Matrix(valuesCloned); return returnValue; } Matrix.prototype.divideScalar = function(scalar) { for (var i = 0; i < this.values.length; i++) { this.values[i] /= scalar; } return this; } Matrix.prototype.multiply = function(other) { // hack // Instantiates a new matrix. var valuesMultiplied = []; for (var y = 0; y < 4; y++) { for (var x = 0; x < 4; x++) { var valueSoFar = 0; for (var i = 0; i < 4; i++) { // This appears backwards, // but the other way doesn't work? valueSoFar += other.values[y * 4 + i] * this.values[i * 4 + x]; } valuesMultiplied[y * 4 + x] = valueSoFar; } } this.overwriteWithValues(valuesMultiplied); return this; } Matrix.prototype.multiplyScalar = function(scalar) { for (var i = 0; i < this.values.length; i++) { this.values[i] *= scalar; } return this; } Matrix.prototype.overwriteWith = function(other) { for (var i = 0; i < this.values.length; i++) { this.values[i] = other.values[i]; } return this; } Matrix.prototype.overwriteWithOrientationBody = function(orientation) { var forward = orientation.forward; var right = orientation.right; var down = orientation.down; this.overwriteWithValues ([ right.x, down.x, forward.x, 0, right.y, down.y, forward.y, 0, right.z, down.z, forward.z, 0, 0, 0, 0, 1, ]); return this; } Matrix.prototype.overwriteWithOrientationCamera = function(orientation) { var forward = orientation.forward; var right = orientation.right; var down = orientation.down; this.overwriteWithValues ([ right.x, right.y, right.z, 0, 0-down.x, 0-down.y, 0-down.z, 0, 0-forward.x, 0-forward.y, 0-forward.z, 0, 0, 0, 0, 1, ]); return this; } Matrix.prototype.overwriteWithPerspectiveForCamera = function(camera) { var viewSize = camera.viewSize; var clipDistanceNear = camera.focalLength; var clipDistanceFar = camera.viewSize.z; var scaleFactorY = 1.0 / Math.tan(viewSize.y / 2); var scaleFactorX = scaleFactorY * viewSize.y / viewSize.x; var clipRange = clipDistanceNear - clipDistanceFar; var distanceFromFocusToClipPlaneFar = clipDistanceFar + clipDistanceNear; var clipDistanceSumOverDifference = distanceFromFocusToClipPlaneFar / clipRange; var clipDistanceProductOverDifference = (clipDistanceFar * clipDistanceNear) / clipRange; this.overwriteWithValues ([ scaleFactorX, 0, 0, 0, 0, scaleFactorY, 0, 0, 0, 0, clipDistanceSumOverDifference, 2 * clipDistanceProductOverDifference, 0, 0, -1, 0, ]); return this; } Matrix.prototype.overwriteWithRotate = function(axisToRotateAround, radiansToRotate) { var x = axisToRotateAround.x; var y = axisToRotateAround.y; var z = axisToRotateAround.z; var cosine = Math.cos(radiansToRotate); var sine = Math.sin(radiansToRotate); var cosineReversed = 1 - cosine; var xSine = x * sine; var ySine = y * sine; var zSine = z * sine; var xCosineReversed = x * cosineReversed; var yCosineReversed = y * cosineReversed; var zCosineReversed = z * cosineReversed; var xyCosineReversed = x * yCosineReversed; var xzCosineReversed = x * zCosineReversed; var yzCosineReversed = y * zCosineReversed; this.overwriteWithValues ([ (x * xCosineReversed + cosine), (xyCosineReversed + z * sine), (xzCosineReversed - ySine), 0, (xyCosineReversed - zSine), (y * yCosineReversed + cosine), (yzCosineReversed + xSine), 0, (xzCosineReversed + ySine), (yzCosineReversed - xSine), (z * zCosineReversed + cosine), 0, 0, 0, 0, 1, ]); return this; } Matrix.prototype.overwriteWithScale = function(scaleFactors) { this.overwriteWithValues ([ scaleFactors.x, 0, 0, 0, 0, scaleFactors.y, 0, 0, 0, 0, scaleFactors.z, 0, 0, 0, 0, 1, ]); return this; } Matrix.prototype.overwriteWithTranslate = function(displacement) { this.overwriteWithValues ([ 1, 0, 0, displacement.x, 0, 1, 0, displacement.y, 0, 0, 1, displacement.z, 0, 0, 0, 1, ]); return this; } Matrix.prototype.overwriteWithValues = function(otherValues) { for (var i = 0; i < this.values.length; i++) { this.values[i] = otherValues[i]; } return this; } Matrix.prototype.toWebGLArray = function() { var returnValues = []; for (var x = 0; x < 4; x++) { for (var y = 0; y < 4; y++) { returnValues.push(this.values[(y * 4 + x)]); } } var returnValues = new Float32Array(returnValues); return returnValues; } } function MediaHelper() { this.images = []; } { MediaHelper.prototype.loadImages = function ( imagesToLoad, methodToCallWhenAllImagesLoaded ) { for (var i = 0; i < imagesToLoad.length; i++) { var imageToLoad = imagesToLoad[i]; this.images.push(imageToLoad); this.images[imageToLoad.name] = imageToLoad; } this.methodToCallWhenAllImagesLoaded = methodToCallWhenAllImagesLoaded; setTimeout ( this.checkWhetherAllImagesAreLoaded, 100 ); } MediaHelper.prototype.checkWhetherAllImagesAreLoaded = function() { var mediaHelper = Globals.Instance.mediaHelper; var numberOfImagesLeftToLoad = 0; for (var i = 0; i < mediaHelper.images.length; i++) { var image = mediaHelper.images[i]; if (image.systemImage.isLoaded == false) { numberOfImagesLeftToLoad++; } } if (numberOfImagesLeftToLoad > 0) { setTimeout ( mediaHelper.checkWhetherAllImagesAreLoaded, 100 ); } else { mediaHelper.methodToCallWhenAllImagesLoaded(); } } } function Mesh ( name, vertexPositions, texture, faces ) { this.name = name; this.vertexPositions = vertexPositions; this.texture = texture; this.faces = faces; } { // constants Mesh.VerticesInATriangle = 3; // instance methods Mesh.prototype.drawToWebGLContext = function(webGLContext, scene) { var gl = webGLContext.gl; var shaderProgram = webGLContext.shaderProgram; var vertexPositionsAsFloatArray = []; var vertexColorsAsFloatArray = []; var vertexNormalsAsFloatArray = []; var vertexTextureUVsAsFloatArray = []; var numberOfTrianglesSoFarWrapped = new NumberWrapper(0); for (var f = 0; f < this.faces.length; f++) { var face = this.faces[f]; face.drawToWebGLContext ( webGLContext, scene, this, // mesh vertexPositionsAsFloatArray, vertexColorsAsFloatArray, vertexNormalsAsFloatArray, vertexTextureUVsAsFloatArray, numberOfTrianglesSoFarWrapped ); } var colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData ( gl.ARRAY_BUFFER, new Float32Array(vertexColorsAsFloatArray), gl.STATIC_DRAW ); gl.vertexAttribPointer ( shaderProgram.vertexColorAttribute, Color.NumberOfComponentsRGBA, gl.FLOAT, false, 0, 0 ); var normalBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer); gl.bufferData ( gl.ARRAY_BUFFER, new Float32Array(vertexNormalsAsFloatArray), gl.STATIC_DRAW ); gl.vertexAttribPointer ( shaderProgram.vertexNormalAttribute, Coords.NumberOfDimensions, gl.FLOAT, false, 0, 0 ); var positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData ( gl.ARRAY_BUFFER, new Float32Array(vertexPositionsAsFloatArray), gl.STATIC_DRAW ); gl.vertexAttribPointer ( shaderProgram.vertexPositionAttribute, Coords.NumberOfDimensions, gl.FLOAT, false, 0, 0 ); if (this.texture != null) { gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.texture.systemTexture); } gl.uniform1i(shaderProgram.samplerUniform, 0); var textureBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer); gl.bufferData ( gl.ARRAY_BUFFER, new Float32Array(vertexTextureUVsAsFloatArray), gl.STATIC_DRAW ); gl.vertexAttribPointer ( shaderProgram.vertexTextureUVAttribute, 2, gl.FLOAT, false, 0, 0 ); gl.drawArrays ( gl.TRIANGLES, 0, numberOfTrianglesSoFarWrapped.value * Mesh.VerticesInATriangle ); } } function NumberWrapper(value) { this.value = value; } function Orientation(forward, down) { this.forward = new Coords(); this.down = new Coords(); this.right = new Coords(); this.overwriteWithForwardDown(forward, down); } { // instance methods Orientation.prototype.clone = function() { return new Orientation ( this.forward, this.down ); } Orientation.prototype.overwriteWithForwardDown = function(forward, down) { this.forward.overwriteWith(forward).normalize(); this.down.overwriteWith(down).normalize(); this.right.overwriteWith(this.down).crossProduct(this.forward).normalize(); } Orientation.prototype.toString = function() { var returnValue = "<Orientation " + "forward='" + this.forward.toString() + "' " + "right='" + this.right.toString() + "' " + "down='" + this.down.toString() + "' " + " />"; return returnValue; } } function Plane(positionsOnPlane) { var pos0 = positionsOnPlane[0]; var displacementFromPos0To1 = Plane.TempCoords0.overwriteWith ( positionsOnPlane[1] ).subtract ( pos0 ); var displacementFromPos0To2 = Plane.TempCoords1.overwriteWith ( positionsOnPlane[2] ).subtract ( pos0 ); var normal = displacementFromPos0To1.clone().crossProduct ( displacementFromPos0To2 ).normalize(); this.normal = normal; this.distanceFromOrigin = this.normal.dotProduct(pos0); } { // helper variables Plane.TempCoords0 = new Coords(); Plane.TempCoords1 = new Coords(); } function Scene(name, lighting, camera, textures, bodies) { this.name = name; this.lighting = lighting; this.camera = camera; this.textures = textures; this.bodies = bodies; // helper variables this.matrixBody = Matrix.buildZeroes(); this.matrixCamera = Matrix.buildZeroes(); this.matrixOrient = Matrix.buildZeroes(); this.matrixPerspective = Matrix.buildZeroes(); this.matrixTranslate = Matrix.buildZeroes(); this.tempCoords = new Coords(); this.tempMatrix0 = Matrix.buildZeroes(); this.tempMatrix1 = Matrix.buildZeroes(); } { Scene.prototype.draw = function() { this.drawToWebGLContext ( Globals.Instance.display.webGLContext ); } Scene.prototype.drawToWebGLContext = function(webGLContext) { var gl = webGLContext.gl; var shaderProgram = webGLContext.shaderProgram; gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); var camera = this.camera; var cameraMatrix = this.matrixCamera.overwriteWithTranslate ( this.tempCoords.overwriteWith(camera.pos).multiplyScalar(-1) ).multiply ( this.matrixOrient.overwriteWithOrientationCamera ( camera.orientation ) ).multiply ( this.matrixPerspective.overwriteWithPerspectiveForCamera ( camera ) ) gl.uniform1f ( shaderProgram.lightAmbientIntensity, this.lighting.ambientIntensity ); gl.uniform3fv ( shaderProgram.lightDirection, this.lighting.direction.toWebGLArray() ); gl.uniform1f ( shaderProgram.lightDirectionalIntensity, this.lighting.directionalIntensity ); gl.uniformMatrix4fv ( shaderProgram.cameraMatrix, false, // transpose cameraMatrix.toWebGLArray() ); for (var b = 0; b < this.bodies.length; b++) { var body = this.bodies[b]; var normalMatrix = this.matrixOrient.overwriteWithOrientationBody ( body.orientation ); var bodyMatrix = this.matrixBody.overwriteWith ( normalMatrix ).multiply ( this.matrixTranslate.overwriteWithTranslate ( body.pos ) ); gl.uniformMatrix4fv ( shaderProgram.bodyMatrix, false, // transpose bodyMatrix.toWebGLArray() ); gl.uniformMatrix4fv ( shaderProgram.normalMatrix, false, // transpose normalMatrix.multiplyScalar(-1).toWebGLArray() ); body.drawToWebGLContext(webGLContext, this); } } Scene.prototype.initialize = function(webGLContext) { for (var t = 0; t < this.textures.length; t++) { var texture = this.textures[t]; texture.initialize(webGLContext); } } } function Texture(name, image) { this.name = name; this.image = image; } { Texture.prototype.initialize = function(webGLContext) { var gl = webGLContext.gl; this.systemTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.systemTexture); //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texImage2D ( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.image.systemImage ); 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.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); gl.bindTexture(gl.TEXTURE_2D, null); } } function WebGLContext(canvas) { this.gl = this.initGL(canvas); this.shaderProgram = this.buildShaderProgram(this.gl); } { WebGLContext.prototype.initGL = function(canvas) { var gl = canvas.getContext("experimental-webgl"); gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; var colorBack = Color.Instances.Cyan; var colorBackComponentsRGBA = colorBack.componentsRGBA; gl.clearColor ( colorBackComponentsRGBA[0], colorBackComponentsRGBA[1], colorBackComponentsRGBA[2], colorBackComponentsRGBA[3] ); gl.enable(gl.DEPTH_TEST); return gl; } WebGLContext.prototype.buildShaderProgram = function(gl) { var shaderProgram = this.buildShaderProgram_Compile ( gl, this.buildShaderProgram_FragmentShader(gl), this.buildShaderProgram_VertexShader(gl) ); this.buildShaderProgram_SetUpInputVariables(gl, shaderProgram); return shaderProgram; } WebGLContext.prototype.buildShaderProgram_FragmentShader = function(gl) { var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); var fragmentShaderCode = document.getElementById ( "divShaderCodeFragment" ).childNodes[0].nodeValue; gl.shaderSource(fragmentShader, fragmentShaderCode); gl.compileShader(fragmentShader); return fragmentShader; } WebGLContext.prototype.buildShaderProgram_VertexShader = function(gl) { var vertexShader = gl.createShader(gl.VERTEX_SHADER); var vertexShaderCode = document.getElementById ( "divShaderCodeVertex" ).childNodes[0].nodeValue; gl.shaderSource(vertexShader, vertexShaderCode); gl.compileShader(vertexShader); return vertexShader; } WebGLContext.prototype.buildShaderProgram_Compile = function ( gl, fragmentShader, vertexShader ) { var shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); gl.useProgram(shaderProgram); return shaderProgram; } WebGLContext.prototype.buildShaderProgram_SetUpInputVariables = function ( gl, shaderProgram ) { shaderProgram.vertexColorAttribute = gl.getAttribLocation ( shaderProgram, "aVertexColor" ); gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); shaderProgram.vertexNormalAttribute = gl.getAttribLocation ( shaderProgram, "aVertexNormal" ); gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute); shaderProgram.vertexPositionAttribute = gl.getAttribLocation ( shaderProgram, "aVertexPosition" ); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.vertexTextureUVAttribute = gl.getAttribLocation ( shaderProgram, "aVertexTextureUV" ); gl.enableVertexAttribArray(shaderProgram.vertexTextureUVAttribute); shaderProgram.bodyMatrix = gl.getUniformLocation ( shaderProgram, "uBodyMatrix" ); shaderProgram.cameraMatrix = gl.getUniformLocation ( shaderProgram, "uCameraMatrix" ); shaderProgram.lightAmbientIntensity = gl.getUniformLocation ( shaderProgram, "uLightAmbientIntensity" ); shaderProgram.lightDirection = gl.getUniformLocation ( shaderProgram, "uLightDirection" ); shaderProgram.lightDirectionalIntensity = gl.getUniformLocation ( shaderProgram, "uLightDirectionalIntensity" ); shaderProgram.normalMatrix = gl.getUniformLocation ( shaderProgram, "uNormalMatrix" ); } } // demo function DemoData() { // do nothing } { DemoData.prototype.scene = function(mediaHelper, displaySize) { var imageTextGL = mediaHelper.images["ImageTextGL"]; var textureTextGL = new Texture ( "TextureTextGL", imageTextGL ); var materials = Material.Instances; var meshGround = new Mesh ( "Ground", // vertexPositions [ new Coords(1000, -1000, 0), new Coords(1000, 1000, 0), new Coords(-1000, 1000, 0), new Coords(-1000, -1000, 0), ], null, // texture // faces [ new Face ( [0, 1, 2, 3], // vertexIndices materials.GreenDark, null, // textureUVs null // vertexNormals - todo ), ] ); var textureUVsForMeshFaces = [ new Coords(0, 1), new Coords(1, 1), new Coords(1, 0), new Coords(0, 0) ]; var meshRainbowMonolith = new Mesh ( "RainbowMonolith", // vertexPositions [ // back new Coords(-40, 0, -10), new Coords(40, 0, -10), new Coords(40, -180, -10), new Coords(-40, -180, -10), // front new Coords(-40, 0, 10), new Coords(40, 0, 10), new Coords(40, -180, 10), new Coords(-40, -180, 10), ], textureTextGL, // faces [ new Face ( [5, 4, 7, 6], materials.Red, textureUVsForMeshFaces ), // front new Face ( [0, 1, 2, 3], materials.Orange, textureUVsForMeshFaces ), // back new Face ( [3, 0, 4, 7], materials.Yellow, textureUVsForMeshFaces ), // left new Face ( [5, 6, 2, 1], materials.Green, textureUVsForMeshFaces ), // right new Face ( [6, 7, 3, 2], materials.Blue, textureUVsForMeshFaces ), // top new Face ( [4, 5, 1, 0], materials.Violet, textureUVsForMeshFaces ), // bottom ] ); var bodyDefnGround = new BodyDefn ( "Ground", meshGround ); var bodyDefnRainbowMonolith = new BodyDefn ( "RainbowMonolith", meshRainbowMonolith ); var scene = new Scene ( "Scene0", new Lighting ( .5, // ambientIntensity new Coords(-1, -1, -1), // direction 2 // directionalIntensity ), new Camera ( displaySize.clone(), 10, // focalLength new Coords(-200, 0, -100), // pos new Orientation ( new Coords(1, 0, 0), // forward new Coords(0, 0, 1) // down ) ), // textures [ textureTextGL, ], // bodies [ new Body ( "Ground0", bodyDefnGround, new Coords(0, 0, 0), // pos new Orientation ( new Coords(0, 0, -1), // forward new Coords(1, 0, 0) // down ) ), new Body ( "RainbowMonolith0", bodyDefnRainbowMonolith, new Coords(0, 0, 0), // pos new Orientation ( new Coords(1, 0, 0), // forward new Coords(0, 0, 1) // down ) ), new Body ( "RainbowMonolith1", bodyDefnRainbowMonolith, new Coords(100, 0, 0), // pos new Orientation ( new Coords(1, 0, 0), // forward new Coords(0, 0, 1) // down ) ), new Body ( "RainbowMonolith2", bodyDefnRainbowMonolith, new Coords(0, 100, 0), // pos new Orientation ( new Coords(1, 1, 0), // forward new Coords(0, 0, 1) // down ) ), ] ); return scene; } } // run new WebGLTest().main(); </script> <!-- WebGL shader programs --> <div id="divShaderCodeFragment" style="display:none"> precision mediump float; uniform sampler2D uSampler; varying vec4 vColor; varying vec3 vLight; varying vec2 vTextureUV; void main(void) { if (vTextureUV.x < 0.0) { gl_FragColor = vColor; } else { vec4 textureColor = texture2D(uSampler, vec2(vTextureUV.s, vTextureUV.t)); gl_FragColor = vec4(vLight * textureColor.rgb, textureColor.a); } } </div> <div id="divShaderCodeVertex" style="display:none"> attribute vec4 aVertexColor; attribute vec3 aVertexNormal; attribute vec3 aVertexPosition; attribute vec2 aVertexTextureUV; uniform mat4 uBodyMatrix; uniform mat4 uCameraMatrix; uniform float uLightAmbientIntensity; uniform vec3 uLightDirection; uniform float uLightDirectionalIntensity; uniform mat4 uNormalMatrix; varying vec4 vColor; varying vec3 vLight; varying vec2 vTextureUV; void main(void) { vColor = aVertexColor; vec4 vertexNormal4 = vec4(aVertexNormal, 0.0); vec4 transformedNormal4 = uNormalMatrix * vertexNormal4; vec3 transformedNormal = vec3(transformedNormal4.xyz) * -1.0; float lightMagnitude = uLightAmbientIntensity; lightMagnitude += uLightDirectionalIntensity * max(dot(transformedNormal, uLightDirection), 0.0); vLight = vec3(1.0, 1.0, 1.0) * lightMagnitude; vTextureUV = aVertexTextureUV; vec4 vertexPos = vec4(aVertexPosition, 1.0); gl_Position = uCameraMatrix * uBodyMatrix * vertexPos; } </div> </body> </html>
Reblogged this on xtmpxg and commented:
test