WebGLICON.jpg

WebGL Deferred Renderer With Custom Javascript 3D Engine


Project Features

  • Custom Javascript 3D Engine

  • WebGL 1.0 / GLSL ES 1.0 with 2.0 Supported Extensions

  • Over 100 Randomly Moving Dynamic Point Lights 

  • FrameBuffer Support with Multiple Render Targets

  • Three Pass Render Pipeline

  • Geometry Buffer with Three Render Targets

  • Light Buffer with Two Render Targets

  • Supports Diffuse and Specular Lighting

  • Debug Views For Render Targets


Project Overview

The goal of this project was to explore a Deferred Rendering Pipeline utilizing WebGL 2.0 extensions, many of which are still in development or experimental stages*. I entered unexplored territory in Chrome and Firefox by enabling experimental WebGL extensions and enabling Direct 3D 11 so I could utilize WebGL_draw_buffers. This type of exploratory project serves as a proof of concept tech for how to implement Deferred Shading in WebGL for when it transitions to version 2.0.

 

* Note: The extension lists can be found in Chrome by typing about:flags into the browser or about:config on Firefox.


Rendering Pipeline

Geometry Pass

The Geometry pass is the first pass in my rendering pipeline. For this pass, The world scene is rendered as normal with the Geometry Buffer's (commonly referred to as the G-Buffer) FrameBuffer bound as the current render target. It consists of three attachments that store RGBA Float values whose contents are denoted in the picture above.* It is important to set the blendfunc to ( ONE, ZERO ) as non alpha data is stored in the alpha channel. No lighting calculations are performed at this stage. In order to capture data to multiple render targets in a GLSL 1.0 fragment shader, three steps must be taken.

  1. One must call drawBuffers declaring the channels one wishes to output too using a reference to the requested draw_buffers extension while the G-Buffer's FBO is the active FrameBuffer. See GBuffer.js in the code sample at the bottom.
  2. In the geometry fragment shader, high precision floating point must be used and a pre-processor directive declared to require draw_buffers --- #extension GL_EXT_draw_buffers : require
  3. Lastly, one can output to multiple channels by indexing gl_FragData as an array. For the G-Buffer, this will be gl_FragData[0], gl_FragData[1], and gl_FragData[2]

See below for an example of the debug views for the G-Buffer render targets.

* It is worth noting that Render Target Three can opt to store depth in a single channel instead of world position in the rgb channels. This leaves room for specular color or other meta data such as the emissive multiplier. Position values can be extracted using the inverse perspective matrix and inverse view matrix with the xy values of the texture coordinates and the texel's depth.

 

Light Pass

The Lighting pass is performed after the Geometry pass and contains two render targets, one for diffuse lighting and specular lighting accumulation. The L-Buffer's FrameBuffer is bound as the current render target. In this pass, scene geometry is not rendered and instead, geometry representing dynamic lights is rendered. For this demo, I chose to showcase point lights, which are represented by a sphere and have their own shader (See PointLightFragmentShader.glsl in the code samples at the bottom). The motivation for using geometry to represent lights is simple. I want to avoid shading pixels for which the light has no effect. By rendering a sphere with front faces culled, I only perform lighting calculations on texels within the point light's outer radius. The reason this works is the blendFunc is set to ( ONE, ONE ) which accumulates data into the render target buffers instead of writing over it. Each light is iterated over and rendered to the L-Buffer FBO. After all the light data has been accumulated, the debug views for the L-Buffer look as follows

Other types of lights can be represented in this manner as well. For example, a local spotlight would take the form of a frustum and a local ambient light a sphere. Unfortunately, global lights such as a directional light, have no benefit from geometric representation. For this type of light, one must perform lighting calculations on each texel in the render targets. For large render target buffers, this can be an expensive operation. In fact, it might even be better to perform calculations for global directional lights in the geometry pass. 

Final Pass

The final pass consists of using the data from Render Targets One, Four, and Five to determine the final texels to output to the screen. A simple quad the size of the screen is rendered using an orthographic projection. In this pass, no custom FrameBuffers are bound and the blendFunc is set to (SRC_ALPHA, ONE_MINUS_SRC_ALPHA). See below for an example of a scene lit with 150 dynamic point lights.

 


Project Post Mortem

What Went Well

  • As this was one of my later projects at The Guildhall, I knew better to focus on debugging tools as early as possible. Within the initial weeks of the project, I focused most of my efforts on integrating WebGL Inspector and debug views for render target data. Even though this slowed down initial progress on the rendering pipeline, it paid huge dividends throughout the project by deepening my understanding of WebGL and squashing bugs in an efficient, timely manner.
  • A great side benefit of the project was the concomitant creation of a Javascript 3D Engine. I now have a sandbox for experimenting with WebGL and Web Development in general.
  • I was able to prove a Deferred Rendering Pipeline can be implemented in WebGL!

What Went Wrong

  • Since I am using many experimental WebGL extensions, documentation was often sparse or not to be found. I spent many hours researching and trying to find the appropriate extensions which would allow me to leverage multiple render targets. This led to a lot of initial frustration and halted progress as it felt like a trial and error process with endless knob tweaking to get WebGL_draw_buffers as an available extension. After browsing the dark catacombs of Google's WebGL bug reports, I finally found the correct set up I would need to run the demo on Chrome. This is a great example where I likely would have sped up the process and evaded frustration by asking the developers themselves.
  • I did not anticipate the experimental extensions breaking WebGL debugging tools. Early in development, I integrated WebGL Inspector to assist in debugging my rendering pipeline. I became very dependent on this tool, as it conveniently displayed buffer contents, sequential WebGL calls, and texture data. Enabling the depth buffer extension and draw buffers extensions broke the tool to where it could no longer display any debug data. This meant that midway through the project I had to ponder more creative ways to debug my rendering pipeline. This is often an unspoken downside of using third party libraries for which there is little control.
  • I simply ran out of time to integrate my 3DS Max Exporter/Importer with my JavaScript Engine. This was discouraging as it limited the final feature-set and visual demonstration. I had a short time frame to work on this tech demo, and ultimately the goal was a deferred rendering pipeline. So, the exporter/importer had to take a backseat.

What I Learned

  • JavaScript is an evolving and very flexible language.  With the development of Google's V8 JavaScript Engine, inclusion of typed arrays, and growing third party developer community, JavaScript and WebGL are becoming a viable option for cross-platform game development. I do not see AAA games using WebGL anytime soon, however I do believe we will see many companies use it as an avenue for tools development.  It has a long way to go, but it is amazing to compare how far web development has come in the last ten years.
  • The garbage collector is often your worst enemy. When doing performance profiling towards the end of the project, often I would notice unpredictable dips in an otherwise steady framerate. These mild dips were occurring with the same magnitude regardless if there were five or one hundred dynamic lights in the scene.  As I continue to develop this engine, a good amount of my focus will be on mitigating garbage collection frequency. Some approaches I would utilize are object pooling and avoiding creating objects when possible. This is a prime example of why game developers opt for C++ as managing memory is in control of the developer.

  • CBRenderer.js

    								
    define( [ "MatrixStack", "MathUtil", "GLMatrix", "Collections", "PostRenderScene" ], function( MatrixStack, MathUtil, Collections )
    {
    	console.log( "CBRenderer has finished loading" );
    });
    
    /*
    	Singleton class which encapsulates the canvas, WebGL Context, 
    	and any extensions to do with rendering. Also, it creates the
    	perspective matrix used for the current scene.
    */
    var CBRenderer = ( function()
    {
    	// ====== Private Variables ====== //
    	this.renderer 						= null;
    	this.drawBuffers 					= null;
    	this.canvasDOMElement 				= null;
    	this.canvasID 						= '';
    
    	this.m_projectionMatrix 			= null;
    
    	this.bWebGLContextValid 			= false;
    	this.bRendererInitialized 			= false;
    
    	function CBRenderer()
    	{
    		// ====== Public Functions ====== //
    		this.isWebGLContextValid = function()
    		{
    			return this.bWebGLContextValid;
    		}
    
    
    		this.initializeRenderer = function( canvID )
    		{
    			if ( this.bRendererInitialized )
    			{
    				console.log( "Warning-> Renderer has already been initialized!" );
    				return;
    			}
    
    			console.log( "CBRenderer is being initialized..." );
    		
    			this.canvasID = canvID;
    			this.canvasDOMElement = this.getCanvasElementWithID();
    			this.setCanvasRenderingParameters();
    			this.renderer = this.getWebGLContext();
    			this.setDefaultRenderingSettings();
    
    			this.validateInitialization();
    
    			this.bRendererInitialized = true;
    		}
    
    
    		// ====== Private Functions ====== //
    		CBRenderer.prototype.getCanvasElementWithID = function ()
    		{
    			var canvasElement = null;
    
    			canvasElement = document.getElementById( this.canvasID );
    
    			return canvasElement;
    		}
    
    
    		CBRenderer.prototype.getWebGLContext = function()
    		{
    			var webGLContext;
    
    			if ( this.canvasDOMElement == null )
    			{
    				console.log( "Warning: Could not locate canvas element with ID: " + this.canvasID );
    				console.log( "Either canvas element is missing or the DOM has not finished loading..." );
    				return;
    			}
    
    			try
    			{
    				webGLContext = this.canvasDOMElement.getContext( "webgl" ) || this.canvasDOMElement.getContext( "experimental-webgl" );
    				// PR: to support UInt32 indices for face buffer
    				var EXT = webGLContext.getExtension( "OES_element_index_uint" ) ||
          				webGLContext.getExtension( "OES_element_index_uint" ) ||
            			webGLContext.getExtension( "WEBKIT_OES_element_index_uint" );
    
            		var DepthEXT = webGLContext.getExtension( "WEBKIT_WEBGL_depth_texture" ) ||
            			webGLContext.getExtension( "WEBGL_depth_texture" );
    
            			
            		var floatTextureExt = webGLContext.getExtension( "OES_texture_float_linear" ) ||
            			webGLContext.getExtension( "OES_texture_half_float_linear" ) ||
            			webGLContext.getExtension( "WEBGL_color_buffer_float" ) ||
            			webGLContext.getExtension( "EXT_color_buffer_half_float" );
            			
            		var floatExt = webGLContext.getExtension("OES_texture_float");
       					
            		// PR: This extension is experimental and will not work for Chrome unless the D3 11 extension is
            		// enabled and WebGL experimental extensions are enabled in About:flags
            		//http://stackoverflow.com/questions/18795477/webgl-draw-buffers-not-supported-on-latest-firefox-chrome
            		var drawBuffersExtension = webGLContext.getExtension( 'WEBGL_draw_buffers' ) || 
            			webGLContext.getExtension( "GL_EXT_draw_buffers" ) ||
            			webGLContext.getExtension( "EXT_draw_buffers" );
    
            		// PR: This extension must have a reference cached as it does not append functionality to
            		// WebGL context unlike the other extensions. This reference can call drawbuffers
            		// and holds constant values such as which FBO attachment channel to render to
            		this.drawBuffers = drawBuffersExtension;
    
            		// Quick sanity check to verify extensions were obtained
            		console.log( floatExt );
            		console.log( floatTextureExt );
            		console.log( drawBuffersExtension );
    
            		if ( !DepthEXT )
            		{
            			console.log( "Warning: Depth Texture extension is not supported with the current browser!" );
            		}
    
    			}
    			catch ( webGLError )
    			{
    				return null;
    			}
    
    			return webGLContext;
    		}
    
    
    		CBRenderer.prototype.setCanvasRenderingParameters = function()
    		{
    			if ( this.canvasDOMElement == null )
    			{
    				return;
    			}
    
    			this.canvasDOMElement.width = 2048;
    			this.canvasDOMElement.height = 1024;
    		}
    
    
    		CBRenderer.prototype.setDefaultRenderingSettings = function()
    		{
    			this.renderer.clearColor( 0.0, 0.0, 0.0, 0.0 );
    			this.renderer.enable( this.renderer.DEPTH_TEST );
    			this.renderer.depthFunc( this.renderer.LEQUAL );
    			this.renderer.depthMask( true );
    			this.renderer.clearDepth( 1.0 );
    			this.renderer.enable( this.renderer.BLEND );
    			this.renderer.blendFunc( this.renderer.SRC_ALPHA, this.renderer.ONE_MINUS_SRC_ALPHA );
    			this.renderer.enable( this.renderer.CULL_FACE );
    			
    			// perspective = function (out, fovy, aspect, near, far)
    			this.m_projectionMatrix = mat4.create();
    			mat4.perspective( this.m_projectionMatrix, 50.6, ( this.canvasDOMElement.width / this.canvasDOMElement.height ), 1.0, 1000.0 );
    		}
    
    
    		CBRenderer.prototype.validateInitialization = function()
    		{
    			if ( this.renderer !== null )
    			{
    				console.log( "WebGLContext has been initialized! Browser supports WebGL" );
    				this.bWebGLContextValid = true;
    			}
    			else
    			{
    				console.log( "Warning: WebGLContext could not be loaded from webgl or experimental-webgl. Browser likely does not support WebGL" );
    				this.bWebGLContextValid = false;
    			}
    		}
    
    
    		CBRenderer.prototype.getCenterOfCanvas = function()
    		{
    			var canvasCenter = vec2.create();
    			canvasCenter[0] = 0;
    			canvasCenter[1] = 0;
    
    			if ( this.canvasDOMElement !== null )
    			{
    				canvasCenter[0] = this.canvasDOMElement.width * 0.50;
    				canvasCenter[1] = this.canvasDOMElement.height * 0.50;
    			}
    
    			return canvasCenter;
    		}
    
    
    		CBRenderer.prototype.applyProjectionMatrix = function()
    		{
    			CBMatrixStack.applyProjectionMatrixAndCache( this.m_projectionMatrix );
    		}
    
    
    		CBRenderer.prototype.applyOrthoMatrix = function()
    		{
    			var orthoMatrix = mat4.create();
    
    			// function (out, left, right, bottom, top, near, far)
    			mat4.ortho( orthoMatrix, 0.0, this.canvasDOMElement.width, 0.0, this.canvasDOMElement.height, 0.0, 1.0 );
    
    			CBMatrixStack.applyOrthoMatrixAndCache( orthoMatrix );
    		}
    
    
    		// ======== Rendering Interface ========== //
    		CBRenderer.prototype.renderScene = function( sceneToRender, deltaSeconds )
    		{
    			CBMatrixStack.clearMatrixStackAndPushIdentityMatrix();
    			this.applyProjectionMatrix();
    
    			this.renderer.blendFunc( this.renderer.SRC_ALPHA, this.renderer.ONE_MINUS_SRC_ALPHA );
    
    			this.renderer.enable( this.renderer.DEPTH_TEST );
    			this.renderer.depthMask( true );
    			this.renderer.clearDepth( 1.0 );
    
    			this.renderer.cullFace( this.renderer.BACK );
    			
    			sceneToRender.render( deltaSeconds );
    
    			this.renderer.bindTexture( this.renderer.TEXTURE_2D, null );
    		}
    
    
    		CBRenderer.prototype.renderSceneToGBuffer = function( sceneToRender, GBufferTarget, deltaSeconds )
    		{
    			CBMatrixStack.clearMatrixStackAndPushIdentityMatrix();
    			this.applyProjectionMatrix();
    
    			GBufferTarget.bindGBufferFrameBuffer();
    
    			this.renderer.enable( this.renderer.DEPTH_TEST );
    			this.renderer.depthMask( true );
    			this.renderer.clearDepth( 1.0 );
    			this.renderer.clearColor( 0.0, 0.0, 0.0, 0.0 );
    
    			// PR: Important we use this blend function so alpha blending is disabled
    			this.renderer.blendFunc( this.renderer.ONE, this.renderer.ZERO );
    			this.renderer.cullFace( this.renderer.BACK );
    
    			this.renderer.clear( this.renderer.COLOR_BUFFER_BIT | this.renderer.DEPTH_BUFFER_BIT );
    
    			sceneToRender.render( deltaSeconds );
    
    			GBufferTarget.m_dirty = false;
    			GBufferTarget.unbindGBufferFrameBuffer();
    	
    			this.renderer.bindTexture( this.renderer.TEXTURE_2D, null );
    		}
    
    
    		CBRenderer.prototype.renderSceneLightsToLBuffer = function( lightsToRender, GBufferTarget, LBufferTarget, deltaSeconds )
    		{
    			CBMatrixStack.clearMatrixStackAndPushIdentityMatrix();
    
    			this.applyProjectionMatrix();
    			CBMatrixStack.applyViewMatrixAndCache( CBMatrixStack.m_currentViewMatrix );
    
    			LBufferTarget.bindLBufferFrameBuffer();
    
    			this.renderer.disable( this.renderer.DEPTH_TEST );
    			this.renderer.depthMask( false );
    			this.renderer.clearColor( 0.0, 0.0, 0.0, 0.0 );
    
    			this.renderer.clear( this.renderer.COLOR_BUFFER_BIT );
    
    			// PR: This blend mode allows accumulation to occur when rendering to the light buffer
    			// Also, for light volume renderering, there is only a need to draw backfaces
    			this.renderer.blendFunc( this.renderer.ONE, this.renderer.ONE );
    			this.renderer.cullFace( this.renderer.FRONT );
    
    			for ( var i = 0; i < lightsToRender.length; ++i )
    			{
    				var light = lightsToRender[i];
    				light.applyLightAndRenderToLBuffer( GBufferTarget, deltaSeconds );
    			}
    
    			this.renderer.cullFace( this.renderer.BACK );
    
    			this.renderer.enable( this.renderer.DEPTH_TEST );
    			this.renderer.depthMask( true );
    
    			LBufferTarget.unbindLBufferFrameBuffer();
    		}
    
    
    		CBRenderer.prototype.renderPostRenderScene = function( sceneToRender, GBufferTarget, deltaSeconds )
    		{
    			CBMatrixStack.clearMatrixStackAndPushIdentityMatrix();
    			CBMatrixStack.clearMatrixMVPCache();
    
    			this.applyOrthoMatrix();
    
    			this.renderer.disable( this.renderer.DEPTH_TEST );
    			this.renderer.depthMask( false );
    
    			this.renderer.blendFunc( this.renderer.ONE, this.ZERO );
    			this.renderer.disable( this.renderer.CULL_FACE );
    
    			sceneToRender.render( deltaSeconds, GBufferTarget );
    
    			this.renderer.enable( this.renderer.CULL_FACE );
    
    			this.renderer.bindTexture( this.renderer.TEXTURE_2D, null );
    		}
    
    
    		CBRenderer.prototype.renderSceneFinalPass = function( sceneToRender, GBufferTarget, LBufferTarget, deltaSeconds )
    		{
    			CBMatrixStack.clearMatrixStackAndPushIdentityMatrix();
    			CBMatrixStack.clearMatrixMVPCache();
    
    			this.applyOrthoMatrix();
    
    			this.renderer.disable( this.renderer.DEPTH_TEST );
    			this.renderer.depthMask( false );
    			this.renderer.disable( this.renderer.CULL_FACE );
    
    			this.renderer.blendFunc( this.renderer.SRC_ALPHA, this.renderer.ONE_MINUS_SRC_ALPHA );
    
    			sceneToRender.render( GBufferTarget, LBufferTarget, deltaSeconds );
    
    			this.renderer.enable( this.renderer.CULL_FACE );
    		}
    	}
    
    	// Singleton Reference
    	var rendererInstance 		= null;
    
    	function createCBRendererInstance()
    	{
    		var rendererInstance = new CBRenderer();
    		return rendererInstance;
    	}
    
    	// Handle to the singleton instance
    	return {
    		getSharedRenderer: function(){
    			if ( rendererInstance == null )
    			{
    				rendererInstance = createCBRendererInstance();
    				rendererInstance.constructor = null;
    			}
    
    			return rendererInstance;	
    		}
    	};
    })();
    							
  • GBuffer.js

    
    define( [ "CBRenderer", "MathUtil", "Collections", "GLMatrix", "Texture" ], function( CBRenderer )
    {
    	console.log( "GBuffer.js has finished loading!" );
    });
    
    
    var INVALID_GBUFFER_TEXTURE_ID = -1;
    
    /*
    	GBuffer.js encapsulates the FrameBuffer Object which holds
    	Render Targets One, Two, and Three. For this demo, it also
    	includes the Depth texture to prove the extension is working
    	and also allows for post processing effects in the future
    */
    var GBuffer = function()
    {
    
    	this.m_frameBuffer					= null;
    	this.m_renderBuffer 				= null;
    
    	this.m_diffuseComponentTexture 		= INVALID_GBUFFER_TEXTURE_ID;
    	this.m_renderTargetTwoTexture 		= INVALID_GBUFFER_TEXTURE_ID;
    	this.m_renderTargetThreeTexture 	= INVALID_GBUFFER_TEXTURE_ID;
    	this.m_depthComponentTexture 		= INVALID_GBUFFER_TEXTURE_ID;
    
    	this.m_dirty 						= true;
    	this.m_initialized 					= false;
    }
    
    
    GBuffer.prototype = 
    {
    	constructor : GBuffer,
    
    
    	initializeGBuffer : function()
    	{
    		this.initializeFrameBuffer();
    
    		if ( this.m_frameBuffer == null )
    		{
    			this.m_initialized = false;
    			console.log( "Warning: FrameBuffer for GBuffer could not be initialized" );
    		}
    
    		this.m_dirty = true;
    	},
    
    
    	initializeFrameBuffer : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		// Frame Buffer
    		this.m_frameBuffer = sharedRenderer.renderer.createFramebuffer();
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, this.m_frameBuffer );
    
    		// Diffuse Component and Specular Level :: AKA Render Target One
    		this.m_diffuseComponentTexture = sharedRenderer.renderer.createTexture();
    
    		sharedRenderer.renderer.bindTexture( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			this.m_diffuseComponentTexture );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	sharedRenderer.renderer.TEXTURE_MAG_FILTER,
    		  	sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_MIN_FILTER, 
    			sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_S, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_T, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    		
    		sharedRenderer.renderer.texImage2D( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.canvasDOMElement.width,
    		 	sharedRenderer.canvasDOMElement.height,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.renderer.FLOAT, 
    		 	null );
    
    
    		// Render Target Two :: Normals and Spec Power
    		this.m_renderTargetTwoTexture = sharedRenderer.renderer.createTexture();
    
    		sharedRenderer.renderer.bindTexture( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			this.m_renderTargetTwoTexture );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	sharedRenderer.renderer.TEXTURE_MAG_FILTER,
    		  	sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_MIN_FILTER, 
    			sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_S, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_T, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    		
    		sharedRenderer.renderer.texImage2D( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.canvasDOMElement.width,
    		 	sharedRenderer.canvasDOMElement.height,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.renderer.FLOAT, 
    		 	null );
    
    
    		// Render Target Three :: Position Data
    		// Note: This can be exchanged for specular color and depth stored in the alpha channel
    		// Position data can be extracted from depth utilizing the inverse perspective matrix
    		// To keep the demo simple, world position data is stored in r,g,b and currently
    		// specular color is not supported in this demo
    		this.m_renderTargetThreeTexture = sharedRenderer.renderer.createTexture();
    
    		sharedRenderer.renderer.bindTexture( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			this.m_renderTargetThreeTexture );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	sharedRenderer.renderer.TEXTURE_MAG_FILTER,
    		  	sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_MIN_FILTER, 
    			sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_S, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_T, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    		
    		sharedRenderer.renderer.texImage2D( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.canvasDOMElement.width,
    		 	sharedRenderer.canvasDOMElement.height,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.renderer.FLOAT, 
    		 	null );
    
    
    		// Depth 
    		// Note: Not needed for Deferred Shading but useful for other post process effects and demonstration of extension working
    		this.m_depthComponentTexture = sharedRenderer.renderer.createTexture();
    		sharedRenderer.renderer.bindTexture( sharedRenderer.renderer.TEXTURE_2D, 
    			this.m_depthComponentTexture );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	sharedRenderer.renderer.TEXTURE_MAG_FILTER,
    		  	sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_MIN_FILTER, 
    			sharedRenderer.renderer.NEAREST );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_S,
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	sharedRenderer.renderer.TEXTURE_WRAP_T, 
    		 	sharedRenderer.renderer.CLAMP_TO_EDGE );
    
    		sharedRenderer.renderer.texImage2D( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	0,
    		 	sharedRenderer.renderer.DEPTH_COMPONENT,
    		 	sharedRenderer.canvasDOMElement.width,
    		 	sharedRenderer.canvasDOMElement.height,
    		 	0,
    		 	sharedRenderer.renderer.DEPTH_COMPONENT,
    		 	sharedRenderer.renderer.UNSIGNED_SHORT, 
    		 	null );
    
    		// FrameBuffer
    		// RT_ONE
    		sharedRenderer.renderer.framebufferTexture2D( 
    			sharedRenderer.renderer.FRAMEBUFFER,
    		 	sharedRenderer.drawBuffers.COLOR_ATTACHMENT0_WEBGL, 
    		 	sharedRenderer.renderer.TEXTURE_2D, 
    		 	this.m_diffuseComponentTexture, 
    		 	0 );
    
    		// RT_TWO
    		sharedRenderer.renderer.framebufferTexture2D( 
    			sharedRenderer.renderer.FRAMEBUFFER,
    		 	sharedRenderer.drawBuffers.COLOR_ATTACHMENT1_WEBGL, 
    		 	sharedRenderer.renderer.TEXTURE_2D, 
    		 	this.m_renderTargetTwoTexture, 
    		 	0 );
    
    		// RT_THREE
    		sharedRenderer.renderer.framebufferTexture2D( 
    			sharedRenderer.renderer.FRAMEBUFFER,
    		 	sharedRenderer.drawBuffers.COLOR_ATTACHMENT2_WEBGL, 
    		 	sharedRenderer.renderer.TEXTURE_2D, 
    		 	this.m_renderTargetThreeTexture, 
    		 	0 );
    		
    		// Depth
    		sharedRenderer.renderer.framebufferTexture2D( 
    			sharedRenderer.renderer.FRAMEBUFFER,
    		 	sharedRenderer.renderer.DEPTH_ATTACHMENT, 
    		 	sharedRenderer.renderer.TEXTURE_2D, 
    		 	this.m_depthComponentTexture, 
    		 	0 );
    
    	
    		console.log( "GBuffer FrameBuffer status after initialization: " );
    		console.log( sharedRenderer.renderer.checkFramebufferStatus( sharedRenderer.renderer.FRAMEBUFFER) == sharedRenderer.renderer.FRAMEBUFFER_COMPLETE );
    
    		sharedRenderer.drawBuffers.drawBuffersWEBGL([
    		  sharedRenderer.drawBuffers.COLOR_ATTACHMENT0_WEBGL, 
    		  sharedRenderer.drawBuffers.COLOR_ATTACHMENT1_WEBGL, 
    		  sharedRenderer.drawBuffers.COLOR_ATTACHMENT2_WEBGL, 
    		]);
    		
    		// Unbind buffers and textures
    		sharedRenderer.renderer.bindTexture( sharedRenderer.renderer.TEXTURE_2D, null );
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, null );
    	},
    
    
    	bindGBufferFrameBuffer : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, this.m_frameBuffer );
    	},
    
    
    	unbindGBufferFrameBuffer : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, null );
    	}
    }								
    							
  • LBuffer.js

    define( ["CBRenderer", "MathUtil", "Collections", "GLMatrix", "Texture" ], function()
    {
    	console.log( "LBuffer.js has finished loading" );
    });
    
    var INVALID_LBUFFER_TEXTURE_ID = -1;
    
    /*
    	LBuffer.js encapsulates the FrameBuffer Object which holds
    	Render Targets Four and Five. Four holds Diffuse lighting
    	and Five holds Specular lighting. These are meant to be used
    	as accumulation buffers, where each light is rendered with
    	geometry which encapsulates the area it effects. The blend func is
    	set to ONE,ONE and each light render accumulates data to the Render
    	Targets. If one desires, the alpha channels can be used to store
    	other meta data.
    */
    var LBuffer = function()
    {
    	this.m_frameBuffer					= null;
    
    	this.m_diffuseAccumulationTarget 	= INVALID_LBUFFER_TEXTURE_ID;
    	this.m_specularAccumulationTarget 	= INVALID_LBUFFER_TEXTURE_ID;
    
    	this.m_initialized 					= false;
    }
    
    
    LBuffer.prototype = 
    {
    	constructor : LBuffer,
    
    
    	initializeLBuffer : function()
    	{
    		this.initializeFrameBuffer();
    	},
    
    
    	initializeFrameBuffer : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		// Frame Buffer
    		this.m_frameBuffer = sharedRenderer.renderer.createFramebuffer();
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, this.m_frameBuffer );
    
    		// Diffuse Accumulation Component :: Render Target Four
    		this.m_diffuseAccumulationTarget = sharedRenderer.renderer.createTexture();
    
    		sharedRenderer.renderer.bindTexture( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			this.m_diffuseAccumulationTarget );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	sharedRenderer.renderer.TEXTURE_MAG_FILTER,
    		  	sharedRenderer.renderer.LINEAR );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_MIN_FILTER, 
    			sharedRenderer.renderer.LINEAR );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_S, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_T, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    		
    		sharedRenderer.renderer.texImage2D( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.canvasDOMElement.width,
    		 	sharedRenderer.canvasDOMElement.height,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.renderer.FLOAT, 
    		 	null );
    
    
    		// Specular Accumulation Component :: Render Target Five
    		this.m_specularAccumulationTarget = sharedRenderer.renderer.createTexture();
    
    		sharedRenderer.renderer.bindTexture( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			this.m_specularAccumulationTarget );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	sharedRenderer.renderer.TEXTURE_MAG_FILTER,
    		  	sharedRenderer.renderer.LINEAR );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_MIN_FILTER, 
    			sharedRenderer.renderer.LINEAR );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_S, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    
    		sharedRenderer.renderer.texParameteri( 
    			sharedRenderer.renderer.TEXTURE_2D, 
    			sharedRenderer.renderer.TEXTURE_WRAP_T, 
    			sharedRenderer.renderer.CLAMP_TO_EDGE );
    		
    		sharedRenderer.renderer.texImage2D( 
    			sharedRenderer.renderer.TEXTURE_2D,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.canvasDOMElement.width,
    		 	sharedRenderer.canvasDOMElement.height,
    		 	0,
    		 	sharedRenderer.renderer.RGBA,
    		 	sharedRenderer.renderer.FLOAT, 
    		 	null );
    
    		// FrameBuffer
    		// Diffuse Accumulation
    		sharedRenderer.renderer.framebufferTexture2D( 
    			sharedRenderer.renderer.FRAMEBUFFER,
    		 	sharedRenderer.drawBuffers.COLOR_ATTACHMENT0_WEBGL, 
    		 	sharedRenderer.renderer.TEXTURE_2D, 
    		 	this.m_diffuseAccumulationTarget, 
    		 	0 );
    
    		// Specular Accumulation
    		sharedRenderer.renderer.framebufferTexture2D( 
    			sharedRenderer.renderer.FRAMEBUFFER,
    		 	sharedRenderer.drawBuffers.COLOR_ATTACHMENT1_WEBGL, 
    		 	sharedRenderer.renderer.TEXTURE_2D, 
    		 	this.m_specularAccumulationTarget, 
    		 	0 );
    
    		console.log( "LBuffer FrameBuffer status after initialization: " );
    		console.log( sharedRenderer.renderer.checkFramebufferStatus( sharedRenderer.renderer.FRAMEBUFFER) == sharedRenderer.renderer.FRAMEBUFFER_COMPLETE );
    
    		sharedRenderer.drawBuffers.drawBuffersWEBGL([
    		  sharedRenderer.drawBuffers.COLOR_ATTACHMENT0_WEBGL, 
    		  sharedRenderer.drawBuffers.COLOR_ATTACHMENT1_WEBGL, 
    		]);
    		
    		// Unbind buffers and textures
    		sharedRenderer.renderer.bindTexture( sharedRenderer.renderer.TEXTURE_2D, null );
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, null );
    	},
    
    
    	bindLBufferFrameBuffer : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, this.m_frameBuffer );
    	},
    
    
    	unbindLBufferFrameBuffer : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.bindFramebuffer( sharedRenderer.renderer.FRAMEBUFFER, null );
    	}
    }
    							
  • PointLight.js

    define( [ "LBuffer", "GLMatrix", "MathUtil", "MatrixStack", "Collections", "CBRenderer", "Texture", "ShaderManager", "Material", "CBEngine" ], function()
    {
    	console.log( "PointLight.js has finished loading" );
    });
    
    // Point Light Shaders
    var POINT_LIGHT_VERTEX_SHADER_NAME 			= "PointLightVertexShader.glsl";
    var POINT_LIGHT_FRAGMENT_SHADER_NAME 		= "PointLightFragmentShader.glsl";
    
    // Point Light Uniforms
    var LIGHT_WORLD_POSITION_UNIFORM 			= "u_lightWorldPosition";
    var LIGHT_COLOR_AND_BRIGHTNESS_UNIFORM 		= "u_lightColorAndBrightness";
    var LIGHT_OUTER_RADIUS_UNIFORM 				= "u_lightOuterRadius";
    var LIGHT_INNER_RADIUS_UNIFORM 			 	= "u_lightInnerRadius";
    
    // Physics Constants
    var LIGHT_MOVE_SPEED 						= 30.0;
    var LIGHT_MIN_ORBIT_RADIUS 					= 40.0;
    var LIGHT_MAX_ORBIT_RADIUS 					= 300.0;
    
    /*
    	PointLight.js is a class which holds all functions, shaders references, and
    	meta data (Color,position,radius, etc...) needed to render a point light to the LBuffer. 
    	Shaders are loaded from the cache upon initialization and the appropriate geometry, which is a
    	sphere for a point light, is initialized. 
    */
    var PointLight = function( outerRadius, innerRadius )
    {
    	// Light properties
    	this.m_position 				= vec3.create();
    	this.m_innerRadius 				= innerRadius;
    	this.m_outerRadius 				= outerRadius;
    	this.m_colorAndBrightness 		= vec4.create();
    
    	this.m_velocity 				= vec3.create();
    	this.m_theta 					= 0.0;
    	this.m_rotationSpeed 			= 0.50;
    	this.m_orbitRadius 				= 70.0;
    	this.m_orbitIndexOne 			= 0;
    	this.m_orbitIndexTwo 			= 1;
    
    	// Shader Related Variables
    	this.m_shaderProgram 			= null;
    	this.m_positionAttribute 		= new ShaderAttribute( POSITION_ATTRIBUTE_NAME );
    	this.m_normalAttribute 			= new ShaderAttribute( NORMAL_ATTRIBUTE_NAME );
    
    	this.m_vertexBuffer 			= null;
    	this.m_normalBuffer 			= null;
    	this.m_faceBuffer 				= null;
    
    	this.m_NPoints 				 	= 0;
    
    	this.m_shaderUniformParams 		= new FastMap();
    }
    
    
    PointLight.prototype = 
    {
    	constructor : PointLight,
    
    	update : function( deltaSeconds )
    	{
    		if ( LIGHT_MOVE_STATE == 1 )
    		{
    			this.m_theta += this.m_rotationSpeed * deltaSeconds;
    			this.m_position[ this.m_orbitIndexOne ] = Math.cos( this.m_theta ) * this.m_orbitRadius;
    			this.m_position[ this.m_orbitIndexTwo ] = Math.sin( this.m_theta ) * this.m_orbitRadius;
    			
    			this.clampLightPositionsToWorldBounds();
    		}
    	},
    
    
    	applyLightAndRenderToLBuffer : function( GBufferTarget, deltaSeconds )
    	{
    		this.applyMatrixTransforms();
    
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.useProgram( this.m_shaderProgram );
    
    		this.enableAttributes();
    		this.setAttributePointers();
    		this.bindTextures( GBufferTarget );
    		this.updateUniforms( GBufferTarget );
    
    		sharedRenderer.renderer.drawElements( sharedRenderer.renderer.TRIANGLES, this.m_NPoints, sharedRenderer.renderer.UNSIGNED_INT, 0 );
    
    		this.unbindTextures();
    		this.disableAttribues();
    	},
    
    
    	applyMatrixTransforms : function()
    	{
    		var translationMatrix 	= mat4.create();
    
    		mat4.translate( translationMatrix, translationMatrix, this.m_position );
    		CBMatrixStack.applyModelMatrixAndCache( translationMatrix );
    	},
    
    
    	updateUniforms : function( GBufferTarget )
    	{
    		this.updateLightUniformData();
    		this.updateGBufferTargetUniforms( GBufferTarget );
    		this.updateMVPAndScreenUniforms();
    	},
    	
    
    	updateLightUniformData : function()
    	{
    		var lightWorldPos 				= this.m_shaderUniformParams.get( LIGHT_WORLD_POSITION_UNIFORM, null );
    		var lightColorAndBrightness 	= this.m_shaderUniformParams.get( LIGHT_COLOR_AND_BRIGHTNESS_UNIFORM, null );
    		var lightOuterRadius 			= this.m_shaderUniformParams.get( LIGHT_OUTER_RADIUS_UNIFORM, null );
    		var lightInnerRadius 			= this.m_shaderUniformParams.get( LIGHT_INNER_RADIUS_UNIFORM, null );
    
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		if ( lightWorldPos !== null )
    		{
    			sharedRenderer.renderer.uniform3f( lightWorldPos.m_uniformLocation, 
    				this.m_position[0], 
    				this.m_position[1], 
    				this.m_position[2] );
    		}
    
    		if ( lightColorAndBrightness !== null )
    		{
    			sharedRenderer.renderer.uniform4f( lightColorAndBrightness.m_uniformLocation, 
    				this.m_colorAndBrightness[0], 
    				this.m_colorAndBrightness[1], 
    				this.m_colorAndBrightness[2], 
    				this.m_colorAndBrightness[3] );
    		}
    
    		if ( lightOuterRadius !== null )
    		{
    			sharedRenderer.renderer.uniform1f( lightOuterRadius.m_uniformLocation, this.m_outerRadius );
    		}
    
    		if ( lightInnerRadius !== null )
    		{
    			sharedRenderer.renderer.uniform1f( lightInnerRadius.m_uniformLocation, this.m_innerRadius );
    		}
    	},
    
    
    	updateGBufferTargetUniforms : function( GBufferTarget )
    	{
    		var rtTwoUniform 				= this.m_shaderUniformParams.get( RENDER_TARGET_TWO_UNIFORM_NAME, null );
    		var rtThreeUniform 				= this.m_shaderUniformParams.get( RENDER_TARGET_THREE_UNIFORM_NAME, null );
    
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		if ( rtTwoUniform !== null )
    		{	
    			sharedRenderer.renderer.uniform1i( rtTwoUniform.m_uniformLocation, 0 );
    		}
    
    		if ( rtThreeUniform !== null )
    		{
    			sharedRenderer.renderer.uniform1i( rtThreeUniform.m_uniformLocation, 1 );
    		}
    	},
    
    
    	bindTextures : function( GBufferTarget )
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.activeTexture( sharedRenderer.renderer.TEXTURE0 );
    	    sharedRenderer.renderer.bindTexture( sharedRenderer.renderer.TEXTURE_2D, GBufferTarget.m_renderTargetTwoTexture );
    
    	    sharedRenderer.renderer.activeTexture( sharedRenderer.renderer.TEXTURE1 );
    	    sharedRenderer.renderer.bindTexture( sharedRenderer.renderer.TEXTURE_2D, GBufferTarget.m_renderTargetThreeTexture );
    	},
    
    
    	unbindTextures : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.activeTexture( sharedRenderer.renderer.TEXTURE0 );
    	    sharedRenderer.renderer.bindTexture( sharedRenderer.renderer.TEXTURE_2D, null );
    
    	    sharedRenderer.renderer.activeTexture( sharedRenderer.renderer.TEXTURE1 );
    	    sharedRenderer.renderer.bindTexture( sharedRenderer.renderer.TEXTURE_2D, null );
    	},
    
    
    	updateMVPAndScreenUniforms : function()
    	{
    		var modelUniform 				= this.m_shaderUniformParams.get( MODEL_MATRIX_UNIFORM_NAME, null );
    		var viewUniform 				= this.m_shaderUniformParams.get( VIEW_MATRIX_UNIFORM_NAME, null );
    		var projectionUniform 			= this.m_shaderUniformParams.get( PROJECTION_MATRIX_UNIFORM_NAME, null );
    		var cameraPositionUniform 		= this.m_shaderUniformParams.get( CAMERA_POSITION_UNIFORM_NAME, null );
    
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		if ( modelUniform !== null )
    		{
    			sharedRenderer.renderer.uniformMatrix4fv( modelUniform.m_uniformLocation, false, CBMatrixStack.m_currentModelMatrix );
    		}
    
    		if ( viewUniform !== null )
    		{
    			sharedRenderer.renderer.uniformMatrix4fv( viewUniform.m_uniformLocation, false, CBMatrixStack.m_currentViewMatrix );
    		}
    
    		if ( projectionUniform !== null )
    		{
    			sharedRenderer.renderer.uniformMatrix4fv( projectionUniform.m_uniformLocation, false, CBMatrixStack.m_currentProjectionMatrix );
    		}
    
    		if ( cameraPositionUniform !== null )
    		{
    			var cameraPos = CBMatrixStack.m_currentCameraPosition;
    			sharedRenderer.renderer.uniform3f( cameraPositionUniform.m_uniformLocation, cameraPos[0], cameraPos[1], cameraPos[2] );
    		}
    
    		var inverseScreenWidthUniform = this.m_shaderUniformParams.get( INVERSE_SCREEN_WIDTH_UNIFORM_NAME, null );
    		var inverseScreenHeightUniform = this.m_shaderUniformParams.get( INVERSE_SCREEN_HEIGHT_UNIFORM_NAME, null );
    
    		if ( inverseScreenWidthUniform !== null )
    		{
    			sharedRenderer.renderer.uniform1f( inverseScreenWidthUniform.m_uniformLocation, ( 1.0 / sharedRenderer.canvasDOMElement.width ) );
    		}
    
    		if ( inverseScreenHeightUniform !== null )
    		{
    			sharedRenderer.renderer.uniform1f( inverseScreenHeightUniform.m_uniformLocation, ( 1.0 / sharedRenderer.canvasDOMElement.height ) );
    		}
    	},
    
    
    	initializePointLight : function()
    	{
    		this.initializePointLightSphere();
    		LoadShaderProgramFromCacheOrCreateProgram( POINT_LIGHT_VERTEX_SHADER_NAME, POINT_LIGHT_FRAGMENT_SHADER_NAME, this );
    		this.determineOrbit();
    	},
    
    
    	initializePointLightSphere : function()
    	{
    		var latitudeBands = 30;
        	var longitudeBands = 30;
    
    		// Verts and Normals
    		var vertexPositionData = [];
    	    var normalData = [];
    	    for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) 
    	    {
    	      var theta = latNumber * Math.PI / latitudeBands;
    	      var sinTheta = Math.sin( theta );
    	      var cosTheta = Math.cos( theta );
    
    	      for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) 
    	      {
    	        var phi = longNumber * 2 * Math.PI / longitudeBands;
    	        var sinPhi = Math.sin(phi);
    	        var cosPhi = Math.cos(phi);
    
    	        var x = cosPhi * sinTheta;
    	        var y = cosTheta;
    	        var z = sinPhi * sinTheta;
    
    	        normalData.push( x );
    	        normalData.push( y );
    	        normalData.push( z );
    
    	        vertexPositionData.push( this.m_outerRadius * x );
    	        vertexPositionData.push( this.m_outerRadius * y );
    	        vertexPositionData.push( this.m_outerRadius * z );
    	      }
    	    }
    
    	    // Face Data
    	    var indexData = [];
    	    for (var latNumber = 0; latNumber < latitudeBands; latNumber++) 
    	    {
    	      for (var longNumber = 0; longNumber < longitudeBands; longNumber++) 
    	      {
    	        var first = (latNumber * (longitudeBands + 1)) + longNumber;
    	        var second = first + longitudeBands + 1;
    	        indexData.push( first );
    	        indexData.push( second );
    	        indexData.push( first + 1 );
    
    	        indexData.push( second );
    	        indexData.push( second + 1 );
    	        indexData.push( first + 1 );
    	      }
    	    }
    
    	    this.createIBOForVertexDataAndFaceData( vertexPositionData, normalData, indexData );
    	},
    
    
    	createIBOForVertexDataAndFaceData : function( vertexData, normalData, faceData )
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		// Vertex Buffer
    		this.m_vertexBuffer = sharedRenderer.renderer.createBuffer();
    		sharedRenderer.renderer.bindBuffer( sharedRenderer.renderer.ARRAY_BUFFER, this.m_vertexBuffer );
    		sharedRenderer.renderer.bufferData( sharedRenderer.renderer.ARRAY_BUFFER, 
    			new Float32Array( vertexData ),
    			sharedRenderer.renderer.STATIC_DRAW );
    
    		// Normal Buffer
    		this.m_normalBuffer = sharedRenderer.renderer.createBuffer();
    		sharedRenderer.renderer.bindBuffer( sharedRenderer.renderer.ARRAY_BUFFER, this.m_normalBuffer );
    		sharedRenderer.renderer.bufferData( sharedRenderer.renderer.ARRAY_BUFFER, 
    			new Float32Array( normalData ),
    			sharedRenderer.renderer.STATIC_DRAW );
    
    		// Face Buffer
    		this.m_faceBuffer = sharedRenderer.renderer.createBuffer();
    		sharedRenderer.renderer.bindBuffer( sharedRenderer.renderer.ELEMENT_ARRAY_BUFFER, this.m_faceBuffer );
    		sharedRenderer.renderer.bufferData( sharedRenderer.renderer.ELEMENT_ARRAY_BUFFER, 
    			new Uint32Array( faceData ),
    			sharedRenderer.renderer.STATIC_DRAW );
    
    		this.m_NPoints = faceData.length;
    	},
    
    
    	createCoreShaderUniformParams : function()
    	{
    		// ===== Model | View | Projection | Other Math Structures ===== //
    		var identityMatrix 	  			= mat4.create(); 
    		var modelUniformParam 			= new ShaderUniform( this );
    		var viewUniformParam 			= new ShaderUniform( this );
    		var projectionUniformParam 		= new ShaderUniform( this );
    		var cameraPositionUniformParam 	= new ShaderUniform( this );
    		var inverseScreenWidthParam 	= new ShaderUniform( this );
    		var inverseScreenHeightParam 	= new ShaderUniform( this );
    
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		modelUniformParam.setUniformParameter( MODEL_MATRIX_UNIFORM_NAME, identityMatrix );
    		viewUniformParam.setUniformParameter( VIEW_MATRIX_UNIFORM_NAME, identityMatrix );
    		projectionUniformParam.setUniformParameter( PROJECTION_MATRIX_UNIFORM_NAME, identityMatrix );
    		cameraPositionUniformParam.setUniformParameter( CAMERA_POSITION_UNIFORM_NAME, vec3.create() ); 
    		inverseScreenWidthParam.setUniformParameter( INVERSE_SCREEN_WIDTH_UNIFORM_NAME, ( 1.0 / sharedRenderer.canvasDOMElement.width ) );
    		inverseScreenHeightParam.setUniformParameter( INVERSE_SCREEN_HEIGHT_UNIFORM_NAME, ( 1.0 / sharedRenderer.canvasDOMElement.height ) );
    		
    		this.m_shaderUniformParams.set( modelUniformParam.m_uniformName, modelUniformParam );
    		this.m_shaderUniformParams.set( viewUniformParam.m_uniformName, viewUniformParam );
    		this.m_shaderUniformParams.set( projectionUniformParam.m_uniformName, projectionUniformParam );
    		this.m_shaderUniformParams.set( cameraPositionUniformParam.m_uniformName, cameraPositionUniformParam );
    		this.m_shaderUniformParams.set( inverseScreenWidthParam.m_uniformName, inverseScreenWidthParam );
    		this.m_shaderUniformParams.set( inverseScreenHeightParam.m_uniformName, inverseScreenHeightParam );
    
    		// ====== TEXTURES ====== //
    		var renderTargetTwoParam 						= new ShaderUniform( this );
    		var renderTargetThreeParam 						= new ShaderUniform( this );
    
    		renderTargetTwoParam.setUniformParameter( RENDER_TARGET_TWO_UNIFORM_NAME, UNIFORM_NOT_LOCATED );
    		this.m_shaderUniformParams.set( renderTargetTwoParam.m_uniformName, renderTargetTwoParam );
    
    		renderTargetThreeParam.setUniformParameter( RENDER_TARGET_THREE_UNIFORM_NAME, UNIFORM_NOT_LOCATED );
    		this.m_shaderUniformParams.set( renderTargetThreeParam.m_uniformName, renderTargetThreeParam );
    
    		// ====== POINT LIGHT DATA ====== //
    		var pointLightPositionParam 					= new ShaderUniform( this );
    		var pointLightColorAndBrightnessParam 			= new ShaderUniform( this );
    		var pointLightOuterRadiusParam 					= new ShaderUniform( this );
    		var pointLightInnerRadiusParam 					= new ShaderUniform( this );
    
    		pointLightPositionParam.setUniformParameter( LIGHT_WORLD_POSITION_UNIFORM, UNIFORM_NOT_LOCATED );
    		this.m_shaderUniformParams.set( pointLightPositionParam.m_uniformName, pointLightPositionParam );
    
    		pointLightColorAndBrightnessParam.setUniformParameter( LIGHT_COLOR_AND_BRIGHTNESS_UNIFORM, UNIFORM_NOT_LOCATED );
    		this.m_shaderUniformParams.set( pointLightColorAndBrightnessParam.m_uniformName, pointLightColorAndBrightnessParam );
    
    		pointLightOuterRadiusParam.setUniformParameter( LIGHT_OUTER_RADIUS_UNIFORM, UNIFORM_NOT_LOCATED );
    		this.m_shaderUniformParams.set( pointLightOuterRadiusParam.m_uniformName, pointLightOuterRadiusParam );
    
    		pointLightInnerRadiusParam.setUniformParameter( LIGHT_INNER_RADIUS_UNIFORM, UNIFORM_NOT_LOCATED );
    		this.m_shaderUniformParams.set( pointLightInnerRadiusParam.m_uniformName, pointLightInnerRadiusParam );
    	},
    
    
    	findAttributeLocations : function()
    	{
    		if ( this.m_shaderProgram == null )
    		{
    			console.log( "Warning: Cannot find locations of material attributes if shader program is null" );
    			return;
    		}
    
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    		this.m_positionAttribute.m_attributeLocation 		= sharedRenderer.renderer.getAttribLocation( this.m_shaderProgram, this.m_positionAttribute.m_attributeName );
    	},
    
    
    	enableAttributes : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.enableVertexAttribArray( this.m_positionAttribute.m_attributeLocation );
    	},
    
    
    	setAttributePointers : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.bindBuffer( sharedRenderer.renderer.ARRAY_BUFFER, this.m_vertexBuffer );
    		sharedRenderer.renderer.vertexAttribPointer( this.m_positionAttribute.m_attributeLocation, 3, sharedRenderer.renderer.FLOAT, false, 0, 0  );
    		
    		sharedRenderer.renderer.bindBuffer( sharedRenderer.renderer.ELEMENT_ARRAY_BUFFER, this.m_faceBuffer );
    	},
    
    
    	disableAttribues : function()
    	{
    		var sharedRenderer = CBRenderer.getSharedRenderer();
    
    		sharedRenderer.renderer.disableVertexAttribArray( this.m_positionAttribute.m_attributeLocation );
    	},
    
    	// For Demonstration purposes 
    	clampLightPositionsToWorldBounds : function()
    	{
    		if ( this.m_position[0] < WORLD_MIN_BOUND )
    		{
    			this.m_position[0] = WORLD_MIN_BOUND;
    		}
    
    		if ( this.m_position[1] < WORLD_MIN_BOUND )
    		{
    			this.m_position[1] = WORLD_MIN_BOUND;
    		}
    
    		if ( this.m_position[2] < WORLD_MIN_BOUND )
    		{
    			this.m_position[2] = WORLD_MIN_BOUND;
    		}
    
    		if ( this.m_position[0] > X_WORLD_BOUND )
    		{
    			this.m_position[0] = X_WORLD_BOUND;
    		}
    
    		if ( this.m_position[1] > Y_WORLD_BOUND )
    		{
    			this.m_position[1] = Y_WORLD_BOUND;
    		}
    
    		if ( this.m_position[2] > Z_WORLD_BOUND )
    		{
    			this.m_position[2] = Z_WORLD_BOUND;
    		}
    	},
    
    	// For Demonstration purposes 
    	determineOrbit : function()
    	{
    		var randomForIndex = Math.random();
    		if ( randomForIndex < 0.15 )
    		{
    			this.m_orbitIndexOne = 0;
    			this.m_orbitIndexTwo = 1;
    		}
    		else if ( randomForIndex >= 0.15 && randomForIndex < 0.30 )
    		{
    			this.m_orbitIndexOne = 1;
    			this.m_orbitIndexTwo = 2;
    		}
    		else if ( randomForIndex >= 0.30 && randomForIndex < 0.45 )
    		{
    			this.m_orbitIndexOne = 0;
    			this.m_orbitIndexTwo = 2;
    		}
    		else if ( randomForIndex >= 0.45 && randomForIndex < 0.60 )
    		{
    			this.m_orbitIndexOne = 2;
    			this.m_orbitIndexTwo = 0;
    		}
    		else if ( randomForIndex >= 0.60 && randomForIndex < 0.75 )
    		{
    			this.m_orbitIndexOne = 2;
    			this.m_orbitIndexTwo = 1;
    		}
    		else if ( randomForIndex >= 0.75 && randomForIndex <= 1.00 )
    		{
    			this.m_orbitIndexOne = 1;
    			this.m_orbitIndexTwo = 0;
    		}
    
    		this.m_orbitRadius = Math.random() * LIGHT_MAX_ORBIT_RADIUS;
    		if ( this.m_orbitRadius < LIGHT_MIN_ORBIT_RADIUS )
    		{
    			this.m_orbitRadius = LIGHT_MIN_ORBIT_RADIUS;
    		}
    	},
    
    
    
    	// ===== Event and Lifecycle Functions ===== //
    	OnShaderProgramLoaded : function( ShaderProgram )
    	{
    		this.m_shaderProgram = ShaderProgram;
    		this.createCoreShaderUniformParams();
    		this.findAttributeLocations();
    	},
    }
    							
  • GeometryFragmentShader.glsl

    #extension GL_EXT_draw_buffers : require
    precision highp float;
    
    varying vec3 vColor;
    varying vec3 vNormal;
    varying vec2 vTexCoords;
    varying vec4 vWorldPosition;
    
    // Fixed until exporter/importer is integrated
    float specularLevel = 0.35;
    float specularPower = 32.0;
    
    uniform sampler2D s_diffuseTexture;
    
    void main(void) 
    {
    	vec3 normalizedInterpolatedNormal = normalize( vNormal );
    
    	vec4 renderTargetOne 	= texture2D( s_diffuseTexture, vTexCoords );
    	vec4 renderTargetTwo 	= vec4( normalizedInterpolatedNormal , specularPower );
    	vec4 renderTargetThree 	= vec4( vWorldPosition.x, vWorldPosition.y, vWorldPosition.z, 1.00 );
    
    	renderTargetThree.a = specularLevel; // Can be in One or Three
    
    	gl_FragData[0] = renderTargetOne;
    	gl_FragData[1] = renderTargetTwo;
    	gl_FragData[2] = renderTargetThree;
    }
    							
  • PointLightFragmentShader.glsl

    #extension GL_EXT_draw_buffers : require
    precision highp float;
    
    // Point Light Data
    uniform vec3  u_lightWorldPosition;
    uniform vec4  u_lightColorAndBrightness;
    uniform float u_lightOuterRadius;
    uniform float u_lightInnerRadius;
    
    uniform float u_inverseScreenWidth;
    uniform float u_inverseScreenHeight;
    
    uniform sampler2D s_renderTargetTwo; // Normals
    uniform sampler2D s_renderTargetThree; // Positions
    
    varying vec4 vWorldPosition;
    varying vec3 vCameraPosition;
    
    
    float smoothNumber( float numToSmooth ) 
    {
    	float smoothedNum = ( ( 3.0 * numToSmooth * numToSmooth ) - ( 2.0 * ( numToSmooth * numToSmooth * numToSmooth ) ) );
    	return smoothedNum;
    }
    
    
    void main( void ) 
    {
    	vec2 inverseScreenCoords 	= vec2( u_inverseScreenWidth, u_inverseScreenHeight );
    	vec2 textureCoords  		= gl_FragCoord.xy * inverseScreenCoords;
    
    	vec4 renderTargetTwo 		= texture2D( s_renderTargetTwo, textureCoords );
    	vec4 renderTargetThree 		= texture2D( s_renderTargetThree, textureCoords );
    
    	vec3 normalInWorldSpace 	= renderTargetTwo.xyz;
    	vec3 worldPositionData 		= renderTargetThree.xyz;
    
    	vec3 differenceVectorLightToPoint = u_lightWorldPosition - worldPositionData;
    	
    	//vec3 differenceVectorLightToPointNormalized = normalize( differenceVectorLightToPoint );
    	float lengthOfDifVectorLToP = length( differenceVectorLightToPoint );
    	vec3 differenceVectorLightToPointNormalized = lengthOfDifVectorLToP == 0.0 ? vec3( 0.0, 1.0, 0.0 ) : differenceVectorLightToPoint / lengthOfDifVectorLToP;
    	vec3 differenceVectorPointToLightNormalized = differenceVectorLightToPointNormalized * -1.0;
    
    	float distanceFromLightToPoint = length( differenceVectorLightToPoint );
    
    	// Fraction Brightness Due To Distance
    	float fractionBrightnessDueToDistance = ( u_lightOuterRadius - distanceFromLightToPoint ) / ( u_lightOuterRadius - u_lightInnerRadius );
    	fractionBrightnessDueToDistance = clamp( fractionBrightnessDueToDistance * u_lightColorAndBrightness.a , 0.0, 1.0 );
    	fractionBrightnessDueToDistance = smoothNumber( fractionBrightnessDueToDistance );
    
    	// ==== Diffuse Lighting ==== //
    	vec3 diffuseColor = vec3( 0.0, 0.0, 0.0 );
    	float diffuseDotResult = dot( normalInWorldSpace, differenceVectorLightToPointNormalized );
    	diffuseColor.x = clamp( ( diffuseDotResult * u_lightColorAndBrightness.x * fractionBrightnessDueToDistance ), 0.0, 1.0 );
    	diffuseColor.y = clamp( ( diffuseDotResult * u_lightColorAndBrightness.y * fractionBrightnessDueToDistance ), 0.0, 1.0 );
    	diffuseColor.z = clamp( ( diffuseDotResult * u_lightColorAndBrightness.z * fractionBrightnessDueToDistance ), 0.0, 1.0 );
    
    	// ==== Specular ( Hardcoded until WebGL importer is finished ) ==== //
    	vec3 specularResult = vec3( 0.0, 0.0, 0.0 );
    
    	// Handle the case where the normal is 0,0,0 ( Black screen pixels )
    	// The diffuseDotResult will always be zero when the normal render target is a black pixel
    	if ( diffuseDotResult > 0.0 )
    	{
    		float specularLevel = renderTargetThree.a;
    		float specularPower = renderTargetTwo.a;
    		vec3 specularColor = vec3( 1.0, 1.0, 1.0 ); // Fixed until exporter/importer integrated
    		vec3 specular = vec3( 0.0, 0.0, 0.0 );
    		
    		vec3 differenceVectorWorldToCamera = worldPositionData - vCameraPosition;
    		vec3 normalizedDifferenceVectorWorldToCamera = normalize( differenceVectorWorldToCamera );
    		vec3 idealReflection = reflect( normalizedDifferenceVectorWorldToCamera, normalInWorldSpace );
    		vec3 normalizedIdealReflection = normalize( idealReflection );
    		
    		float specDotResult = dot( normalizedIdealReflection, differenceVectorLightToPointNormalized );
    		float clampedSpecDotResult = clamp( specDotResult, 0.0, 1.0 );
    		float specPower = pow( clampedSpecDotResult, 1.0 + specularPower ); 
    		specularResult.x = ( specPower * specularLevel * fractionBrightnessDueToDistance );
    		specularResult.y = ( specPower * specularLevel * fractionBrightnessDueToDistance );
    		specularResult.z = ( specPower * specularLevel * fractionBrightnessDueToDistance );
    	}
    	
    	// ==== Final Output ==== //
    	gl_FragData[0] = vec4( diffuseColor, 1.0 );
    	gl_FragData[1] = vec4( specularResult , 1.0 );
    }
    							
  • FinalPassFragmentShader.glsl

    
    precision mediump float;
    
    uniform float u_inverseScreenWidth;
    uniform float u_inverseScreenHeight;
    
    uniform sampler2D s_renderTargetOne; 	// Diffuse
    uniform sampler2D s_renderTargetTwo; 	// Normals
    uniform sampler2D s_renderTargetFour; 	// Accumulated Diffuse Lighting
    uniform sampler2D s_renderTargetFive; 	// Accumulated Specular Lighting
    
    
    void main(void) 
    {
    	vec2 inverseScreenCoords = vec2( u_inverseScreenWidth, u_inverseScreenHeight );
    	vec2 texCoords = gl_FragCoord.xy * inverseScreenCoords;
    
    	vec4 renderTargetOne  			= texture2D( s_renderTargetOne, texCoords );
    	vec4 renderTargetFour 			= texture2D( s_renderTargetFour, texCoords );
    	vec4 renderTargetFive 			= texture2D( s_renderTargetFive, texCoords );
    
    	vec3 diffuseColor 				= renderTargetOne.xyz;
    	vec3 accumulatedDiffuseLight 	= renderTargetFour.xyz;
    	vec3 accumulatedSpecularLight 	= renderTargetFive.xyz;
    
    	// Fixed until Ambient Lights are considered for demonstration purposes
    	vec3 ambientGlobalLight 		= vec3( 0.075, 0.075, 0.075 );
    
    	accumulatedSpecularLight.x 		= clamp( accumulatedSpecularLight.x, 0.0, 1.0 );
    	accumulatedSpecularLight.y 		= clamp( accumulatedSpecularLight.y, 0.0, 1.0 );
    	accumulatedSpecularLight.z 		= clamp( accumulatedSpecularLight.z, 0.0, 1.0 );
    
    	vec3 finalColor = ( diffuseColor * accumulatedDiffuseLight + accumulatedSpecularLight + ( diffuseColor * ambientGlobalLight ) );
    
    	gl_FragColor = vec4( finalColor, 1.0 );
    }