Blog

Signals Blog

WebGL for Chemistry Part 2: Displaying a Sphere in a View

This series began with discussion of WebGL™ and its potential in Chemistry. That first article also outlined a simple goal: the creation of a browser-based component (M3D) capable of rendering static 3D representations of small organic molecules.

This article will take the first steps toward that goal by defining a JavaScript View object, and by adding a colored, shaded sphere to that view.

All Articles in This Series

  1. WebGL for Chemistry Part 1: Introduction
  2. WebGL for Chemistry Part 2: Displaying a Sphere in a View
  3. WebGL for Chemistry Part 3: Hello, Benzene!
  4. WebGL for Chemistry Part 4: Bond, James Bond

What it Does

Viewing the code in this tutorial in a WebGL-enabled browser such as Chrome gives a black square containing a shaded red sphere (see screenshot above). Scrolling up or down with the mouse wheel zooms the view in and out.

Also see the live demo, and the up-to-date listing of all demos.

Source Code

All code for this series of tutorials is available on the Metamolecular GitHub account and can be used under the terms of the ultra-permissive MIT license.

A View with Room

We'll be using the excellent WebGL JavaScript library PhiloGL throughout these tutorials.

Excellent though it may be, PhiloGL doesn't do everything the way I'd expect. For example, the PhiloGL examples are written using a declarative style syntax in which all application state and functions are passed into a global init function.

The main limitation of this style is that makes it somewhat difficult to manipulate application state on the fly in a browser console, or through an automated test suite.

Instead, this series of tutorials will use a View object. View will expose a high-level interface that sits on top of PhiloGL and abstracts as much complexity away as possible. In addition to simplifying testing through the console or test suites, this approach will also enable us to easily replace PhiloGL with another WebGL API, should we choose.

The View

View represents a 3D view:

/**
 * @constructor
 * @param {number} width The canvas width
 * @param {number} height The canvas height
 */
var View = function(width, height) {
  this.width_ = width;
  this.height_ = height;
  this.philo_ = undefined;
  this.camera_ = undefined;
  this.gl_ = undefined;
  this.scene_ = undefined;
};

/**
 * @param {Element} parent Parent element
 */
View.prototype.render = function(parent) {
  var view = this;
  var canvas = this.createCanvas_(parent);

  PhiloGL(canvas, {
    events: { onMouseWheel: function(event) { view.wheelEvent_(event); } },
    onError: function() { alert("There was an error creating the app."); },
    onLoad: function(app) {
      view.philo_ = app;
      view.camera_ = app.camera;
      view.gl_ = app.gl;
      view.scene_ = app.scene;

      view.enterDocument();
    }
  });
};

/**
 * Called after canvas tag has been created and WebGL context has been established.
 */
View.prototype.enterDocument = function() {
  this.configureGraphics_();
  this.illuminate(0.25, 0.8);
  this.aimLight(-1, -1, -1);
  this.addObjects_();
  this.moveCamera(0, 0, -7);
  this.redraw();
};

/**
 * @param {number} ambient Ambient light intensity (0-1)
 * @param {number} directional Directional light intensity (0-1)
 */
View.prototype.illuminate = function(ambient, directional) {
  this.scene_.config.lights.enable = true;
  this.scene_.config.lights.ambient = { r: ambient, g: ambient, b: ambient };
  this.scene_.config.lights.directional.color = { r: directional, g: directional, b: directional };
};

/**
 * @param {number} x
 * @param {number} y
 * @param {number} z
 */
View.prototype.aimLight = function(x, y, z) {
  this.scene_.config.lights.directional.direction = { x: x, y: y, z: z };
};

/**
 * @param {number} x
 * @param {number} y
 * @param {number} z
 */
View.prototype.moveCamera = function(x, y, z) {
  this.camera_.position = { x: x, y: y, z: z };
  this.camera_.update();
};

/**
 * Must be called after any state-changing function call, such as moveCamera.
 */
View.prototype.redraw = function() {
  this.gl_.clear(this.gl_.COLOR_BUFFER_BIT | this.gl_.DEPTH_BUFFER_BIT);
  this.scene_.render();
};

/**
 * @private
 */
View.prototype.addObjects_ = function() {
  var atom = new PhiloGL.O3D.Sphere({ nlat: 20, nlong: 20, radius: 1, colors: [1, 0, 0, 1] });

  this.scene_.add(atom);
};

/**
 * @private
 */
View.prototype.configureGraphics_ = function() {
  this.philo_.gl.clearColor(0.0, 0.0, 0.0, 1.0);
  this.philo_.gl.clearDepth(1.0);
  this.philo_.gl.enable(this.philo_.gl.DEPTH_TEST);
  this.philo_.gl.depthFunc(this.philo_.gl.LEQUAL);
  this.philo_.gl.viewport(0, 0, this.philo_.canvas.width, this.philo_.canvas.height);
};

/**
 * @private
 */
 View.prototype.wheelEvent_ = function(event) {
   event.stop();
   this.moveCamera(0, 0, this.camera_.position.z + event.wheel);
   this.redraw();
 };

/**
 * @private
 * @param {Element} parent
 */
View.prototype.createCanvas_ = function(parent) {
  var canvas = document.createElement('canvas');

  canvas.setAttribute('width', this.width_);
  canvas.setAttribute('height', this.height_);
  parent.appendChild(canvas);

  return canvas;
};

/**
 * @param {string} id The dom id of the element into which the view will be placed
 * @return {View}
 */
var createView = function(id) {
  var view = new View(500, 500);

  view.render(document.getElementById(id));

  return view;
}
 

View is used from an HTML document by first defining an element that will contain the View, and then generating it through the createView function:

<body>
  <div id="scene"></div>
  <script>
    var view = createView('scene');
  </script>
</body>
 

Using the View

We can now use a browser console session to manipulate the View. For example, to change angle of lighting, we could use:

view.aimLight(0, -1, -1);
view.redraw();
 

Likewise, to move the camera, we could use:

view.moveCamera(0, 0, -5);
view.redraw();
 

Conclusions

We now have a View that can render a simple scene using WebGL. Future articles will evolve View and introduce the other functionality we'll need to create a simple molecule viewer.

WebGL and the WebGL logo are trademarks of the Khronos Group Inc.