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

WebGL as method for generating 3D graphic in browser and it 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 🙂 ). 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:

Float32Array { 0=1, 1=1 }

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve : *
3 + 16 =