Drawing Lines: A Field of Vectors

Another way of representing vector fields is to draw individual vectors showing the magnitude and direction of the field rather than drawing field lines. We follow common practice in that each vector represents the value of the field at the base of the vector. A little thought will show that these vectors are tangent to the field lines.

The electric field from a point charge represented as a vector field.

To draw these vectors we extend what we learned in drawing the field lines and directional arrows. Each vector will be generated in the vertex shader from its base and tip, along with parallel and perpendicular displacements.

The Geometry

Interestingly, the geometry for the vector is a combination of what we used for the field line and the directional arrows.

Drawing a vector from the base, tip, and displacements. The green triangles make up the arrow shaft. The black triangles are the arrow head, and the red line is the base to tip line relative to which we apply displacements. The displacements are shown for vertex 4.

The JavaScript to setup these vertices is a bit more complicated than previous examples, but it remains highly performant.

We start with the base of the vector (x0, y0, z0).


  base.x = x0;
  base.y = y0;
  base.z = z0;

The tip is then the base translated in the direction of the field. This generates a vector that reflects the magnitude and direction of the field.


  tip.x = x0 + field[0]*arrowSize;
  tip.y = y0 + field[1]*arrowSize;
  tip.z = z0 + field[2]*arrowSize;

We push the data for each vertex onto the indexedVertices array.


  // Vertex 0
  this.pushVertex(indexedVertices, base, tip,  lineWidth, 0.0);

This sample sets up the data for vertex 0, the first vertex that in the line. It has a displacement of lineWidth perpendicular to the base to tip line (D), and zero displacement along that line (D).


  // Vertex 4 The first barb
  this.pushVertex(indexedVertices, tip, base,  arrowHeadWidth, arrowHeadSize*1.33);

Vertex 4 is the first vertex in the arrow head. It has a D of arrowHeadWidth, and a D of 1.33*arrowHeadSize.

The vertex shader is a bit simpler. It follows a slightly simpler path than before:

We can visualize how the data for each vertex is organized.

We provide the current point, the other point, and parallel and perpendicular displacements.

This time the attributes include normal and parallel displacements.


  attribute vec3  current;
  attribute vec3  other;
  attribute float normalDisplacement;
  attribute float parallelDisplacement;

As well as the x and y resolutions.


  uniform   vec2  resolution;

Transforming the current and other points to screen space, then finding the connecting line follows a now familiar pattern. We multiple by the model view projection, to transform into clip space. Then perform the perspective division to get screen space points.


  mat4 projModelView    = projectionMatrix * modelViewMatrix;

  vec4 currentProjected = projModelView * vec4(current, 1.0);
  vec4 otherProjected   = projModelView * vec4(other, 1.0);

  vec2 currentScreen    = currentProjected.xy / currentProjected.w;
  vec2 otherScreen      = otherProjected.xy / otherProjected.w;

We will need unit (normalized) vectors along the line connecting our points and the normal to that line. We tuned the normalization a bit to our new method where we directly specify the arrow head size through the displacements.


  vec2 dir              = otherScreen - currentScreen;
  dir                   = (dir == vec2(0.0, 0.0) ? dir : normalize(dir));
  vec2 normal           = vec2(-dir.y, dir.x);

Now that we have these unit vectors, multiply them by the displacements and add the results to the current point to get the actual vertex.


  // Adjustments perpendicular to the arrow
  vec4 offset = vec4(normal*normalDisplacement, 0.0, 0.0);
  // Adjustments along the arrow
  offset     +=  vec4(dir * parallelDisplacement, 0.0, 0.0);
  offset.xy  /= resolution * currentProjected.w;

  gl_Position = currentProjected + offset;

We have continued the practice of drawing discrete objects by generating vertices with some data and a little simple computation. This is quite common, indeed, it is now built into OpenGL in the form of Geometry Shaders. However, Geometry Shaders are not available through the current version of WebGL, so we do this work directly.

Creative Commons License
VField Documentation by Vizit Solutions is licensed under a Creative Commons Attribution 4.0 International License.