WebGL:如何将移动的纹理覆盖在另一个纹理之上?

时间:2018-11-17 21:18:08

标签: javascript html webgl textures

对于独立于荣誉学分的类项目,我需要使用WebGL显示我选择的图像作为纹理使用,然后对纹理进行变形以使其看起来像在以某种方式移动或更改。

为此,我选择对雨滴纹理进行变形以使其看起来像雨滴从屏幕上掉下来。我有一个风景背景纹理(.jpg图像),我想用雨滴纹理(一个.png图像)覆盖它,但是我不确定如何覆盖背景纹理,这样我就可以在同时。我现在拥有的代码仅显示背景图像。

任何人都可以帮助我解释我做错了什么,以及如何解决这个问题?如果您需要我上传部分代码以查看,请告诉我。我几乎不了解WebGL,所以我不知道要显示什么地方才能看到我要去哪里。就我所知,整个事情可能是错误的。

编辑:我被告知要添加代码,因此由于我不知道要显示什么代码段,所以这是我的整个javascript和html文件:

还请注意:此代码仅在FireFox中有效! Google Chrome浏览器会出错! (尽管我认为这会产生错误,因为我没有提供.html文件中引用的文件)*

var canvas;
var gl;

var Index = 0;

var texCoord = [
    vec2(0, 0),
    vec2(0, 1),
    vec2(1, 1),
    vec2(1, 1),
    vec2(1, 0),
    vec2(0, 0)
];

var vertices = [
    vec2( -1, -1 ),
    vec2(  -1,  1 ),
    vec2(  1, 1 ),
    vec2( 1, 1 ),
    vec2(  1,  -1 ),
    vec2(  -1, -1 )
];

var pointsArray = [];

//PROGRAMS
var program1; //for background
var program2; //for rain texture
var program3; //for rain texture updates


//TEXTURES
var texture1; //background image (STATIC IMAGE)
var texture2; // Raindrop texture (bounce between 2 and 3)
var texture3; // Raindrop texture (transform to be sent between 2 and 3)


//BUFFERS--------------------------------------------------------------------
var framebuffer; 

var buffer1; //buffer for static background (texture1) vertices
var buffer2; //buffer for static background (texture1) texcoordinates
var buffer3; //buffer for raindrops (texture2) vertices
var buffer4; //buffer for raindrops (texture2) texcoordinates
var buffer5; //buffer for texture3 vertices
var buffer6; //buffer for texture3 texcoordinates


window.onload = function init() {
    canvas = document.getElementById( "gl-canvas" );
    
    gl = WebGLUtils.setupWebGL( canvas );
    if ( !gl ) { alert( "WebGL isn't available" ); }

//----------------------------------------------------------------------------------------------------------------------------------------
//BACKGROUND texture
texture1 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture1);
 
// Fill the texture with a blue screen (fill canvas) FILLER!!!
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE,
              new Uint8Array([0, 0, 255, 255]));
 
// load image

    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);

    // Get the IMG tag by its ID
    var bgpic = document.getElementById("background");

    
	gl.bindTexture(gl.TEXTURE_2D, texture1);
	
    // Load data from the IMG
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bgpic);
	
	gl.generateMipmap(gl.TEXTURE_2D);

//------------------------------------------------------------------------------------------------------------------------------------------
	
//RAINDROP TEXTURE
texture2 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture2);
 
// Fill the texture with a blue screen (fill canvas) FILLER!!!
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE,
              new Uint8Array([0, 0, 255, 255]));
 
// load image

    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);

    // Get the IMG tag by its ID
    var rainPic = document.getElementById("raindrop");

    
	gl.bindTexture(gl.TEXTURE_2D, texture2);
	
    // Load data from the IMG
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, rainPic);
	
	gl.generateMipmap(gl.TEXTURE_2D);


//------------------------------------------------------------------------------------------------------------------------------------------
//EMPTY TEXTURE TO PASS TEXTURE2 TO
	texture3 = gl.createTexture();
    gl.bindTexture( gl.TEXTURE_2D, texture2 );
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );	
	
//------------------------------------------------------------------------------------------------------------------------------------------	
// Allocate a frame buffer object

   framebuffer = gl.createFramebuffer();
   gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
   framebuffer.width = 4096;
   framebuffer.height = 2048;

//------------------------------------------------------------------------------------------------------------------------------------------

// Attach color buffer

   gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture1, 0);

//------------------------------------------------------------------------------------------------------------------------------------------
// check for completeness

   var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
   if(status != gl.FRAMEBUFFER_COMPLETE) alert('Frame Buffer Not Complete');
//------------------------------------------------------------------------------------------------------------------------------------------
    //
    //  Load shaders and initialize attribute buffers
    //
    program1 = initShaders( gl, "vertex-shader1", "fragment-shader1" );
	program2 = initShaders( gl, "vertex-shader2", "fragment-shader2" );
    program3 = initShaders(gl, "vertex-shader3","fragment-shader3");
//------------------------------------------------------------------------------------------------------------------------------------------
//PROGRAM 1 CODE
    gl.useProgram( program1 );
  
     gl.useProgram(program1);
        
    gl.activeTexture(gl.TEXTURE0);
    
    gl.bindTexture(gl.TEXTURE_2D, texture1);
    
    // send data to GPU for normal render
        
    buffer1 = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, buffer1);
    gl.bufferData(gl.ARRAY_BUFFER,   flatten(vertices), gl.STATIC_DRAW);
    
    var vPosition = gl.getAttribLocation( program1, "vPosition" );
    gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vPosition );

    buffer2 = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, buffer2);
    gl.bufferData( gl.ARRAY_BUFFER, flatten(texCoord), gl.STATIC_DRAW);
    
    var vTexCoord = gl.getAttribLocation( program1, "vTexCoord"); 
    gl.vertexAttribPointer( vTexCoord, 2, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vTexCoord );
	
    gl.uniform1i( gl.getUniformLocation(program1, "texture1"), 0);
	
	gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
	
    gl.clearColor( 1.0, 1.0, 1.0, 1.0 ); 
    gl.viewport(0, 0, 2048, 1024);
	
		gl.bindFramebuffer(gl.FRAMEBUFFER, null);
   
//------------------------------------------------------------------------------------------------------------------------------------------    

//PROGRAM 2 CODE
	gl.useProgram(program2);

    // Create and initialize a buffer object
    gl.activeTexture(gl.TEXTURE0);
	gl.bindTexture(gl.TEXTURE_2D, texture2);
	
    buffer3 = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, buffer3 );
    gl.bufferData( gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW );

    var vPosition = gl.getAttribLocation( program2, "vPosition" );
    gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vPosition );

    // Bind FBO and render
    
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

                  
    gl.viewport(0, 0, 2048, 1024);
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT );
    
//    gl.drawArrays(gl.TRIANGLES, 0, numVertices);

    // Bind to window system frame buffer, unbind the texture
    
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
	
//------------------------------------------------------------------------------------------------------------------------------------------    
//PROGRAM 3 CODE	
    gl.useProgram(program3);
        
    gl.activeTexture(gl.TEXTURE0);
    
    gl.bindTexture(gl.TEXTURE_2D, texture3);
    
    // send data to GPU for normal render
        
    buffer5 = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, buffer5);
    gl.bufferData(gl.ARRAY_BUFFER,   flatten(vertices), gl.STATIC_DRAW);
    
    var vPosition = gl.getAttribLocation( program3, "vPosition" );
    gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vPosition );

    buffer6 = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, buffer6);
    gl.bufferData( gl.ARRAY_BUFFER, flatten(texCoord), gl.STATIC_DRAW);
    
    var vTexCoord = gl.getAttribLocation( program3, "vTexCoord"); 
    gl.vertexAttribPointer( vTexCoord, 2, gl.FLOAT, false, 0, 0 );
    gl.enableVertexAttribArray( vTexCoord );
	
    gl.uniform1i( gl.getUniformLocation(program3, "texture2"), 0);
	
    gl.clearColor( 1.0, 1.0, 1.0, 1.0 ); 
    gl.viewport(0, 0, 2048, 1024);
   
    
    render();
    
}


function render() {

   // render to texture
   
//------------------------------------------------------------------------------------------------------------------------------------------
	// SECTION FOR BACKGROUND TEXTURE!
	//BACKGROUND TEXTURE: USE PROGRAM 1
	gl.useProgram(program1);  
    
	gl.clear( gl.COLOR_BUFFER_BIT );      
    gl.drawArrays( gl.TRIANGLES, 0, 6 );
    
    gl.bindFramebuffer( gl.FRAMEBUFFER, null); 
    gl.bindTexture(gl.TEXTURE_2D, texture1);
            
    gl.clear( gl.COLOR_BUFFER_BIT );      
    gl.drawArrays(gl.TRIANGLES, 0, 6);
	
	//------------------------------------------------------------------------------------------------------------------------------------------
//RAIN TEXTURE: USE PROGRAM 2	
	gl.useProgram(program2);
   
    gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer);
    
    if(flag) {
        gl.bindTexture(gl.TEXTURE_2D, texture2);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture3, 0);

    }
    else {
        gl.bindTexture(gl.TEXTURE_2D, texture3);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture2, 0);

    }
   
//------------------------------------------------------------------------------------------------------------------------------------------
// RAIN TEXTURE: USE PROGRAM 3
	
    gl.useProgram(program3);
    
    
    gl.drawArrays( gl.TRIANGLES, 0, 6 );
    
    gl.bindFramebuffer( gl.FRAMEBUFFER, null); 
    if(flag) gl.bindTexture(gl.TEXTURE_2D, texture2);
    else gl.bindTexture(gl.TEXTURE_2D, texture2);
            
    gl.clear( gl.COLOR_BUFFER_BIT );      
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    
    flag = !flag
    
    requestAnimFrame(render);

}
<!DOCTYPE html>
<html>

<script id="vertex-shader1" type="x-shader/x-vertex">

attribute  vec4 vPosition;
attribute vec2 vTexCoord;

varying vec2 fTexCoord;

void main() 
{
    gl_Position = vPosition;
	fTexCoord= vTexCoord;
} 
</script>

<script id="vertex-shader2" type="x-shader/x-vertex">

attribute  vec4 vPosition;

attribute vec2 vTexCoord;

varying vec2 fTexCoord;

void main() 
{
    gl_Position = vPosition;
	fTexCoord= vTexCoord;

} 
</script>

<script id="vertex-shader3" type="x-shader/x-vertex">

attribute vec4 vPosition;
attribute vec2 vTexCoord;

varying vec2 fTexCoord;

void main()
{

	gl_Position = vPosition;
	fTexCoord = vTexCoord;

}
</script>

<script id="fragment-shader1" type="x-shader/x-fragment">

precision mediump float;

uniform sampler2D texture1;

varying vec2 fTexCoord;

void
main()
{
    gl_FragColor= texture2D(texture1, fTexCoord);
}

</script>

<script id="fragment-shader2" type="x-shader/x-fragment">

precision mediump float;

uniform sampler2D texture2;

varying vec2 fTexCoord;

void main()
{
	gl_FragColor= texture2D(texture2, fTexCoord);

}
</script>

<script id="fragment-shader3" type="x-shader/x-fragment">

precision mediump float;

varying  vec2 fTexCoord;

uniform sampler2D texture2;

void main() 
{ 
	float y= fTexCoord.y - 0.1;
	float x= fTexCoord.x;
	
	if(y < 0.0) {
		y = 1.0;
		x = fTexCoord.x + 0.1 ;
	}
	if(x > 1.0){
		x = 0.0;
	}
	
	gl_FragColor = texture2D( texture2, vec2(x,y));         

} 

</script>

<script type="text/javascript" src="../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../Common/InitShaders.js"></script>
<script type="text/javascript" src="../Common/MV.js"></script>
<script type="text/javascript" src="testingBG.js"></script>

<img src="Raindrops_scaled.png" id="raindrop" hidden />
<img src="Background.jpg" id="background" hidden />

<body>
<canvas id="gl-canvas" width="2048" height="1024">
Oops ... your browser doesn't support the HTML5 canvas element
</canvas>
</body>
</html>

1 个答案:

答案 0 :(得分:1)

我已尝试提供更全面的说明,以解决可能对您有用的解决方案。

由于cors>,这可能无法在Chrome中使用。>

edit:通过手动将图像插入为位图数组来解决问题。 T.T

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<style>
			body {
				background-color: black;
			}
			
			canvas {
				display: block;
				margin: auto;
				border: solid 1px white;
				border-radius: 10px;
			}
			
			img {
				display: none;
			}
		</style>
	</head>
	
	<body>
		<canvas id="canvas"></canvas>
		<script type="application/javascript">
		
		// I've only placed this here to get around cors < _ >
		var images = function() {
			
			"use strict";
			
			function Image(width,height,pixels) {
				this.width = width;
				this.height = height;
				this.pixels = pixels;
			}
			
			return {
				background: new Image(16,16,new Uint8Array([127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,95,95,95,255,95,95,95,255,127,127,127,255,127,127,127,255,127,127,127,255,95,95,95,255,127,127,127,255,127,127,127,255,95,95,95,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,95,95,95,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,95,95,95,255,127,127,127,255,127,127,127,255,95,95,95,255,127,127,127,255,127,127,127,255,127,127,127,255,127,127,127,255,95,95,95,255,95,95,95,255,95,95,95,255,153,217,234,255,95,95,95,255,127,127,127,255,127,127,127,255,95,95,95,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,95,95,95,255,95,95,95,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,22,116,50,255,22,116,50,255,22,116,50,255,22,116,50,255,22,116,50,255,22,116,50,255,22,116,50,255,22,116,50,255,153,217,234,255,153,217,234,255,153,217,234,255,153,217,234,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255,34,177,76,255])),
				raindrop: new Image(8,8,new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,41,49,151,255,41,49,151,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,49,151,255,64,73,202,255,64,73,202,255,41,49,151,255,0,0,0,0,0,0,0,0,0,0,0,0,41,49,151,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,41,49,151,255,0,0,0,0,0,0,0,0,41,49,151,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,41,49,151,255,0,0,0,0,41,49,151,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,41,49,151,255,41,49,151,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,41,49,151,255,0,0,0,0,41,49,151,255,64,73,202,255,64,73,202,255,64,73,202,255,64,73,202,255,41,49,151,255,0,0,0,0,0,0,0,0,0,0,0,0,41,49,151,255,41,49,151,255,41,49,151,255,41,49,151,255,0,0,0,0,0,0,0,0]))
			};
			
		}();
		
		void function() {
			
			"use strict";
			
			// Variables
			var canvasWidth = 180;
			var canvasHeight = 160;
			var canvas = null;
			var gl = null;
			var raindrops = [];
			
			// WebGL programs (contains code that modifies your vertex data & code that processes each pixel)
			var backgroundProgram = null;
			var raindropProgram = null;
			
			// WebGL program attributes (indexes into attributes inside the program, webgl uses these to interpret data in the buffers)
			var aPositionLocation = 0;
			var aUVLocation = 1;
			
			// WebGL program uniforms (It's a variable that's shared between Javascript and the program, you just need to call a function to update it's value)
			var uRaindropPosition = null; // vec2
			var uRaindropSize = null; // vec2
			
			// WebGL buffers (It's like an array '[]', but it's on the GPU, we'll use it here to specify the properties of triangles we want to draw)
			var backgroundBuffer = null;
			
			// WebGL textures
			var backgroundTexture = null;
			var raindropTexture = null;
			
			// Constructors
			// If this function is called with 'new Raindrop(0,0,10);' it'll create a new raindrop object that has these properties
			function Raindrop(x,y,size) {
				this.x = x;
				this.y = y;
				this.size = size;
			}
			
			// Functions
			function createProgram(vertexCode,fragmentCode) {
				var program = gl.createProgram();
				var vertexShader = gl.createShader(gl.VERTEX_SHADER);
				var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
				
				// Upload source code
				gl.shaderSource(vertexShader,vertexCode);
				gl.shaderSource(fragmentShader,fragmentCode);
				
				// Compile shaders
				gl.compileShader(vertexShader);
				gl.compileShader(fragmentShader);
				
				// Check there isn't any problems
				try {
					if (!gl.getShaderParameter(vertexShader,gl.COMPILE_STATUS)) {
						throw "Vertex Shader: " + gl.getShaderInfoLog(vertexShader);
					}
					
					if (!gl.getShaderParameter(fragmentShader,gl.COMPILE_STATUS)) {
						throw "Fragment Shader: " + gl.getShaderInfoLog(fragmentShader);
					}
				} catch(log) {
					gl.deleteProgram(program);
					gl.deleteShader(vertexShader);
					gl.deleteShader(vertexShader);
					console.error(log);
				}
				
				// Attach to the main program
				gl.attachShader(program,vertexShader);
				gl.attachShader(program,fragmentShader);
				gl.linkProgram(program);
				gl.deleteShader(vertexShader);
				gl.deleteShader(fragmentShader);
				
				return program;
			}
			
			function createBuffer(data) {
				// Create a GPU buffer, it's a really simple object
				// Buffers can be best thought of as a normal array that sits in GPU memory (VRAM)
				var buffer = gl.createBuffer();
				
				// attach our new buffer on the gpu to 'ARRAY_BUFFER'
				// Think of it as, whatever is attached to 'ARRAY_BUFFER' is the current buffer that get used with WebGl functions
				gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
				
				// Upload our array of verticies as 32 bit floats
				gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(data),gl.STATIC_DRAW);
				
				// Tell WebGL we're not using the buffer anymore at the moment
				gl.bindBuffer(gl.ARRAY_BUFFER,null);
				
				return buffer;
			}
			
			function createTexture(image) {
				var texture = gl.createTexture();
				
				// attach our new texture on the gpu to 'TEXTURE_2D'
				// Think of it as, whatever is attached is our current texture we're using for webgl functions
				gl.bindTexture(gl.TEXTURE_2D,texture);
				
				// Set texture parameters, i.e. how are we going to scale it up or down/ what should happen if we
				// try to get a pixel from outside the texture
				gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST); // Use nearest neighbour when sizing up
				gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST); // Use nearest neighbour when sizing down
				gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.REPEAT); // Apply a modulo to x coordinates if there outside the texture
				gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.REPEAT); // Apply a modulo to y coordinates if there outside the texture
				
				// Upload our image to our gpu texture
				gl.texImage2D(
					gl.TEXTURE_2D, // I want it to get uploaded to what's attached to TEXTURE_2D
					0, // Mipmap level, it can stay at 0
					gl.RGBA, // Data format the gpu texture will use, in this case 8 bit RGBA for each pixel
					image.width,
					image.height,
					0, // Border, always should be zero
					gl.RGBA, // The data format of the image we're uploading to the gpu
					gl.UNSIGNED_BYTE, // Datatype of each actual colour channel, here it's going to be an unsigned 8 bit int that will go from 0-255
					image.pixels // The image we finally want to upload!, any HTML image element will work just fine
				);
				
				// Now we'll unbind the texture since we are done with it now
				gl.bindTexture(gl.TEXTURE_2D,null); // Passing null tells WebGL that we're not currently 'using' any texture
				
				return texture;
			}
			
			// Convert from Javascript canvas coordinate system to WebGL's normalized device coordinates
			/*
				The position 0,0 moves to the center of the screen.
				The top left corner becomes -1,1
				The bottom right corner becomes 1,-1
				
				Coordinates need to be converted before they can be passed to WebGL
			*/
			function convertXCoordinate(x) {
				return (x / canvasWidth) * 2.0 - 1.0;
			}
			
			function convertYCoordinate(y) {
				return ((canvasHeight - y) / canvasHeight) * 2.0 - 1.0;
			}
			
			function loop() {
				// Update
				
				// Update raindrop positions
				// Just to simulate rain 'falling'
				for (var i = 0; i < raindrops.length; ++i) {
					var raindrop = raindrops[i];
					
					raindrop.y += 2.5;
					
					if (raindrop.y > canvasHeight) {
						raindrop.x = Math.random() * canvasWidth;
						raindrop.y = 0;
					}
				}
				
				// Render
				
				// Clear the canvas
				gl.clear(gl.COLOR_BUFFER_BIT);
				
				// Draw the background
				gl.bindBuffer(gl.ARRAY_BUFFER,backgroundBuffer);
				
				// We use this function to tell WebGL how to interpret the data in the current buffer
				gl.vertexAttribPointer(
					aPositionLocation, // attribute index, usually just the order they were declared in the vertex shader
					2, // Number of values in attribute, vec2 has two floats
					gl.FLOAT, // The kind of data to be expected in the buffer
					false, // Telling WebGL we don't want the data to get normalized to 0.0 -> 1.0
					Float32Array.BYTES_PER_ELEMENT * 4, // This is the size of a complete vertex in bytes (2 vec2's so four floats)
					Float32Array.BYTES_PER_ELEMENT * 0 // This is the 'offset', or the number of floats that come before this attribute in each vertex. aPosition comes first so it's zero
				);
				
				gl.vertexAttribPointer(
					aUVLocation,
					2,
					gl.FLOAT,
					false,
					Float32Array.BYTES_PER_ELEMENT * 4,
					Float32Array.BYTES_PER_ELEMENT * 2 // aUV comes second, so it's offset is two floats
				);
				
				// Tell WebGL we want to turn these attributes on for rendering
				gl.enableVertexAttribArray(aPositionLocation);
				gl.enableVertexAttribArray(aUVLocation);
				
				// Attach our background texture so the sampler2D can use it
				gl.bindTexture(gl.TEXTURE_2D,backgroundTexture);
				
				// Attach our program we want to render with
				gl.useProgram(backgroundProgram);
				
				// Tell WebGL to start rendering!
				gl.drawArrays(
					gl.TRIANGLES, // Takes groups of 3 verticies to draw one complete triangle
					0, // Start at index 0 in our buffer
					6 // Use six complete verticies (whole sets of attributes) to draw 2 triangles
				);
				
				// We no longer need the background buffer, program or texture
				gl.bindBuffer(gl.ARRAY_BUFFER,null);
				gl.disableVertexAttribArray(aPositionLocation);
				gl.disableVertexAttribArray(aUVLocation);
				gl.bindTexture(gl.TEXTURE_2D,null);
				gl.useProgram(null);
				
				// Draw raindrops
				// Since we're going to pass coordinates through uniforms, this can be done without a buffer
				
				// Bind texture for sampler2D
				gl.bindTexture(gl.TEXTURE_2D,raindropTexture);
				
				// After attaching a program, we can upload values to uniforms using the values we got before from getUniformLocation()
				gl.useProgram(raindropProgram);
				
				for (var i = 0; i < raindrops.length; ++i) {
					var raindrop = raindrops[i];
					
					// Upload position
					gl.uniform2f(
						uRaindropPosition,
						convertXCoordinate(raindrop.x),
						convertYCoordinate(raindrop.y)
					);
					
					// Upload size
					gl.uniform1f(
						uRaindropSize,
						raindrop.size
					);
					
					gl.drawArrays(gl.POINTS,0,1);
				}
				
				//
				requestAnimationFrame(loop);
			}
			
			// This gets called when the page finishes loading
			onload = function() {
				// Get the canvas
				canvas = document.getElementById("canvas");
				canvas.width = canvasWidth;
				canvas.height = canvasHeight;
				
				// Don't forget the webgl context!
				gl = canvas.getContext("webgl");
				
				// Create our program, it's made up of two smaller programs.
				// One processes verticies (one whole set of attributes) & one that decides the colour of each pixel
				// These smaller programs need to be created, compiled then attached to the main program
				
				// modern Javascript has a nice feature called string templates, if we use `` instead of "" we can
				// write strings that can have multiple lines!
				backgroundProgram = createProgram(
					` // Vertex Shader
						precision lowp float;
						
						// These values are filled from our buffer
						attribute vec2 aPosition;
						attribute vec2 aUV;
						
						// Anything passed to this variable gets interpolated across any
						// pixels inside our triangle
						varying vec2 vUV;
						
						void main() {
							vUV = aUV;
							gl_Position = vec4(aPosition,0.0,1.0);
						}
						
					`,` // Fragment shader
						precision lowp float;
						
						// Freshly interpolated value from our vertex shader
						varying vec2 vUV;
						
						// A uniform is a variable shared between webgl & javascript
						// A sampler2D is a special datatype we use with texture2D
						// to get a single pixel from the texture that's currently
						// attached to TEXTURE_2D
						uniform sampler2D uTexture;
						
						void main() {
							gl_FragColor = texture2D(uTexture,vUV);
						}
					`
				);
				
				raindropProgram = createProgram(
					` // Vertex shader
						precision lowp float;
						
						attribute vec2 aPosition;
						
						uniform vec2 uRaindropPosition;
						uniform float uRaindropSize;
						
						void main() {
							gl_PointSize = uRaindropSize;
							gl_Position = vec4(uRaindropPosition + aPosition,0.0,1.0);
						}
					`,` // Fragment shader
						precision lowp float;
						
						uniform sampler2D uTexture;
						
						void main() {
							gl_FragColor = texture2D(uTexture,gl_PointCoord);
							
							// Discard the pixel if it has no alpha
							if (gl_FragColor.a == 0.0) {
								discard;
							}
						}
					`
				);
				
				// Get the 'location' of uniform variables in our raindrop program
				// These will be used later to upload new uniform values to the gpu
				uRaindropPosition = gl.getUniformLocation(raindropProgram,"uRaindropPosition");
				uRaindropSize = gl.getUniformLocation(raindropProgram,"uRaindropSize");
				
				/*
					Create and upload an array of data to the GPU,
					each line contains all the attribute that make up one
					vertex, here it's an x y coorinate for the position on the screen
					followed by another x y coordinate for the position of the texture that
					should be displayed at that corner (coordinates for textures are normally called UVs U = x, V = y)
					
					Below is a list of verticies that make up two triangles that forms a rectangle that covers the whole screen,
					triangles in WebGL should go in counter clockwise order
				*/
				backgroundBuffer = createBuffer([
					// X    Y	  U   V
					-1.0, 1.0,	0.0,0.0, // Top left
					-1.0,-1.0,	0.0,1.0, // Bottom left
					 1.0, 1.0,	1.0,0.0, // Top right
					 
					 1.0, 1.0,	1.0,0.0, // Top right
					-1.0,-1.0,	0.0,1.0, // Top left
					 1.0,-1.0,	1.0,1.0  // Bottom right
				]);
				
				// Create & upload textures
				backgroundTexture = createTexture(images.background);
				raindropTexture = createTexture(images.raindrop);
				
				// Set clearcolour, when we want an empty canvas
				//            R   G   B   A
				gl.clearColor(0.5,0.5,0.5,1.0);
				
				// Create some randomly placed raindrops to simulate
				for (var i = 0; i < 10; ++i) {
					raindrops.push(
						new Raindrop(
							Math.random() * canvasWidth,
							Math.random() * canvasHeight,
							25.0
						)
					);
				}
				
				loop();
			}
			
			// Called when the page is reloading or getting closed
			onunload = function() {
				gl.deleteProgram(backgroundProgram);
				gl.deleteProgram(raindropProgram);
				gl.deleteBuffer(backgroundBuffer);
				gl.deleteTexture(backgroundTexture);
				gl.deleteTexture(raindropTexture);
			}
			
		}();
		
		</script>
	</body>
</html>