UNPKG

speck-renderer

Version:

Browser-based WebGL molecule renderer with the goal of producing figures that are as attractive as they are practical.

547 lines (464 loc) 23.1 kB
"use strict"; var glm = require('./gl-matrix'); var webgl = require('./webgl.js'); var cube = require("./cube"); var elements = require("./elements"); var View = require("./view"); var System = require("./system"); import accumulatorShader from "./shaders/accumulator.glsl.js"; import aoShader from "./shaders/ao.glsl.js"; import atomsShader from "./shaders/atoms.glsl.js"; import blurShader from "./shaders/blur.glsl.js"; import bondsShader from "./shaders/bonds.glsl.js"; import dofShader from "./shaders/dof.glsl.js"; import fxaaShader from "./shaders/fxaa.glsl.js"; import texturedQuadShader from "./shaders/textured-quad.glsl.js"; module.exports = function (canvas, resolution, aoResolution) { var self = this; var range, samples, system; var gl, canvas; var rAtoms = null, rBonds = null, rDispQuad = null, rAccumulator = null, rAO = null, rDOF = null, rFXAA = null; var tSceneColor, tSceneNormal, tSceneDepth, tRandRotDepth, tRandRotColor, tAccumulator, tAccumulatorOut, tFXAA, tFXAAOut, tDOF, tAO; var fbSceneColor, fbSceneNormal, fbRandRot, fbAccumulator, fbFXAA, fbDOF, fbAO; var progAtoms, progBonds, progAccumulator, progAO, progFXAA, progDOF, progDisplayQuad; var ext; var sampleCount = 0, colorRendered = false, normalRendered = false; self.getAOProgress = function() { return sampleCount/1024; } self.initialize = function() { // Initialize canvas/gl. canvas.width = canvas.height = resolution; gl = canvas.getContext('webgl'); gl.enable(gl.DEPTH_TEST); gl.enable(gl.CULL_FACE); gl.clearColor(0,0,0,0); gl.clearDepth(1); gl.viewport(0,0,resolution,resolution); window.gl = gl; //debug ext = webgl.getExtensions(gl, [ "EXT_frag_depth", "WEBGL_depth_texture", ]); self.createTextures(); // Initialize shaders. progAtoms = loadProgram(gl, atomsShader); progBonds = loadProgram(gl, bondsShader); progDisplayQuad = loadProgram(gl, texturedQuadShader); progAccumulator = loadProgram(gl, accumulatorShader); progAO = loadProgram(gl, aoShader); progFXAA = loadProgram(gl, fxaaShader); progDOF = loadProgram(gl, dofShader); var position = [ -1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0 ]; // Initialize geometry. var attribs = webgl.buildAttribs(gl, {aPosition: 3}); attribs.aPosition.buffer.set(new Float32Array(position)); var count = position.length / 9; rDispQuad = new webgl.Renderable(gl, progDisplayQuad, attribs, count); rAccumulator = new webgl.Renderable(gl, progAccumulator, attribs, count); rAO = new webgl.Renderable(gl, progAO, attribs, count); rFXAA = new webgl.Renderable(gl, progFXAA, attribs, count); rDOF = new webgl.Renderable(gl, progDOF, attribs, count); samples = 0; } self.createTextures = function() { // fbRandRot tRandRotColor = new webgl.Texture(gl, 0, null, aoResolution, aoResolution); tRandRotDepth = new webgl.Texture(gl, 1, null, aoResolution, aoResolution, { internalFormat: gl.DEPTH_COMPONENT, format: gl.DEPTH_COMPONENT, type: gl.UNSIGNED_SHORT }); fbRandRot = new webgl.Framebuffer(gl, [tRandRotColor], tRandRotDepth); // fbScene tSceneColor = new webgl.Texture(gl, 2, null, resolution, resolution); tSceneNormal = new webgl.Texture(gl, 3, null, resolution, resolution); tSceneDepth = new webgl.Texture(gl, 4, null, resolution, resolution, { internalFormat: gl.DEPTH_COMPONENT, format: gl.DEPTH_COMPONENT, type: gl.UNSIGNED_SHORT }); fbSceneColor = new webgl.Framebuffer(gl, [tSceneColor], tSceneDepth); fbSceneNormal = new webgl.Framebuffer(gl, [tSceneNormal], tSceneDepth); // fbAccumulator tAccumulator = new webgl.Texture(gl, 5, null, resolution, resolution); tAccumulatorOut = new webgl.Texture(gl, 6, null, resolution, resolution); fbAccumulator = new webgl.Framebuffer(gl, [tAccumulatorOut]); // fbAO tAO = new webgl.Texture(gl, 7, null, resolution, resolution); fbAO = new webgl.Framebuffer(gl, [tAO]); // fbFXAA tFXAA = new webgl.Texture(gl, 8, null, resolution, resolution); tFXAAOut = new webgl.Texture(gl, 9, null, resolution, resolution); fbFXAA = new webgl.Framebuffer(gl, [tFXAAOut]); // fbDOF tDOF = new webgl.Texture(gl, 10, null, resolution, resolution); fbDOF = new webgl.Framebuffer(gl, [tDOF]); } self.setResolution = function(res, aoRes) { aoResolution = aoRes; resolution = res; canvas.width = canvas.height = resolution; gl.viewport(0,0,resolution,resolution); self.createTextures(); } self.setSystem = function(newSystem, view) { system = newSystem; function make36(arr) { var out = []; for (var i = 0; i < 36; i++) { out.push.apply(out, arr); } return out; } // Atoms var attribs = webgl.buildAttribs(gl, { aImposter: 3, aPosition: 3, aRadius: 1, aColor: 3 }); var imposter = []; var position = []; var radius = []; var color = []; for (var i = 0; i < system.atoms.length; i++) { imposter.push.apply(imposter, cube.position); var a = system.atoms[i]; position.push.apply(position, make36([a.x, a.y, a.z])); radius.push.apply(radius, make36([elements[a.symbol].radius])); var c = elements[a.symbol].color; color.push.apply(color, make36([c[0], c[1], c[2]])); } attribs.aImposter.buffer.set(new Float32Array(imposter)); attribs.aPosition.buffer.set(new Float32Array(position)); attribs.aRadius.buffer.set(new Float32Array(radius)); attribs.aColor.buffer.set(new Float32Array(color)); var count = imposter.length / 9; rAtoms = new webgl.Renderable(gl, progAtoms, attribs, count); // Bonds if (view.bonds) { rBonds = null; if (system.bonds.length > 0) { var attribs = webgl.buildAttribs(gl, { aImposter: 3, aPosA: 3, aPosB: 3, aRadA: 1, aRadB: 1, aColA: 3, aColB: 3 }) var imposter = []; var posa = []; var posb = []; var rada = []; var radb = []; var cola = []; var colb = []; for (var i = 0; i < system.bonds.length; i++) { var b = system.bonds[i]; if (b.cutoff > view.bondThreshold) break; imposter.push.apply(imposter, cube.position); posa.push.apply(posa, make36([b.posA.x, b.posA.y, b.posA.z])); posb.push.apply(posb, make36([b.posB.x, b.posB.y, b.posB.z])); rada.push.apply(rada, make36([b.radA])); radb.push.apply(radb, make36([b.radB])); cola.push.apply(cola, make36([b.colA.r, b.colA.g, b.colA.b])); colb.push.apply(colb, make36([b.colB.r, b.colB.g, b.colB.b])); } attribs.aImposter.buffer.set(new Float32Array(imposter)); attribs.aPosA.buffer.set(new Float32Array(posa)); attribs.aPosB.buffer.set(new Float32Array(posb)); attribs.aRadA.buffer.set(new Float32Array(rada)); attribs.aRadB.buffer.set(new Float32Array(radb)); attribs.aColA.buffer.set(new Float32Array(cola)); attribs.aColB.buffer.set(new Float32Array(colb)); var count = imposter.length / 9; rBonds = new webgl.Renderable(gl, progBonds, attribs, count); } } } self.reset = function() { sampleCount = 0; colorRendered = false; normalRendered = false; tAccumulator.reset(); tAccumulatorOut.reset(); } self.render = function(view) { if (system === undefined) { return; } if (rAtoms == null) { return; } range = System.getRadius(system) * 2.0; if (!colorRendered) { color(view); } else if (!normalRendered){ normal(view); } else { for (var i = 0; i < view.spf; i++) { if (sampleCount > 1024) { break; } sample(view); sampleCount++; } } display(view); } function color(view) { colorRendered = true; gl.viewport(0, 0, resolution, resolution); fbSceneColor.bind(); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); var rect = View.getRect(view); var projection = glm.mat4.create(); glm.mat4.ortho(projection, rect.left, rect.right, rect.bottom, rect.top, 0, range); var viewMat = glm.mat4.create(); glm.mat4.lookAt(viewMat, [0, 0, 0], [0, 0, -1], [0, 1, 0]); var model = glm.mat4.create(); glm.mat4.translate(model, model, [0, 0, -range/2]); glm.mat4.multiply(model, model, view.rotation); progAtoms.setUniform("uProjection", "Matrix4fv", false, projection); progAtoms.setUniform("uView", "Matrix4fv", false, viewMat); progAtoms.setUniform("uModel", "Matrix4fv", false, model); progAtoms.setUniform("uBottomLeft", "2fv", [rect.left, rect.bottom]); progAtoms.setUniform("uTopRight", "2fv", [rect.right, rect.top]); progAtoms.setUniform("uAtomScale", "1f", 2.5 * view.atomScale); progAtoms.setUniform("uRelativeAtomScale", "1f", view.relativeAtomScale); progAtoms.setUniform("uRes", "1f", resolution); progAtoms.setUniform("uDepth", "1f", range); progAtoms.setUniform("uMode", "1i", 0); progAtoms.setUniform("uAtomShade", "1f", view.atomShade); rAtoms.render(); if (view.bonds && rBonds != null) { fbSceneColor.bind(); progBonds.setUniform("uProjection", "Matrix4fv", false, projection); progBonds.setUniform("uView", "Matrix4fv", false, viewMat); progBonds.setUniform("uModel", "Matrix4fv", false, model); progBonds.setUniform("uRotation", "Matrix4fv", false, view.rotation); progBonds.setUniform("uDepth", "1f", range); progBonds.setUniform("uBottomLeft", "2fv", [rect.left, rect.bottom]); progBonds.setUniform("uTopRight", "2fv", [rect.right, rect.top]); progBonds.setUniform("uRes", "1f", resolution); progBonds.setUniform("uBondRadius", "1f", 2.5 * View.getBondRadius(view)); progBonds.setUniform("uBondShade", "1f", view.bondShade); progBonds.setUniform("uAtomScale", "1f", 2.5 * view.atomScale); progBonds.setUniform("uRelativeAtomScale", "1f", view.relativeAtomScale); progBonds.setUniform("uMode", "1i", 0); rBonds.render(); } } function normal(view) { normalRendered = true; gl.viewport(0, 0, resolution, resolution); fbSceneNormal.bind(); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); var rect = View.getRect(view); var projection = glm.mat4.create(); glm.mat4.ortho(projection, rect.left, rect.right, rect.bottom, rect.top, 0, range); var viewMat = glm.mat4.create(); glm.mat4.lookAt(viewMat, [0, 0, 0], [0, 0, -1], [0, 1, 0]); var model = glm.mat4.create(); glm.mat4.translate(model, model, [0, 0, -range/2]); glm.mat4.multiply(model, model, view.rotation); progAtoms.setUniform("uProjection", "Matrix4fv", false, projection); progAtoms.setUniform("uView", "Matrix4fv", false, viewMat); progAtoms.setUniform("uModel", "Matrix4fv", false, model); progAtoms.setUniform("uBottomLeft", "2fv", [rect.left, rect.bottom]); progAtoms.setUniform("uTopRight", "2fv", [rect.right, rect.top]); progAtoms.setUniform("uAtomScale", "1f", 2.5 * view.atomScale); progAtoms.setUniform("uRelativeAtomScale", "1f", view.relativeAtomScale); progAtoms.setUniform("uRes", "1f", resolution); progAtoms.setUniform("uDepth", "1f", range); progAtoms.setUniform("uMode", "1i", 1); progAtoms.setUniform("uAtomShade", "1f", view.atomShade); rAtoms.render(); if (view.bonds && rBonds != null) { fbSceneNormal.bind(); progBonds.setUniform("uProjection", "Matrix4fv", false, projection); progBonds.setUniform("uView", "Matrix4fv", false, viewMat); progBonds.setUniform("uModel", "Matrix4fv", false, model); progBonds.setUniform("uRotation", "Matrix4fv", false, view.rotation); progBonds.setUniform("uDepth", "1f", range); progBonds.setUniform("uBottomLeft", "2fv", [rect.left, rect.bottom]); progBonds.setUniform("uTopRight", "2fv", [rect.right, rect.top]); progBonds.setUniform("uRes", "1f", resolution); progBonds.setUniform("uBondRadius", "1f", 2.5 * View.getBondRadius(view)); progBonds.setUniform("uBondShade", "1f", view.bondShade); progBonds.setUniform("uAtomScale", "1f", 2.5 * view.atomScale); progBonds.setUniform("uRelativeAtomScale", "1f", view.relativeAtomScale); progBonds.setUniform("uMode", "1i", 1); rBonds.render(); } } function sample(view) { gl.viewport(0, 0, aoResolution, aoResolution); var v = View.clone(view); v.zoom = 1/range; v.translation.x = 0; v.translation.y = 0; var rot = glm.mat4.create(); for (var i = 0; i < 3; i++) { var axis = glm.vec3.random(glm.vec3.create(), 1.0); glm.mat4.rotate(rot, rot, Math.random() * 10, axis); } v.rotation = glm.mat4.multiply(glm.mat4.create(), rot, v.rotation); fbRandRot.bind() gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); var rect = View.getRect(v); var projection = glm.mat4.create(); glm.mat4.ortho(projection, rect.left, rect.right, rect.bottom, rect.top, 0, range); var viewMat = glm.mat4.create(); glm.mat4.lookAt(viewMat, [0, 0, 0], [0, 0, -1], [0, 1, 0]); var model = glm.mat4.create(); glm.mat4.translate(model, model, [0, 0, -range/2]); glm.mat4.multiply(model, model, v.rotation); progAtoms.setUniform("uProjection", "Matrix4fv", false, projection); progAtoms.setUniform("uView", "Matrix4fv", false, viewMat); progAtoms.setUniform("uModel", "Matrix4fv", false, model); progAtoms.setUniform("uBottomLeft", "2fv", [rect.left, rect.bottom]); progAtoms.setUniform("uTopRight", "2fv", [rect.right, rect.top]); progAtoms.setUniform("uAtomScale", "1f", 2.5 * v.atomScale); progAtoms.setUniform("uRelativeAtomScale", "1f", view.relativeAtomScale); progAtoms.setUniform("uRes", "1f", aoResolution); progAtoms.setUniform("uDepth", "1f", range); progAtoms.setUniform("uMode", "1i", 0); progAtoms.setUniform("uAtomShade", "1f", view.atomShade); rAtoms.render(); if (view.bonds && rBonds != null) { progBonds.setUniform("uProjection", "Matrix4fv", false, projection); progBonds.setUniform("uView", "Matrix4fv", false, viewMat); progBonds.setUniform("uModel", "Matrix4fv", false, model); progBonds.setUniform("uRotation", "Matrix4fv", false, v.rotation); progBonds.setUniform("uDepth", "1f", range); progBonds.setUniform("uBottomLeft", "2fv", [rect.left, rect.bottom]); progBonds.setUniform("uTopRight", "2fv", [rect.right, rect.top]); progBonds.setUniform("uRes", "1f", aoResolution); progBonds.setUniform("uBondRadius", "1f", 2.5 * View.getBondRadius(view)); progBonds.setUniform("uBondShade", "1f", view.bondShade); progBonds.setUniform("uAtomScale", "1f", 2.5 * view.atomScale); progBonds.setUniform("uRelativeAtomScale", "1f", view.relativeAtomScale); progBonds.setUniform("uMode", "1i", 0); rBonds.render(); } gl.viewport(0, 0, resolution, resolution); var sceneRect = View.getRect(view); var rotRect = View.getRect(v); var invRot = glm.mat4.invert(glm.mat4.create(), rot); fbAccumulator.bind(); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); progAccumulator.setUniform("uSceneDepth", "1i", tSceneDepth.index); progAccumulator.setUniform("uSceneNormal", "1i", tSceneNormal.index); progAccumulator.setUniform("uRandRotDepth", "1i", tRandRotDepth.index); progAccumulator.setUniform("uAccumulator", "1i", tAccumulator.index); progAccumulator.setUniform("uSceneBottomLeft", "2fv", [sceneRect.left, sceneRect.bottom]); progAccumulator.setUniform("uSceneTopRight", "2fv", [sceneRect.right, sceneRect.top]); progAccumulator.setUniform("uRotBottomLeft", "2fv", [rotRect.left, rotRect.bottom]); progAccumulator.setUniform("uRotTopRight", "2fv", [rotRect.right, rotRect.top]); progAccumulator.setUniform("uRes", "1f", resolution); progAccumulator.setUniform("uDepth", "1f", range); progAccumulator.setUniform("uRot", "Matrix4fv", false, rot); progAccumulator.setUniform("uInvRot", "Matrix4fv", false, invRot); progAccumulator.setUniform("uSampleCount", "1i", sampleCount); rAccumulator.render(); tAccumulator.activate(); tAccumulator.bind(); gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, resolution, resolution, 0); } function display(view) { gl.viewport(0, 0, resolution, resolution); if (view.fxaa > 0 || view.dofStrength > 0) { fbAO.bind(); } else { gl.bindFramebuffer(gl.FRAMEBUFFER, null); } gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); progAO.setUniform("uSceneColor", "1i", tSceneColor.index); progAO.setUniform("uSceneDepth", "1i", tSceneDepth.index); progAO.setUniform("uAccumulatorOut", "1i", tAccumulatorOut.index); progAO.setUniform("uRes", "1f", resolution); progAO.setUniform("uAO", "1f", 2.0 * view.ao); progAO.setUniform("uBrightness", "1f", 2.0 * view.brightness); progAO.setUniform("uOutlineStrength", "1f", view.outline); rAO.render(); if (view.fxaa > 0) { if (view.dofStrength > 0) { fbFXAA.bind(); } else { gl.bindFramebuffer(gl.FRAMEBUFFER, null); } for (var i = 0; i < view.fxaa; i++) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); if (i == 0) { progFXAA.setUniform("uTexture", "1i", tAO.index); } else { progFXAA.setUniform("uTexture", "1i", tFXAA.index); } progFXAA.setUniform("uRes", "1f", resolution); rFXAA.render(); tFXAA.activate(); tFXAA.bind(); gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, resolution, resolution, 0); } } if (view.dofStrength > 0) { gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); if (view.fxaa > 0) { progDOF.setUniform("uColor", "1i", tFXAA.index); } else { progDOF.setUniform("uColor", "1i", tAO.index); } progDOF.setUniform("uDepth", "1i", tSceneDepth.index); progDOF.setUniform("uDOFPosition", "1f", view.dofPosition); progDOF.setUniform("uDOFStrength", "1f", view.dofStrength); progDOF.setUniform("uRes", "1f", resolution); rDOF.render(); } // gl.bindFramebuffer(gl.FRAMEBUFFER, null); // gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // progDisplayQuad.setUniform("uTexture", "1i", tSceneColor.index); // progDisplayQuad.setUniform("uRes", "1f", resolution); // rDispQuad.render(); } self.initialize(); } function loadProgram(gl, src) { src = src.split('// __split__'); return new webgl.Program(gl, src[0], src[1]); }