Dynamic WebGL Draw in Game Loop - javascript

I am fairly new to WebGL and I am working on a 3D game that dynamically generates land around the player. So, I am trying to add vertices to draw in game. Things worked fine, until I started to add this feature, making hundreds of gl.drawArrays() calls per frame, which made it super laggy. After having done some research I found that a better way to approach this is to make a huge array containing all of the vertices (each shape separated by degenerate triangles) and then make one gl.drawArray() call per frame.
Here is the part of my code that runs on load:
function loadGraphics() {
// ground
// buffers
quadVertexPositionBuffer = gl.createBuffer();
vertices = [];
verticesItemCount = 0;
quadVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexColorBuffer);
var colors = [
0.0, 0.4, 0.0, 1.0,
0.0, 0.4, 0.0, 1.0,
0.0, 0.4, 0.0, 1.0,
0.0, 0.4, 0.0, 1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.DYNAMIC_DRAW);
quadVertexColorBuffer.itemSize = 4;
quadVertexColorBuffer.numItems = 4;
}
Here is the part that runs per frame:
function drawGraphics() {
// draw code for graphics
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clearColor(0.35, 0.4, 1.0, 1.0 );
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
mat4.identity(mvMatrix);
// perspective
var cameraX = camera.x, cameraY = camera.y, cameraZ = camera.z;
mat4.rotate(mvMatrix, rotMatrix[1], [1, 0, 0]);
mat4.rotate(mvMatrix, rotMatrix[0], [0, 1, 0]);
mat4.translate(mvMatrix, [-cameraX/33, -cameraY/33, -cameraZ/33]);
debug.add("{camera} x:"+camera.x+",y:"+camera.y+",z:"+camera.z+";");
debug.add("\n{mouse delta} x:"+mouse.x-mouse.prevX+",y:"+mouse.y-mouse.prevY+";");
debug.add("\n{rm}[0]:"+rotMatrix[0]+",[1]:"+rotMatrix[1]);
// ground
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexColorBuffer);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, quadVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
// land plots
vertices = [];
verticesItemCount = 0;
for (var i = 0; i < landPlots.length; i++) {
var oX = landPlots[i].x*3;
var oZ = landPlots[i].z*3;
var plotVertices = [
-1.5+oX, 0.0, 1.5+oZ, 1.0,
1.5+oX, 0.0, 1.5+oZ, 1.0,
-1.5+oX, 0.0, -1.5+oZ, 1.0,
1.5+oX, 0.0, -1.5+oZ, 1.0
];
pushDrawArray(plotVertices, 4);
for(var j = 1; j <= 2; j++) {
debug.add(" " + renderLandPlotIntersection(landPlots[i], j));
}
}
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, verticesItemCount);
}
function renderLandPlotIntersection(landPlot, side) {
var x = landPlot.x;
var z = landPlot.z;
var lvl = landPlot.level;
var olvl = null;
var plot;
switch (side) {
case 0: plot =getLandPlot(x-1, z ); if (plot !== null) olvl = plot.level/66; else return 0; break;
case 1: plot =getLandPlot(x, z+1); if (plot !== null) olvl = plot.level/66; else return 0; break;
case 2: plot =getLandPlot(x+1, z ); if (plot !== null) olvl = plot.level/66; else return 0; break;
case 3: plot =getLandPlot(x, z-1); if (plot !== null) olvl = plot.level/66; else return 0; break;
default: throw "Land plot intersection drawing: side out of range."; return -1;
}
var intersectionVertices = [
x*3, lvl, z*3,
x*3, lvl, z*3,
x*3, olvl, z*3,
x*3, olvl, z*3
];
pushDrawArray(intersectionVertices, 4);
return +1;
}
function pushDrawArray(array, itemCount) {
if (vertices.length > 0) {
// degenerate
vertices.push(vertices[vertices.length-3]);
vertices.push(vertices[vertices.length-2]);
vertices.push(vertices[vertices.length-1]);
vertices.push(array[0]);
vertices.push(array[1]);
vertices.push(array[2]);
verticesItemCount += 2 ;
}
gl.bufferSubData(gl.ARRAY_BUFFER, verticesItemCount*4, array);
verticesItemCount += itemCount;
}
I started using DYNAMIC_DRAW, though I don't really know how to use it. verticesItemCount marks how many vertices are in the vertices array.
gl.drawArrays() returns this error:
[.Offscreen-For-WebGL-060B7ED8]GL ERROR :GL_INVALID_OPERATION : glDrawArrays: attempt to access out of range vertices in attribute 1 localhost/:1 WebGL: too many errors, no more errors will be reported to the console for this context.
How can I fix this code to not cause an error?

I know what I did. I made the vertex position array longer than my vertex color array, so it was trying to access something out of bounds. The fix is to keep the vertex color array the same length as the vertex position array.

Related

Vertex buffer object data being overwritten for some reason

I'm having this weird issue where my object vertex data is being overridden when I create a new vertex buffer object... I believe this section of code is the culprit, as I'm aware it's in the constructor, since the data that is overwritten switches if i change the order of instancing:
this.shader = shaderProgram(srcContainer.vert, srcContainer.frag);
this.theta = 0;
this.vbo = gl.createBuffer();
this.ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.useProgram(this.shader);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.verts), gl.STATIC_DRAW);
// vertex position
gl.vertexAttribPointer(0, /*size of attribute(x,y,z) in count*/3, gl.FLOAT, gl.FALSE, /*size of a vertex in bytes*/6*Float32Array.BYTES_PER_ELEMENT, /*offset to attribute in vertex*/0*Float32Array.BYTES_PER_ELEMENT);
gl.enableVertexAttribArray(0);
// vertex color
gl.vertexAttribPointer(1, /*size of attribute(r,g,b) in count*/3, gl.FLOAT, gl.FALSE, /*size of a vertex in bytes*/6*Float32Array.BYTES_PER_ELEMENT, /*offset to attribute in vertex*/3*Float32Array.BYTES_PER_ELEMENT);
gl.enableVertexAttribArray(1);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
this.verts is vertex data that is joined with color data vertex-wise. The following code is used to display the cubes to the screen:
let box, box2;
let canv, gl, time;
function setup() {
canv = document.querySelector('#glCanvas');
gl = loadGL(canv); // Grabs a reference to webGL from canvas in the middle of the page
box2 = new Cube([
1-0.4, 1-0.1, 1-0.8,
1-0.5, 1-0.0, 1-0.2,
1-0.6, 1-0.0, 1-0.0,
1-0.7, 1-0.2, 1-0.0,
1-0.6, 1-0.2, 1-0.9,
1-0.5, 1-0.1, 1-0.6,
1-0.7, 1-0.0, 1-0.5,
1-0.9, 1-0.5, 1-0.0], 0.5, 1/7); // initialize box2 to have inverted color vertex data
box = new Cube([
0.4, 0.1, 0.8,
0.5, 0.0, 0.2,
0.6, 0.0, 0.0,
0.7, 0.2, 0.0,
0.6, 0.2, 0.9,
0.5, 0.1, 0.6,
0.7, 0.0, 0.5,
0.9, 0.5, 0.0], 0.5, 1/10); // initialize box to have normal color data, which ends up overwriting the inverted data above.
box2.time = Math.PI/6; // Angle one box differently to see both
time = new Time(0); // Custom time class to keep track of the deltaTime, pass in 0 to start at 0 milliseconds.
window.requestAnimationFrame(draw); // the first call of requestAnimationFrame will start the draw loop.
}
// Draw is called every animation frame with requestAnimationFrame
function draw(timestamp) {
time.update(timestamp); // Calculate time.deltaTime
background(0, 0, 0);
//box.update();
box.show(); // show box 1 (meant to be normal colored)
//box2.update();
box2.show(); // show box 2 (meant to be inverted colored)
window.requestAnimationFrame(draw);
}
and this is the show, and bind functions:
bind() {
gl.useProgram(this.shader);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo);
};
show() {
this.bind();
gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_SHORT, 0);
this.unbind();
};
this code renders them both to have normal vertex data which can be seen here
Answered by user LJᛃ,
"... you need to setup the attributes each time you render (so in your bind method), they're a global state. You may want to look into VAOs."

WebGL is not rendering 3d objects properly

I have a generator of 3d objects in a canvas context.
The rendering is carried out with the painter's algorithm. However, I need a more accurate approach for my project.
Therefore, I have implemented a WebGL renderer. The idea is to transform the objects generated in the canvas context to the WebGL context ( I have two overlayed canvases for this purpose) in order to render them accurately with regards to the HSA (hidden surface algorithm) problem.
I have a function that transforms the canvas coordinates to clip coordinates, and that sets up the different elements and peculiarities required by WebGL, basically, the function prepares the 3d objects to be rendered by WebGL.
As I also need the segments of the shapes to be rendered, my approach consists in creating a buffer, in which all the coordinates of all the objects are stored, together with the coordinates of the segments of these objects, which will be represented and drawn as thin 3d squares (that is, each line will be formed by 2 thin coupled-up triangles, whose vertices and positions will be determined beforehand in canvas coordinates).
So far I am only testing and have coded the rendering of the shapes without lines. The problem is, it is not working properly. Triangles are drawn wrongly, with points and positions that either do not exist in the buffer or are mistaken.
Here is what should be drawn: (this cube is rendered in the canvas with the painter's algorithm)
Here is the colored silhouette that is drawn, however:
If only drawing the face 0 with LINE_LOOP
soon things get pretty messed up, face 0 + face 1 (face 1 is obviously being mistakenly drawn)
3 faces
Things get worse with more complicated objects (notice that this one is not perfectly rendered either)
I do not really know what is happening. My knowledge of WebGL and of 3d graphics, in general, is pretty limited, not to say inexistent. I do not have studies in computer science or any IT-related domain either. I just need a function to render my javascript 3d objects properly for a personal project. Here is the code that I am using:
function webglPrepare(escena){ //Takes canvas 3d objects as inputs, and outputs the arrays requiered by webgl; vertices, indices, and colors
var zprep=2000;
var nbVertices; var FacesHSA=[]; var cont=0; var zmean=[]; var temp=[]; var vertices=[]; var cont2=0; var indices=[]; var sumer=0; var controlador=0;
var colors=[]; var cont3=0; var prueba=0; //These variables are irrelevant
object_for: for(var i=0; i<escena.length; i++){ //this is irrelevant
face_for: for(var a=0; a<escena[i].arrayObjetos.length; a++){ //Just looping over all the objects
check_loop: for(var ff=0; ff<escena[i].arrayObjetos[a].faces.length; ff++){ //for each face of the object
if(escena[i].arrayObjetos[a].faces[ff].vertices.length==3){ //If the face has 3 vertices, then
indices[cont2]=sumer; cont2=cont2+1; sumer=sumer+1; //We setup the indices array, which will store the indices to form the triangles, needed by webgl
indices[cont2]=sumer; cont2=cont2+1; sumer=sumer+1;
indices[cont2]=sumer; cont2=cont2+1; sumer=sumer+1;
}
else if(escena[i].arrayObjetos[a].faces[ff].vertices.length==4){ //The same, but if the face has 4 vertices. I do not have faces longer than that
indices[cont2]=sumer; cont2=cont2+1; sumer=sumer+1;
indices[cont2]=sumer; cont2=cont2+1; sumer=sumer+1;
indices[cont2]=sumer; cont2=cont2+1;
indices[cont2]=indices[cont2-3]; cont2=cont2+1;
indices[cont2]=indices[cont2-2]; cont2=cont2+1; sumer=sumer+1;
indices[cont2]=sumer; sumer=sumer+1; cont2=cont2+1;
}
for (var j = 0; j < (nbVertices = escena[i].arrayObjetos[a].faces[ff].vertices.length) ; j++) { // For each vertex of the face.
vertices[cont] = escena[i].arrayObjetos[a].vertices[j].x/ gl.canvas.width * 2 - 1; //The x coordinate is transformed to clip coordinates
cont=cont+1;
vertices[cont] = escena[i].arrayObjetos[a].faces[ff].vertices[j].y/ gl.canvas.height * -2 + 1; //Same with the y coordinate
cont=cont+1;
vertices[cont] = escena[i].arrayObjetos[a].faces[ff].vertices[j].z; //Same with the Z. Zprep is an arbitrary Zmax value, used to
//Used to carry out the transformation
if(vertices[cont]>=0){ if(vertices[cont]>zprep){alert("error en Z, es mayor");} vertices[cont]=vertices[cont]/zprep; }
else if(vertices[cont]<=0){ if(vertices[cont]>zprep){alert("error en Z, es menor");} vertices[cont]= -(vertices[cont]/zprep); }
//Supuestamente el z- es el mas cercano y el + el mas
cont=cont+1; //The colours are also prepared
//cont=cont+3;
colors[cont3]=0;
colors[cont3+1]=0;
colors[cont3+2]=0;
cont3=cont3+3;
}
}
}}
webgl2(vertices, indices, colors); //Once everything is ready, we call the WebGL renderer
}
The webgl2 function (which does the rendering)
function webgl2(vertices, indices, colors){
// Create and store data into vertex buffer
var vertex_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Create and store data into color buffer
var color_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
// Create and store data into index buffer
var index_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
/*=================== SHADERS =================== */
var vertCode = 'attribute vec3 position;'+
'uniform mat4 Pmatrix;'+
'uniform mat4 Vmatrix;'+
'uniform mat4 Mmatrix;'+
'attribute vec3 color;'+//the color of the point
'varying vec3 vColor;'+
'void main(void) { '+//pre-built function
'gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);'+
'vColor = color;'+
'}';
var fragCode = 'precision mediump float;'+
'varying vec3 vColor;'+
'void main(void) {'+
'gl_FragColor = vec4(vColor, 1.);'+
'}';
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
var shaderprogram = gl.createProgram();
gl.attachShader(shaderprogram, vertShader);
gl.attachShader(shaderprogram, fragShader);
gl.linkProgram(shaderprogram);
/*======== Associating attributes to vertex shader =====*/
var _Pmatrix = gl.getUniformLocation(shaderprogram, "Pmatrix");
var _Vmatrix = gl.getUniformLocation(shaderprogram, "Vmatrix");
var _Mmatrix = gl.getUniformLocation(shaderprogram, "Mmatrix");
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
var _position = gl.getAttribLocation(shaderprogram, "position");
gl.vertexAttribPointer(_position, 3, gl.FLOAT, false,0,0);
gl.enableVertexAttribArray(_position);
gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
var _color = gl.getAttribLocation(shaderprogram, "color");
gl.vertexAttribPointer(_color, 3, gl.FLOAT, false,0,0) ;
gl.enableVertexAttribArray(_color);
gl.useProgram(shaderprogram);
var proj_matrix = get_projection(40, canvas.width/canvas.height, 1, 100); //The parameters inserted here are not used.
// Right now, get_projection returns an identity matrix
var mo_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];
var view_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];
gl.enable(gl.DEPTH_TEST);
// gl.depthFunc(gl.LEQUAL);
gl.clearColor(0.5, 0.5, 0.5, 0.9);
// gl.clearDepth(1.0);
gl.viewport(0.0, 0.0, canvas.width, canvas.height);
// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(_Pmatrix, false, proj_matrix);
gl.uniformMatrix4fv(_Vmatrix, false, view_matrix);
gl.uniformMatrix4fv(_Mmatrix, false, mo_matrix);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
// gl.drawElements(gl.LINE_LOOP, indices.length, gl.UNSIGNED_SHORT, 0);
gl.drawElements(gl.TRIANGLES, indices.length , gl.UNSIGNED_SHORT, 0);
// gl.drawArrays(gl.TRIANGLES, 0, indices.length);
// gl.drawArrays(gl.LINE_LOOP, 0, vertices.length/3);
// gl.drawArrays(gl.LINE_LOOP, 0, 56);
}
The get_projection function:
function get_projection(angle, a, zMin, zMax) {
var ang = Math.tan((angle*.5)*Math.PI/180);//angle*.5
/* return [
0.5/ang, 0 , 0, 0,
0, 0.5*a/ang, 0, 0,
0, 0, -(zMax+zMin)/(zMax-zMin), -1,
0, 0, (-2*zMax*zMin)/(zMax-zMin), 0
]; */
return [
1, 0 , 0, 0,
0, 1 , 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
];
}
It might probably be due to several errors instead of just 1, I just can't find any of them.

WebGL camera object doesn't seem to follow character

I've been following instructions for game development using webGL from this book, and in the chapter about cameras, it discusses and gives a very limited example of an orbital camera, but goes on to continue the rest of the book focusing on a simple first-person, free camera.
The orbital camera works very well, but if I try to create a controllable character object, and set the camera's orbit point to follow it, the camera seems to... get ahead of itself? It somehow moves faster than the character.
Here's the relevant method from the OrbitalCamera object:
OrbitCamera.prototype.setOrbitPoint = function (orbitPoint)
{
// get the distance the camera was from the orbit point.
var orbitPointToCam =vec3.create();
vec3.multiply(orbitPointToCam,this.dir, -this.getDistance());
this.orbitPoint[0] = orbitPoint[0];
this.orbitPoint[1] = orbitPoint[1];
this.orbitPoint[2] = orbitPoint[2];
vec3.add(this.pos,this.orbitPoint, orbitPointToCam);
}
And here is where the function is being called:
function animate(){
var lSpeed = 0;
var uSpeed = 0;
var distance = cam.getDistance();
if(stage.stageObjects.length != 0){
var pos = stage.stageObjects[0].location;
if (left) {
lSpeed = +0.05;
}
if (right) {
lSpeed = -0.05;
}
if (up) {
uSpeed = 0.05;
}
if (down) {
uSpeed = -0.05;
}
stage.stageObjects[0].location = [(uSpeed*Math.round(cam.dir[0]))+(lSpeed*Math.round(cam.left[0]))+pos[0], pos[1], (uSpeed*Math.round(cam.dir[2]))+(lSpeed*Math.round(cam.left[2]))+pos[2]]
cam.setOrbitPoint([stage.stageObjects[0].location[0],0,stage.stageObjects[0].location[2]]);
cam.setDistance(distance);
if (cup) {
cam.pitch(Math.PI*0.01);
}
if (cdown) {
cam.pitch(-Math.PI*0.01);
}
if (cleft) {
cam.yaw(-Math.PI*0.01);
}
if (cright) {
cam.yaw(Math.PI*0.01);
}
}
}
And here is the draw call:
function drawScene(){
gl.viewport(0,0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
setLightUniform();
pushMatrix();
calculateMvMatrix();
for (var i = 0; i<stage.stageObjects.length; i++){
pushMatrix();
mat4.translate(mvMatrix, mvMatrix, stage.stageObjects[i].location);
mat4.rotateX(mvMatrix, mvMatrix, stage.stageObjects[i].rotationX);
mat4.rotateY(mvMatrix, mvMatrix, stage.stageObjects[i].rotationY);
mat4.rotateZ(mvMatrix, mvMatrix, stage.stageObjects[i].rotationZ);
setMatrixUniforms();
gl.uniform3f(shaderProgram.materialDiffuseColor, stage.stageObjects[i].diffuseColor[0],stage.stageObjects[i].diffuseColor[1],stage.stageObjects[i].diffuseColor[2]);
gl.uniform3f(shaderProgram.materialAmbientColor, stage.stageObjects[i].ambientColor[0],stage.stageObjects[i].ambientColor[1],stage.stageObjects[i].ambientColor[2]);
gl.bindBuffer(gl.ARRAY_BUFFER, stage.stageObjects[i].vbo);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, stage.stageObjects[i].vbo.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, stage.stageObjects[i].nbo);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, stage.stageObjects[i].nbo.itemSize, gl.FLOAT, false, 0,0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, stage.stageObjects[i].ibo);
gl.drawElements(gl.TRIANGLES, stage.stageObjects[i].geometry.indices.length, gl.UNSIGNED_SHORT,0);
popMatrix();
}
popMatrix();
}
I can post the whole code for the Camera object if the problem isn't in here. Any help is greatly appreciated, this has been driving me crazy

glmatrix.js anomaly - changing order of transforms does not change outcome

In creating a webgl "hello world" for demonstration to a high school class, I am experiencing an anomalous result.
Using gl-matrix.js version 2.2.0, reversing the order of two transforms does not produce a difference in output.
This code produces the desired result:
function DrawScene(gl) {
var time_now = new Date();
var elapsed_time = new Date();
elapsed_time.setDate(time_program_started - time_now);
var projection_matrix = mat4.create();
var modelview_matrix = mat4.create();
var mvp = mat4.create();
gl.viewport(0, 0, gl.size[0], gl.size[1]);
gl.clearColor(0.2, 0.2, 0.2, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
mat4.perspective(projection_matrix, D2R(50.0), gl.size[0] / gl.size[1], 1.0, 10.0);
mat4.translate(modelview_matrix, modelview_matrix, [0.0, 0.0, -3.5]);
mat4.rotate(modelview_matrix, modelview_matrix, D2R(elapsed_time / 1000.0 * 60.0), [0.0, 1.0, 0.0]);
mat4.multiply(mvp, projection_matrix, modelview_matrix);
triangle.Render(gl, shader_index, [1.0, 1.0, 1.0, 1.0], mvp);
}
namely a triangle that spins in place about its Y axis.
Reversing the order of the translate and rotate should spin the triangle around the camera (part of my intended lesson), but instead continues to spin the triangle in place. I sanity checked this in an equivalent legacy OpenGL "hello world" producing exactly the expected results.
The vertex shader simply multiples the vertex position attribute with the mvp uniform. Is there a problem in gl-matrix.js or in this code?
For completeness, here is the render function.
Triangle.prototype.Render = function (gl, shader_index, color, mvp) {
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertex_buffer_object);
gl.vertexAttribPointer(shaders[shader_index].VP, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(shaders[shader_index].VP);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.element_buffer_object);
shaders[shader_index].Use(gl);
gl.uniformMatrix4fv(shaders[shader_index].MVP, false, mvp);
if (shader_index == 0)
gl.uniform4fv(shaders[shader_index].COLOR, color);
else {
gl.bindBuffer(gl.ARRAY_BUFFER, this.color_buffer_object);
gl.vertexAttribPointer(shaders[shader_index].COLOR, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(shaders[shader_index].COLOR);
}
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
shaders[shader_index].StopUsing(gl);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
Thank you.
I believe there is an error in my use of gl-matrix or gl-matrix itself. Replacing gl-matrix with mjs.js works as expacted.
function DrawScene(gl) {
var time_now = new Date();
var elapsed_time = new Date();
elapsed_time.setDate(time_program_started - time_now);
var projection_matrix = M4x4.I;
var modelview_matrix = M4x4.I;
var mvp = M4x4.I;
gl.viewport(0, 0, gl.size[0], gl.size[1]);
gl.clearColor(0.2, 0.2, 0.2, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
projection_matrix = M4x4.makePerspective(50.0, gl.size[0] / gl.size[1], 1.0, 10.0);
modelview_matrix = M4x4.rotate(D2R(elapsed_time / 1000.0 * 60.0), [0.0, 1.0, 0.0], modelview_matrix);
modelview_matrix = M4x4.translate([0.0, 0.0, -5.5], modelview_matrix);
mvp = M4x4.mul(projection_matrix, modelview_matrix);
triangle.Render(gl, shader_index, [1.0, 1.0, 1.0, 1.0], mvp);
}
Does indeed rotate about the camera. Reversing to translate then rotate spins about the Y axis of the triangle.
I would be very pleased to have my code for gl-matrix corrected. If it cannot, gl-matrix is very broken.

Click to zoom in WebGL

I need to implement what amounts to a "Google Maps" style zoom effect in WebGL. Specifically I have a simple 2-dimensional scene that is always perpendicular to the camera. When a user clicks on the scene, the camera should zoom to a location that is over the click, but closer to the 2-dimensional scene.
For example see this jsfiddle, that implements the scene but no zooming:
http://jsfiddle.net/JqBs8/4/
If you have a WebGL enabled browser, you should see a triangle and a square (2-dimensional) rendered at -7 on the Z axis. I have put in a placeholder handleMouseUp() event handler that logs any click events, but I'm a little lost as to how to translate the coordinates given by the click event into a new location for the camera (or I guess equivalently a new view matrix).
(The jsfiddle is based on tutorial 1 from learningwebgl.com and uses the glMatrix http://code.google.com/p/glmatrix/ library for matrix operations. Keep in mind that this is WebGL, which is similar to OpenGL ES, and has no access to glu* functions.)
I've written something in this jsfiddle that should help you.
http://jsfiddle.net/hedzys6r/
(or https://codepen.io/brainjam/pen/gBZyGm)
Just click on the WebGL window to zoom in to where the mouse is pointing.
The basic idea is that a point in the WebGL window is obtained by projecting it from 3-space using the projection matrix pMatrix and the view matrix (the view matrix depends on where the camera is and the direction in which it is looking). The composition of these matrices is named pvMatrix in the code.
If you want the opposite transform from the window back to three space, you have to take a clip space coordinate (x,y,z) and 'unproject' it back into 3D using the inverse of pvMatrix. In clip space, coordinates are in the range [-1,1], and the z coordinate is depth.
In the OpenGL world, these transforms are implemented in gluProject() and gluUnproject() (which you can Google for more information and source code).
In the jsfiddle example, we calculate the (x,y) coordinates in clip space, and then unproject (x,y,z) for two different values of z. From that we get two points in 3D space that map onto (x,y), and we can infer a direction vector. We can then move the camera in that direction to get a zoom effect.
In the code, the camera position is at the negation of the eye vector.
This example shows you how to move the camera in the direction that you are clicking. If you want to actually move towards specific objects in the scene, you have to implement something like object selection, which is a different kettle of fish. The example I've given is unaware of the objects in the scene.
This is really part of brainjam's answer, but just in case that jsfiddle were to go away, I wanted to make sure the code was archived. Here is the primary bit:
function handleMouseUp(event) {
var world1 = [0,0,0,0] ;
var world2 = [0,0,0,0] ;
var dir = [0,0,0] ;
var w = event.srcElement.clientWidth ;
var h = event.srcElement.clientHeight ;
// calculate x,y clip space coordinates
var x = (event.offsetX-w/2)/(w/2) ;
var y = -(event.offsetY-h/2)/(h/2) ;
mat4.inverse(pvMatrix, pvMatrixInverse) ;
// convert clip space coordinates into world space
mat4.multiplyVec4(pvMatrixInverse, [x,y,-1,1], world1) ;
vec3.scale(world1,1/world1[3]) ;
mat4.multiplyVec4(pvMatrixInverse, [x,y,0,1], world2) ;
vec3.scale(world2,1/world2[3]) ;
// calculate world space view vector
vec3.subtract(world2,world1,dir) ;
vec3.normalize(dir) ;
vec3.scale(dir,0.3) ;
// move eye in direction of world space view vector
vec3.subtract(eye,dir) ;
drawScene();
console.log(event)
}
And the entirety of the JS...
var gl;
function initGL(canvas) {
try {
gl = canvas.getContext("experimental-webgl");
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch (e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}
var mvMatrix = mat4.create();
var pMatrix = mat4.create();
function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}
var triangleVertexPositionBuffer;
var squareVertexPositionBuffer;
function initBuffers() {
triangleVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
var vertices = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
triangleVertexPositionBuffer.itemSize = 3;
triangleVertexPositionBuffer.numItems = 3;
squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
}
var eye = vec3.create([0,0,0]) ; // negation of actual eye position
var pvMatrix = mat4.create();
var pvMatrixInverse = mat4.create();
function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
mat4.identity(mvMatrix);
// calculate the view transform mvMatrix, and the projection-view matrix pvMatrix
mat4.translate(mvMatrix, eye);
mat4.multiply(pMatrix,mvMatrix,pvMatrix) ;
mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}
function handleMouseUp(event) {
var world1 = [0,0,0,0] ;
var world2 = [0,0,0,0] ;
var dir = [0,0,0] ;
var w = event.srcElement.clientWidth ;
var h = event.srcElement.clientHeight ;
// calculate x,y clip space coordinates
var x = (event.offsetX-w/2)/(w/2) ;
var y = -(event.offsetY-h/2)/(h/2) ;
mat4.inverse(pvMatrix, pvMatrixInverse) ;
// convert clip space coordinates into world space
mat4.multiplyVec4(pvMatrixInverse, [x,y,-1,1], world1) ;
vec3.scale(world1,1/world1[3]) ;
mat4.multiplyVec4(pvMatrixInverse, [x,y,0,1], world2) ;
vec3.scale(world2,1/world2[3]) ;
// calculate world space view vector
vec3.subtract(world2,world1,dir) ;
vec3.normalize(dir) ;
vec3.scale(dir,0.3) ;
// move eye in direction of world space view vector
vec3.subtract(eye,dir) ;
drawScene();
console.log(event)
}
function webGLStart() {
var canvas = document.getElementById("lesson01-canvas");
initGL(canvas);
initShaders();
initBuffers();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
canvas.onmouseup = handleMouseUp;
drawScene();
}
webGLStart();

Categories

Resources