I've just started learning WebGL.
I am rendering multiple spheres but I'm not sure about the "bindBuffer" and "bufferData" calls inside the render loops.
I can render a single sphere with 2 million vertices no problem. But once I try to render 3 spheres with 100k vertices each (300k total, 85% less vertices), the performance starts to go down.
I want to know exactly what needs to remain inside the render loop and what doesn't. And if there is something else I am missing.
Here is my Sphere "class":
function Sphere (resolution, gl, vertex, fragment) {
const {positions, indexes} = createPositionsAndIndexes(resolution);
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertex);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragment);
const program = createProgram(gl, vertexShader, fragmentShader);
this.x = 0;
this.y = 0;
this.z = -6;
this.angle = {x:0,y:0,z:0};
const positionBuffer = gl.createBuffer();
const indexBuffer = gl.createBuffer();
const positionLocation = gl.getAttribLocation(program, "position");
const viewLocation = gl.getUniformLocation(program, "view");
const projectionLocation = gl.getUniformLocation(program, "projection");
this.render = () => {
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indexes), gl.STATIC_DRAW);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
const viewMatrix = glMatrix.mat4.create();
glMatrix.mat4.translate(viewMatrix, viewMatrix, [this.x, this.y, this.z]);
glMatrix.mat4.rotateX(viewMatrix, viewMatrix, this.angle.x);
glMatrix.mat4.rotateY(viewMatrix, viewMatrix, this.angle.y);
glMatrix.mat4.rotateZ(viewMatrix, viewMatrix, this.angle.z);
gl.uniformMatrix4fv(viewLocation, false, viewMatrix);
const projectionMatrix = glMatrix.mat4.create();
glMatrix.mat4.perspective(projectionMatrix, 45 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.1, 100.0);
gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
gl.drawElements(gl.TRIANGLES, indexes.length, gl.UNSIGNED_INT, 0);
};
}
And here is the main "class":
document.addEventListener("DOMContentLoaded", () => {
const canvas = document.querySelector("canvas");
const width = canvas.width = canvas.clientWidth;
const height = canvas.height = canvas.clientHeight;
const gl = canvas.getContext("webgl2");
const sphere1 = new Sphere(300, gl, vertexShaderSource, fragmentShaderSource);
sphere1.x = -0.5;
const sphere2 = new Sphere(300, gl, vertexShaderSource, fragmentShaderSource);
sphere2.x = 0.0;
const sphere3 = new Sphere(300, gl, vertexShaderSource, fragmentShaderSource);
sphere3.x = +0.5;
const render = () => {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.clearDepth(1.0);
gl.depthFunc(gl.LEQUAL);
sphere1.angle.y -= 0.01;
sphere1.render();
sphere2.angle.y -= 0.01;
sphere2.render();
sphere3.angle.y -= 0.005;
sphere3.render();
window.requestAnimationFrame(render);
};
render();
});
You shouldn't call bufferData at render time unless you're changing the data in the buffer.
unction Sphere (resolution, gl, vertex, fragment) {
const {positions, indexes} = createPositionsAndIndexes(resolution);
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertex);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragment);
const program = createProgram(gl, vertexShader, fragmentShader);
this.x = 0;
this.y = 0;
this.z = -6;
this.angle = {x:0,y:0,z:0};
const positionBuffer = gl.createBuffer();
const indexBuffer = gl.createBuffer();
const positionLocation = gl.getAttribLocation(program, "position");
const viewLocation = gl.getUniformLocation(program, "view");
const projectionLocation = gl.getUniformLocation(program, "projection");
// create buffers and put data in them
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indexes), gl.STATIC_DRAW);
this.render = () => {
gl.useProgram(program);
// bind the position buffer to the attribute
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
const viewMatrix = glMatrix.mat4.create();
glMatrix.mat4.translate(viewMatrix, viewMatrix, [this.x, this.y, this.z]);
glMatrix.mat4.rotateX(viewMatrix, viewMatrix, this.angle.x);
glMatrix.mat4.rotateY(viewMatrix, viewMatrix, this.angle.y);
glMatrix.mat4.rotateZ(viewMatrix, viewMatrix, this.angle.z);
gl.uniformMatrix4fv(viewLocation, false, viewMatrix);
const projectionMatrix = glMatrix.mat4.create();
glMatrix.mat4.perspective(projectionMatrix, 45 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.1, 100.0);
gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
gl.drawElements(gl.TRIANGLES, indexes.length, gl.UNSIGNED_INT, 0);
};
}
you might find these articles and in particular this one
The problem in your code is that you are trying to do way too much in your render method. You generally don't want to send any buffer data to GPU in render loop as it is quite expensive.
In your case, you are also "overusing" uniforms. It is great that you are using MVP but whey are you generating it every frame for every object? View and projection are seldom object-specific as they relate to camera and window projections. You are also compiling shader for every sphere which isn't really needed either.
Optimizing the code could look like this:
function Sphere (resolution, gl, shaderProgram) {
const {positions, indexes} = createPositionsAndIndexes(resolution);
// bind shader program so you can retrieve attribute locations later on (when you use WebGL2, you can define location directly in shader files)
gl.useProgram(shaderProgram);
// create vao - this will enable you to bind vbo and ibo to one WebGL "object" which makes your life so much easier..
const vertexArrayObject = gl.createVertexArray();
gl.bindVertexArray(vertexArrayObject);
// create vbo
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// create ibo
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indexes), gl.STATIC_DRAW);
// define attribute locations
const positionAttributeLocation = gl.getAttribLocation(program, "position");
gl.vertexAttribPointer(positionAttributeLocation , 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation );
// unbind vao as it is not needed for now
gl.bindVertexArray(null);
// great! your geometry is ready, now prepare mvp matrix
// the sphere only cares about the model uniform
const modelLocation = gl.getUniformLocation(program, "model");
const modelMatrix = glMatrix.mat4.create();
// helper methods to avoid re-generating model matrix
this.rotate = (valueInRadians, axis) => {
glMatrix.mat4.fromRotation(modelMatrix, valueInRadians, axis);
}
this.translate = (vector) => {
glMatrix.mat4.fromTranslation(modelMatrix, vector);
}
// set initial rotation/translation
this.translate([0, 0, -6]);
// time to render
this.render = () => {
// bind shader as per usual
gl.useProgram(program);
// instead of binding all buffers, bind VAO
gl.bindVertexArray(vertexArrayObject);
// bind model uniform
gl.uniformMatrix4fv(modelLocation, false, modelMatrix );
// draw elements
gl.drawElements(gl.TRIANGLES, indexes.length, gl.UNSIGNED_INT, 0);
// don't forget to unbind VAO after
gl.bindVertexArray(null);
};
}
After changes, the main program would look like this
document.addEventListener("DOMContentLoaded", () => {
const canvas = document.querySelector("canvas");
const width = canvas.width = canvas.clientWidth;
const height = canvas.height = canvas.clientHeight;
const gl = canvas.getContext("webgl2");
// prepare shader
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertex);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragment);
const shaderProgram = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(shaderProgram);
// prepare uniforms for view and projection
const viewLocation = gl.getUniformLocation(shaderProgram, "view");
const projectionLocation = gl.getUniformLocation(shaderProgram, "projection");
// prepare view matrix with initial rotations/translations
const viewMatrix = glMatrix.mat4.create();
glMatrix.mat4.translate(viewMatrix, viewMatrix, [this.x, this.y, this.z]);
glMatrix.mat4.rotateX(viewMatrix, viewMatrix, this.angle.x);
glMatrix.mat4.rotateY(viewMatrix, viewMatrix, this.angle.y);
glMatrix.mat4.rotateZ(viewMatrix, viewMatrix, this.angle.z);
// prepare projection matrix with perspective projection
const projectionMatrix = glMatrix.mat4.create();
glMatrix.mat4.perspective(projectionMatrix, 45 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.1, 100.0);
// create objects
const sphere1 = new Sphere(300, gl, shaderProgram);
const sphere2 = new Sphere(300, gl, shaderProgram);
const sphere3 = new Sphere(300, gl, shaderProgram);
sphere1.translate([-0.5, 0, 0]);
// finally, define render method
const render = () => {
// reset viewport
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.clearDepth(1.0);
gl.depthFunc(gl.LEQUAL);
// update objects
sphere1.rotate(-0.01, [0, 1, 0]);
sphere1.rotate(-0.01, [0, 1, 0]);
sphere1.rotate(-0.005, [0, 1, 0]);
// bind view and projection uniforms
gl.uniformMatrix4fv(viewLocation, false, viewMatrix);
gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
// render individual spheres
sphere1.render();
sphere2.render();
sphere3.render();
window.requestAnimationFrame(render);
};
render();
});
Related
I have some problems using Glsl while I'm searching to drawing two object independently. When I search to see the result of my code, the object considered by the compiler is the first texture while the texture is the second.Additionally, I don't understand how I can set the initial position of the object in independent way using the same shaders.
This is my code:
var program;
var gl;
var shaderDir;
var baseDir;
var missileModel;
var pigModel;
var missileStr = 'model/R73-Ready.obj';
var missileTexture = 'model/R73 Texture.png';
var modelStr = 'model/mount.obj';
var modelTexture = 'model/ground_grass_3264_4062_Small.jpg';
function main() {
var lastUpdateTime = (new Date).getTime();
var Rx = 0.0;
var Ry = 0.0;
var Rz = 0.0;
var S = 0.5;
utils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0.85, 1.0, 0.85, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
//Here we extract the position of the vertices, the normals, the indices, and the uv coordinates
var missileVertices = missileModel.vertices;
var missileNormals = missileModel.vertexNormals;
var missileIndices = missileModel.indices;
var missileTexCoords = missileModel.textures;
var pigVertices = pigModel.vertices;
var pigNormals = pigModel.vertexNormals;
var pigIndices = pigModel.indices;
var pigTexCoords = pigModel.textures;
//###################################################################################
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
var uvAttributeLocation = gl.getAttribLocation(program, "a_uv");
var matrixLocation = gl.getUniformLocation(program, "matrix");
var textLocation = gl.getUniformLocation(program, "u_texture");
var perspectiveMatrix = utils.MakePerspective(120, gl.canvas.width/gl.canvas.height, 0.1, 100.0);
var viewMatrix = utils.MakeView(0, 0.0, 3.0, 0.0, 0.0);
//drawing land
var vao = gl.createVertexArray();
gl.bindVertexArray(vao);
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pigVertices), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
var uvBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pigTexCoords), gl.STATIC_DRAW);
gl.enableVertexAttribArray(uvAttributeLocation);
gl.vertexAttribPointer(uvAttributeLocation, 2, gl.FLOAT, false, 0, 0);
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(pigIndices), gl.STATIC_DRAW);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
var image = new Image();
image.src = baseDir+modelTexture;
image.onload= function() {
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, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.generateMipmap(gl.TEXTURE_2D);
};
//drawing the missile
var missile = gl.createVertexArray();
gl.bindVertexArray(missile);
var misspositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, misspositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(missileVertices), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
var missileuvBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, missileuvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(missileTexCoords), gl.STATIC_DRAW);
gl.enableVertexAttribArray(uvAttributeLocation);
gl.vertexAttribPointer(uvAttributeLocation, 2, gl.FLOAT, false, 0, 0);
var missindexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, missindexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(missileIndices), gl.STATIC_DRAW);
var misstexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, misstexture);
var missimage = new Image();
missimage.src = baseDir+missileTexture;
missimage.onload= function() {
gl.bindTexture(gl.TEXTURE_2D, misstexture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, missimage);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.generateMipmap(gl.TEXTURE_2D);
};
drawScene();
function animate(){
var currentTime = (new Date).getTime();
if(lastUpdateTime != null){
//var deltaC = 0; //(30 * (currentTime - lastUpdateTime)) / 1000.0;
Rx = 90;
Ry = 90;
Rz = 90;
}
worldMatrix = utils.MakeWorld(0.0, 0.0, 0.0, Rx, Ry, Rz, S);
lastUpdateTime = currentTime;
}
function drawScene() {
animate();
utils.resizeCanvasToDisplaySize(gl.canvas);
gl.clearColor(0.85, 0.85, 0.85, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var viewWorldMatrix = utils.multiplyMatrices(viewMatrix, worldMatrix);
var projectionMatrix = utils.multiplyMatrices(perspectiveMatrix, viewWorldMatrix);
gl.uniformMatrix4fv(matrixLocation, gl.FALSE, utils.transposeMatrix(projectionMatrix));
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(textLocation, misstexture);
gl.bindVertexArray(missile);
gl.drawElements(gl.TRIANGLES, missileIndices.length, gl.UNSIGNED_SHORT, 0 );
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(textLocation, texture);
gl.bindVertexArray(vao);
gl.drawElements(gl.TRIANGLES, pigIndices.length, gl.UNSIGNED_SHORT, 0 );
window.requestAnimationFrame(drawScene);
}
}
async function init(){
var path = window.location.pathname;
var page = path.split("/").pop();
baseDir = window.location.href.replace(page, '');
shaderDir = baseDir+"shaders/";
var canvas = document.getElementById("c");
gl = canvas.getContext("webgl2");
if (!gl) {
document.write("GL context not opened");
return;
}
await utils.loadFiles([shaderDir + 'vs.glsl', shaderDir + 'fs.glsl'], function (shaderText) {
var vertexShader = utils.createShader(gl, gl.VERTEX_SHADER, shaderText[0]);
var fragmentShader = utils.createShader(gl, gl.FRAGMENT_SHADER, shaderText[1]);
program = utils.createProgram(gl, vertexShader, fragmentShader);
});
gl.useProgram(program);
//###################################################################################
//This loads the obj model in the pigModel variable
var pigObjStr = await utils.get_objstr(baseDir+ missileStr);
missileModel = new OBJ.Mesh(pigObjStr);
var pigObjStr1 = await utils.get_objstr(baseDir+ modelStr);
pigModel = new OBJ.Mesh(pigObjStr1);
main();
}
window.onload = init;
bindTexture binds a named texture to a texturing target and the current texture unit. The current texture unit is set by activeTexture.
The texture unit is the binding point between the named texture object and the texture sampler uniform. The sampler uniform has to be set by the texture unit, rather than the texture object name.
You have to assign the texture unit (index) to the texture sampler uniform, and you have to bind the texture to the texture unit before drawing the object:
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, misstexture);
gl.uniform1i(textLocation, 0); // 0 because 'misstexture' is bound to texture unit 0
gl.bindVertexArray(missile);
gl.drawElements(gl.TRIANGLES, missileIndices.length, gl.UNSIGNED_SHORT, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(textLocation, 0); // 0 because 'texture' is bound to texture unit 0
gl.bindVertexArray(vao);
gl.drawElements(gl.TRIANGLES, pigIndices.length, gl.UNSIGNED_SHORT, 0);
Of course it is possible to bind the textures to different texture units:
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, misstexture);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture);
and to set the texture sampler uniform before drawing the object
gl.uniform1i(textLocation, 0); // 0 because 'misstexture' is bound to texture unit 0
gl.bindVertexArray(missile);
gl.drawElements(gl.TRIANGLES, missileIndices.length, gl.UNSIGNED_SHORT, 0);
gl.uniform1i(textLocation, 1); // 1 because 'texture' is bound to texture unit 1
gl.bindVertexArray(vao);
gl.drawElements(gl.TRIANGLES, pigIndices.length, gl.UNSIGNED_SHORT, 0);
As #Rabbid76 pointed out your calls to gl.uniform1i for textures are wrong. You need to pass in a texture unit index, not a WebGLTexture
As for positions you generally set the position of an object when rendering by passing in a matrix to the shader as a uniform
for each object
set vertex array
gl.useProgram(whateverProgramThisObjectNeeds)
bind textures to texture units
set uniforms to tell shader which texture units you bound the textures to
set uniforms for material settings (colors, etc..)
set uniforms for matrices (projection, view, model)
gl.drawArrays or gl.drawElements
you don't generally hard code positions into shaders. Setting "initial" positions is not part of WebGL. WebGL just draws points, lines, and triangles. Positions of objects are part of your code. Store them in an array, an map, a tree, a scene graph, a gameobject, something you makeup and then loop through the ones you want to draw, setting some uniforms to draw them in the correct places.
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>
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 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 want to draw a line in WebGL context but it isn't working. I can clear the canvas color so I know the program is compiled, but not sure where is the error.
The main part of the code is
let canvas = document.getElementById("gl-canvas");
let gl = WebGLUtils.setupWebGL(canvas);
if (!gl) {
alert("WebGL isn't available");
}
let program = initShaders(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 0.5);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
let color = gl.getAttribLocation(program, "color");
let colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1,0,0,1, 1,0,0,1]), gl.STATIC_DRAW);
gl.vertexAttribPointer(color, 4, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(color);
let vPosition = gl.getAttribLocation(program, "vPosition");
let positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1,1,1,1, -1,-1,-1,1]), gl.STATIC_DRAW);
gl.vertexAttribPointer(vPosition, 4, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vPosition);
gl.drawArrays(gl.LINES, 0, 1);
The full code can be found here
The problem is your drawArrays call:
count
Specifies the number of indices to be rendered.
Every line needs two indices: starting point and end point:
gl.drawArrays(gl.LINES, 0, 2);