InternetException

About coding and whatnot.

Line in WebGL and why you gonna do this on your own.

clock February 26, 2013 02:15 by author n.podbielski

WebGL as method for generating 3D graphic in browser is pretty powerful. You can do really awesome things with this, like porting Quake 2 to JavaScript or creating model of human body. But all of this 3D object are complex collection of hundreds or thousands triangles. What if you just want to draw a simple thin line? Luckily OpenES have support for that. So it is possible. Lets see how to do that.

I will use for that changed code from Minimum length WebGL code to draw least meaningful output - A straight line. What I changed was just removing of few unnecessary lines and putting all uninteresting boilerplate code into functions. First, initGl, initializes WebGl context from canvas tag. Second, initShaders, creates shaders program and initializes it with location of uniforms and attributes. Next initializes scene by clearing plane, creates model-view matrix and perspective matrix. I changed those matrices to those, which I think will help to understand line points coordinates, because Z axis is orthogonal to screen, so point (-1,-1,0) will actually at bottom-left part of canvas. To put it simple: scene viewer and you will look from the same point (more or less of course Smile). Model view matrix will also translates all Z coordinates by -7, so points with Z = 0, will be visible. Now code:

 

<html>
<head>
    <title></title>
    <script type="text/javascript">

        var fragShaderSource = "\
precision highp float;\
uniform vec4 u_color;\
void main(void) {\
gl_FragColor = u_color;\
}\
";

        var vtxShaderSource = "\
attribute vec3 a_position;\
uniform vec4 u_color;\
uniform mat4 u_mvMatrix;\
uniform mat4 u_pMatrix;\
void main(void) {\
gl_Position = u_pMatrix * u_mvMatrix * vec4(a_position, 1.0);\
}\
";

        function get_shader(type, source) {
            var shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);
            return shader;
        }

        var gl, pMatrix, mvMatrix, vbuf,ibuf;

        function initGl() {
            var canvas = document.getElementsByTagName('canvas')[0];
            gl = canvas.getContext("experimental-webgl", { antialias: true });
            gl.viewport(0, 0, canvas.width, canvas.height);
        }

        function initShaders() {
            var vertexShader = get_shader(gl.VERTEX_SHADER, vtxShaderSource);
            var fragmentShader = get_shader(gl.FRAGMENT_SHADER, fragShaderSource);
            shaderProgram = gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);
            gl.useProgram(shaderProgram);
            shaderProgram.aposAttrib = gl.getAttribLocation(shaderProgram, "a_position");
            gl.enableVertexAttribArray(shaderProgram.aposAttrib);
            shaderProgram.colorUniform = gl.getUniformLocation(shaderProgram, "u_color");
            shaderProgram.pMUniform = gl.getUniformLocation(shaderProgram, "u_pMatrix");
            shaderProgram.mvMUniform = gl.getUniformLocation(shaderProgram, "u_mvMatrix");
        }

        function initScene() {
            gl.clearColor(0.0, 0.0, 0.0, 0.0);
            mvMatrix =
                              [1, 0, 0, 0
                              , 0, 1, 0.00009999999747378752, 0,
                              0, -0.00009999999747378752, 1, 0,
                              0, 1.3552527156068805e-20, -8, 1];
            pMatrix =
                              [2.4142136573791504, 0, 0, 0,
                              0, 2.4142136573791504, 0, 0,
                              0, 0, -1.0020020008087158, -1,
                              0, 0, -0.20020020008087158, 0];
            gl.enable(gl.DEPTH_TEST);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
            gl.uniformMatrix4fv(shaderProgram.pMUniform, false, new Float32Array(pMatrix));
            gl.uniformMatrix4fv(shaderProgram.mvMUniform, false, new Float32Array(mvMatrix));
        }

        function initBuffer(glELEMENT_ARRAY_BUFFER, data) {
            var buf = gl.createBuffer();
            gl.bindBuffer(glELEMENT_ARRAY_BUFFER, buf);
            gl.bufferData(glELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
            return buf;
        }

        function initBuffers(vtx, idx) {
            vbuf = initBuffer(gl.ARRAY_BUFFER, vtx);
            ibuf = initBuffer(gl.ELEMENT_ARRAY_BUFFER, idx);
            gl.vertexAttribPointer(shaderProgram.aposAttrib, 3, gl.FLOAT, false, 0, 0);
        }

        function unbindBuffers() {
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
        }

        function onready() {
            initGl();
            initShaders();    
            initScene();
            //#
            var vtx = new Float32Array(
                [-1.0, -5.0, -5.0, 
                5.0, 5.0, 5.0]
            );
            var idx = new Uint16Array([0, 1]);
            initBuffers(vtx, idx);
            gl.lineWidth(1.0);
            gl.uniform4f(shaderProgram.colorUniform, 0, 0, 0, 1);
            gl.drawElements(gl.LINES, 2, gl.UNSIGNED_SHORT, 0);
            //#
            unbindBuffers();
        }

    </script>
</head>
<body onload="onready();">
    <canvas width="400" height="300"></canvas>
</body>
</html>

As you can see code necessary to draw a line is put inside //# in onready function. What that code does? First is array of 6 numbers representing 2 points with 3 coordinates (X,Y,Z). Second array is just indexes for points from first array. We want draw line from first and second point so it is just 2 numbers: 0 and 1. After initializing buffers with those arrays, we are defining width of our line. Next is defined color of our line and lined is drawed to scene. Pretty straightforward. It will give something like in image below.

Live example and code for this you can get here.

What we can change in this code?

First color. It is just rgba format in range from 0.0 to 1.0. You can also add more points to draw now single line but whole path. Great! What about width of line? This is sadly not working how you would want it to. In mozilla you can set whatever value you want and width still be constant. Why?

Answers can be found in OpenGL ES specification:

"There is a range of supported line widths.  Only width 1 is guaranteed to be supported; others depend on the implementation. To query the range of supported widths, call glGet with argument GL_ALIASED_LINE_WIDTH_RANGE"

So if you are in browser development just implement line with width = 1 and you are done. Not very helpfull. So if you are looking into using this part of WebGL extensively I bet you will write your own library for lines anyway.

Just out of curiosity I have tried to 'query range' of available widths:

gl.getParameter( gl.ALIASED_LINE_WIDTH_RANGE)

and not for big suprise I got this:

 
So... range is from 1 to 1. Not very wide I must say (I tested it in FF 18.0.2).
 
I wanted to have control of width of my lines so I ended up doing it on my own. Next time I will write how I complished that.


Simple 2D graphs

clock February 17, 2013 02:26 by author n.podbielski

While writing application for my Masters Degree diploma I was required to came up with simple library for creating simple 2D graphs in the browser. Today I decided to share it with the rest of the world. Smile

My lib uses HTML5 tag canvas and jQueryUI library, which is used for creating dialogs for every figure.

Using is really simple. All you need to have is some data in four arrays:

XAxis - it's array with 2 values, start and end of XAxis range. Those values are also drawn as a legend for X values

XData - it's just array of X values

YAxis - same as XAxis but for Y axis

YData - Y values

It's important to have same number of items in XData and YData arrays. But it's common sense anyway. Smile

Those 4 arrays are values of object, figure representation in JS. This object should also have Type property with value 'line'. This is because I am planning to have unified start point for drawing 2D and 3D graphs. About three dimensional graphs I will post soon.

Object for single figure should look something like this:

{ Type: 'line', XAxis: [0, 1], XData: [...], YAxis: [0, 1], YData: [...]}

This is one figure, but my library can show multiple graphs at once so this figure-data-object must be added to dictionary. Keys from this dictionary will also serve as figure names and canvas ids so this cannot be anything that also is not permitted as DOM element id.

Dictionary with single figure named 'Test1' should resemble following example:

{ "Test1": { Type: 'line', XAxis: [0, 1], XData: [...], YAxis: [0, 1], YData: [...]} }

Dictionary with figures data should be send as param to figures figuresDrawer.draw function. And that is all you need to know before start using my library.

As a example I written JavaScript white noise generator Smile:

$(function () {
            YData = [];
            XData = [];
            length = 100;
            for (var i = 0; i < length; i++) {
                YData.push(Math.random());
                XData.push(i / length);
            }
            figuresDrawer.draw({ "Test1": { Type: 'line', XAxis: [0, 1], XData: XData, YAxis: [0, 1], YData: YData} });
        });

As result we will see graph in jQueryUI dialog:

 

For more configuration of graph you can change values in figuresConfig.js file.

As jQueryUI library allows to resize dialogs, graphs can be resized to. In this config file you can choose default values of width and height of new dialog.

this.default2dHeight = 400;
this.default2dWidth = 600;

'partialCanvasId' values is just template for graphs canvas tags ids. You can change it, but it has not impact on graphs functionality. I decided to put this here because I needed it in one place for 2D graphs and 3D graphs.

More interesting configuration values are in Figures2d.js file.

__axis_numbers_size  - size of font for drawing axes ranges

__axis_numbers_style - here you can change font of those numbers

So  changing code to:

var __axis_numbers_size = 20;
var __axis_numbers_style = __axis_numbers_size.toString() + "pt Times New Roman";

will set font to 20p size Times New Roman font.

 

Next 4 variables are colors of:

__figure_bg_color - background color for axis ranges description; in image above it's gray strips around graph space itself.

__figure_grid_bg_color - this is background color for graphs space; white color above

__axes_color - it is color of axes lines, color of black rectangle separating gray strips and white graph space, but it is also color of axis numbers

__plot_color - color of plot lines; blue color in example

 After cnahging code:

var __figure_bg_color = "black";
var __figure_grid_bg_color = "gray";
var __axes_color = "White";
var __plot_color = "#FF0000";

we can  see black figure background with white axes and red plot lines on gray background.

 

You can change it but I think that default are much nicer Smile

 Another thing that can be worht mentioning is that after resizing graphs are redrawn after some time after resizing. Without it it would looking strange because resizing of drawn canvas tag work like resizing an image. You can try this for example resizing to small window (it will be not readable in that size), and then again to bigger dialog window (when you will see eventually big pixels with lite smooth effect). After 2 seconds graphs will automatically resize itself to stretch to new size.

I understand that there are plenty of better libraries for this, but maybe this one will suit somebody. It's really simple and lite, less then 300 lines. And without jQueryUI which can be slow sometimes it could be really fast. And it can starting point for someone that would want to write something on their own.

 

2Dgraph2.zip (190.06 kb)