瀏覽代碼

Merge pull request #5124 from DaoshengMu/SoftwareRenderTexturing

Software render texturing
Mr.doob 11 年之前
父節點
當前提交
715981d624
共有 2 個文件被更改,包括 589 次插入50 次删除
  1. 413 50
      examples/js/renderers/SoftwareRenderer.js
  2. 176 0
      examples/software_geometry_earth.html

+ 413 - 50
examples/js/renderers/SoftwareRenderer.js

@@ -2,6 +2,7 @@
  * @author mrdoob / http://mrdoob.com/
  * @author ryg / http://farbrausch.de/~fg
  * @author mraleph / http://mrale.ph/
+ * @author daoshengmu / http://dsmu.me/
  */
 
 THREE.SoftwareRenderer = function ( parameters ) {
@@ -10,7 +11,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 
 	parameters = parameters || {};
 
-	var canvas = document.createElement( 'canvas' );
+	var canvas = parameters.canvas !== undefined
+			 ? parameters.canvas
+			 : document.createElement( 'canvas' );
 	var context = canvas.getContext( '2d', {
 		alpha: parameters.alpha === true
 	} );
@@ -60,7 +63,7 @@ THREE.SoftwareRenderer = function ( parameters ) {
 	this.setClearColor = function ( color, alpha ) {
 
 		clearColor.set( color );
-
+		cleanColorBuffer();
 	};
 
 	this.setSize = function ( width, height ) {
@@ -75,10 +78,11 @@ THREE.SoftwareRenderer = function ( parameters ) {
 		viewportXScale =  fixScale * canvasWidth  / 2;
 		viewportYScale = -fixScale * canvasHeight / 2;
 		viewportZScale =             maxZVal      / 2;
+              
 		viewportXOffs  =  fixScale * canvasWidth  / 2 + 0.5;
 		viewportYOffs  =  fixScale * canvasHeight / 2 + 0.5;
 		viewportZOffs  =             maxZVal      / 2 + 0.5;
-
+        
 		canvas.width = canvasWidth;
 		canvas.height = canvasHeight;
 
@@ -106,6 +110,8 @@ THREE.SoftwareRenderer = function ( parameters ) {
 
 		}
 
+		cleanColorBuffer();
+
 	};
 
 	this.setSize( canvas.width, canvas.height );
@@ -223,41 +229,235 @@ THREE.SoftwareRenderer = function ( parameters ) {
 
 	};
 
+	function cleanColorBuffer() {
+
+		var size = canvasWidth * canvasHeight * 4;
+
+		for ( var i = 0; i < size; i+=4 ) {
+			
+			data[ i ] = clearColor.r * 255 | 0;
+			data[ i+1 ] = clearColor.g * 255 | 0;
+			data[ i+2 ] = clearColor.b * 255 | 0;
+			data[ i+3 ] = 255; 
+		}
+
+		context.fillStyle = clearColor.getStyle();
+		context.fillRect( 0, 0, canvasWidth, canvasHeight );
+	}
+
+    function getPalette( material, bSimulateSpecular ) {
+        var diffuseR = material.ambient.r + material.color.r * 255;
+        var diffuseG = material.ambient.g + material.color.g * 255;
+        var diffuseB = material.ambient.b + material.color.b * 255;
+        var palette = new Uint8Array(256*3);
+        
+        if ( bSimulateSpecular ) {
+            
+            var i = 0, j = 0;
+            while(i < 204) {
+                var r = i * diffuseR / 204;
+                var g = i * diffuseG / 204;
+                var b = i * diffuseB / 204;
+                if(r > 255)
+                    r = 255;
+                if(g > 255)
+                    g = 255;
+                if(b > 255)
+                    b = 255;
+
+                palette[j++] = r;
+                palette[j++] = g;
+                palette[j++] = b;
+                ++i;
+            }
+
+            while(i < 256) { // plus specular highlight
+                var r = diffuseR + (i - 204) * (255 - diffuseR) / 82;
+                var g = diffuseG + (i - 204) * (255 - diffuseG) / 82;
+                var b = diffuseB + (i - 204) * (255 - diffuseB) / 82;
+                if(r > 255)
+                    r = 255;
+                if(g > 255)
+                    g = 255;
+                if(b > 255)
+                    b = 255;
+				
+                palette[j++] = r;
+                palette[j++] = g;
+                palette[j++] = b;
+                ++i;
+            }
+            
+        } else {
+          
+            var i = 0, j = 0;
+            while(i < 256) {
+                var r = i * diffuseR / 255;
+                var g = i * diffuseG / 255;
+                var b = i * diffuseB / 255;
+                if(r > 255)
+                    r = 255;
+                if(g > 255)
+                    g = 255;
+                if(b > 255)
+                    b = 255;
+
+                palette[j++] = r;
+                palette[j++] = g;
+                palette[j++] = b;
+                ++i;
+            }           
+            
+        }
+        
+        return palette;
+    }
+    
+    function basicMaterialShader( buffer, offset, u, v, n, face, material ) {
+
+    	if ( material.map.needsUpdate ) {
+    		material.texture.CreateFromImage( material.map.image );
+    		material.map.needsUpdate = false;
+    	}
+
+    	if ( !material.texture.data )
+    		return;
+
+        var tdim = material.texture.width;
+        var isTransparent = material.transparent;
+        var tbound = tdim - 1;
+        var tdata = material.texture.data;
+        var tIndex = (((v * tdim) & tbound) * tdim + ((u * tdim) & tbound)) * 4;
+        
+        if ( !isTransparent ) {
+            buffer[ offset ] = tdata[tIndex];
+            buffer[ offset + 1 ] = tdata[tIndex+1];
+            buffer[ offset + 2 ] = tdata[tIndex+2];
+            buffer[ offset + 3 ] = material.opacity * 255;
+        }
+        else { 
+            var opaci = tdata[tIndex+3] * material.opacity;
+            var texel = tdata[tIndex] << 16 + tdata[tIndex+1] << 8 + tdata[tIndex+2];
+            if(opaci < 250) {
+                var backColor = buffer[ offset ] << 24 + buffer[ offset + 1 ] << 16 + buffer[ offset + 2 ] << 8;
+                texel = texel * opaci + backColor * (1-opaci);                         
+            }
+            
+            buffer[ offset ] = (texel & 0xff0000) >> 16;
+            buffer[ offset + 1 ] = (texel & 0xff00) >> 8;
+            buffer[ offset + 2 ] = (texel & 0xff);
+            buffer[ offset + 3 ] = material.opacity * 255;
+        }
+    }
+    
+    function lightingMaterialShader( buffer, offset, u, v, n, face, material ) {
+        
+    	if ( material.map.needsUpdate ) {
+    		material.texture.CreateFromImage( material.map.image );
+    		material.map.needsUpdate = false;
+
+    		return;
+    	}
+
+    	if ( !material.texture.data )
+    		return;
+
+        var tdim = material.texture.width;
+        var isTransparent = material.transparent;
+        var cIndex = (n > 0 ? (~~n) : 0) * 3;
+        var tbound = tdim - 1;
+        var tdata = material.texture.data;
+        var tIndex = (((v * tdim) & tbound) * tdim + ((u * tdim) & tbound)) * 4;
+        
+        if ( !isTransparent ) {
+          buffer[ offset ] = (material.palette[cIndex] * tdata[tIndex]) >> 8;
+          buffer[ offset + 1 ] = (material.palette[cIndex+1] * tdata[tIndex+1]) >> 8;
+          buffer[ offset + 2 ] = (material.palette[cIndex+2] * tdata[tIndex+2]) >> 8;
+          buffer[ offset + 3 ] = material.opacity * 255;
+        } else { 
+          var opaci = tdata[tIndex+3] * material.opacity;
+          var foreColor = ((material.palette[cIndex] * tdata[tIndex]) << 16) 
+          				+ ((material.palette[cIndex+1] * tdata[tIndex+1]) << 8 )
+          				+ (material.palette[cIndex+2] * tdata[tIndex+2]);
+
+          
+          if(opaci < 250) {
+            var backColor = buffer[ offset ] << 24 + buffer[ offset + 1 ] << 16 + buffer[ offset + 2 ] << 8;
+            foreColor = foreColor * opaci + backColor * (1-opaci);                          
+          }
+          buffer[ offset ] = (foreColor & 0xff0000) >> 16;
+          buffer[ offset + 1 ] = (foreColor & 0xff00) >> 8;
+          buffer[ offset + 2 ] = (foreColor & 0xff);
+          buffer[ offset + 3 ] = material.opacity * 255;
+        }
+        
+    }
+    
 	function getMaterialShader( material ) {
 
 		var id = material.id;
 		var shader = shaders[ id ];
 
 		if ( shaders[ id ] === undefined ) {
-
+            
 			if ( material instanceof THREE.MeshBasicMaterial ||
 			     material instanceof THREE.MeshLambertMaterial ||
 			     material instanceof THREE.MeshPhongMaterial ||
 			     material instanceof THREE.SpriteMaterial ) {
 
-				var string;
-
-				if ( material.vertexColors === THREE.FaceColors ) {
-
-					string = [
-						'buffer[ offset ] = face.color.r * 255;',
-						'buffer[ offset + 1 ] = face.color.g * 255;',
-						'buffer[ offset + 2 ] = face.color.b * 255;',
-						'buffer[ offset + 3 ] = material.opacity * 255;',
-					].join('\n');
+                if ( material instanceof THREE.MeshLambertMaterial ) {             
+                    // Generate color palette
+                    if ( !material.palette ) {
+                        material.palette = getPalette( material, false );
+                    }
+                    
+                } else if ( material instanceof THREE.MeshPhongMaterial ) {             
+                    // Generate color palette
+                    if ( !material.palette ) {
+                        material.palette = getPalette( material, true );
+                    }
+                }
 
-				} else {
-
-					string = [
-						'buffer[ offset ] = material.color.r * 255;',
-						'buffer[ offset + 1 ] = material.color.g * 255;',
-						'buffer[ offset + 2 ] = material.color.b * 255;',
-						'buffer[ offset + 3 ] = material.opacity * 255;',
-					].join('\n');
-
-				}
-
-				shader = new Function( 'buffer, offset, u, v, face, material', string );
+				var string;
+                
+                if ( material.map ) {
+                    
+					var texture = new THREE.SoftwareRenderer.Texture();
+					material.texture = texture;					
+                     
+                    if ( material instanceof THREE.MeshBasicMaterial ) {
+                        
+                        shader = basicMaterialShader;
+                    } else {
+                        
+                        shader = lightingMaterialShader;
+                    }
+                    
+                    
+                } else {
+                    
+                    if ( material.vertexColors === THREE.FaceColors ) {
+
+                        string = [
+                            'buffer[ offset ] = face.color.r * 255;',
+                            'buffer[ offset + 1 ] = face.color.g * 255;',
+                            'buffer[ offset + 2 ] = face.color.b * 255;',
+                            'buffer[ offset + 3 ] = material.opacity * 255;'
+                        ].join('\n');
+
+                    } else {
+
+                        string = [
+                            'buffer[ offset ] = material.color.r * 255;',
+                            'buffer[ offset + 1 ] = material.color.g * 255;',
+                            'buffer[ offset + 2 ] = material.color.b * 255;',
+                            'buffer[ offset + 3 ] = material.opacity * 255;'
+                        ].join('\n');
+
+                    }
+                    
+                    shader = new Function( 'buffer, offset, u, v, n, face, material', string );
+                }			
 
 			} else {
 
@@ -328,8 +528,22 @@ THREE.SoftwareRenderer = function ( parameters ) {
 		var z1 = (v1.z * viewportZScale + viewportZOffs) | 0;
 		var z2 = (v2.z * viewportZScale + viewportZOffs) | 0;
 		var z3 = (v3.z * viewportZScale + viewportZOffs) | 0;
-
-		// Deltas
+        
+        // UV values
+        
+        var tu1 = face.uvs[0].x; 
+        var tv1 = 1-face.uvs[0].y; 
+        var tu2 = face.uvs[1].x; 
+        var tv2 = 1-face.uvs[1].y; 
+        var tu3 = face.uvs[2].x; 
+        var tv3 = 1-face.uvs[2].y; 
+        
+        // Normal values
+        var n1 = face.vertexNormalsModel[0], n2 = face.vertexNormalsModel[1], n3 = face.vertexNormalsModel[2];        
+        var nz1 = n1.z * 255;
+        var nz2 = n2.z * 255;
+        var nz3 = n3.z * 255;
+        // Deltas
 
 		var dx12 = x1 - x2, dy12 = y2 - y1;
 		var dx23 = x2 - x3, dy23 = y3 - y2;
@@ -358,9 +572,12 @@ THREE.SoftwareRenderer = function ( parameters ) {
 
 		// Constant part of half-edge functions
 
-		var c1 = dy12 * ((minx << subpixelBits) - x1) + dx12 * ((miny << subpixelBits) - y1);
-		var c2 = dy23 * ((minx << subpixelBits) - x2) + dx23 * ((miny << subpixelBits) - y2);
-		var c3 = dy31 * ((minx << subpixelBits) - x3) + dx31 * ((miny << subpixelBits) - y3);
+		var minXfixscale = (minx << subpixelBits);
+        var minYfixscale = (miny << subpixelBits);
+
+		var c1 = dy12 * ((minXfixscale) - x1) + dx12 * ((minYfixscale) - y1);
+		var c2 = dy23 * ((minXfixscale) - x2) + dx23 * ((minYfixscale) - y2);
+		var c3 = dy31 * ((minXfixscale) - x3) + dx31 * ((minYfixscale) - y3);
 
 		// Correct for fill convention
 
@@ -383,14 +600,50 @@ THREE.SoftwareRenderer = function ( parameters ) {
 
 		// Z at top/left corner of rast area
 
-		var cz = ( z1 + ((minx << subpixelBits) - x1) * dzdx + ((miny << subpixelBits) - y1) * dzdy ) | 0;
+		var cz = ( z1 + ((minXfixscale) - x1) * dzdx + ((minYfixscale) - y1) * dzdy ) | 0;
 
 		// Z pixel steps
 
-		var zfixscale = (1 << subpixelBits);
-		dzdx = (dzdx * zfixscale) | 0;
-		dzdy = (dzdy * zfixscale) | 0;
-
+		var fixscale = (1 << subpixelBits);
+		dzdx = (dzdx * fixscale) | 0;
+		dzdy = (dzdy * fixscale) | 0;
+        
+        // UV interpolation setup
+        
+        var dtu12 = tu1 - tu2, dtu31 = tu3 - tu1;
+        var dtudx = (invDet * (dtu12*dy31 - dtu31*dy12)); // dtu per one subpixel step in x
+        var dtudy = (invDet * (dtu12*dx31 - dx12*dtu31)); // dtu per one subpixel step in y
+        var dtv12 = tv1 - tv2, dtv31 = tv3 - tv1;
+        var dtvdx = (invDet * (dtv12*dy31 - dtv31*dy12)); // dtv per one subpixel step in x
+        var dtvdy = (invDet * (dtv12*dx31 - dx12*dtv31)); // dtv per one subpixel step in y
+       
+        // UV at top/left corner of rast area
+        
+		var ctu = ( tu1 + (minXfixscale - x1) * dtudx + (minYfixscale - y1) * dtudy );
+        var ctv = ( tv1 + (minXfixscale - x1) * dtvdx + (minYfixscale - y1) * dtvdy );
+
+		// UV pixel steps
+		
+		dtudx = dtudx * fixscale;
+		dtudy = dtudy * fixscale;        
+		dtvdx = dtvdx * fixscale;
+		dtvdy = dtvdy * fixscale;
+
+        // Normal interpolation setup
+        
+        var dnz12 = nz1 - nz2, dnz31 = nz3 - nz1;
+        var dnzdx = (invDet * (dnz12*dy31 - dnz31*dy12)); // dnz per one subpixel step in x
+        var dnzdy = (invDet * (dnz12*dx31 - dx12*dnz31)); // dnz per one subpixel step in y
+        
+         // Normal at top/left corner of rast area
+       
+        var cnz = ( nz1 + (minXfixscale - x1) * dnzdx + (minYfixscale - y1) * dnzdy );
+
+		// Normal pixel steps
+
+        dnzdx = (dnzdx * fixscale);
+		dnzdy = (dnzdy * fixscale);
+        
 		// Set up min/max corners
 		var qm1 = q - 1; // for convenience
 		var nmin1 = 0, nmax1 = 0;
@@ -414,11 +667,17 @@ THREE.SoftwareRenderer = function ( parameters ) {
 		var cb2 = c2;
 		var cb3 = c3;
 		var cbz = cz;
+        var cbtu = ctu;
+        var cbtv = ctv;
+        var cbnz = cnz;
 		var qstep = -q;
 		var e1x = qstep * dy12;
 		var e2x = qstep * dy23;
 		var e3x = qstep * dy31;
 		var ezx = qstep * dzdx;
+        var etux = qstep * dtudx;
+        var etvx = qstep * dtvdx;
+        var enzx = qstep * dnzdx;
 		var x0 = minx;
 
 		for ( var y0 = miny; y0 < maxy; y0 += q ) {
@@ -431,7 +690,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 				cb2 += e2x;
 				cb3 += e3x;
 				cbz += ezx;
-
+                cbtu += etux;
+                cbtv += etvx;
+                cbnz += enzx;
 			}
 
 			// Okay, we're now in a block we know is outside. Reverse direction and go into main loop.
@@ -440,6 +701,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 			e2x = -e2x;
 			e3x = -e3x;
 			ezx = -ezx;
+            etux = -etux;
+            etvx = -etvx;
+            enzx = -enzx;
 
 			while ( 1 ) {
 
@@ -449,6 +713,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 				cb2 += e2x;
 				cb3 += e3x;
 				cbz += ezx;
+                cbtu += etux;
+                cbtv += etvx;
+                cbnz += enzx;
 
 				// We're done with this block line when at least one edge completely out
 				// If an edge function is too small and decreasing in the current traversal
@@ -474,7 +741,7 @@ THREE.SoftwareRenderer = function ( parameters ) {
 
 				// Offset at top-left corner
 				var offset = x0 + y0 * canvasWidth;
-
+                
 				// Accept whole block when fully covered
 				if ( cb1 >= nmin1 && cb2 >= nmin2 && cb3 >= nmin3 ) {
 
@@ -484,27 +751,34 @@ THREE.SoftwareRenderer = function ( parameters ) {
 					var cy1 = cb1;
 					var cy2 = cb2;
 					var cyz = cbz;
+                    var cytu = cbtu;
+                    var cytv = cbtv;
+                    var cynz = cbnz;
 
 					for ( var iy = 0; iy < q; iy ++ ) {
 
 						var cx1 = cy1;
 						var cx2 = cy2;
 						var cxz = cyz;
+                        var cxtu = cytu;
+                        var cxtv = cytv;
+                        var cxnz = cynz;                        
 
 						for ( var ix = 0; ix < q; ix ++ ) {
 
 							var z = cxz;
-
+                           
 							if ( z < zbuffer[ offset ] ) {
-								zbuffer[ offset ] = z;
-								var u = cx1 * scale;
-								var v = cx2 * scale;
-								shader( data, offset * 4, u, v, face, material );
+								zbuffer[ offset ] = z;            
+								shader( data, offset * 4, cxtu, cxtv, cxnz, face, material );                                
 							}
 
 							cx1 += dy12;
 							cx2 += dy23;
 							cxz += dzdx;
+                            cxtu += dtudx;
+                            cxtv += dtvdx;
+                            cxnz += dnzdx;
 							offset++;
 
 						}
@@ -512,6 +786,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 						cy1 += dx12;
 						cy2 += dx23;
 						cyz += dzdy;
+                        cytu += dtudy;
+                        cytv += dtvdy;
+                        cynz += dnzdy;
 						offset += linestep;
 
 					}
@@ -522,6 +799,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 					var cy2 = cb2;
 					var cy3 = cb3;
 					var cyz = cbz;
+                    var cytu = cbtu;
+                    var cytv = cbtv;
+                    var cynz = cbnz;
 
 					for ( var iy = 0; iy < q; iy ++ ) {
 
@@ -529,20 +809,19 @@ THREE.SoftwareRenderer = function ( parameters ) {
 						var cx2 = cy2;
 						var cx3 = cy3;
 						var cxz = cyz;
+                        var cxtu = cytu;
+                        var cxtv = cytv;    
+                        var cxnz = cynz;     
 
 						for ( var ix = 0; ix < q; ix ++ ) {
 
 							if ( ( cx1 | cx2 | cx3 ) >= 0 ) {
 
-								var z = cxz;
+								var z = cxz;                              
 
 								if ( z < zbuffer[ offset ] ) {
-									var u = cx1 * scale;
-									var v = cx2 * scale;
-
-									zbuffer[ offset ] = z;
-									shader( data, offset * 4, u, v, face, material );
-
+									zbuffer[ offset ] = z;                                    
+									shader( data, offset * 4, cxtu, cxtv, cxnz, face, material );
 								}
 
 							}
@@ -551,6 +830,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 							cx2 += dy23;
 							cx3 += dy31;
 							cxz += dzdx;
+                            cxtu += dtudx;
+                            cxtv += dtvdx;
+                            cxnz += dnzdx;
 							offset++;
 
 						}
@@ -559,6 +841,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 						cy2 += dx23;
 						cy3 += dx31;
 						cyz += dzdy;
+                        cytu += dtudy;
+                        cytv += dtvdy;
+                        cynz += dnzdy;
 						offset += linestep;
 
 					}
@@ -572,6 +857,9 @@ THREE.SoftwareRenderer = function ( parameters ) {
 			cb2 += q*dx23;
 			cb3 += q*dx31;
 			cbz += q*dzdy;
+            cbtu += q*dtudy;
+            cbtv += q*dtvdy;
+            cbnz += q*dnzdy;
 		}
 
 	}
@@ -627,3 +915,78 @@ THREE.SoftwareRenderer = function ( parameters ) {
 	}
 
 };
+
+THREE.SoftwareRenderer.Texture = function() {
+    var canvas = null;
+    
+    this.CreateFromImage = function( image ) {
+        
+        if(image.width <=0 || image.height <=0)
+            return;
+    
+        var isCanvasClean = false;
+        var canvas = THREE.SoftwareRenderer.Texture.canvas;
+        if ( !canvas ) {
+
+            try {
+
+                canvas = document.createElement('canvas');
+                THREE.SoftwareRenderer.Texture.canvas = canvas;
+                isCanvasClean = true;
+            } catch( e ) {
+                return;
+            }
+
+        }
+
+        var dim = image.width > image.height ? image.width : image.height;
+        if(dim <= 32)
+            dim = 32;
+        else if(dim <= 64)
+            dim = 64;
+        else if(dim <= 128)
+            dim = 128;
+        else if(dim <= 256)
+            dim = 256;
+        else if(dim <= 512)
+            dim = 512;
+        else
+            dim = 1024;
+
+        if(canvas.width != dim || canvas.height != dim) {
+            canvas.width = canvas.height = dim;
+            isCanvasClean = true;
+        }
+
+        var data;
+        try {
+            var ctx = canvas.getContext('2d');
+            if(!isCanvasClean)
+                ctx.clearRect(0, 0, dim, dim);
+            ctx.drawImage(image, 0, 0, dim, dim);
+            var imgData = ctx.getImageData(0, 0, dim, dim);
+            data = imgData.data;
+        }
+        catch(e) {
+            return;
+        }
+
+        var size = data.length;
+        this.data = new Uint8Array(size);
+        var alpha;
+        for(var i=0, j=0; i<size; ) {
+            this.data[i++] = data[j++];
+            this.data[i++] = data[j++];
+            this.data[i++] = data[j++];
+            alpha = data[j++];
+            this.data[i++] = alpha;
+
+            if(alpha < 255)
+                this.hasTransparency = true;
+        }
+
+        this.width = dim;
+        this.height = dim;
+        this.srcUrl = image.src;
+    };
+};

+ 176 - 0
examples/software_geometry_earth.html

@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js software - geometry - earth</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #808080;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+
+				background-color: #ffffff;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+
+			a {
+
+				color: #0080ff;
+			}
+
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info"><a href="http://threejs.org" target="_blank">three.js</a> - earth demo</div>
+
+		<script src="../build/three.min.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+        <script src="js/renderers/SoftwareRenderer.js"></script>
+
+		<script>
+
+			var container, stats;
+			var camera, scene, renderer;
+			var group;
+			var mouseX = 0, mouseY = 0;
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.z = 500;
+
+				scene = new THREE.Scene();
+
+				group = new THREE.Object3D();
+				scene.add( group );
+
+				// earth
+
+				var loader = new THREE.TextureLoader();
+				loader.load( 'textures/land_ocean_ice_cloud_2048.jpg', function ( texture ) {
+
+					var geometry = new THREE.SphereGeometry( 200, 20, 20 );
+
+					var material = new THREE.MeshLambertMaterial( { map: texture, overdraw: 0.5 } );
+					var mesh = new THREE.Mesh( geometry, material );
+					group.add( mesh );
+
+				} );
+
+				// shadow
+
+				var canvas = document.createElement( 'canvas' );
+				canvas.width = 128;
+				canvas.height = 128;
+
+				var context = canvas.getContext( '2d' );
+				var gradient = context.createRadialGradient(
+					canvas.width / 2,
+					canvas.height / 2,
+					0,
+					canvas.width / 2,
+					canvas.height / 2,
+					canvas.width / 2
+				);
+				gradient.addColorStop( 0.1, 'rgba(210,210,210,1)' );
+				gradient.addColorStop( 1, 'rgba(255,255,255,1)' );
+
+				context.fillStyle = gradient;
+				context.fillRect( 0, 0, canvas.width, canvas.height );
+
+				var texture = new THREE.Texture( canvas );
+				texture.needsUpdate = true;
+
+				var geometry = new THREE.PlaneGeometry( 300, 300, 3, 3 );
+				var material = new THREE.MeshBasicMaterial( { map: texture, overdraw: 0.5 } );
+
+				var mesh = new THREE.Mesh( geometry, material );
+				mesh.position.y = - 250;
+				mesh.rotation.x = - Math.PI / 2;
+				group.add( mesh );
+
+				renderer = new THREE.SoftwareRenderer();
+				renderer.setClearColor( 0x000000 );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = ( event.clientX - windowHalfX );
+				mouseY = ( event.clientY - windowHalfY );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				camera.position.x += ( mouseX - camera.position.x ) * 0.05;
+				camera.position.y += ( - mouseY - camera.position.y ) * 0.05;
+				camera.lookAt( scene.position );
+
+				group.rotation.y -= 0.005;
+
+				renderer.render( scene, camera );
+
+			}
+
+
+		</script>
+
+	</body>
+</html>