Reputation: 782
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();
});
Upvotes: 1
Views: 887
Reputation: 1617
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();
});
Upvotes: 2
Reputation:
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
Upvotes: 2