diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts index a1c108c6e33a8777fdc5023c877bc7f31e52b0d9..982a0284b71e9f41f0606af4fe00d8e287d6ba7a 100644 --- a/src/apps/render-test/state.ts +++ b/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) }) diff --git a/src/helpers.d.ts b/src/helpers.d.ts index 6eb695ff0c7604c08f557db098ffcfa7eb96a1bb..27cbb9bcb166ddccae443ba4b17ef8b1880b55c3 100644 --- a/src/helpers.d.ts +++ b/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[] } \ No newline at end of file diff --git a/src/mol-geo/primitive/box.ts b/src/mol-geo/primitive/box.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2ec6c9a4831e052ea208d5693b2aff9d71071bb --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/src/mol-geo/primitive/icosahedron.ts b/src/mol-geo/primitive/icosahedron.ts new file mode 100644 index 0000000000000000000000000000000000000000..b57f0a0d9b72b2a5df477553d32e3565b07337b8 --- /dev/null +++ b/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) +} \ No newline at end of file diff --git a/src/mol-geo/primitive/polyhedron.ts b/src/mol-geo/primitive/polyhedron.ts new file mode 100644 index 0000000000000000000000000000000000000000..11c2f9798541386cc755b2acba987b684901704c --- /dev/null +++ b/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) + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/mol-geo/util.ts b/src/mol-geo/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..f87508ebb84e0951d92e2392074661f90b03638e --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index 92f209c0c63968109484382699ee36ac8ab32f4d..e0caa9da7a40c50fe5543db7585885ca2ad67c6d 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/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' }) diff --git a/src/mol-gl/shader/attenuation.glsl b/src/mol-gl/shader/attenuation.glsl new file mode 100644 index 0000000000000000000000000000000000000000..fdc3f4c78c8c86cbc68b8e0b2d9ef109695dd129 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/src/mol-gl/shader/inverse.glsl b/src/mol-gl/shader/inverse.glsl new file mode 100644 index 0000000000000000000000000000000000000000..a8bb99b4b68c4932f76dbc79f3eb31502c581f70 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index 1e94b10f5cb3a8ebdfd39b1a807f33351d54ead9..4a224d852dcf63e0c5f033f95b6d9997b8c0c87f 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/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; } \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert index c240d9638f606026553e5dcffb81b6b087f7257d..3c760d523329804a185ff3a0af346911afbed687 100644 --- a/src/mol-gl/shader/mesh.vert +++ b/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); } \ No newline at end of file diff --git a/src/mol-gl/shader/transpose.glsl b/src/mol-gl/shader/transpose.glsl new file mode 100644 index 0000000000000000000000000000000000000000..e14ea7e32879b7d8ebc065c961f0f7804c5746f3 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/src/mol-math/linear-algebra/3d.ts b/src/mol-math/linear-algebra/3d.ts index 6749c74280de6f46492bf18163e21353ce489ee4..727ef49ee3d61f75372de38bd44c84aafee1066c 100644 --- a/src/mol-math/linear-algebra/3d.ts +++ b/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;