I'm working on creating an color-trail effect in webgl. Basically I'd like to be able to select a range of colors from a texture and have them trail after the texture as its animated across the screen. My strategy was to both draw the texture to the canvas as well as to a framebuffer, using the additionsl buffer to select and fade out the selected colors, resulting in a trail after the image.
I've had some success setting preserveAlphaBuffer to true and having the trail appear in the framebuffer but I can't seem to be able to get the framebuffer to blend with the backbuffer. All I get is the framebuffer with a solid background. I'm beginning to feel its not possible as I've tried just about every combination on blendFunc and blendFuncSeparate. Hopefully there's someone who can help me realize this effect?
var sketch;
function init() {
sketch = new Sketch();
document.body.appendChild(sketch);
sketch.width = window.innerWidth;
sketch.height = window.innerHeight;
loop();
}
function loop() {
requestAnimationFrame(loop);
sketch.draw();
}
var Sketch = function () {
var _canvas = document.createElement("canvas");
var _gl;
var _image;
var _program, _fboProgram;
var _loaded = false;
var _fbo, _fboTexture,_texture;
var pos = {
x: 0, y: 0
};
var speed = {
x: 10,
y: 10
}
function init() {
//start preloading image
_image = new Image();
_image.onload = function () {
setTimeout(function () {
render();
}, 1);
}
_image.src = "img/texture.png";
}
function render() {
_gl = _canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});//setupWebGL(canvas, {preserveDrawingBuffer: true});//getWebGLContext(_canvas);
initCanvas();
initFbo();
_loaded = true;
}
function initCanvas() {
_program = initShaders(_gl, "vertex-shader", "fragment-shader");
_gl.useProgram(_program);
initVertexShaderLocations(_program);
_gl.clearColor(1, 1, 1, 0); // red
_gl.clear(_gl.COLOR_BUFFER_BIT);
_texture = initTexture(_gl, _image);
_gl.enable(_gl.BLEND);
_gl.disable(_gl.DEPTH_TEST);
_gl.colorMask(1, 1, 1, 1);
}
function initFbo() {
_fboProgram = initShaders(_gl, "vertex-shader", "fbo-fragment-shader");//"2d-fragment-shader");
_gl.useProgram(_fboProgram);
initVertexShaderLocations(_fboProgram);
_texture = initTexture(_gl, _image);
// create an empty texture
_fboTexture = _gl.createTexture();
_gl.bindTexture(_gl.TEXTURE_2D, _fboTexture);
_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);
_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST);
_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.NEAREST);
_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _gl.canvas.width, _gl.canvas.height, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null);
// Create a framebuffer and attach the texture.
_fbo = _gl.createFramebuffer();
_gl.bindFramebuffer(_gl.FRAMEBUFFER, _fbo);
_gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, _fboTexture, 0);
_gl.viewport(0, 0, _gl.canvas.width, _gl.canvas.height);
_gl.clearColor(1, 1, 1, 0.2);
_gl.clear(_gl.COLOR_BUFFER_BIT);
}
function drawCanvas() {
_gl.useProgram(_program);
_gl.clearColor(1, 1, 1, 1); // red
_gl.clear(_gl.COLOR_BUFFER_BIT);
renderTexture(_program);
renderFbo(_program);
_loaded = true;
}
function drawFbo() {
_gl.bindFramebuffer(_gl.FRAMEBUFFER, _fbo);
_gl.useProgram(_fboProgram);
_gl.blendFunc(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA);
var blur = [
0, 1, 0,
1, 1, 1,
0, 1, 0
];
var kernelLocation = _gl.getUniformLocation(_fboProgram, "u_kernel[0]");
_gl.uniform1fv(kernelLocation, blur);
var textureSizeLocation = _gl.getUniformLocation(_fboProgram, "u_textureSize");
_gl.uniform2f(textureSizeLocation, _image.width, _image.height);
// Render to the texture (using clear because it's simple)
_gl.clearColor(1, 1, 1, 0); // green;
_gl.clear(_gl.COLOR_BUFFER_BIT);
renderTexture(_fboProgram);
_gl.bindFramebuffer(_gl.FRAMEBUFFER, null);
}
function renderFbo(program) {
_gl.uniform2f(program.resolutionLocation, _gl.canvas.width, _gl.canvas.height);
_gl.uniform1f(program.flipYLocation, 1);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.texCoordBuffer);
setRectangle(_gl, 0, 0, 1, 1);
_gl.enableVertexAttribArray(program.texCoordLocation);
_gl.vertexAttribPointer(program.texCoordLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.positionBuffer);
setRectangle(_gl, 0, 0, _gl.canvas.width, _gl.canvas.height);
_gl.vertexAttribPointer(program.positionLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.bindTexture(_gl.TEXTURE_2D, _fboTexture);
_gl.drawArrays(_gl.TRIANGLES, 0, 6);
}
function renderTexture(program) {
_gl.uniform1f(program.flipYLocation, 1.0);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.texCoordBuffer);
setRectangle(_gl, 0, 0, 1, 1);
_gl.enableVertexAttribArray(program.texCoordLocation);
_gl.vertexAttribPointer(program.texCoordLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.positionBuffer);
setRectangle(_gl, pos.x, pos.y, _image.width, _image.height);
_gl.vertexAttribPointer(program.positionLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.enableVertexAttribArray(program.positionLocation);
// Tell the shader the resolution of the framebuffer.
_gl.uniform2f(program.resolutionLocation, _gl.canvas.width, _gl.canvas.height);
_gl.bindTexture(_gl.TEXTURE_2D, _texture);
_gl.drawArrays(_gl.TRIANGLES, 0, 6);
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
function initVertexShaderLocations(program){
program.texCoordLocation = _gl.getAttribLocation(program, "a_texCoord");
program.positionLocation = _gl.getAttribLocation(program, "a_position");
program.resolutionLocation = _gl.getUniformLocation(program, "u_resolution");
program.flipYLocation = _gl.getUniformLocation(program, "u_flipY");
program.positionBuffer = _gl.createBuffer();
program.texCoordBuffer = _gl.createBuffer();
}
function initTexture(gl, image) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set up texture so we can render any size image and so we are
// working with pixels.
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
return texture;
}
function initShaders(gl, vert, frag) {
// setup a GLSL program
var vertexShader = createShaderFromScriptElement(gl, vert);
var fragmentShader = createShaderFromScriptElement(gl, frag);
return createProgram(gl, [vertexShader, fragmentShader]);
}
_canvas.draw = function () {
if (!_loaded)
return;
if (pos.x + 256 > _gl.canvas.width || pos.x < 0) speed.x *= -1;
if (pos.y + 256 > _gl.canvas.height || pos.y < 0) speed.y *= -1;
pos.x += speed.x;
pos.y += speed.y;
drawFbo();
drawCanvas();
}
init();
return _canvas;
}
init();
<body>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
uniform vec2 u_resolution;
uniform float u_flipY;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
v_texCoord = a_texCoord;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// Look up a color from the texture.
vec4 color = texture2D(u_image, v_texCoord);
if(color.a < 0.9)
color.a = 0.1;
gl_FragColor = color;
}
</script>
<script id="fbo-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_image, v_texCoord);
if(color.r > 0.5)
color.a = 0.01;
gl_FragColor = color;
}
</script>
</body>
So I did this short demo:
http://jsfiddle.net/windkiller/c46Lvbzp/
// custom fn
function createShaderFromScriptElement(gl, scriptId) {
var shaderSource = "",
shaderType;
var shaderScript = document.getElementById(scriptId);
if (!shaderScript) {
throw ("*** Error: unknown script element" + scriptId);
}
shaderSource = shaderScript.text;
if (shaderScript.type == "x-shader/x-vertex") {
shaderType = gl.VERTEX_SHADER;
} else if (shaderScript.type == "x-shader/x-fragment") {
shaderType = gl.FRAGMENT_SHADER;
} else if (shaderType != gl.VERTEX_SHADER && shaderType != gl.FRAGMENT_SHADER) {
throw ("*** Error: unknown shader type");
return null;
}
var shader = gl.createShader(shaderType);
// Load the shader source
gl.shaderSource(shader, shaderSource);
// Compile the shader
gl.compileShader(shader);
// Check the compile status
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
// Something went wrong during compilation; get the error
var lastError = gl.getShaderInfoLog(shader);
errFn("*** Error compiling shader '" + shader + "':" + lastError);
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, shaders) {
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, shaders[0]);
gl.attachShader(shaderProgram, shaders[1]);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
// HINT dont use program here
return shaderProgram;
}
I had to do few things manually, this is summary:
* createShaderFromScript and createProgram functions were missing, so I did it. Im sure you have them, so just ignore it.
* I didnt have texture, so I made base64 image with green and red colors and used it as texture. It is quite long, so I didnt append code here.
After this, demo started to work. But as you reported, it is just floating image. I tried to investigate your code, but I was too lazy and didnt have that much time.
But I had feeling that you have error in order how you work with frame buffers! So I tried to swap draw calls:
drawCanvas();
drawFbo();
And it did something! So I also:
* augment speed
* change shaders a little (just variables for alpha calculation)
* made button stop/start
Now when you run the demo and use stop, you will see two pictures, one greenred and second blended greenred, where more red = less visible (green color does bigger trail then red).
So I think you are very close to finish it. Problem must be with order how you draw in frame buffers and when you generate texture. Did you see link which I provided in comments?
Related
The goal is a textured open cylinder. I believe the issue is with the setCamera() function. I havent made any changes from when I used the function as it is previously. Running on an http-server localhost in firefox(javascript is enabled). I would really appreciate a solution on how to make this work as well as any tips.
"glmatrix" is a Javascript Matrix and Vector library.
Here is the code:
var mat4 = glMatrix.mat4;
var segments = 64;
var cylVertices=[]; //sidevertices
var positionAttribLocation = null;
var gl = null;
var program = null;
var cylVertexBufferObject = null;
var matWorldUniformLocation =null;
var matViewUniformLocation =null;
var matProjectionUniformLocation =null;
var worldMatrix = null;
var viewMatrix = null;
var projectionMatrix = null;
var canvas = null;
var vertexShaderText =
[
'precision mediump float;',
'',
'attribute vec3 vertPosition;',
'attribute vec2 vertTexCoord;',
'varying vec2 fragTexCoord;',
'uniform mat4 mWorld;',
'uniform mat4 mView;',
'uniform mat4 mProjection;',
'',
'void main()',
'{',
' fragTexCoord = vertTexCoord;',
' gl_Position = mProjection * mView * mWorld * vec4(vertPosition, 1.0);',
'}'
].join('\n');
var fragmentTexShaderText =
[
'precision mediump float;',
'',
'varying vec2 fragTexCoord;',
'uniform sampler2D sampler;',
'void main()',
'{',
' gl_FragColor = texture2D(sampler,fragTexCoord);',
'}'
].join('\n');
var initDemo = function () {
console.log('This is working');
canvas = document.getElementById('game-surface');
gl = canvas.getContext('webgl');
if (!gl) {
console.log('WebGL not supported, falling back on experimental-webgl');
gl = canvas.getContext('experimental-webgl');
}
if (!gl) {
alert('Your browser does not support WebGL');
}
clear();
gl.enable(gl.DEPTH_TEST);//enable drawing over if closer to the virtual camera
//
//Create Shaders
//
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader,vertexShaderText);
gl.shaderSource(fragmentShader,fragmentTexShaderText);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader,gl.COMPILE_STATUS)){
console.error('ERROR compiling vertex shader!', gl.getShaderInfoLog(vertexShader));
return;
}
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader,gl.COMPILE_STATUS)){
console.error('ERROR compiling fragment shader!', gl.getShaderInfoLog(fragmentShader));
return;
}
program = gl.createProgram();
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader)
gl.linkProgram(program);
if (!gl.getProgramParameter(program,gl.LINK_STATUS)){
console.error('ERROR linking program!', gl.getProgramInfoLog(program));
return;
}
a=0, b=0, y=0, u=0, v=0, s=0, t=0; //The origin
r=1.0,g=0.4,b=0.6;
theta = (Math.PI/180) * (360);
//theta = (Math.PI/180) * (360/(segments)); //Degrees = radians * (180 / )
for (i =0;i<=(segments);i++){
x = i*Math.cos(theta);
z = i*Math.sin(theta);
s=(theta+180)/360;
cylVertices.push(x, y, z, s,0); //Sidevertices along the bottom
cylVertices.push(x, y+2, z, s,2); //Sidevertices along the top
}
cylArray = new Float32Array(cylVertices); //sidearray
//
//------MAIN RENDER LOOP-------
//
var angle = 0;
var loop = function (){
setCamera();
angle = performance.now() / 1000 / 6 * 2 * Math.PI;
mat4.rotate(xRotationMatrix,identityMatrix, angle, [0,1,0]);//x rotation
gl.uniformMatrix4fv(matWorldUniformLocation, gl.FALSE, xRotationMatrix);
clear();
theVertexBufferObject = gl.createBuffer();
drawCylSide();
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
};
//
// --------------------functions--------------------
//
function drawCylSide(){
gl.bindBuffer(gl.ARRAY_BUFFER,theVertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cylArray),gl.STATIC_DRAW);
setPointer(5);
var texCoordAttribLocation = gl.getAttribLocation(program,'vertTexCoord');
gl.vertexAttribPointer(
texCoordAttribLocation, //Attribute location
2, //Number of vertix elements
gl.FLOAT, //Type of elements
gl.FALSE, //Normalised
5 * Float32Array.BYTES_PER_ELEMENT, //Size of individual vertex
3 * Float32Array.BYTES_PER_ELEMENT, //Offset
);
gl.enableVertexAttribArray(positionAttribLocation);
gl.enableVertexAttribArray(texCoordAttribLocation);
var boxTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, boxTexture);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,gl.LINEAR);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER,gl.LINEAR);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
gl.UNSIGNED_BYTE,
document.getElementById('wall-img')
);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.useProgram(program);
gl.bindTexture(gl.TEXTURE_2D, boxTexture);
gl.activeTexture(gl.TEXTURE0);
gl.drawArrays(gl.TRIANGLE_STRIP,0, segments);
};
function setPointer(n){
positionAttribLocation = gl.getAttribLocation(program,'vertPosition');
gl.vertexAttribPointer(
positionAttribLocation, //Attribute location
3, //Number of vertix elements
gl.FLOAT, //Type of elements
gl.FALSE, //Normalised
n * Float32Array.BYTES_PER_ELEMENT, //Size of individual vertex
0 //Offset
);
};
function clear(){
gl.clearColor(0.75, 0.85, 0.8, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
};
function setCamera(){
mat4.identity(worldMatrix);
mat4.lookAt(viewMatrix,[0,1,-8],[0,0,0],[0,10,0]);
mat4.perspective(projectionMatrix,glMatrix.glMatrix.toRadian(45),canvas.width/canvas.height,0.1,1000.0);
gl.uniformMatrix4fv(matWorldUniformLocation,gl.FALSE,worldMatrix);
gl.uniformMatrix4fv(matViewUniformLocation,gl.FALSE,viewMatrix);
gl.uniformMatrix4fv(matProjectionUniformLocation,gl.FALSE,projectionMatrix);
};
You need to initialize vectors and matrices using the respective create function of glMatrix.
var worldMatrix = mat4.create();
var viewMatrix = mat4.create();
var projectionMatrix = mat4.create();
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I tried to study WebGL, but I have nothing.
I do not want to use the library, they are too big.
I wrote what I want on the canvas, help me to do it on WebGL
https://jsfiddle.net/g9tx903c/
<canvas id="canvas" width="500" height="200"></canvas>
<script type='text/javascript'>
function DrawPoint(x, y, size, blur, opacity) {
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var halfSize = size / 2;
var radgrad = ctx.createRadialGradient(halfSize + x, halfSize + y, 0, halfSize + x, halfSize + y, halfSize);
radgrad.addColorStop(blur, 'rgba(255, 255, 255, ' + opacity +')');
radgrad.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = radgrad;
ctx.fillRect(x, y, size, size);
}
function DrawcGradient() {
var w = 500;
var h = 200;
var pointR = w / 2;
var x = (w / 2);
var y = (h / 2);
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var grd = ctx.createRadialGradient(x, y, 0, x, y, pointR);
grd.addColorStop(0, '#5A6977');
grd.addColorStop(1, '#000');
ctx.fillStyle = grd;
ctx.fillRect(0, 0, w, h);
}
DrawcGradient();
DrawPoint(50, 100, 50, 0.5, 0.2);
DrawPoint(70, 10, 150, 0.93, 0.2);
</script>
This just happens to fall into a domain I am catching up on again. Maybe not quite what you want but you can remove all the 2D canvas stuff and just keep the webGL stuff.
This demo mixes 2D and 3D canvas interfaces to get high performance GPU processing on 2D canvas content.
The fragment shader does the work of creating the gradients near bottom of code called FragmentShader
Radial gradients are created by calculating the distance from the center and getting a value 0 to 1. I use this value to get the 3 gradients. To change the sharpness of the gradients I multiply, pull down and clamp the values.
eg the big circle near mouse is 1 at center and 0 away. I multiply by 350 to give 350 to 0. then I pull down by 270 to give 80 to -270, then I clamp to 0 and 1. The result is a sharp gradient.
I position the circles on a line from a point just at the top of the canvas and one near the mouse the other further along the line.
There is a lot of boiler plate stuff and I am a little over it today so you will have to nut out the rest.
Very easy to convert to all webGL. Just remove all the canvas mouse stuff up the top. Create an image and call startWebGL(image) Add webGL canvas (after you create it in startWebGL function) to the document body and call at least once the function webGLRender to set the shader variables and render a frame.
Warning the shaders have high level directive and can not be used without supporting code. To use with other setups remove the # infront of all uniform and attribute variables in shaders. Ie #uniform vec2 mouse; becomes uniform vec2 mouse;
//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable
// like canvas, ctx, mouse, w, h (width and height), globalTime
// It should not be used as an example of how to write a canvas interface.
// By Blindman67
const U = undefined;
const RESIZE_DEBOUNCE_TIME = 100;
var onresize; // demo use this to do your thing
var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0;
createCanvas = function () { // create 2D display canvas
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) {
canvas = createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onresize === "function"){
resizeCount += 1;
setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);
}
}
function debounceResize(){
resizeCount -= 1;
if(resizeCount <= 0){
onresize();
}
}
setGlobals = function(){
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
mouse.updateBounds();
}
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3],
active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}
e.preventDefault();
}
m.updateBounds = function(){
if(m.active){
m.bounds = m.element.getBoundingClientRect();
}
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
m.active = true;
m.updateBounds();
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
m.active = false;
}
}
return mouse;
})();
/** SimpleFullCanvasMouse.js end **/
function display(){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if(webGL !== undefined){
webGLRender();
}
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
// END of boilerplate stuff.
/* ***************************************************************************************************
The following functions are helpers for Shader variables. Rather than having to type all the mumbo
jumbo to locate shader variable and then store the location ID getVariables and getLocations does
it for you. uniform and attribute variable that are prefixed with # are can used via the gl.locs.name
(no space between # and variable type #attribute is good # attribute is bad)
For example
#uniform vec3 myVec; // the shader source code
Is located and given the name myVec in gl.locs and can be set in javascript
gl.uniform3f(gl.locs.myVec, 0.3, 0.5, 0.8); //
Please not that this makes shaders source code none standard and will not complie as is without these
functions . Just remove the #
*************************************************************************************************** */
const VAR_TYPES = ["attribute","uniform"];
const VAR_LOCATE_FUNC = {attribute : "getAttribLocation", uniform : "getUniformLocation"}
// get # delimited variables from shader source
function getVariables(script,types){
VAR_TYPES.forEach(f => {
if(types.items === undefined){ types.items = []; }
script = script.replace(new RegExp("#" + f+".+;","g"), str => {
var data = str.replace(/ /g," ").split(" ");
types.items.push({use : f , type : data[1] , name : data[2].replace(";","")});
return str.substr(1);
})
})
return script;
}
// get location IDs for shader variables
var getLocations = function(gl,shaders){
var locs = {};
shaders.variables.items.forEach(v => { locs[v.name] = gl[VAR_LOCATE_FUNC[v.use]](shaders.program, v.name); });
return locs;
}
/* end of var heplers ***************************************************************************** */
// creates vertex and fragment shaders
function createProgramFromScripts( gl, ids) {
var shaders = [];
var variables = {};
for (var i = 0; i < ids.length; i += 1) {
var script = shadersSource[ids[i]];
if (script !== undefined) {
var shader = gl.createShader(gl[script.type]);
var source = getVariables(script.source,variables)
gl.shaderSource(shader, source);
gl.compileShader(shader);
shaders.push(shader);
}else{
throw new ReferenceError("*** Error: unknown script ID : " + ids[i]);
}
}
var program = gl.createProgram();
shaders.forEach((shader) => { gl.attachShader(program, shader); });
gl.linkProgram(program);
gl.locs = getLocations(gl,{ program : program,variables : variables});
return program;
}
// setup simple 2D webGL image processor
var webGL;
function startWebGL(image) {
webGL = document.createElement("canvas");
webGL.width = image.width;
webGL.height = image.height;
var gl = webGL.gl = webGL.getContext("webgl");
var program = createProgramFromScripts(gl, ["VertexShader", "FragmentShader"]);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(gl.locs.texCoord);
gl.vertexAttribPointer(gl.locs.texCoord, 2, gl.FLOAT, false, 0, 0);
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform2f(gl.locs.resolution, webGL.width, webGL.height);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.enableVertexAttribArray(gl.locs.position);
gl.vertexAttribPointer(gl.locs.position, 2, gl.FLOAT, false, 0, 0);
setRectangle(gl, 0, 0, image.width, image.height);
}
function setRectangle(gl, x, y, width, height) { // set draw rectangle
var x1 = x + width;
var y1 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ x, y, x1, y, x, y1, x, y1, x1, y, x1, y1]), gl.STATIC_DRAW);
}
var shadersSource = {
VertexShader : {
type : "VERTEX_SHADER",
source : `
// the # is a higher level directive to indicate that the variable needs to
// be loacted as value set or read
#attribute vec2 position;
#attribute vec2 texCoord;
#uniform vec2 resolution;
varying vec2 u_texCoord; // varying means this is moved to the frag shader
float aspect = resolution.x/resolution.y;
varying float aspect1;
void main() {
vec2 zeroToOne = position / resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
u_texCoord = vec2(texCoord.x ,texCoord.y / aspect);
aspect1 = aspect;
}`
},
FragmentShader : {
type : "FRAGMENT_SHADER",
source : `
// the # is a higher level directive to indicate that the variable needs to
// be loacted as value set or read
precision mediump float;
#uniform sampler2D u_image;
#uniform vec4 backDark; // the background dark colour
#uniform vec4 backLight; // backgroun light colour
#uniform vec4 ringCol; // rings colour
#uniform vec2 lightLoc; // location of point from which to project second ring
#uniform vec2 mouse; // location of big ring
varying float aspect1;
varying vec2 u_texCoord; // texture coord for all mapping and comes from the
// vertext shader. If you change the name here
// change it in the vert shader
float dist; // used for background gradient
vec4 pixelCol; // to hold the final pixel colour
vec2 gradCenter = vec2(0.5,0.25); // location of background gradient center
vec2 secondRing = lightLoc+ (mouse - lightLoc) * (1.2 + distance(mouse,lightLoc));
void main() {
pixelCol = texture2D(u_image, vec2(u_texCoord.x,u_texCoord.y * aspect1));
//pixelCol = texture2D(u_image, vec2(u_texCoord.x,u.texCoord.y );
// get distance from center of background gradient
dist = distance(gradCenter,u_texCoord) / 0.707;
// use dist to caculate the background gradient
pixelCol += (backDark - backLight) * dist + backLight;
// add the big ring colour to the background. mouse is the center of big ring
pixelCol += clamp((1.0-distance(mouse,u_texCoord)) * 345.0 - 270.5 ,0.0,1.0) * ringCol;
// add the second rign colour to the background colour. secondRing is location of second ring
pixelCol += clamp((1.0-distance(secondRing,u_texCoord)) * 29.0 - 27. ,0.0,1.0) * ringCol;
gl_FragColor = pixelCol; // set the fragment to the colour caculated
}`
}
}
var firstRun = true;
function webGLRender(){
var gl = webGL.gl;
if(firstRun ){
firstRun = false;
gl.uniform4f(gl.locs.backDark, 0,0,0,1)
gl.uniform4f(gl.locs.backLight, 0.3,0.5,0.8,1)
gl.uniform4f(gl.locs.ringCol, 0.1,0.1,0.38,0.41)
gl.uniform2f(gl.locs.lightLoc, 0.5,0.0);
}
gl.uniform2f(gl.locs.mouse, mouse.x / w,mouse.y / h);
gl.drawArrays(gl.TRIANGLES, 0, 6);
ctx.drawImage(webGL,0,0, canvas.width, canvas.height);
}
function createImageAndStartWebGL(){
var image = document.createElement("canvas");
image.width = canvas.width;
image.height = canvas.height;
image.ctx = image.getContext("2d");
image.ctx.fillRect(0,0,canvas.width,canvas.height);
image.ctx.font = "48px arial";
image.ctx.textAlign = "center";
image.ctx.fillStyle = "white";
image.ctx.fillText("WebGL & Canvas 2D demo",canvas.width/2,48);
image.ctx.font = "16px arial";
image.ctx.fillText("WebGL fragment shader processing 2D canvas image, then render back to 2D canvas.",canvas.width/2,66);
firstRun = true;
startWebGL(image);
}
// Add the setup to the debounced resize
onresize = createImageAndStartWebGL;
// start it all happening
resizeCanvas();
mouse.start(canvas,true);
window.addEventListener("resize",resizeCanvas);
requestAnimationFrame(update);
Very new to WebGL and attempting to port some 2D image processing shaders in order to get a handle on things. I initially was misled by the MDN tutorials into thinking WebGL was like OpenGL desktop, but then found these tutorials, which I found much more true to form as well as my purposes. However, I'm still having some trouble in formatting a render loop so I can pass a continually updating texture for processing. In cases where it does render, I just get a muddy mess and in cases where the vertex shader isn't a simple pass through I get nothing. I understand GLSL and the basics of how buffers work, but clearly am doing something very wrong here... Any help would be greatly appreciated. Thanks!
class GL {
constructor(canvas){
this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!this.gl) {
alert("Unable to initialize WebGL. Your browser may not support it.");
this.gl = null;
}
//init shaders
var fragmentShader = getShader(this.gl, "fshader");
var vertexShader = getShader(this.gl, "vshader");
var shaderProgram = this.gl.createProgram();
this.gl.attachShader(shaderProgram, vertexShader);
this.gl.attachShader(shaderProgram, fragmentShader);
this.gl.linkProgram(shaderProgram);
this.gl.useProgram(shaderProgram);
this.positionLocation = this.gl.getAttribLocation(shaderProgram, "position");
this.texCoordLocation = this.gl.getAttribLocation(shaderProgram, "texcoord");
var resolutionLocation = this.gl.getUniformLocation(shaderProgram, "resolution");
this.width = this.gl.getUniformLocation(shaderProgram, "width");
//set resolution
this.gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
function getShader(gl, id) {
var shaderScript, theSource, currentChild, shader;
shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
theSource = "";
currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == currentChild.TEXT_NODE) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
// Unknown shader type
return null;
}
gl.shaderSource(shader, theSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
};
render(bufferCanvas, x, y) {
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
//texture coordinates
var texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
this.gl.enableVertexAttribArray(this.texCoordLocation);
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]),
this.gl.STATIC_DRAW);
//create texture
var texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
//normalize image to powers of two
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
//load texture from 2d canvas
this.gl.texImage2D(this.gl.TEXTURE_2D,
0,
this.gl.RGBA,
this.gl.RGBA,
this.gl.UNSIGNED_BYTE,
bufferCanvas);
//load buffer
var buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.enableVertexAttribArray(this.positionLocation);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
//draw size and position
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([
x, y,
x + bufferCanvas.width, y,
x, y + bufferCanvas.height,
x, y + bufferCanvas.height,
x+ bufferCanvas.width, y,
x+ bufferCanvas.width, y + bufferCanvas.height]), this.gl.STATIC_DRAW);
//blur width
this.gl.enableVertexAttribArray(this.width);
this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
//draw
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
};
};
var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));
canvasGL.width = 5.0;
for(var i=0; i<10; i++) {
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var a = Math.floor(Math.random() * 255);
context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
var x = Math.random() * canvas2d.width;
var y = Math.random() * canvas2d.height;
var width = canvas2d.width - (Math.random() * canvas2d.width);
var height = canvas2d.height - (Math.random() * canvas2d.height);
context2d.fillRect(x, y, width , height);
canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"), canvas2d.getBoundingClientRect("top"));
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sylvester/0.1.3/sylvester.min.js"></script>
<script src="https://github.com/mdn/webgl-examples/blob/gh-pages/tutorial/glUtils.js"></script>
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 position;
attribute vec2 texcoord;
uniform vec2 resolution;
uniform float width;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);
// get texcoords
texcoord11 = texcoord;
texcoord00 = texcoord + vec2(-width, -width);
texcoord02 = texcoord + vec2( width, -width);
texcoord20 = texcoord + vec2( width, width);
texcoord22 = texcoord + vec2(-width, width);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D image;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
vec4 blur;
blur = texture2D(image, texcoord11);
blur += texture2D(image, texcoord00);
blur += texture2D(image, texcoord02);
blur += texture2D(image, texcoord20);
blur += texture2D(image, texcoord22);
gl_FragColor = 0.2 * blur;
}
</script>
</head>
<body>
<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="visibility:hidden;"></canvas>
</body>
There's several issues with the code
neither of the script tags seem to be needed
You assign this.width to a uniform location
this.width = this.gl.getUniformLocation(shaderProgram, "width");
But then later destroy that with
canvasGL.width = 5.0
width is a uniform but you're trying to set it as an attribute
wrong
this.gl.enableVertexAttribArray(this.width);
this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
right
this.gl.uniform1f(this.width, whateverYouWantedWidthToBe);
You've got unneeded strides on all your attributes
wrong
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
right
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 0, 0);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 0, 0);
Well I suppose the texcoord one is not wrong if you want to set strides but the position one is wrong since you're using 2 floats per position not 3. Why not just set them to 0?
You're assigning the texCoordLocation to a var but then using it as a property on this
wrong
var texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
right?
this.texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
On top of that the structure of the code is probably not what you intended.
In WebGL you don't generally call gl.createXXX functions in your render function. You'd call gl.createXXX functions those during initialization. See here for a more typical structure.
It's not clear at all what this line is trying to do
canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"),
canvas2d.getBoundingClientRect("top"))
What do you think canvas2d.getBoundingClientRect() is going to return that's useful for rendering? Also that's not how getBoundingClientRect works. It returns a rect, it doesn't take any arguments.
Once you have all that fixed it's not clear what you want width to be. I'm assuming you want it to be pixels but in that case it needs to be 1 / canvas2D.width and you need a separate value for height in your shader since you'll need different values to move up and down a certain number of pixels vs left and right.
other suggestions
Pull gl into a local variable. Then you don't have to do this.gl everywhere. Less typing, shorter, and faster
You can get the contents of script tag with just this
var theSource = shaderScript.text;
You're checking for shader compile errors but not link errors.
visibility: hidden; doesn't do what I think you think it does. You probably want display: none;.
Here's one version that does something.
class GL {
constructor(canvas){
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl) {
alert("Unable to initialize WebGL. Your browser may not support it.");
return;
}
this.gl = gl;
//init shaders
var fragmentShader = getShader(gl, "fshader");
var vertexShader = getShader(gl, "vshader");
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("An error occurred linking the shaders: " + gl.getProgramInfoLog(shaderProgram));
return;
}
this.shaderProgram = shaderProgram;
this.positionLocation = gl.getAttribLocation(shaderProgram, "position");
this.texCoordLocation = gl.getAttribLocation(shaderProgram, "texcoord");
this.resolutionLocation = gl.getUniformLocation(shaderProgram, "resolution");
this.blurOffsetLocation = gl.getUniformLocation(shaderProgram, "blurOffset");
// init texture coordinates
this.texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]),
gl.STATIC_DRAW);
// create position buffer
this.positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
//create texture
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
//normalize image to powers of two
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
function getShader(gl, id) {
var shaderScript, theSource, currentChild, shader;
shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var theSource = shaderScript.text;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
// Unknown shader type
return null;
}
gl.shaderSource(shader, theSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
};
render(bufferCanvas, x, y, blurAmount) {
var gl = this.gl;
gl.clear(this.gl.COLOR_BUFFER_BIT);
gl.useProgram(this.shaderProgram);
// setup buffers and attributes
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
gl.enableVertexAttribArray(this.texCoordLocation);
gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
//load buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
gl.enableVertexAttribArray(this.positionLocation);
gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0);
//draw size and position
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x, y,
x + bufferCanvas.width, y,
x, y + bufferCanvas.height,
x, y + bufferCanvas.height,
x+ bufferCanvas.width, y,
x+ bufferCanvas.width, y + bufferCanvas.height]), gl.STATIC_DRAW);
//load texture from 2d canvas
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
bufferCanvas);
//blur width
gl.uniform2f(this.blurOffsetLocation,
blurAmount / bufferCanvas.width,
blurAmount / bufferCanvas.height);
//set resolution
gl.uniform2f(this.resolutionLocation,
gl.canvas.width,
gl.canvas.height);
//draw
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
};
var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));
function render(time) {
time *= 0.001;
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var a = Math.floor(Math.random() * 255);
context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
var x = Math.random() * canvas2d.width;
var y = Math.random() * canvas2d.height;
var width = canvas2d.width - (Math.random() * canvas2d.width);
var height = canvas2d.height - (Math.random() * canvas2d.height);
context2d.fillRect(x, y, width , height);
canvasGL.render(canvas2d, 0, 0, Math.sin(time) * 5);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 position;
attribute vec2 texcoord;
uniform vec2 resolution;
uniform vec2 blurOffset;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);
// get texcoords
texcoord11 = texcoord;
texcoord00 = texcoord + blurOffset * vec2(-1, -1);
texcoord02 = texcoord + blurOffset * vec2( 1, -1);
texcoord20 = texcoord + blurOffset * vec2( 1, 1);
texcoord22 = texcoord + blurOffset * vec2(-1, 1);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D image;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
vec4 blur;
blur = texture2D(image, texcoord11);
blur += texture2D(image, texcoord00);
blur += texture2D(image, texcoord02);
blur += texture2D(image, texcoord20);
blur += texture2D(image, texcoord22);
// do you really want to blend the alpha?
gl_FragColor = 0.2 * blur;
}
</script>
<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="display: none;"></canvas>
code:
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
Problem in that on a figure "superfluous" is drawn:
how to correct it?
P.S. alpha=0.9
What is your perspective zNear and zFar set to? Is it possible you're setting it too close and the back of your cube is being clipped? See sample below where it's set too close. That doesn't look like your issue but it's hard to tell.
Also are you sorting your polygons? When rendering transparent things you generally have to draw front to back. For a convex object like a sphere, pyramid, or cube you can draw twice with culling on, first with gl.cullFace(gl.FRONT) to draw only the backfacing triangles, the ones further from the camera, and then again with gl.cullFace(gl.BACK) to draw only the front facing triangles, the ones closer to the camera.
Yet another issue is are you correctly providing premultiplied alpha to the canvas? Most shaders do this
gl_FragColor = someUnpremultipliedAlphaColor;
But by default you need to provide pre-multiplied alpha colors
gl_FragColor = vec4(color.rgb * color.a, color.a);
Or you can set the canvas to use un-premultiplied colors
gl = someCanvas.getContext("webgl", { premultipliedAlpha: false });
window.onload = function() {
// Get A WebGL context
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
var programInfo = webglUtils.createProgramInfo(gl, ["vs", "fs"]);
var createFlattenedVertices = function(gl, vertices) {
return webglUtils.createBufferInfoFromArrays(
gl,
primitives.makeRandomVertexColors(
primitives.deindexVertices(vertices),
{
vertsPerColor: 6,
rand: function(ndx, channel) {
return channel < 3 ? ((128 + Math.random() * 128) | 0) : 255;
}
})
);
};
var bufferInfo = createFlattenedVertices(gl, primitives.createCubeVertices(1));
function degToRad(d) {
return d * Math.PI / 180;
}
var cameraAngleRadians = degToRad(0);
var fieldOfViewRadians = degToRad(60);
var uniforms = {
u_color: [1, 1, 1, 0.8],
u_matrix: null,
};
var zClose = false;
var zNear = 1;
var zFar = 3;
var zElem = document.getElementById("z");
var bElem = document.getElementById("b");
bElem.addEventListener('click', toggleZDepth, false);
toggleZDepth();
function toggleZDepth() {
zClose = !zClose;
zFar = zClose ? 3.5 : 4;
zElem.innerHTML = zFar;
}
function drawScene() {
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var aspect = canvas.clientWidth / canvas.clientHeight;
var projectionMatrix =
makePerspective(fieldOfViewRadians, aspect, zNear, zFar);
var time = Date.now() * 0.0005;
var radius = 3;
var cameraPosition = [Math.cos(time) * radius, 1, Math.sin(time) * radius];
var target = [0, 0, 0];
var up = [0, 1, 0];
var cameraMatrix = makeLookAt(cameraPosition, target, up);
var viewMatrix = makeInverse(cameraMatrix);
uniforms.u_matrix = matrixMultiply(viewMatrix, projectionMatrix);
gl.useProgram(programInfo.program);
webglUtils.setBuffersAndAttributes(gl, programInfo.attribSetters, bufferInfo);
webglUtils.setUniforms(programInfo.uniformSetters, uniforms);
// draw back facing polygons first
gl.cullFace(gl.FRONT);
gl.drawArrays(gl.TRIANGLES, 0, bufferInfo.numElements);
// now draw front facing polygons
gl.cullFace(gl.BACK);
gl.drawArrays(gl.TRIANGLES, 0, bufferInfo.numElements);
requestAnimationFrame(drawScene);
}
drawScene();
}
canvas {
border: 1px solid black;
}
#overlay {
position: absolute;
top: 20px;
left: 20px;
z-index: 2;
}
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="//webglfundamentals.org/webgl/resources/webgl-3d-math.js"></script>
<script src="//webglfundamentals.org/webgl/resources/primitives.js"></script>
<canvas id="c" width="400" height="200"></canvas>
<div id="overlay">
<button id="b">toggle z-far</button>
<div>z-far = <span id="z"></span></div>
</div>
<!-- vertex shader -->
<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 v_color;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * a_position;
v_color = a_color;
}
</script>
<!-- fragment shader -->
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
varying vec4 v_color;
void main() {
vec4 color = v_color * u_color;
gl_FragColor = vec4(color.rgb * color.a, color.a); // premultiply color
}
</script>
For some reason why i map the texture to the boxes i have drawn on the 3d canvas it is not showing correctly, all i get is a blue box and not the full texture.
<script id="shader-fs" type="x-shader/x-fragment" type="text/javascript">
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
void main(void) {
gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
}
</script>
<script id="shader-vs" type="x-shader/x-vertex" type="text/javascript">
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec2 vTextureCoord;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vTextureCoord = aTextureCoord;
}
</script>
<script type="text/javascript">
var gl;
var neheTexture;
function initGL(canvas) {
try {
gl = canvas.getContext("experimental-webgl");
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch (e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}
var mvMatrix = mat4.create();
var pMatrix = mat4.create();
function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}
var squareVertexPositionBuffer;
function initBuffers() {
squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
}
var z = -50.0;
var eye = vec3.create([0, 0, z]); // negation of actual eye position
var pvMatrix = mat4.create();
var pvMatrixInverse = mat4.create();
function drawScene() {
var canvas = document.getElementById("MyCanvas");
var widthamount = Math.round(canvas.width/20);
var heightamount = Math.round(canvas.height / 20);
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(90, gl.viewportWidth / gl.viewportHeight, 0.1, 3000.0, pMatrix);
mat4.identity(mvMatrix);
// calculate the view transform mvMatrix, and the projection-view matrix pvMatrix
mat4.translate(mvMatrix, eye);
mat4.multiply(pMatrix, mvMatrix, pvMatrix);
var startHeight = -((heightamount * 2.1) / 2);
var startWidth = -((widthamount * 2.1) / 2);
mat4.translate(mvMatrix, [startWidth, startHeight, 0.0]);
for (i = 0; i < heightamount; ++i) {
for (q = 0; q < widthamount; ++q) {
mat4.translate(mvMatrix, [2.1, 0.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, neheTexture);
gl.uniform1i(shaderProgram.samplerUniform, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}
mat4.translate(mvMatrix, [-(widthamount*2.1), 2.1, 0.0]);
}
}
function webGLStart() {
resizeCanvas();
var canvas = document.getElementById("MyCanvas");
initGL(canvas);
initShaders();
initBuffers();
initTexture();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
window.addEventListener('resize', resizeCanvas, false);
tick();
}
function resizeCanvas() {
var canvas = document.getElementById("MyCanvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//drawScene();
}
function handleLoadedTexture(texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.bindTexture(gl.TEXTURE_2D, null);
}
function initTexture() {
neheTexture = gl.createTexture();
neheTexture.image = new Image();
neheTexture.image.onload = function () {
handleLoadedTexture(neheTexture)
}
neheTexture.image.src = "nehe.gif";
}
</script>
Image for texture looks like a full texture https://github.com/gpjt/webgl-lessons/blob/master/lesson05/nehe.gif
However boxes turn out to show like a blue box, i need 10 rep to put images :(((
You have no texture coordinates.
You need to set up a buffer with texture coordinates
squareTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareTextureCoordBuffer);
texcoords = [
1.0, 1.0,
0.0, 1.0,
1.0, 0.0,
0.0, 0.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW);
Then you're going to need to look up the location of the where the shader wants the texture coordinates
textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
And set up that attribute
gl.enableVertexAttribArray(textureCoordAttribute);
gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
Also while we're at it there's a bunch of minor things about the code you might want to consider
The code is turning on attributes in initShaders but if you have multiple shaders you'll need to turn on/off attributes in your draw call
The code is assigning properties to WebGL objects as in shaderProgram.pMatrixUniform = ... and squareVertexPositionBuffer.itemSize = 3; but if you ever decide to handle WebGLContextLost events that code will break because gl.createBuffer and gl.createShader will return null which means your code will be doing null.itemSize = 3 for example. It's better to make your own objects with WebGL as in var buffer = { id: gl.createBuffer(), itemSize: 3, ...}; so that if you do decide to handle WebGLContextLost events your code won't break.
The code is setting the attributes and uniforms for every face of the cube. But since they're the same for every face it would be more efficient to set the ones that don't change just once outside the loop.
The stuff about gl.viewportWidth and gl.viewportHeight is semi confusing since it makes it look like viewportWidth and viewportHeight are official parts of WebGL even though they are not. On top of that if you have a canvas that scales or changes shape they'll get out of sync which it does in resizeCanvas. Better just to use gl.canvas.width, gl.canvas.height directly or even better gl.drawBufferWidth, gl.drawingBufferHeight
The size the canvas is displayed is separate from its resolution. It's more appropriate to set your aspect ratio to the size it is displayed. In other words mat4.perspective(90, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.1, 3000.0, pMatrix); will make it render in the correct aspect ratio regardless of the resolution of the canvas.