WebGL as a 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 these 3D objects are a complex collection of hundreds or thousands of triangles. What if you just want to draw a simple thin line? Luckily, OpenES has support for that. So it is possible. Let's 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 simply: scene viewer and you will look from the same point (more or less of course ). Model view matrix will also translate 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, the code necessary to draw a line is put inside //# in onready
function. What does that code do? First is array of 6 numbers representing 2 points with 3 coordinates (X,Y,Z). Second array is just indexes for points from the first array. We want to draw line from the first and second points 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 drawn to scene. Pretty straightforward. It will give something like in the image below:
You can get the live example and code for this 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 will 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 helpful. 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 accomplished that.
CodeProject