|Android (0.90)||2012-08-29 23:56|
|GLmol (0.47)||2012-08-29 23:58|
|pymol-exporter (0.01)||2012-01-11 21:50|
Comment and suggestions are welcome. Please mail to biochem_fan at sourceforge.jp.
This document is not completed yet. Some parts are missing. Also please feel free to copyedit this page and fix grammatical errors.
Toward this goal,
Three dimensional visualization of molecules involves following steps.
As I will describe below, step 1 to 3 are done only once unless user changes representation.
Atomic coordinates are provided in many formats. Popular formats include PDB, MDL MOL/SDF and CIF. PDB and MOL are easier to parse because they are fixed-length format. CIF is more complex as it is free-spaced and has complex syntax like "repeat".
Currently, GLmol supports PDB and MOL/SDF. Supporting CIF is possible but I am not sure if it is worth doing. Chemists can easily convert CIF files to PDB or MOL.
While MOL/SDF format contains a bond table, PDB format contains bonding information for only heteroatoms (= non standard residues and ligands). So we have to detect it.
Current bond detection algorithm is very simple. Iterate over atom pairs and if the distance is within threshold, we assume a bond is present. This naive O(n2) algorithm is too slow for large molecules. To make it faster, GLmol considers only atom pairs with similar serial numbers. This is justied because atoms in PDB files are sorted by residue ID and a atom can bond only to atoms in the same or neighboring residues (there are exceptions but they are explicitly described in BOND record).
Secondary structure is read from "SHEET" and "HELIX" records in the input PDB file. These records are present in PDB files downloaded from RCSB PDB. However, PDB files produced by refinement programs, modeling programs and simulation programs do not usually contain this record and GLmol fails to show their secondary structures.
De-facto standard of secondary structure assignment algorighm is DSSP. Although it is technically possible to implement it in Javascipt, much simpler and faster algorithm might be more suitable for GLmol. For example, we can use Ramachandran plot to assign secondary structures (but with less accuracy).
This is the most time consuming part of GLmol. It often takes several hundreds of milliseconds. For example, in the case of 2POR on my PC, polygon generation took 103msec while parsing and rendering took only 11msec and 5msec, respectively (you can check it on Firebug or Chrome console).
While "sphere", "line" and "stick" representation are trivial to construct, "ribbon" and "strand" for proteins are more difficult. Basic idea is to smoothly connect alpha carbons of protein's backbone (or O3' of nucleic acids). This is done by Catmull-Rom subdivision. Vectors connecting alpha carbons to carbonyl oxgens are used to define ribbon's plane. Special care must be taken for beta sheets as this vector flips residue by residue. Secondary structure (alpha helix and beta sheet) is distinguished from loops by being drawn wider.
For molecular surface calculation, please read http://webglmol.sourceforge.jp/surface.html.
I think GLmol's graphics is beautiful enough but we can improve. For example,
you can give thickness to ribbons (Implemented in 0.40). Displaying arrowheads for beta sheets would be a nice improvement.
you might want to "smoothen" beta sheet. This can be achieved by connecting midpoints of alpha carbons instead of alpha carbons itself. I have tested this and indeed it worked nicely (Implemented in 0.40). The drawback is that now the ribbon doesn't necessarily pass through alpha carbons so that side chain rendering gets trickier. Just connecting an alpha carbon and a beta carbon makes "floating" sidechains.
Drawing nice cartoon diagrams is a very complicated task. PyMOL does lots of calculation and fine-tuning to achive it. Indeed, PyMOL's implementation of cartoon representation is longer than the entire source code of GLmol! We can pursue this direction but it would make GLmol bigger and slower. I want to keep GLmol compact and fast. I think my current implementation achieve a good blance in this regard.
GLmol renders a scene by WebGL through THREE.js library https://github.com/mrdoob/three.js/. Polygons generated above are stored as THREE.Object3D objects and added to the scene (THREE.Scene). A scene also contains a camera and lights.
GLmol keeps track of only atoms within the asymmetric unit. Polygons are generated for these atoms. Symmetry mates are drawn by repeatedly rendering them with different transformation matrices. Now symmetry operations are carried out on GPU. Very quick!
As GLmol doesn't store atomic coordinates of symmetry neighbors, bonds across two asymmetric units cannot be detected. Although this is not a problem for biomolecules, inorganic crystals cannot be drawn correctly.
User interaction means rotation/translation/scaling of molecules by mouse, keyboard or touchpanel. They can be (and MUST be) handled by GPU without changing polygon definition. We just change transformation matrix (model-view matrix) and render the same scene. We should never change polygon definitions in event handlers; otherwise, performance is severly compromised.
TODO: Write about modelGroup and rotationGroup
Classical molecular visualization programs construct 3D objects from atomic coordinates every frame. However, it is not necessary. Once representation is defined, polygons (vertices, faces, colors, normals) can be defined. This is CPU's task and very time consuming. But once this is done, polygons are valid until representation are changed. Rotation, translation and scaling can be done on GPU and it is very fast.
As I described above, GLmol always separates (1) Polygon generation and (2) Actual rendering. Polygon generation is done only once and rendering is done on GPU.
In terms of implementation, 3D objects are THREE.Object3D. It contains THREE.Mesh, which stores actual polygon definition(vertices, faces, colors, normals). Methods like drawAtomsAsSpheres and drawCartoon calculate polygons from atomic coordinates, create THREE.Object3Ds and add them into the scene.
Here comes another important trick, mesh merging.
Once mesh is stored in the video memory as VBO, rendering in WebGL is very fast, almost comparable to native code. You can draw millions of polygons in less than 10msec, provided that it is rendered by one glDrawElements call. In contrast, drawing five five thousand objects(THREE.Object3Ds) seperatedly is unpractically slow already.
Current implementation of GLmol doesn't combine spheres and cylinders. So GLmol is not very good at drawing huge moleculars in "sphere" or "stick" representation. Below is the explanation.
Suppose you want to draw a bond, that is, a cylinder. THREE.CylinderGeometry generates polygons for a cylinder located at origin, with its long axis parallel to z axis. To connect two atoms with this cylinder, we have to move and rotate it. To do so, we multiply every vertex coordinate by a 4x4 transformation matrix. This is done by GPU and very fast. This transformation matrix is stored as "matrix" property of each THREE.Oject3D object.
Now we want to draw many bonds. We can generate many THREE.Object3D, one for each bond, and put them into the scene but this is slow at rendering. So we try to merge all polygons into one large THREE.Object3D. This can be done with THREE.GeometryUtils.merge as described in http://learningthreejs.com/blog/2011/10/05/performance-merging-geometry/. However, as you can see in its source code, this method do matrix multiplication by CPU. It is very slow!
I tested it; I loaded a structure with 12641 atoms and tried to draw it as "spheres". It is impossible to draw so many objects one by one (my browser went unresponsive while rendering). Then I tried THREE.GeometryUtils.merge. Still unresponsive!
In this case, objects to be merged are spheres, not cylinders and there is a trick. Rotation doesn't do anything on spheres; we can ignore it. Translation can be done by only three additions. This is a nice optimization(rotation needs nine multiplication and additions). In this way, I finally managed to combine mesh. With combined mesh, it took only 7msec to render. Great! However, mesh merging still took 2500msec. Do you think this is acceptable? And still we have to find a method for cylinders...
The drawback is that keeping polygons is memory consuming. This is not a problem for modern PC but might be a problem for older PCs and mobile devices.
The design stated above makes it hard to dynamically change representations. We have to rebuild entire mesh to change color of one residue. I believe this is reasonable decision because we spend lots of time rotating molecules before we change representation. But if we do need interactive, realtime change of representation, we have to reconsider the architecture.
If we want to change colors only, there might be a solution. Instead of assigning 'real' colors to vertices, assign atom serial number to each vertex. Also setup a atom-serial-to-color table. Then make our fragment shader lookup the table for each pixel. Now the problem reduced to just updating the table, which is trivial and hopefully fast. Proof-of-concept implementation of this idea is uploaded to https://github.com/biochem-fan/GLmol/tree/dynamic-coloring .
The most important feature currently lacking might be "labels". Implementation can be done in two ways. One method is to use "billboard" with label as a texture. The other method is to render label as HTML elements and place it in right position.
Some programs (like PyMOL) allow users to pick atoms or residues by clicking on a rendered image. We can implement this by adding a special renderer, which renders each atom on FBO(frame buffer object) with different colors.
This limitation is imposed by WebGL specification and we cannot do anything (if you find some nice workaround, please let me know.) But don't be too pessimistic! What we want on browsers is visualization, not numerical value itself. As long as you don't transfer floats too often, this is not a problem. For example, we might want to do molecular dynamics on browser. Time evolution can be calculated on GPU. The result can be visualized by GPU. Unless we try to dump trajectories every step, everything works fine.