I am under the impression that WebGL is much more powerful than the 2d renderer in the browser but for some reason, my WebGL code runs much slower. Are there any recommended optimization strategies I can use to make my WebGL code run a little bit faster?
Are there any code in my rect function that should be left out because I am new to WebGL and most tutorials don't cover how to make a rect function.
WebGL Code
const canvas = document.getElementById("canvas");
const vertexCode = `
precision mediump float;
attribute vec4 position;
uniform mat4 matrix;
uniform vec4 color;
varying vec4 col;
void main() {
col = color;
gl_Position = matrix * position;
}
`;
const fragmentCode = `
precision mediump float;
varying vec4 col;
void main() {
gl_FragColor = col;
}
`;
const width = canvas.width;
const height = canvas.height;
const gl = canvas.getContext("webgl");
if(!gl) {
console.log("WebGL not supported");
}
const projectionMatrix = [
2/width, 0, 0, 0,
0, -2/height, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1
];
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexCode);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentCode);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
function rect(x, y, w, h) {
const vertex = [
x, y, 0, 1,
x+w, y, 0, 1,
x, y+h, 0, 1,
x+w, y+h, 0, 1
]
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertex), gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 4, gl.FLOAT, false, 0, 0);
const projectionLocation = gl.getUniformLocation(program, `matrix`);
gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function fill(r, g, b, a) {
const projectionLocation = gl.getUniformLocation(program, `color`);
gl.uniform4fv(projectionLocation, [r, g, b, a]);
}
let lastTime = new Date();
function animate() {
let currentTime = new Date();
console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
lastTime = new Date();
requestAnimationFrame(animate);
for(let i=0;i<200;i++) {
fill(1, 0, 0, 1);
rect(random(0, 800), random(0, 600), 10, 10);
}
}
animate();
function random(low, high) {
return low + Math.random() * (high-low)
}
Using the normal 2D renderer
const canvas = document.getElementById("canvas");
const c = canvas.getContext("2d");
let lastTime = new Date();
function animate() {
let currentTime = new Date();
console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
lastTime = new Date();
requestAnimationFrame(animate);
c.fillStyle = "black";
c.fillRect(0, 0, 800, 600);
c.fillStyle = "red";
for(let i=0;i<200;i++) {
c.fillRect(random(0, 800), random(0, 600), 10, 10);
}
}
animate();
function random(low, high) {
return low + Math.random() * (high-low)
}
There are 1000s of ways to optimized WebGL. Which one you choose depends on your needs. The more you optimize generally the less flexible.
First you should do the obvious and not create new buffers for every rectangle as your code is doing now and also move anything outside the rendering that can be moved outside (like looking up locations) which is shown in the answer by Józef Podlecki.
Another is you could move and scale the rectangle use the uniform matrix rather than uploading new vertices for each rectangle. It's unclear whether or not updating the matrix would be faster or slower for rectangles but if you were drawing something with more vertices it would definitely be faster do it by matrix. This series of articles mentions that and builds up to matrices.
Another is you can use vertex arrays though that's not important for your example since you're only drawing a single thing.
Another is if the shapes are all the same (as yours are) you can use instanced drawing to draw all 200 rectangles with one draw call
If the shapes are not all the same you can use creative techniques like storing their data in textures.
Another is you can put more than one shape in a buffer. So for example instead of putting one rectangle in the buffer put all 200 rectangles in the buffer and then draw them with one draw call. From this presentation
Also note: the callback to requestAnimationFrame is passed the time since the page loaded so there is no need to create Date objects. Creating Date objects is slower than not and the time passed to requestAnimationFrame is more accurate than Date
let lastTime = 0;
function animate(currentTime) {
console.log(1000 / (currentTime - lastTime));
lastTime = currentTime;
...
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
It's also slow to print to the console. You'd be better off updating an element's content
let lastTime = 0;
function animate(currentTime) {
someElement.textContent = (1000 / (currentTime - lastTime)).toFixed(1);
lastTime = currentTime;
...
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
You can reuse buffer, extract getAttribLocation,getUniformLocation outside of rect function as well as vertexAttribPointer once you bound buffer and lastly call requestAnimationFrame after rendering
const canvas = document.getElementById("canvas");
const vertexCode = `
precision mediump float;
attribute vec4 position;
uniform mat4 matrix;
uniform vec4 color;
varying vec4 col;
void main() {
col = color;
gl_Position = matrix * position;
}
`;
const fragmentCode = `
precision mediump float;
varying vec4 col;
void main() {
gl_FragColor = col;
}
`;
const width = canvas.width;
const height = canvas.height;
const gl = canvas.getContext("webgl");
if(!gl) {
console.log("WebGL not supported");
}
const projectionMatrix = [
2/width, 0, 0, 0,
0, -2/height, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1
];
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexCode);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentCode);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const positionBuffer = gl.createBuffer();
const positionLocation = gl.getAttribLocation(program, "position");
const projectionLocation = gl.getUniformLocation(program, `matrix`);
const projectionColorLocation = gl.getUniformLocation(program, `color`);
gl.enableVertexAttribArray(positionLocation);
const vertex = [
0, 0, 0, 1,
0, 0, 0, 1,
0, 0, 0, 1,
0, 0, 0, 1
]
const floatArray = new Float32Array(vertex)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 4, gl.FLOAT, false, 0, 0);
gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
function rect(x, y, w, h) {
floatArray[0] = x;
floatArray[1] = y;
floatArray[4] = x + w;
floatArray[5] = y;
floatArray[8] = x;
floatArray[9] = y + h;
floatArray[12] = x + w;
floatArray[13] = y + h;
gl.bufferData(gl.ARRAY_BUFFER, floatArray, gl.STATIC_DRAW);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function fill(r, g, b, a) {
gl.uniform4fv(projectionColorLocation, [r, g, b, a]);
}
let lastTime = new Date();
function animate() {
let currentTime = new Date();
console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
lastTime = new Date();
for(let i=0;i<200;i++) {
fill(1, 0, 0, 1);
rect(random(0, 800), random(0, 600), 10, 10);
}
requestAnimationFrame(animate);
}
animate();
function random(low, high) {
return low + Math.random() * (high-low)
}
<canvas id="canvas" width="500" height="300"></canvas>
Related
I am writing a fragment shader for an image processing algorithm. The shader will run multiple times between two framebuffers in a loop (ping-pong). At some point I need to stop the loop when input and output textures are identical.
What I intend to do is the last step of Canny edge detector algorithm, "hysterezis edge tracking". I want to make a real time GPU/WebGL2 version of Canny algorithm and upload it to a web site.
This last step is as follows:
Given a double thresholded image containing "strong" edge pixels (1.0) and "weak" edge pixels (0.5)
find all chains of weak pixels connected with a strong pixel and mark them "strong"
keep all "strong" pixels and discard all remaining "weak" ones.
This can be implemented in a fragment shader running multiple times in a loop. The current "weak" pixel is marked "strong" if there is at least one strong pixel in its 8-pixel neighbourhood. At every iteration, we should have more strong pixels and less weak pixels. At the end, only isolated chains of weak pixel should remain. This is the point where the fragment shader becomes a pass-through shader and should be detected to stop the loop.
Update Sept 2019: I uploaded the GPU Canny Edge Detector here http://www.oldrinb.info/dip/canny/ . It works in browsers with WebGL2 support, as well in browsers that support WebGL1 and 'WEBGL_draw_buffers' extension. I'll put also the source code to github shortly.
I'm not 100% sure what you're asking. You're asking to compare on the CPU. You can read the contents of a texture by attaching it to a framebuffer and then calling gl.readPixels. you can then compare all the pixels. Note: not all texture formats can be attached to a framebuffer but assuming you're using a format that can. You've already attached textures to framebuffers for your ping-ponging so what more did you want?
Like I wrote in the comment on the GPU you can write a shader to compare 2 textures
#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
now just draw 1 pixel and read it with readPixels. if it's 0 the textures are the same. If it's not they are different.
The code assumes the textures are the same size but of course if they aren't the same size then we already know they can't be the same.
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
compareTextures(0, 1);
compareTextures(1, 2);
function compareTextures(ndx1, ndx2) {
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
// read the pixel
const result = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, result);
console.log('textures', ndx1, 'and', ndx2, 'are', result[0] ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
You could also use occlusion queries. The plus is they might not block the GPU where as readPixels does. The minus is you can't check them in the same JavaScript event so they might not fit your needs
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
if (len > 0.0) {
discard;
}
outColor = vec4(1);
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
function wait(ms = 0) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function test() {
await compareTextures(0, 1);
await compareTextures(1, 2);
}
test();
async function compareTextures(ndx1, ndx2) {
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
const query = gl.createQuery();
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
gl.endQuery(gl.ANY_SAMPLES_PASSED);
gl.flush();
let ready = false;
while(!ready) {
await wait();
ready = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
}
const same = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('textures', ndx1, 'and', ndx2, 'are', same ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
I want to do any ANGLE_instanced_arrays and I was read the documentation on MDN and I did not understand anything. Well, I understood that I can make graphic 2D and 3D like : examples but for me these examples are too advanced and it's difficult to understand all the code. Can anyone help me with an example?
This part I understand of the documentation official:
The ANGLE_instanced_arrays extension is part of the WebGL API and allows to draw the same object, or groups of similar objects multiple times, if they share the same vertex data, primitive count and type.
here is what I read
Can you draw without ANGLE_instanced_arrays? The difference between drawing with and without are
you call a different draw function and pass the an extra parameter of how many instances to draw. ext.drawArraysInstancedANGLE or ext.drawElementsInstancedANGLE instead of the normal ext.drawArrays or ext.drawElements
you add one or more attributes to your vertex shader who's values will only change once per instance drawn. In other words if you're drawing a cube the attribute's value will be the same value for every vertex while drawing the first cube, a different value while drawing the 2nd cube, a 3rd value while drawn the 3rd cube. Where as normal attributes change for each vertex these attributes only change once per cube/item.
The most obvious attribute to add is an extra per cube position so that each cube can have a different position added to the vertex positions but you could add another attribute for a pure cube color or add matrix attributes so you can orient each cube completely independently or whatever you want.
For those attributes that only change once per cube you set their vertex divisor to 1 by calling ext.vertexAttribDivisorANGLE. The 1 means "advance the attribute every 1 instance". 0 (the default) means advance the attribute every vertex (every iteration of the vertex shader).
Here's an example drawing a single quad (2 triangles, 6 vertices)
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const program = twgl.createProgram(gl, [vs, fs]);
const positionLocation = gl.getAttribLocation(program, "position");
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
const colorLocation = gl.getUniformLocation(program, "u_color");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// one face
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
],
), gl.STATIC_DRAW);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
}
gl.useProgram(program);
gl.uniform4fv(colorLocation, [1, .5, .2, 1]);
gl.uniformMatrix4fv(matrixLocation, false, m4.scaling([.25, .25, .25]));
const offset = 0;
const vertexCount = 6;
gl.drawArrays(gl.TRIANGLES, offset, vertexCount);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
and here's an example drawing 100 quads using ANGLE_instanced_arrays. We've added a planeOffset for an offset for each quad and a planeColor for a color for each quad.
const vs = `
attribute vec4 position;
attribute vec2 planeOffset; // per plane offset
attribute vec4 planeColor; // per plane color
uniform mat4 u_matrix;
varying vec4 v_color;
void main() {
mat4 translation = mat4(
vec4(1, 0, 0, 0),
vec4(0, 1, 0, 0),
vec4(0, 0, 1, 0),
vec4(planeOffset, 0, 1));
gl_Position = u_matrix * translation * position;
v_color = planeColor;
}
`;
const fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
function main() {
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("ANGLE_instanced_arrays");
if (!ext) {
alert("need ANGLE_instanced_arrays");
return;
}
const program = twgl.createProgram(gl, [vs, fs]);
const positionLocation = gl.getAttribLocation(program, "position");
const offsetLocation = gl.getAttribLocation(program, "planeOffset");
const colorLocation = gl.getAttribLocation(program, "planeColor");
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// one face
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
],
), gl.STATIC_DRAW);
// make 100 offsets and 100 colors
const colors = [];
const offsets = [];
const numInstances = 100;
for (let i = 0; i < 100; ++i) {
colors.push(Math.random(), Math.random(), Math.random(), 1);
offsets.push(Math.random() * 20 - 10, Math.random() * 20 - 10);
}
// put those in buffers
const offsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(offsets), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
}
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.enableVertexAttribArray(offsetLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(offsetLocation, size, type, normalize, stride, offset);
ext.vertexAttribDivisorANGLE(offsetLocation, 1);
}
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.enableVertexAttribArray(colorLocation);
{
const size = 4; // 4 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
ext.vertexAttribDivisorANGLE(colorLocation, 1);
}
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLocation, false, m4.scaling([.1, .1, .1]));
const offset = 0;
const vertexCount = 6;
ext.drawArraysInstancedANGLE(gl.TRIANGLES, offset, vertexCount, numInstances);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
I'm trying to learn WebGL (and some math from codingmath). The goal today is to draw a sine wave at any start and ending direction.
Something like this:
I'm just missing something in my makePoints() method. My points plot out oddly and I'm kinda dumbfounded on where to go next.
QUESTION:
How do I fix my makePoints() function, so that it will plot out the x and y coords of a sine wave.
let gl,
shaderProgram,
vertices,
canvas;
const VERTEX_LENGTH = 1500;
const VERTEX_SHADER = `
attribute vec4 coords;
attribute float pointSize;
void main(void) {
gl_Position = coords;
gl_PointSize = pointSize;
}
`;
const FRAGMENT_SHADER = `
precision mediump float;
uniform vec4 color;
void main(void) {
gl_FragColor = color;
}
`;
initGL();
createShader();
createVertices();
draw();
window.addEventListener('resize', setCanvasSize, false);
function setCanvasSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
function initGL() {
canvas = document.querySelector('#canvas');
gl = canvas.getContext('webgl');
setCanvasSize();
console.log(gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 1);
}
function makePoints(points) {
const diff = (Math.PI * 2) / (points - 1);
const len = {length: points};
return Array.from(len, (_, i) => Math.sin(i * diff));
}
function createVertices() {
vertices = makePoints(VERTEX_LENGTH);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
const coords = gl.getAttribLocation(shaderProgram, 'coords');
gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coords);
// gl.bindBuffer(gl.ARRAY_BUFFER, null);
const pointSize = gl.getAttribLocation(shaderProgram, 'pointSize');
gl.vertexAttrib1f(pointSize, 2);
const uniformColor = gl.getUniformLocation(shaderProgram, 'color');
gl.uniform4f(uniformColor, 0, normalize(200), normalize(83), 1);
}
function createShader() {
const vs = VERTEX_SHADER;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vs);
gl.compileShader(vertexShader);
const fs = FRAGMENT_SHADER;
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fs);
gl.compileShader(fragmentShader);
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
}
function draw() {
console.log(vertices)
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(vertices));
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, VERTEX_LENGTH/2);
requestAnimationFrame(draw);
}
function normalize(val, max=255, min=0) { return (val - min) / (max - min); }
html, body, canvas {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
display: block;
position: relative;
}
<canvas id="canvas" width="500" height="500"></canvas>
Since your code expects a 2 points per vertex you need your makePoints to return different values for even (x) and odd (y) values.
I find that it's much easier to understand verbose code so here's my makePoints. Note that I find it useful to always compute a lerp0to1 value in the loop like this. I can then use that value to easily convert to nearly any type of data I want.
function makePoints(points) {
const highestPointNdx = points / 2 - 1;
return Array.from({length: points}, (_, i) => {
const pointId = i / 2 | 0;
const lerp0To1 = pointId / highestPointNdx;
const odd = i % 2;
return odd
? Math.sin(lerp0To1 * Math.PI * 2) // Y
: (lerp0To1 * 2 - 1); // X
});
}
let gl,
shaderProgram,
vertices,
canvas;
const VERTEX_LENGTH = 1500;
const VERTEX_SHADER = `
attribute vec4 coords;
attribute float pointSize;
void main(void) {
gl_Position = coords;
gl_PointSize = pointSize;
}
`;
const FRAGMENT_SHADER = `
precision mediump float;
uniform vec4 color;
void main(void) {
gl_FragColor = color;
}
`;
initGL();
createShader();
createVertices();
draw();
window.addEventListener('resize', setCanvasSize, false);
function setCanvasSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
function initGL() {
canvas = document.querySelector('#canvas');
gl = canvas.getContext('webgl');
setCanvasSize();
console.log(gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 1);
}
function makePoints(points) {
const highestPointNdx = points / 2 - 1;
return Array.from({length: points}, (_, i) => {
const pointId = i / 2 | 0;
const lerp0To1 = pointId / highestPointNdx;
const odd = i % 2;
return odd
? Math.sin(lerp0To1 * Math.PI * 2) // Y
: (lerp0To1 * 2 - 1); // X
});
}
function createVertices() {
vertices = makePoints(VERTEX_LENGTH);
console.log(vertices);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
const coords = gl.getAttribLocation(shaderProgram, 'coords');
gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coords);
// gl.bindBuffer(gl.ARRAY_BUFFER, null);
const pointSize = gl.getAttribLocation(shaderProgram, 'pointSize');
gl.vertexAttrib1f(pointSize, 2);
const uniformColor = gl.getUniformLocation(shaderProgram, 'color');
gl.uniform4f(uniformColor, 0, normalize(200), normalize(83), 1);
}
function createShader() {
const vs = VERTEX_SHADER;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vs);
gl.compileShader(vertexShader);
const fs = FRAGMENT_SHADER;
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fs);
gl.compileShader(fragmentShader);
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
}
function draw() {
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(vertices));
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, VERTEX_LENGTH/2);
requestAnimationFrame(draw);
}
function normalize(val, max=255, min=0) { return (val - min) / (max - min); }
html, body, canvas {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
display: block;
position: relative;
}
<canvas id="canvas" width="500" height="500"></canvas>
Let me add I think makePoints is currently a little confusing. I'd change it to take the number of points you want, not the number of values in the vertex buffer (which is what it takes now) which is different from the number of points. You if you want N points you need 2*N values. So, I'd change it to
function makePoints(numPoints) {
const highestPointNdx = numPoints - 1;
return Array.from({length: numPoints * 2}, (_, i) => {
const pointId = i / 2 | 0;
const lerp0To1 = pointId / highestPointNdx;
const isY = i % 2;
return isY
? Math.sin(lerp0To1 * Math.PI * 2) // Y
: (lerp0To1 * 2 - 1); // X
});
}
Then I pass in VERTEX_LENGTH and I use the same value for gl.drawArrays and neither would have to change if I was using 3D points instead of 2D points.
let gl,
shaderProgram,
vertices,
canvas;
const VERTEX_LENGTH = 1500;
const VERTEX_SHADER = `
attribute vec4 coords;
attribute float pointSize;
void main(void) {
gl_Position = coords;
gl_PointSize = pointSize;
}
`;
const FRAGMENT_SHADER = `
precision mediump float;
uniform vec4 color;
void main(void) {
gl_FragColor = color;
}
`;
initGL();
createShader();
createVertices();
draw();
window.addEventListener('resize', setCanvasSize, false);
function setCanvasSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
function initGL() {
canvas = document.querySelector('#canvas');
gl = canvas.getContext('webgl');
setCanvasSize();
console.log(gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 1);
}
function makePoints(numPoints) {
const highestPointNdx = numPoints - 1;
return Array.from({length: numPoints * 2}, (_, i) => {
const pointId = i / 2 | 0;
const lerp0To1 = pointId / highestPointNdx;
const isY = i % 2;
return isY
? Math.sin(lerp0To1 * Math.PI * 2) // Y
: (lerp0To1 * 2 - 1); // X
});
}
function createVertices() {
vertices = makePoints(VERTEX_LENGTH);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
const coords = gl.getAttribLocation(shaderProgram, 'coords');
gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coords);
// gl.bindBuffer(gl.ARRAY_BUFFER, null);
const pointSize = gl.getAttribLocation(shaderProgram, 'pointSize');
gl.vertexAttrib1f(pointSize, 2);
const uniformColor = gl.getUniformLocation(shaderProgram, 'color');
gl.uniform4f(uniformColor, 0, normalize(200), normalize(83), 1);
}
function createShader() {
const vs = VERTEX_SHADER;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vs);
gl.compileShader(vertexShader);
const fs = FRAGMENT_SHADER;
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fs);
gl.compileShader(fragmentShader);
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
}
function draw() {
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(vertices));
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, VERTEX_LENGTH);
requestAnimationFrame(draw);
}
function normalize(val, max=255, min=0) { return (val - min) / (max - min); }
html, body, canvas {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
display: block;
position: relative;
}
<canvas id="canvas" width="500" height="500"></canvas>
The vertex buffer you provided, wasn't big enough. It should store 2 floats for x AND y (instead of 1)
I've rewritten it: (check the makePoints2)
let gl,
shaderProgram,
vertices,
canvas;
const VERTEX_LENGTH = 1500;
const VERTEX_SHADER = `
attribute vec4 coords;
attribute float pointSize;
void main(void) {
gl_Position = coords;
gl_PointSize = pointSize;
}
`;
const FRAGMENT_SHADER = `
precision mediump float;
uniform vec4 color;
void main(void) {
gl_FragColor = color;
}
`;
initGL();
createShader();
createVertices();
draw();
window.addEventListener('resize', setCanvasSize, false);
function setCanvasSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
function initGL() {
canvas = document.querySelector('#canvas');
gl = canvas.getContext('webgl');
setCanvasSize();
console.log(gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.clearColor(0, 0, 0, 1);
}
function makePoints(points) {
const diff = (Math.PI * 2) / (points - 1);
const len = {length: points};
return Array.from(len, (_, i) => Math.sin(i * diff));
}
function makePoints2(points) {
let arr = Array(points * 2);
let index = 0;
for(var i=0;i<points;i++) {
let val = (i/points) * (Math.PI * 2); // lerp 0..points => 0..2PI
arr[index] = ((i/points)*2)-1; // x, lerp 0..points => -1..1 range
arr[index+1] = Math.sin(val); // y, the sinus function...
index += 2; // next vertex
}
return arr;
}
function createVertices() {
// Feel like my function is close but I'm missing something
vertices = makePoints2(VERTEX_LENGTH);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
const coords = gl.getAttribLocation(shaderProgram, 'coords');
gl.enableVertexAttribArray(coords);
gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0);
const pointSize = gl.getAttribLocation(shaderProgram, 'pointSize');
gl.vertexAttrib1f(pointSize, 2);
const uniformColor = gl.getUniformLocation(shaderProgram, 'color');
gl.uniform4f(uniformColor, 0, normalize(200), normalize(83), 1);
}
function createShader() {
const vs = VERTEX_SHADER;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vs);
gl.compileShader(vertexShader);
const fs = FRAGMENT_SHADER;
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fs);
gl.compileShader(fragmentShader);
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
}
function draw() {
console.log(vertices)
//gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(vertices));
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, VERTEX_LENGTH);
//requestAnimationFrame(draw);
}
function normalize(val, max=255, min=0) { return (val - min) / (max - min); }
html, body, canvas {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
display: block;
position: relative;
}
<canvas id="canvas" width="500" height="500"></canvas>
Thanks gman about how to do the snippets
I am trying to draw square grid on canvas.The array is filled with vertices. But the page remains blank. There seems to be no error. Thank you
var canvas;
var gl;
var grid = [];
var maxNumTriangles = 200;
var maxNumVertices = 3 * maxNumTriangles;
var index = 0;
window.onload = function init() {
canvas = document.getElementById("gl-canvas");
gl = WebGLUtils.setupWebGL(canvas);
if (!gl) { alert("WebGL isn't available"); }
canvas.addEventListener("mousedown", function (event) {
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gridarray();
document.write(grid[0]);
gl.bufferSubData(gl.ARRAY_BUFFER, 8 * index, flatten(grid));
});
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(1.0, 1.0, 1.0, 1.0);
var program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
var vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
gl.bufferData(gl.ARRAY_BUFFER, 8 * maxNumVertices, gl.STATIC_DRAW);
var vPosition = gl.getAttribLocation(program, "vPosition");
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
var cBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cBuffer);
//gl.bufferData(gl.ARRAY_BUFFER, 16 * maxNumVertices, gl.STATIC_DRAW);
render();
}
function gridarray() {
p = 10;
for (var x = 0; x <= 500; x += 40) {
var g = new Float32Array([0.5 + x + p, p]);
var g1 = new Float32Array([0.5 + x + p, 500 + p]);
grid.push(g);
grid.push(g1);
}
for (var x = 0; x <= 500; x += 40) {
var g = new Float32Array([p, 0.5 + x + p]);
var g1 = new Float32Array([500 + p, 0.5 + x + p]);
grid.push(g);
grid.push(g1);
}
}
function render() {
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.LINES, 0, 20);
window.requestAnimFrame(render);
}
Fragment and Vertex shader
attribute vec4 vPosition;
void
main()
{
gl_Position = vPosition;
}
void main()
{
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
}
you should post executable code. Without seeing the rest of the code it's hard to tell what's wrong.
Of the top of my head
What does flatten return?
I know of no flatten that takes an array of Float32Arrays and
and returns a Float32Array but I guess your's does?
It looks like you're passing in pixel coordinates but
WebGL requires clip space coordinates.
I'm want my javascript program to be shifting a triangle to the right.
What I'm focusing on right now are matrices and draw loops. I've never done such a thing so I may be off road but what I'm attempting to do, for study purposes is this:
-Setup the webgl pipeline so I display correctly a triangle (OK)
-Write a function with a matrix that allows me to pass in the values of translation (seems ok but I'm not sure)
var translation_prototype = [1,0,0,0,
0,1,0,0,
0,0,1,0,
tx,ty,0,1];
Leaving out for the moment rotation a scaling and modelview, since I'm perfectly happy(just for the sake of an exercise) with the orthographic view webgl provides.
-Setup a loop that cycles through the drawArrays (even here I'm not sure if the loop starts and ends in the correct place)
I suspect I'm really close, but the triangle doesn't move (tx remains constant).
Here's the code(I think I don't even need to clear the color and depth buffer since I'm only translating on x axis)
<!DOCTYPE HTML>
<html>
<canvas id = "can" width="400" height="400">
</canvas>
<script>
var webgl_canvas = document.getElementById('can');
var gl = webgl_canvas.getContext('experimental-webgl');
var triangles = [-0.8,-0.8,0,0.8,-0.8,0,0,0.8,0];
var vertexBuffer = gl.createBuffer();
var tx = 0.1;
var ty = 0;
var translation_prototype = [1,0,0,0,
0,1,0,0,
0,0,1,0,
tx,ty,0,1];
var vertexShader_source = 'attribute vec3 a_position;' + 'uniform vec4 u_translation;' + 'void main() { gl_Position = u_translation * vec4 (a_position,1);}';
var fragmentShader_source = 'precision mediump float;' + 'void main() { gl_FragColor = vec4 (0.9,0,0.1,1); }';
function getTimeInSeconds () {
return Date.now() * 0.001;
}
function makeTranslation (tx, ty) {
return translation_prototype;
}
//Compile shaders
var buildShader = function (shaderSource, typeOfShader) {
var shader = gl.createShader(typeOfShader);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert (gl.getShaderInfoLog(shader));
}
return shader;
}
var compiledVertexShader = buildShader (vertexShader_source, gl.VERTEX_SHADER);
var compiledFragmentShader = buildShader (fragmentShader_source, gl.FRAGMENT_SHADER);
//setup GLSL program
program = gl.createProgram();
gl.attachShader(program,compiledVertexShader);
gl.attachShader(program,compiledFragmentShader);
gl.linkProgram(program);
//Fill the buffer with vertex data
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(triangles), gl.STATIC_DRAW);
vertexBuffer.itemSize = 3;
vertexBuffer.numItems = 3;
gl.clear(gl.COLOR_BUFFER_BIT);
var positionLocation = gl.getAttribLocation(program,"a_position");
gl.enableVertexAttribArray(positionLocation);
gl.useProgram(program);
var shaderTranlsationMatrix = gl.getUniformLocation(program, "u_translation");
gl.uniformMatrix4fv(shaderTranlsationMatrix,false,new Float32Array(translation_prototype));
gl.vertexAttribPointer(positionLocation, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
var startTime = 0;
function animate (time) {
//Draw loop
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var deltaTime = (time - startTime);
makeTranslation((tx*deltaTime),(ty*deltaTime));
console.log(tx,ty,deltaTime);
gl.drawArrays (gl.TRIANGLES, 0, vertexBuffer.numItems);
startTime = time;
window.requestAnimationFrame(animate);
}
animate(0);
</script>
</html>
<!-- start last edited snippet -->
<!DOCTYPE HTML>
<html>
<canvas id = "can" width="400" height="400">
</canvas>
<script>
var webgl_canvas = document.getElementById('can');
var gl = webgl_canvas.getContext('experimental-webgl');
var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,0,1,-1,0,0,1,0]), gl.STATIC_DRAW);
vertexBuffer.itemSize = 3;
vertexBuffer.numItems = 3;
var identityMatrix = [1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1];
function translation (tx,ty,tz) {
return [1,0,0,0,
0,1,0,0,
0,0,1,0,
tx,ty,tz,1]
}
var vertexShader_source = 'attribute vec3 a_position;' + 'uniform mat4 u_move;' + 'void main() { gl_Position = u_move * vec4 (a_position,1); }';
var fragmentShader_source = 'precision mediump float;' + 'void main() { gl_FragColor = vec4 (0.9,0,0.1,1); }';
//Compile shaders
var buildShader = function (shaderSource, typeOfShader) {
var shader = gl.createShader(typeOfShader);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert (gl.getShaderInfoLog(shader));
}
return shader;
}
var compiledVertexShader = buildShader (vertexShader_source, gl.VERTEX_SHADER);
var compiledFragmentShader = buildShader (fragmentShader_source, gl.FRAGMENT_SHADER);
//setup GLSL program
program = gl.createProgram();
gl.attachShader(program,compiledVertexShader);
gl.attachShader(program,compiledFragmentShader);
gl.linkProgram(program);
var positionLocation = gl.getAttribLocation(program,"a_position");
gl.enableVertexAttribArray(positionLocation);
gl.useProgram(program);
var tx = 0, ty = 0, tz = 0;
var translate = gl.getUniformLocation (program, "u_move");
gl.uniformMatrix4fv(translate,false,new Float32Array(identityMatrix));
gl.vertexAttribPointer(positionLocation, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
//Draw
var start_time =0;
var animate=function(time) {
var dt= time-start_time;
tx+=0.5;
translation((dt*tx),0,0);
console.log(dt);
console.log(tx);
start_time=time;
gl.drawArrays (gl.TRIANGLES, 0, vertexBuffer.numItems);
window.requestAnimationFrame(animate);
}
animate(0);
</script>
</html>
<!-- end last edited snippet -->
Here is the working snippet
JSFIDDLE
Your vertex shader should look like this:
attribute vec3 a_position;' + 'uniform mat4 u_translation;' + 'void main() { gl_Position = u_translation*vec4 (a_position,1);}
In order to make objects move in space you have to multiply all your vectors and matrices with the position vector to get the result. Tranlation Wiki
You have to update your translation_prototype variable every loop cycle:
deltaTime += 0.005;
makeTranslation(tx+deltaTime,ty+deltaTime);
the deltaTime was declared outside the loop and incremented every cycle
Also your makeTranslation function should look like this:
function makeTranslation (x, y) {
translation_prototype =
[1,0,0,0,
0,1,0,0,
0,0,1,0,
x,y,0,1]
return translation_prototype;
}
(you can get rid of the return statement if you are using global variables, though is recommended to use local variables)
(I had to try this new snippet feature :D)
var webgl_canvas = document.getElementById('can');
var gl = webgl_canvas.getContext('experimental-webgl');
var triangles = [-0.5,-0.5,0,0.5,-0.5,0,0,0.5,0];
var vertexBuffer = gl.createBuffer();
var tx = 0;
var ty = 0;
var translation_prototype = [1,0,0,0,
- 0,1,0,0,
0,0,1,0,
0,0,0,1];
var vertexShader_source = 'attribute vec3 a_position;' + 'uniform mat4 u_translation;' + 'void main() { gl_Position = u_translation*vec4 (a_position,1);}';
var fragmentShader_source = 'precision mediump float;' + 'void main() { gl_FragColor = vec4 (0.9,0,0.1,1); }';
function getTimeInSeconds () {
return Date.now() * 0.001;
}
function makeTranslation (x, y) {
translation_prototype =
[1,0,0,0,
0,1,0,0,
0,0,1,0,
x,y,0,1]
return translation_prototype;
}
//Compile shaders
var buildShader = function (shaderSource, typeOfShader) {
var shader = gl.createShader(typeOfShader);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert (gl.getShaderInfoLog(shader));
}
return shader;
}
var compiledVertexShader = buildShader (vertexShader_source, gl.VERTEX_SHADER);
var compiledFragmentShader = buildShader (fragmentShader_source, gl.FRAGMENT_SHADER);
//setup GLSL program
program = gl.createProgram();
gl.attachShader(program,compiledVertexShader);
gl.attachShader(program,compiledFragmentShader);
gl.linkProgram(program);
//Fill the buffer with vertex data
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(triangles), gl.STATIC_DRAW);
vertexBuffer.itemSize = 3;
vertexBuffer.numItems = 3;
gl.clear(gl.COLOR_BUFFER_BIT);
var positionLocation = gl.getAttribLocation(program,"a_position");
gl.enableVertexAttribArray(positionLocation);
gl.useProgram(program);
var shaderTranlsationMatrix = gl.getUniformLocation(program, "u_translation");
gl.uniformMatrix4fv(shaderTranlsationMatrix,false,new Float32Array(translation_prototype));
gl.vertexAttribPointer(positionLocation, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
var startTime = 0;
var deltaTime = 0;
function animate (time) {
//Draw loop
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
deltaTime += 0.005;
makeTranslation(tx+deltaTime,ty+deltaTime);
gl.useProgram(program);
var shaderTranlsationMatrix = gl.getUniformLocation(program, "u_translation");
gl.uniformMatrix4fv(shaderTranlsationMatrix,false,new Float32Array(translation_prototype));
gl.vertexAttribPointer(positionLocation, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.drawArrays (gl.TRIANGLES, 0, vertexBuffer.numItems);
startTime = time;
window.requestAnimationFrame(animate);
}
animate(0);
<canvas id = "can" width="300" height="300">
</canvas>