Bladeren bron

wip, primites rendering, geometry

Alexander Rose 7 jaren geleden
bovenliggende
commit
152b5219c3

+ 48 - 12
src/apps/render-test/state.ts

@@ -13,7 +13,8 @@ import Attribute from 'mol-gl/attribute';
 import Model from 'mol-gl/model';
 import { createTransformAttributes } from 'mol-gl/renderable/util';
 import { calculateTextureInfo } from 'mol-gl/util';
-// import { positionFromModel } from 'mol-geo/shape/point'
+import Icosahedron from 'mol-geo/primitive/icosahedron'
+import Box from 'mol-geo/primitive/box'
 
 export default class State {
     regl: REGL.Regl
@@ -44,6 +45,7 @@ export default class State {
         const model3 = Model(regl, { position: p2 })
 
         const position = Attribute.create(regl, new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]), { size: 3 })
+        const normal = Attribute.create(regl, new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]), { size: 3 })
 
         const transformArray1 = new Float32Array(16)
         const transformArray2 = new Float32Array(16 * 3)
@@ -55,6 +57,8 @@ export default class State {
         Mat4.setTranslation(m4, p2)
         Mat4.toArray(m4, transformArray2, 32)
 
+
+
         const colorTexInfo = calculateTextureInfo(3, 3)
         const color = new Uint8Array(colorTexInfo.length)
         color.set([
@@ -84,6 +88,7 @@ export default class State {
         const mesh = MeshRenderable.create(regl,
             {
                 position,
+                normal,
                 ...createTransformAttributes(regl, transformArray2)
             },
             {
@@ -92,6 +97,35 @@ export default class State {
             }
         )
 
+        const sphere = Icosahedron(1, 1)
+        console.log(sphere)
+
+        const box = Box(1, 1, 1, 1, 1, 1)
+        console.log(box)
+
+        const points2 = PointRenderable.create(regl, {
+            position: Attribute.create(regl, new Float32Array(box.vertices), { size: 3 }),
+            ...createTransformAttributes(regl, transformArray1)
+        })
+
+        const mesh2 = MeshRenderable.create(regl,
+            {
+                position: Attribute.create(regl, new Float32Array(box.vertices), { size: 3 }),
+                normal: Attribute.create(regl, new Float32Array(box.normals), { size: 3 }),
+                ...createTransformAttributes(regl, transformArray2)
+            },
+            {
+                colorTex,
+                colorTexSize: [ colorTexInfo.width, colorTexInfo.height ],
+                'light.position': Vec3.create(0, 0, -20),
+                'light.color': Vec3.create(1.0, 1.0, 1.0),
+                'light.ambient': Vec3.create(0.5, 0.5, 0.5),
+                'light.falloff': 0,
+                'light.radius': 500
+            },
+            box.indices
+        )
+
         const baseContext = regl({
             context: {
                 model: Mat4.identity(),
@@ -111,17 +145,19 @@ export default class State {
                     regl.clear({color: [0, 0, 0, 1]})
                     position.update(array => { array[0] = Math.random() })
                     // points.update(a => { a.position[0] = Math.random() })
-                    mesh.draw()
-                    points.draw()
-                    model1({}, ({ transform }) => {
-                        points.draw()
-                    })
-                    model2({}, ({ transform }) => {
-                        points.draw()
-                        model3({ transform }, () => {
-                            points.draw()
-                        })
-                    })
+                    // mesh.draw()
+                    // points.draw()
+                    mesh2.draw()
+                    points2.draw()
+                    // model1({}, ({ transform }) => {
+                    //     points.draw()
+                    // })
+                    // model2({}, ({ transform }) => {
+                    //     points.draw()
+                    //     model3({ transform }, () => {
+                    //         points.draw()
+                    //     })
+                    // })
                 })
             }, undefined)
         })

+ 1 - 0
src/helpers.d.ts

@@ -11,4 +11,5 @@ declare module Helpers {
     }
     export type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array
     export type NumberArray = TypedArray | number[]
+    export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[]
 }

+ 95 - 0
src/mol-geo/primitive/box.ts

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// adapted from three.js, MIT License Copyright 2010-2018 three.js authors
+
+import { Vec3 } from 'mol-math/linear-algebra'
+
+export default function Box(width: number, height: number, depth: number, widthSegments: number, heightSegments: number, depthSegments: number) {
+    widthSegments = Math.floor(widthSegments)
+    heightSegments = Math.floor(heightSegments)
+    depthSegments = Math.floor(depthSegments)
+
+    // buffers
+    const indices: number[] = [];
+    const vertices: number[] = [];
+    const normals: number[] = [];
+
+    // helper variables
+    let numberOfVertices = 0;
+
+    // build each side of the box geometry
+    buildPlane(2, 1, 0, -1, -1, depth, height, width, depthSegments, heightSegments); // px
+    buildPlane(2, 1, 0, 1, -1, depth, height, -width, depthSegments, heightSegments); // nx
+    buildPlane(0, 2, 1, 1, 1, width, depth, height, widthSegments, depthSegments); // py
+    buildPlane(0, 2, 1, 1, -1, width, depth, -height, widthSegments, depthSegments); // ny
+    buildPlane(0, 1, 2, 1, -1, width, height, depth, widthSegments, heightSegments); // pz
+    buildPlane(0, 1, 2, -1, -1, width, height, -depth, widthSegments, heightSegments); // nz
+
+    return { vertices, indices, normals }
+
+    function buildPlane(u: number, v: number, w: number, udir: number, vdir: number, width: number, height: number, depth: number, gridX: number, gridY: number) {
+
+        const segmentWidth = width / gridX;
+        const segmentHeight = height / gridY;
+
+        const widthHalf = width / 2;
+        const heightHalf = height / 2;
+        const depthHalf = depth / 2;
+
+        const gridX1 = gridX + 1;
+        const gridY1 = gridY + 1;
+
+        let vertexCounter = 0;
+
+        const vector = Vec3.zero();
+
+        // generate vertices and normals
+        for (let iy = 0; iy < gridY1; ++iy) {
+            const y = iy * segmentHeight - heightHalf;
+            for (let ix = 0; ix < gridX1; ++ix) {
+                const x = ix * segmentWidth - widthHalf;
+
+                // set values to correct vector component
+                vector[ u ] = x * udir;
+                vector[ v ] = y * vdir;
+                vector[ w ] = depthHalf;
+
+                // now apply vector to vertex buffer
+                vertices.push(...vector);
+
+                // set values to correct vector component
+                vector[ u ] = 0;
+                vector[ v ] = 0;
+                vector[ w ] = depth > 0 ? 1 : -1;
+
+                // now apply vector to normal buffer
+                normals.push(...vector);
+
+                vertexCounter += 1;
+            }
+        }
+
+        // indices
+        // 1. you need three indices to draw a single face
+        // 2. a single segment consists of two faces
+        // 3. so we need to generate six (2*3) indices per segment
+        for (let iy = 0; iy < gridY; ++iy) {
+            for (let ix = 0; ix < gridX; ++ix) {
+                const a = numberOfVertices + ix + gridX1 * iy;
+                const b = numberOfVertices + ix + gridX1 * (iy + 1);
+                const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
+                const d = numberOfVertices + (ix + 1) + gridX1 * iy;
+
+                // faces
+                indices.push(a, b, d);
+                indices.push(b, c, d);
+            }
+        }
+
+        numberOfVertices += vertexCounter;
+    }
+}

+ 28 - 0
src/mol-geo/primitive/icosahedron.ts

@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// adapted from three.js, MIT License Copyright 2010-2018 three.js authors
+
+import Polyhedron from './polyhedron'
+
+const t = ( 1 + Math.sqrt( 5 ) ) / 2;
+
+const vertices = [
+    - 1, t, 0, 	1, t, 0, 	- 1, - t, 0, 	1, - t, 0,
+     0, - 1, t, 	0, 1, t,	0, - 1, - t, 	0, 1, - t,
+     t, 0, - 1, 	t, 0, 1, 	- t, 0, - 1, 	- t, 0, 1
+];
+
+const indices = [
+     0, 11, 5, 	0, 5, 1, 	0, 1, 7, 	0, 7, 10, 	0, 10, 11,
+     1, 5, 9, 	5, 11, 4,	11, 10, 2,	10, 7, 6,	7, 1, 8,
+     3, 9, 4, 	3, 4, 2,	3, 2, 6,	3, 6, 8,	3, 8, 9,
+     4, 9, 5, 	2, 4, 11,	6, 2, 10,	8, 6, 7,	9, 8, 1
+];
+
+export default function Icosahedron(radius: number, detail: number) {
+    return Polyhedron(vertices, indices, radius, detail)
+}

+ 94 - 0
src/mol-geo/primitive/polyhedron.ts

@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// adapted from three.js, MIT License Copyright 2010-2018 three.js authors
+
+import { Vec3 } from 'mol-math/linear-algebra'
+import { computeVertexNormals, appplyRadius } from '../util'
+
+export default function Polyhedron(_vertices: Helpers.NumberArray, _indices: Helpers.NumberArray, radius: number, detail: number) {
+    radius = radius || 1;
+    detail = detail || 0;
+
+    const vertices: number[] = [];
+    const indices: number[] = [];
+
+    // the subdivision creates the vertex buffer data
+    subdivide(detail);
+
+    // all vertices should lie on a conceptual sphere with a given radius
+    appplyRadius(vertices, radius);
+
+    const normals = new Float32Array(vertices.length);
+    computeVertexNormals(vertices, normals)
+    // this.normalizeNormals(); // smooth normals
+
+    return { vertices, indices, normals }
+
+    // helper functions
+
+    function subdivide(detail: number) {
+        const a = Vec3.zero()
+        const b = Vec3.zero()
+        const c = Vec3.zero()
+
+        // iterate over all faces and apply a subdivison with the given detail value
+        for (let i = 0; i < _indices.length; i += 3) {
+            // get the vertices of the face
+            Vec3.fromArray(a, _vertices, _indices[ i + 0 ] * 3)
+            Vec3.fromArray(b, _vertices, _indices[ i + 1 ] * 3)
+            Vec3.fromArray(c, _vertices, _indices[ i + 2 ] * 3)
+
+            // perform subdivision
+            subdivideFace(a, b, c, detail)
+        }
+
+    }
+
+    function subdivideFace(a: Vec3, b: Vec3, c: Vec3, detail: number) {
+        const cols = Math.pow(2, detail)
+
+        // we use this multidimensional array as a data structure for creating the subdivision
+        const v: Vec3[][] = []
+
+        // construct all of the vertices for this subdivision
+        for (let i = 0; i <= cols; ++i) {
+            v[i] = []
+
+            const aj = Vec3.zero()
+            Vec3.lerp(aj, a, c, i / cols)
+
+            const bj = Vec3.zero()
+            Vec3.lerp(bj, b, c, i / cols)
+
+            const rows = cols - i
+            for (let j = 0; j <= rows; ++j) {
+                if (j === 0 && i === cols) {
+                    v[i][j] = aj
+                } else {
+                    const abj = Vec3.zero()
+                    Vec3.lerp(abj, aj, bj, j / rows)
+
+                    v[i][j] = abj
+                }
+            }
+        }
+
+        // construct all of the faces
+        for (let i = 0; i < cols; ++i) {
+            for (let j = 0; j < 2 * (cols - i) - 1; ++j) {
+                const k = Math.floor(j / 2)
+                if (j % 2 === 0) {
+                    vertices.push(...v[i][k + 1], ...v[i + 1][k], ...v[i][k])
+                } else {
+                    vertices.push(...v[i][k + 1], ...v[i + 1][k + 1], ...v[i + 1][k])
+                }
+            }
+        }
+
+        console.log(v)
+    }
+}

+ 0 - 0
src/mol-geo/representation/structure/spacefill.ts


+ 113 - 0
src/mol-geo/util.ts

@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from 'mol-math/linear-algebra'
+
+export function normalizeVec3array<T extends Helpers.NumberArray> (a: T) {
+    const n = a.length
+    for (let i = 0; i < n; i += 3) {
+        const x = a[ i ]
+        const y = a[ i + 1 ]
+        const z = a[ i + 2 ]
+        const s = 1 / Math.sqrt(x * x + y * y + z * z)
+        a[ i ] = x * s
+        a[ i + 1 ] = y * s
+        a[ i + 2 ] = z * s
+    }
+}
+
+export function setArrayZero(array: Helpers.NumberArray) {
+    const n = array.length
+    for (let i = 0; i < n; ++i) array[i] = 0
+}
+
+// iterate over the entire buffer and apply the radius to each vertex
+export function appplyRadius(vertices: Helpers.NumberArray, radius: number) {
+    const v = Vec3.zero()
+    const n = vertices.length
+    for (let i = 0; i < n; i += 3) {
+        Vec3.fromArray(v, vertices, i)
+        Vec3.normalize(v, v)
+        Vec3.scale(v, v, radius)
+        Vec3.toArray(v, vertices, i)
+    }
+}
+
+// indexed vertex normals weighted by triangle areas http://www.iquilezles.org/www/articles/normals/normals.htm
+// normal array must contain only zeros
+export function computeIndexedVertexNormals<T extends Helpers.NumberArray> (vertices: Helpers.NumberArray, indices: Helpers.NumberArray, normals: T) {
+    const a = Vec3.zero()
+    const b = Vec3.zero()
+    const c = Vec3.zero()
+    const cb = Vec3.zero()
+    const ab = Vec3.zero()
+
+    for (let i = 0, il = indices.length; i < il; i += 3) {
+        const ai = indices[ i ] * 3
+        const bi = indices[ i + 1 ] * 3
+        const ci = indices[ i + 2 ] * 3
+
+        Vec3.fromArray(a, vertices, ai)
+        Vec3.fromArray(b, vertices, bi)
+        Vec3.fromArray(c, vertices, ci)
+
+        Vec3.sub(cb, c, b)
+        Vec3.sub(ab, a, b)
+        Vec3.cross(cb, cb, ab)
+
+        normals[ ai ] += cb[ 0 ]
+        normals[ ai + 1 ] += cb[ 1 ]
+        normals[ ai + 2 ] += cb[ 2 ]
+
+        normals[ bi ] += cb[ 0 ]
+        normals[ bi + 1 ] += cb[ 1 ]
+        normals[ bi + 2 ] += cb[ 2 ]
+
+        normals[ ci ] += cb[ 0 ]
+        normals[ ci + 1 ] += cb[ 1 ]
+        normals[ ci + 2 ] += cb[ 2 ]
+    }
+
+    normalizeVec3array(normals)
+    return normals
+}
+
+// vertex normals for unindexed triangle soup
+// normal array must contain only zeros
+export function computeVertexNormals<T extends Helpers.NumberArray> (vertices: Helpers.NumberArray, normals: T) {
+    setArrayZero(normals)
+
+    const a = Vec3.zero()
+    const b = Vec3.zero()
+    const c = Vec3.zero()
+    const cb = Vec3.zero()
+    const ab = Vec3.zero()
+
+     for (let i = 0, il = vertices.length; i < il; i += 9) {
+        Vec3.fromArray(a, vertices, i)
+        Vec3.fromArray(b, vertices, i + 3)
+        Vec3.fromArray(c, vertices, i + 6)
+
+        Vec3.sub(cb, c, b)
+        Vec3.sub(ab, a, b)
+        Vec3.cross(cb, cb, ab)
+
+        normals[ i ] = cb[ 0 ]
+        normals[ i + 1 ] = cb[ 1 ]
+        normals[ i + 2 ] = cb[ 2 ]
+
+        normals[ i + 3 ] = cb[ 0 ]
+        normals[ i + 4 ] = cb[ 1 ]
+        normals[ i + 5 ] = cb[ 2 ]
+
+        normals[ i + 6 ] = cb[ 0 ]
+        normals[ i + 7 ] = cb[ 1 ]
+        normals[ i + 8 ] = cb[ 2 ]
+    }
+
+    normalizeVec3array(normals)
+    return normals
+}

+ 10 - 7
src/mol-gl/renderable/mesh.ts

@@ -13,11 +13,6 @@ import { MeshShaders } from '../shaders'
 
 type Mesh = 'mesh'
 
-// TODO
-interface Elements {
-
-}
-
 type Uniforms = { [k: string]: REGL.Uniform | REGL.Texture }
 
 export function fillSerial<T extends Helpers.NumberArray> (array: T) {
@@ -29,6 +24,7 @@ export function fillSerial<T extends Helpers.NumberArray> (array: T) {
 namespace Mesh {
     export type DataType = {
         position: { type: Float32Array, itemSize: 3 }
+        normal: { type: Float32Array, itemSize: 3 }
         transformColumn0: { type: Float32Array, itemSize: 4 }
         transformColumn1: { type: Float32Array, itemSize: 4 }
         transformColumn2: { type: Float32Array, itemSize: 4 }
@@ -37,7 +33,7 @@ namespace Mesh {
     export type Data = { [K in keyof DataType]: DataType[K]['type'] }
     export type Attributes = { [K in keyof Data]: Attribute<Data[K]> }
 
-    export function create(regl: REGL.Regl, attributes: Attributes, uniforms: Uniforms, elements?: Elements): Renderable<Data> {
+    export function create(regl: REGL.Regl, attributes: Attributes, uniforms: Uniforms, elements?: Helpers.UintArray): Renderable<Data> {
         console.log('mesh', {
             count: attributes.position.getCount(),
             instances: attributes.transformColumn0.getCount(),
@@ -58,7 +54,14 @@ namespace Mesh {
                 instanceId: Attribute.create(regl, instanceId, { size: 1, divisor: 1 }),
                 ...attributes
             }),
-            count: attributes.position.getCount(),
+            elements: elements && regl.elements({
+                data: new Uint16Array(elements),
+                primitive: 'triangles',
+                // type: 'uint16',
+                // count: elements.length / 3,
+                // length: elements.length * 2
+            }),
+            count: elements ? elements.length : attributes.position.getCount(),
             instances: instanceCount,
             primitive: 'triangles'
         })

+ 14 - 0
src/mol-gl/shader/attenuation.glsl

@@ -0,0 +1,14 @@
+// by Tom Madams
+// Simple:
+// https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
+//
+// Improved
+// https://imdoingitwrong.wordpress.com/2011/02/10/improved-light-attenuation/
+float attenuation(float r, float f, float d) {
+    float denom = d / r + 1.0;
+    float attenuation = 1.0 / (denom*denom);
+    float t = (attenuation - f) / (1.0 - f);
+    return max(t, 0.0);
+}
+
+#pragma glslify: export(attenuation)

+ 70 - 0
src/mol-gl/shader/inverse.glsl

@@ -0,0 +1,70 @@
+// (c) 2014 Mikola Lysenko. MIT License
+// https://github.com/glslify/glsl-inverse
+
+float inverse(float m) {
+  return 1.0 / m;
+}
+
+mat2 inverse(mat2 m) {
+  return mat2(m[1][1],-m[0][1],
+             -m[1][0], m[0][0]) / (m[0][0]*m[1][1] - m[0][1]*m[1][0]);
+}
+
+mat3 inverse(mat3 m) {
+  float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];
+  float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];
+  float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];
+
+  float b01 = a22 * a11 - a12 * a21;
+  float b11 = -a22 * a10 + a12 * a20;
+  float b21 = a21 * a10 - a11 * a20;
+
+  float det = a00 * b01 + a01 * b11 + a02 * b21;
+
+  return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11),
+              b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10),
+              b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det;
+}
+
+mat4 inverse(mat4 m) {
+  float
+      a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
+      a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],
+      a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3],
+      a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3],
+
+      b00 = a00 * a11 - a01 * a10,
+      b01 = a00 * a12 - a02 * a10,
+      b02 = a00 * a13 - a03 * a10,
+      b03 = a01 * a12 - a02 * a11,
+      b04 = a01 * a13 - a03 * a11,
+      b05 = a02 * a13 - a03 * a12,
+      b06 = a20 * a31 - a21 * a30,
+      b07 = a20 * a32 - a22 * a30,
+      b08 = a20 * a33 - a23 * a30,
+      b09 = a21 * a32 - a22 * a31,
+      b10 = a21 * a33 - a23 * a31,
+      b11 = a22 * a33 - a23 * a32,
+
+      det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
+
+  return mat4(
+      a11 * b11 - a12 * b10 + a13 * b09,
+      a02 * b10 - a01 * b11 - a03 * b09,
+      a31 * b05 - a32 * b04 + a33 * b03,
+      a22 * b04 - a21 * b05 - a23 * b03,
+      a12 * b08 - a10 * b11 - a13 * b07,
+      a00 * b11 - a02 * b08 + a03 * b07,
+      a32 * b02 - a30 * b05 - a33 * b01,
+      a20 * b05 - a22 * b02 + a23 * b01,
+      a10 * b10 - a11 * b08 + a13 * b06,
+      a01 * b08 - a00 * b10 - a03 * b06,
+      a30 * b04 - a31 * b02 + a33 * b00,
+      a21 * b02 - a20 * b04 - a23 * b00,
+      a11 * b07 - a10 * b09 - a12 * b06,
+      a00 * b09 - a01 * b07 + a02 * b06,
+      a31 * b01 - a30 * b03 - a32 * b00,
+      a20 * b03 - a21 * b01 + a22 * b00) / det;
+}
+
+#pragma glslify: export(inverse)

+ 65 - 3
src/mol-gl/shader/mesh.frag

@@ -6,8 +6,70 @@
 
 precision mediump float;
 
-varying vec3 vColor;
+struct Light {
+    vec3 position;
+    vec3 color;
+    vec3 ambient;
+    float falloff;
+    float radius;
+};
 
-void main(){
-    gl_FragColor = vec4(vColor, 1);
+uniform Light light;
+uniform mat4 view;
+
+varying vec3 vNormal, vViewPosition, vColor;
+
+float phongSpecular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float shininess) {
+    //Calculate Phong power
+    vec3 R = -reflect(lightDirection, surfaceNormal);
+    return pow(max(0.0, dot(viewDirection, R)), shininess);
+}
+
+#define PI 3.14159265
+
+float orenNayarDiffuse(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float roughness, float albedo) {
+    float LdotV = dot(lightDirection, viewDirection);
+    float NdotL = dot(lightDirection, surfaceNormal);
+    float NdotV = dot(surfaceNormal, viewDirection);
+
+    float s = LdotV - NdotL * NdotV;
+    float t = mix(1.0, max(NdotL, NdotV), step(0.0, s));
+
+    float sigma2 = roughness * roughness;
+    float A = 1.0 + sigma2 * (albedo / (sigma2 + 0.13) + 0.5 / (sigma2 + 0.33));
+    float B = 0.45 * sigma2 / (sigma2 + 0.09);
+
+    return albedo * max(0.0, NdotL) * (A + B * s / t) / PI;
+}
+
+#pragma glslify: attenuation = require(./attenuation.glsl)
+
+const float specularScale = 0.65;
+const float shininess = 10.0;
+const float roughness = 5.0;
+const float albedo = 0.95;
+
+void main() {
+    // determine surface to light direction
+    vec4 lightPosition = view * vec4(light.position, 1.0);
+    vec3 lightVector = lightPosition.xyz - vViewPosition;
+
+    // calculate attenuation
+    float lightDistance = length(lightVector);
+    float falloff = 1.0; // attenuation(light.radius, light.falloff, lightDistance);
+
+    vec3 L = normalize(lightVector); // light direction
+    vec3 V = normalize(vViewPosition); // eye direction
+    vec3 N = normalize(vNormal); // surface normal
+
+    // compute our diffuse & specular terms
+    float specular = phongSpecular(L, V, N, shininess) * specularScale * falloff;
+    vec3 diffuse = light.color * orenNayarDiffuse(L, V, N, roughness, albedo) * falloff;
+    vec3 ambient = light.ambient;
+
+    // add the lighting
+    vec3 color = vColor * (diffuse + ambient) + specular;
+
+    gl_FragColor.rgb = N;
+    gl_FragColor.a = 1.0;
 }

+ 15 - 2
src/mol-gl/shader/mesh.vert

@@ -21,16 +21,20 @@ uniform int instanceCount;
 #endif
 
 attribute vec3 position;
+attribute vec3 normal;
 attribute vec4 transformColumn0, transformColumn1, transformColumn2, transformColumn3;
 attribute float instanceId;
 // attribute int elementId;
 
 varying vec3 vColor;
+varying vec3 vNormal;
+varying vec3 vViewPosition;
 
+#pragma glslify: inverse = require(./inverse.glsl)
 #pragma glslify: read_vec3 = require(./read-vec3.glsl)
+#pragma glslify: transpose = require(./transpose.glsl)
 
 void main(){
-    mat4 transform = mat4(transformColumn0, transformColumn1, transformColumn2, transformColumn3);
     #if defined( ATTRIBUTE_COLOR )
         vColor = color;
     #elif defined( INSTANCE_COLOR )
@@ -41,5 +45,14 @@ void main(){
         vColor = vec3(0.0, 1.0, 0.0);
     #endif
 
-    gl_Position = projection * view * model * transform * vec4(position, 1.0);
+    mat4 transform = mat4(transformColumn0, transformColumn1, transformColumn2, transformColumn3);
+    mat4 modelView = view * model * transform;
+
+    vec4 mvPosition = modelView * vec4(position, 1.0);
+    vViewPosition = mvPosition.xyz;
+    gl_Position = projection * mvPosition;
+
+    // TODO do on CPU side
+    mat3 normalMatrix = transpose(inverse(mat3(modelView)));
+    vNormal = normalize(normalMatrix * normal);
 }

+ 26 - 0
src/mol-gl/shader/transpose.glsl

@@ -0,0 +1,26 @@
+// (c) 2014 Mikola Lysenko. MIT License
+// https://github.com/glslify/glsl-transpose
+
+float transpose(float m) {
+  return m;
+}
+
+mat2 transpose(mat2 m) {
+  return mat2(m[0][0], m[1][0],
+              m[0][1], m[1][1]);
+}
+
+mat3 transpose(mat3 m) {
+  return mat3(m[0][0], m[1][0], m[2][0],
+              m[0][1], m[1][1], m[2][1],
+              m[0][2], m[1][2], m[2][2]);
+}
+
+mat4 transpose(mat4 m) {
+  return mat4(m[0][0], m[1][0], m[2][0], m[3][0],
+              m[0][1], m[1][1], m[2][1], m[3][1],
+              m[0][2], m[1][2], m[2][2], m[3][2],
+              m[0][3], m[1][3], m[2][3], m[3][3]);
+}
+
+#pragma glslify: export(transpose)

+ 37 - 6
src/mol-math/linear-algebra/3d.ts

@@ -17,11 +17,11 @@
  * furnished to do so, subject to the following conditions:
  */
 
-export interface Mat4 { [d: number]: number, '@type': 'mat4' }
-export interface Mat3 { [d: number]: number, '@type': 'mat3' }
-export interface Vec3 { [d: number]: number, '@type': 'vec3' | 'vec4' }
-export interface Vec4 { [d: number]: number, '@type': 'vec4' }
-export interface Quat { [d: number]: number, '@type': 'quat' }
+export interface Mat4 extends Array<number> { [d: number]: number, '@type': 'mat4', length: 16 }
+export interface Mat3 extends Array<number> { [d: number]: number, '@type': 'mat3', length: 9 }
+export interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 }
+export interface Vec4 extends Array<number> { [d: number]: number, '@type': 'vec4', length: 4 }
+export interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 }
 
 const enum EPSILON { Value = 0.000001 }
 
@@ -112,7 +112,7 @@ export namespace Mat4 {
         a[4 * j + i] = value;
     }
 
-    export function toArray(a: Mat4, out: number[]|Helpers.TypedArray, offset = 0) {
+    export function toArray(a: Mat4, out: Helpers.NumberArray, offset: number) {
         out[offset + 0] = a[0];
         out[offset + 1] = a[1];
         out[offset + 2] = a[2];
@@ -131,6 +131,25 @@ export namespace Mat4 {
         out[offset + 15] = a[15];
     }
 
+    export function fromArray(a: Mat4, array: Helpers.NumberArray, offset: number) {
+        a[0] = array[offset + 0]
+        a[1] = array[offset + 1]
+        a[2] = array[offset + 2]
+        a[3] = array[offset + 3]
+        a[4] = array[offset + 4]
+        a[5] = array[offset + 5]
+        a[6] = array[offset + 6]
+        a[7] = array[offset + 7]
+        a[8] = array[offset + 8]
+        a[9] = array[offset + 9]
+        a[10] = array[offset + 10]
+        a[11] = array[offset + 11]
+        a[12] = array[offset + 12]
+        a[13] = array[offset + 13]
+        a[14] = array[offset + 14]
+        a[15] = array[offset + 15]
+    }
+
     export function copy(out: Mat4, a: Mat4) {
         out[0] = a[0];
         out[1] = a[1];
@@ -760,6 +779,18 @@ export namespace Vec3 {
         return { x: v[0], y: v[1], z: v[2] };
     }
 
+    export function fromArray(v: Vec3, array: Helpers.NumberArray, offset: number) {
+        v[0] = array[offset + 0]
+        v[1] = array[offset + 1]
+        v[2] = array[offset + 2]
+    }
+
+    export function toArray(v: Vec3, out: Helpers.NumberArray, offset: number) {
+        out[offset + 0] = v[0]
+        out[offset + 1] = v[1]
+        out[offset + 2] = v[2]
+    }
+
     export function create(x: number, y: number, z: number): Vec3 {
         const out = zero();
         out[0] = x;