Introducing WebGL
Introduction
The article was created in order to show the basic steps required to display 3d in a modern browser using WebGL technology. To achieve the goal, we consider the task of constructing several lines in three-dimensional space.
Scheme of work:
- Get the WebGL context from the canvas.
- Download the shader program. Namely:
- create a shader program;
- we get the source code separately for the vertex and fragment shaders;
- compiling shader codes;
- attach to the program;
- activate the program.
- We install two matrices: model-view and projection.
- We place, fill, activate the vertex data buffers.
- We draw.
1. WebGL context
A WebGL context can be obtained from the canvas DOM element by calling the getContext (“experimental-webgl”) method. It should be noted that the Khronos Group recommends (https://www.khronos.org/webgl/wiki/FAQ) to use the following method to get the WebGL context:
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
gl = null;
for (var ii = 0; ii < names.length; ++ii) {
try {
gl = canvas.getContext(names[ii]);
} catch(e) {}
if (gl) {
break;
}
}
When the context is successfully obtained, the gl object has methods whose names are very similar to the functions of OpenGL ES. For example, the clear function (COLOR_BUFFER_BIT) for WebGL will be gl.clear (gl.COLOR_BUFFER_BIT), which is very convenient. But keep in mind that not all WebGL functions have the same syntax as OpenGL ES 2.0 functions.
2. Shaders
A shader program is an integral part of building images using WebGL. It is through it that the position and color of each vertex of our lines is set. In our task, two shaders are used: vertex and fragment. When constructing lines in three-dimensional space, the vertex shader is responsible for the position of the vertices in space, based on the values of the view matrix and the perspective projection matrix. The fragment shader is used to calculate the color of our lines.
Vertex shader
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
uniform mat4 mvMatrix;
uniform mat4 prMatrix;
varying vec4 color;
void main(void)
{
gl_Position = prMatrix * mvMatrix * vec4 ( aVertexPosition, 1.0 );
color = aVertexColor;
}Fragment shader
#ifdef GL_ES
precision highp float;
#endif
varying vec4 color;
void main(void)
{
gl_FragColor = color;
}
What is defined after “uniform” is common to all vertices. Here they are transformation matrices: species and perspective. What is defined after the attribute is used to calculate each vertex. Here is the position of the peak and its color. After "varying" we define a variable that will be transferred from the vertex to the fragment shader. We assign the result of position calculation to the variable gl_Position, the colors to gl_FragColor.
3. Model-view matrix and perspective projection matrix
Both matrices are 4x4 in size and are used to calculate the display of three-dimensional objects on a two-dimensional plane. Their difference is that the species matrix determines how the objects will look for the observer, for example, when changing his position, and the projection matrix initially determines the projection method.
In our program, the values of the projection matrix are set when the gluPerspective function is called at the initialization stage, in the future this matrix does not change its values. The gluPerspective function is not standard, we defined it ourselves. Her arguments are: fovy, aspect, zNear, zFar. fovy - the area of the vertical viewing angle in degrees; aspect - the ratio of the width of the viewport to the height; zNear - distance to the near clipping plane (everything closer is not drawn); zFar - distance to the far clipping plane (everything further is not drawn).
To set the values of the model-specific matrix, several approaches can be used. For example, create and use the gluLookAt function (camX, camY, camZ, tarX, tarY, tarZ, upX, upY, upZ) - an analogue of the function for OpenGL, which takes as its arguments the coordinates of the camera, the coordinates of the camera target and the camera's up-vector. Another way is to create and use the functions glTranslate, glRotate, glScale, which produce a shift, rotation, scaling relative to the observer (camera). You can use gluLookAt for the initial determination of the camera position, and for subsequent transformations use glTranslate, glRotate, glScale. One way or another, these functions only change the values of the same model-specific matrix. For the convenience of calculating matrices, you can use the sylvester.js library, which we will do.
Now that you have found a way to change the values of both matrices, consider their transfer to the shader program. In our vertex shader for the model-view matrix, we use the variable "mvMatrix". To pass matrix values to this variable, we first need to get its index in the program. To do this, use the function loc = gl.getUniformLocation (shaderProgram, name), which is standard. As you might guess, the first argument is a variable that points to the shader program that was obtained in the second stage, and the “name” argument is the name of the variable to which we want to pass the value, in our case name = “mvMatrix”. Now that we have the index, we use the function gl.uniformMatrix4fv (loc, false, new Float32Array (mat.flatten ())) to pass the value of the matrix mat. Similarly, we get the index and set the value for the projection matrix.
4. Data buffers
Using buffers in WebGL is required. The position of each point and its color will be stored in two buffers. Consider a piece of code that does all the work for a buffer that stores the coordinates of points between which we will draw lines.
/* Создаем буфер */
vPosBuffer = gl.createBuffer();
/* Активируем буфер*/
gl.bindBuffer(gl.ARRAY_BUFFER, vPosBuffer);
/* Копируем в буфер координаты вершин */
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticies), gl.DYNAMIC_DRAW);
/* Определяем, что координаты вершин имеют определенный индекс атрибута и содержат 3 floats на вершину */
gl.vertexAttribPointer(vPosLoc, 3, gl.FLOAT, false, 0, 0);
/* Задействуем индекс аттрибута */
gl.enableVertexAttribArray(vPosLoc);
Here verticies is an array of coordinates of line points. The coordinates are 6 pieces, the first 3 of which are the x-, y-, z-coordinates of the beginning of the line, the following, respectively, of the end. vPosLoc is the index of the “aVertexPosition” attribute in the shader program. Because in our program, indices were explicitly set using gl.bindAttribLocation (shaderProgram, loc, shadersAttrib) at the stage of assembling the shader program, then you do not need to get them again. If this were not the case, you should get the index using the command "vPosLoc = getAttribLocation (shaderProgram," aVertexPosition ")." Similar actions are carried out with the second buffer, the data will differ (instead of verticies an array of colors) and the index in the shader program (instead of vPosLoc).
5. We draw
Clearing the color buffer or, more simply, setting the background will be done using standard commands
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);Now let's draw
gl.drawArrays(gl.LINES, drawArrayIndexStart, drawArrayLength);The first argument of this function says that we will draw the lines, the second is the index in the buffer from which we start drawing, drawArrayLength - the number of elements to draw. Because we transfer to the buffer the coordinates of the vertices from the verticies array, then
drawArrayLength = verticies.length / 3;If our lines have changed, then we follow steps 4, 5 to redraw. If we have changed the position of the camera, then perform step 3 and step 5.
Conclusion
The task of constructing straight lines was not taken from the ceiling. There is a program that solves a system of differential equations and builds the result in 3d using OpenGL. It was decided to port the program to php and display the result using WebGL. To solve the problem of displaying lines in three-dimensional space, modern engines from the list (http://ru.wikipedia.org/wiki/WebGL) were studied: WebGLU, GLGE, C3DL, Copperlicht, SpiderGL and SceneJS. For this, an interface was created that allows universalizing the communication of the main program with third-party engines. The results were achieved with WebGLU, C3DL. In others, either there is no simple way to build a line, or it is not optimal. In one of them, the line function is documented, but at the project forum it was made clear that it would not be possible to use it, and they suggested drawing it with polygons.
Unfortunately, when using C3DL, it was not yet possible to optimize the process, which led to a low fps value. When working with WebGLU, an error was made that also affected the fps value. This made me write my own engine, which is now in use. By no means do I want to blame third-party engines, they are designed for a wider range of tasks than simple line drawing.
A few words about browsers. Tested on Firefox 4 beta 8, Chrome 8 with –enable-webgl. On this task, Firefox showed a fps value above Chrome 1.5-2 times. When you upgrade Chrome to beta 9, the indicators have not changed. The fps values did not change even when Firefox beta 8 was upgraded to beta 9, unless the console had more obscure errors and the scene using WebGLU began to display incorrectly.
Links to the working version
- rnix.dyndns.org/3dpr - display lines on your own engine.
- rnix.dyndns.org/3dpr/c3dl.html - using the cd3l library
- rnix.dyndns.org/3dpr/webglu.html - using the webglu library
List of references
- cvs.khronos.org/svn/repos/registry/trunk/public/webgl/doc/spec/WebGL-spec.html - WebGL specification, command syntax, differences from OpenGL ES,
- steps3d.narod.ru/tutorials/webgl-tutorial.html - several examples described in detail, including textures and lighting.
- www.khronos.org/webgl/wiki/Demo_Repository - a few examples from Google, Apple, Mozilla. You can make sure that each of the three authors has their own approach to the individual parts of the entire program.