WebGL是一种在浏览器中实现图形处理代码的JavaScript接口,它允许浏览器利用GPU进行图形渲染。本文旨在测试使用WebGL进行编程的难度,并尝试创建一个基于JavaScript和WebGL的分子查看器。令人惊讶的是,这个过程相对简单!
虽然WebGL与OpenGL在概念和格式上有许多相似之处,但WebGL是专为Web浏览器设计的,可以直接在网页上渲染3D图形。本文将介绍如何使用WebGL创建一个简单的分子查看器,用于查看PDB(蛋白质数据银行)文件中的分子结构。
要使用本文提供的代码,首先需要将其安装在Web服务器上,无论是本地还是远程服务器。然后访问index.html页面,点击“选择文件”按钮,选择本地计算机上的PDB文件,即可查看分子模型。
以下是一些基本的操作键:
以下是一个简单的PDB文件示例,展示了丙烷的分子结构:
COMPND PROPANE
AUTHOR DAVE WOODCOCK 95 12 18
ATOM 1 C 1 1.241 0.444 0.349 1.00 0.00
ATOM 2 C 1 -0.011 -0.441 0.333 1.00 0.00
ATOM 3 C 1 -1.176 0.296 -0.332 1.00 0.00
ATOM 4 H 1 1.516 0.699 -0.675 1.00 0.00
ATOM 5 H 1 2.058 -0.099 0.827 1.00 0.00
ATOM 6 H 1 1.035 1.354 0.913 1.00 0.00
ATOM 7 H 1 -0.283 -0.691 1.359 1.00 0.00
ATOM 8 H 1 0.204 -1.354 -0.225 1.00 0.00
ATOM 9 H 1 -0.914 0.551 -1.359 1.00 0.00
ATOM 10 H 1 -1.396 1.211 0.219 1.00 0.00
ATOM 11 H 1 -2.058 -0.345 -0.332 1.00 0.00
TER 12 1
END
首先,需要选择一个文件并解析文件中的ATOM条目。使用文件输入按钮的onclick操作符来选择文件。接下来,设置一个FileReader对象并解析文件。一旦有了文件内容,调用以下代码将文件分割成行:
var lines = contents.split("\n");
接下来,需要遍历这些行,使用空格将它们分割成一个数组。然后,创建一个数组来保存ATOM对象,并用行条目填充它。在分割行时,会有一些空白数组条目,所以需要遍历行并只复制想要的值。另外,有些行的空格比其他行多,所以不能使用固定位置。因此,只复制所有非空值。这个过程如下所示:
for (var y = 0; y < singleLine.length; y++) {
if (singleLine[y] != "" && singleLine[y] != ",") {
objects[object_count][counter++] = singleLine[y];
}
}
接下来,需要遍历数组,找到原子并绘制它们。注意,并没有预先将原子分开,而是复制了所有非空值。所以现在需要找到想要绘制的ATOM结构。从上面的文件格式中,可以看到每个原子的x、y、z坐标分别在第5、6、7列。代码如下所示:
for (var i = 0; i < object_count; ++i) {
if (objects[i][0] == "ATOM") {
if (!setcamera) {
g.perspectiveMatrix.lookat(0, 0, objects[i][7], 0, 0, 0, 0, 1, 0);
setcamera = true;
}
drawOne(ctx, 30, objects[i][5], objects[i][6], objects[i][7], 0.75, molTexture);
}
}
接下来,需要设置相机和对象的正确位置。为此,使用一个模型-视图矩阵,并将其设置为通过相机进行平移和旋转,然后再次通过正在绘制的原子进行平移。代码如下所示:
var pos = controller.getPosition();
mvMatrix.translate(pos[0], pos[1], pos[2]);
mvMatrix.rotate(controller.getRoll(), 1, 0, 0);
mvMatrix.rotate(controller.getYaw(), 0, 1, 0);
mvMatrix.rotate(controller.getPitch(), 0, 0, 1);
mvMatrix.translate(-x, -y, -z);
现在有了绘制原子的位置矩阵,可以在任何最终投影之后绘制原子:
ctx.bindTexture(ctx.TEXTURE_2D, texture);
ctx.drawElements(ctx.TRIANGLES, g.sphere.numIndices, ctx.UNSIGNED_SHORT, 0);
最后,需要使用键来移动相机:
function keyCamera(event) {
var cam = controller;
if (event.shiftKey) {
switch (event.keyCode) {
case 65:
cam.roll(-Math.PI * 0.25);
break;
case 37:
cam.yaw(Math.PI * 0.25);
break;
case 68:
cam.roll(Math.PI * 0.25);
break;
case 39:
cam.yaw(-Math.PI * 0.25);
break;
case 83:
case 40:
cam.pitch(Math.PI * 0.25);
break;
case 87:
case 38:
cam.pitch(-Math.PI * 0.25);
break;
}
} else {
var pos = cam.getPosition();
switch (event.keyCode) {
case 65:
case 37:
cam.setPosition(pos[0] - 0.5, pos[1], pos[2]);
break;
case 68:
case 39:
cam.setPosition(pos[0] + 0.5, pos[1], pos[2]);
break;
case 83:
cam.setPosition(pos[0], pos[1] - 0.5, pos[2]);
break;
case 40:
cam.setPosition(pos[0], pos[1], pos[2] + 0.5);
break;
case 87:
cam.setPosition(pos[0], pos[1] + 0.5, pos[2]);
break;
case 38:
cam.setPosition(pos[0], pos[1], pos[2] - 0.5);
break;
}
}
}
使用以下代码初始化WebGL:
var gl = initWebGL("molview");
if (!gl) {
return;
}
var c = document.getElementById("molview");
c.addEventListener('webglcontextlost', handleContextLost, false);
c.addEventListener('webglcontextrestored', handleContextRestored, false);
g.program = simpleSetup(gl, "vshader", "fshader", ["vNormal", "vTexCoord", "vPosition"], [0, 0, 0, 1], 10000);
gl.uniform3f(gl.getUniformLocation(g.program, "lightDir"), 0, 0, 1);
gl.uniform1i(gl.getUniformLocation(g.program, "sampler2d"), 0);
if (g.program) {
g.u_normalMatrixLoc = gl.getUniformLocation(g.program, "u_normalMatrix");
g.u_modelViewProjMatrixLoc = gl.getUniformLocation(g.program, "u_modelViewProjMatrix");
}
g.sphere = makeSphere(gl, 1, 30, 30);
molTexture = loadImageTexture(gl, "./h1.jpg");
最后,只需要实现一个跟踪位置和旋转的相机,然后就可以开始了。相机的代码太多,无法在这里发布,所以有兴趣的读者可以查看附加的源代码。已经省略了一些功能,以保持简洁。附加的代码包含了所有需要的内容和几个PDB文件样本。