├── .npmignore ├── docs └── images │ ├── cartesian.png │ └── barycentric.png ├── barycentric ├── unscaled.glsl └── scaled.glsl ├── examples ├── cartesian.js ├── barycentric.js └── demo.js ├── index.js ├── cartesian ├── unscaled.glsl └── scaled.glsl ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | test 3 | docs 4 | -------------------------------------------------------------------------------- /docs/images/cartesian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rreusser/glsl-solid-wireframe/HEAD/docs/images/cartesian.png -------------------------------------------------------------------------------- /docs/images/barycentric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rreusser/glsl-solid-wireframe/HEAD/docs/images/barycentric.png -------------------------------------------------------------------------------- /barycentric/unscaled.glsl: -------------------------------------------------------------------------------- 1 | float gridFactor (vec2 vBC, float width, float feather) { 2 | vec3 bary = vec3(vBC.x, vBC.y, 1.0 - vBC.x - vBC.y) * 3.0; 3 | vec3 a3 = smoothstep(vec3(width - feather), vec3(width + feather), bary); 4 | return min(min(a3.x, a3.y), a3.z); 5 | } 6 | 7 | float gridFactor (vec2 vBC, float width) { 8 | vec3 bary = vec3(vBC.x, vBC.y, 1.0 - vBC.x - vBC.y) * 3.0; 9 | vec3 a3 = smoothstep(vec3(width - 0.05), vec3(width + 0.05), bary); 10 | return min(min(a3.x, a3.y), a3.z); 11 | } 12 | 13 | #pragma glslify: export(gridFactor) 14 | -------------------------------------------------------------------------------- /barycentric/scaled.glsl: -------------------------------------------------------------------------------- 1 | float gridFactor (vec2 vBC, float width, float feather) { 2 | float w1 = width - feather * 0.5; 3 | vec3 bary = vec3(vBC.x, vBC.y, 1.0 - vBC.x - vBC.y); 4 | vec3 d = fwidth(bary); 5 | vec3 a3 = smoothstep(d * w1, d * (w1 + feather), bary); 6 | return min(min(a3.x, a3.y), a3.z); 7 | } 8 | 9 | float gridFactor (vec2 vBC, float width) { 10 | vec3 bary = vec3(vBC.x, vBC.y, 1.0 - vBC.x - vBC.y); 11 | vec3 d = fwidth(bary); 12 | vec3 a3 = smoothstep(d * (width - 0.5), d * (width + 0.5), bary); 13 | return min(min(a3.x, a3.y), a3.z); 14 | } 15 | 16 | #pragma glslify: export(gridFactor) 17 | -------------------------------------------------------------------------------- /examples/cartesian.js: -------------------------------------------------------------------------------- 1 | const regl = require('regl')({ 2 | extensions: ['oes_standard_derivatives'] 3 | }); 4 | const glsl = require('glslify'); 5 | document.querySelector('canvas').addEventListener('mousewheel', e => e.preventDefault()); 6 | const camera = require('regl-camera')(regl, {distance: 15, center: [0, 4, 0], theta: 1.9, phi: 0.3}); 7 | const mesh = require('bunny'); 8 | 9 | const draw = regl({ 10 | frag: glsl` 11 | #extension GL_OES_standard_derivatives : enable 12 | precision mediump float; 13 | #pragma glslify: grid = require(../cartesian/scaled) 14 | varying vec3 p; 15 | void main () { 16 | gl_FragColor = vec4(vec3(grid(p.xy * 5.0, 1.0)), 1); 17 | } 18 | `, 19 | vert: ` 20 | precision mediump float; 21 | uniform mat4 projection, view; 22 | attribute vec3 position; 23 | varying vec3 p; 24 | void main () { 25 | p = position; 26 | gl_Position = projection * view * vec4(position, 1); 27 | } 28 | `, 29 | attributes: { 30 | position: mesh.positions, 31 | }, 32 | elements: mesh.cells, 33 | }); 34 | 35 | regl.frame(function () { 36 | regl.clear({color: [1, 1, 1, 0], depth: 1}); 37 | camera(draw); 38 | }); 39 | -------------------------------------------------------------------------------- /examples/barycentric.js: -------------------------------------------------------------------------------- 1 | const regl = require('regl')({ 2 | extensions: ['oes_standard_derivatives'] 3 | }); 4 | const glsl = require('glslify'); 5 | document.querySelector('canvas').addEventListener('mousewheel', e => e.preventDefault()); 6 | const camera = require('regl-camera')(regl, {distance: 15, center: [0, 4, 0], theta: 1.9, phi: 0.3}); 7 | const mesh = require('../')(require('bunny')); 8 | 9 | const draw = regl({ 10 | frag: glsl` 11 | #extension GL_OES_standard_derivatives : enable 12 | precision mediump float; 13 | #pragma glslify: grid = require(../barycentric/scaled) 14 | varying vec2 b; 15 | void main () { 16 | gl_FragColor = vec4(vec3(grid(b, 1.0)), 1); 17 | } 18 | `, 19 | vert: ` 20 | precision mediump float; 21 | uniform mat4 projection, view; 22 | attribute vec3 position; 23 | attribute vec2 barycentric; 24 | varying vec2 b; 25 | void main () { 26 | b = barycentric; 27 | gl_Position = projection * view * vec4(position, 1); 28 | } 29 | `, 30 | attributes: { 31 | position: mesh.positions, 32 | barycentric: mesh.barycentric 33 | }, 34 | elements: mesh.cells, 35 | }); 36 | 37 | regl.frame(function () { 38 | regl.clear({color: [1, 1, 1, 0], depth: 1}); 39 | camera(draw); 40 | }); 41 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (mesh, opts) { 2 | if (!opts) opts = {}; 3 | var vars = opts.attributes ? {} : null; 4 | var vkeys = vars && Object.keys(opts.attributes) 5 | if (vars) { 6 | for (var k = 0; k < vkeys.length; k++) { 7 | vars[vkeys[k]] = [] 8 | } 9 | } 10 | 11 | var i, j; 12 | var pts = []; 13 | var cells = []; 14 | var barycentricAttrs = []; 15 | 16 | var mpts = mesh.positions; 17 | var mcells = mesh.cells; 18 | 19 | var c = 0; 20 | for (i = 0; i < mesh.cells.length; i++) { 21 | var cell = mcells[i]; 22 | if (cell.length === 3) { 23 | pts.push(mpts[cell[0]]); 24 | pts.push(mpts[cell[1]]); 25 | pts.push(mpts[cell[2]]); 26 | barycentricAttrs.push([0, 0]); 27 | barycentricAttrs.push([1, 0]); 28 | barycentricAttrs.push([0, 1]); 29 | cells.push(c++); 30 | cells.push(c++); 31 | cells.push(c++); 32 | if (vkeys) { 33 | for (j = 0; j < vkeys.length; j++) { 34 | var vkey = vkeys[j]; 35 | vars[vkey].push(opts.attributes[vkey][cell[0]]); 36 | vars[vkey].push(opts.attributes[vkey][cell[1]]); 37 | vars[vkey].push(opts.attributes[vkey][cell[2]]); 38 | } 39 | } 40 | } 41 | } 42 | 43 | var ret = { 44 | positions: pts, 45 | attributes: vars, 46 | barycentric: barycentricAttrs 47 | }; 48 | 49 | if (mesh.cells) { 50 | ret.cells = cells; 51 | } 52 | 53 | return ret; 54 | }; 55 | -------------------------------------------------------------------------------- /cartesian/unscaled.glsl: -------------------------------------------------------------------------------- 1 | float gridFactor (float parameter, float width, float feather) { 2 | float l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 3 | return smoothstep(width - feather, width + feather, l); 4 | } 5 | 6 | float gridFactor (vec2 parameter, float width, float feather) { 7 | vec2 l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 8 | vec2 a2 = smoothstep(width - feather, width + feather, l); 9 | return min(a2.x, a2.y); 10 | } 11 | 12 | float gridFactor (vec3 parameter, float width, float feather) { 13 | vec3 l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 14 | vec3 a3 = smoothstep(width - feather, width + feather, l); 15 | return min(min(a3.x, a3.y), a3.z); 16 | } 17 | 18 | float gridFactor (vec4 parameter, float width, float feather) { 19 | vec4 l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 20 | vec4 a4 = smoothstep(width - feather, width + feather, l); 21 | return min(min(min(a4.x, a4.y), a4.z), a4.w); 22 | } 23 | 24 | float gridFactor (float parameter, float width) { 25 | float l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 26 | return smoothstep(width - 0.05, width + 0.05, l); 27 | } 28 | 29 | float gridFactor (vec2 parameter, float width) { 30 | vec2 l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 31 | vec2 a2 = smoothstep(width - 0.05, width + 0.05, l); 32 | return min(a2.x, a2.y); 33 | } 34 | 35 | float gridFactor (vec3 parameter, float width) { 36 | vec3 l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 37 | vec3 a3 = smoothstep(width - 0.05, width + 0.05, l); 38 | return min(min(a3.x, a3.y), a3.z); 39 | } 40 | 41 | float gridFactor (vec4 parameter, float width) { 42 | vec4 l = 1.0 - 2.0 * abs(mod(parameter, 1.0) - 0.5); 43 | vec4 a4 = smoothstep(width - 0.05, width + 0.05, l); 44 | return min(min(min(a4.x, a4.y), a4.z), a4.w); 45 | } 46 | 47 | #pragma glslify: export(gridFactor) 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glsl-solid-wireframe", 3 | "version": "1.0.2", 4 | "description": "draw wireframes on a solid mesh using a fragment shader", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "angle-normals": "^1.0.0", 8 | "browserify": "^13.3.0", 9 | "budo": "^9.4.1", 10 | "bunny": "^1.0.1", 11 | "control-panel": "^1.2.0", 12 | "es2040": "^1.2.4", 13 | "github-cornerify": "^1.0.6", 14 | "glsl-diffuse-lambert": "^1.0.0", 15 | "glsl-noise": "0.0.0", 16 | "glsl-specular-blinn-phong": "^1.0.1", 17 | "h": "^0.1.0", 18 | "indexhtmlify": "^1.3.1", 19 | "metadataify": "^1.0.3", 20 | "regl": "^1.3.0", 21 | "regl-camera": "^1.1.0", 22 | "tape": "^4.6.0" 23 | }, 24 | "scripts": { 25 | "start": "budo --open --live --host localhost examples/fancy.js -- -t glslify -t es2040", 26 | "build:cartesian": "browserify examples/cartesian.js -t glslify -t es2040 | indexhtmlify | metadataify | github-cornerify > docs/cartesian.html", 27 | "build:barycentric": "browserify examples/barycentric.js -t glslify -t es2040 | indexhtmlify | metadataify | github-cornerify > docs/barycentric.html", 28 | "build:fancy": "browserify examples/fancy.js -t glslify -t es2040 | indexhtmlify | metadataify | github-cornerify > docs/fancy.html", 29 | "build:demo": "browserify examples/demo.js -t glslify -t es2040 | indexhtmlify | metadataify | github-cornerify > docs/demo.html", 30 | "test": "tape test/*.js" 31 | }, 32 | "keywords": [ 33 | "3d", 34 | "lines", 35 | "webgl", 36 | "glslify", 37 | "glsl", 38 | "simplicial complex", 39 | "wireframe", 40 | "mesh", 41 | "stackgl" 42 | ], 43 | "author": "Ricky Reusser", 44 | "license": "MIT", 45 | "directories": { 46 | "examples": "examples", 47 | "test": "test" 48 | }, 49 | "repository": { 50 | "type": "git", 51 | "url": "git+https://github.com/rreusser/glsl-solid-wireframe.git" 52 | }, 53 | "bugs": { 54 | "url": "https://github.com/rreusser/glsl-solid-wireframe/issues" 55 | }, 56 | "github-corner": { 57 | "fg": "#333", 58 | "bg": "#fff", 59 | "url": "https://github.com/rreusser/glsl-solid-wireframe" 60 | }, 61 | "homepage": "https://github.com/rreusser/glsl-solid-wireframe#readme" 62 | } 63 | -------------------------------------------------------------------------------- /cartesian/scaled.glsl: -------------------------------------------------------------------------------- 1 | float gridFactor (float parameter, float width, float feather) { 2 | float w1 = width - feather * 0.5; 3 | float d = fwidth(parameter); 4 | float looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 5 | return smoothstep(d * w1, d * (w1 + feather), looped); 6 | } 7 | 8 | float gridFactor (vec2 parameter, float width, float feather) { 9 | float w1 = width - feather * 0.5; 10 | vec2 d = fwidth(parameter); 11 | vec2 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 12 | vec2 a2 = smoothstep(d * w1, d * (w1 + feather), looped); 13 | return min(a2.x, a2.y); 14 | } 15 | 16 | float gridFactor (vec3 parameter, float width, float feather) { 17 | float w1 = width - feather * 0.5; 18 | vec3 d = fwidth(parameter); 19 | vec3 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 20 | vec3 a3 = smoothstep(d * w1, d * (w1 + feather), looped); 21 | return min(min(a3.x, a3.y), a3.z); 22 | } 23 | 24 | float gridFactor (vec4 parameter, float width, float feather) { 25 | float w1 = width - feather * 0.5; 26 | vec4 d = fwidth(parameter); 27 | vec4 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 28 | vec4 a4 = smoothstep(d * w1, d * (w1 + feather), looped); 29 | return min(min(min(a4.x, a4.y), a4.z), a4.w); 30 | } 31 | 32 | float gridFactor (float parameter, float width) { 33 | float d = fwidth(parameter); 34 | float looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 35 | return smoothstep(d * (width - 0.5), d * (width + 0.5), looped); 36 | } 37 | 38 | float gridFactor (vec2 parameter, float width) { 39 | vec2 d = fwidth(parameter); 40 | vec2 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 41 | vec2 a2 = smoothstep(d * (width - 0.5), d * (width + 0.5), looped); 42 | return min(a2.x, a2.y); 43 | } 44 | 45 | float gridFactor (vec3 parameter, float width) { 46 | vec3 d = fwidth(parameter); 47 | vec3 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 48 | vec3 a3 = smoothstep(d * (width - 0.5), d * (width + 0.5), looped); 49 | return min(min(a3.x, a3.y), a3.z); 50 | } 51 | 52 | float gridFactor (vec4 parameter, float width) { 53 | vec4 d = fwidth(parameter); 54 | vec4 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5); 55 | vec4 a4 = smoothstep(d * (width - 0.5), d * (width + 0.5), looped); 56 | return min(min(min(a4.x, a4.y), a4.z), a4.z); 57 | } 58 | 59 | #pragma glslify: export(gridFactor) 60 | -------------------------------------------------------------------------------- /examples/demo.js: -------------------------------------------------------------------------------- 1 | var glsl = require('glslify'); 2 | var wireframe = require('../'); 3 | var extend = require('xtend/mutable'); 4 | var bunny = require('bunny'); 5 | 6 | var regl = require('regl')({extensions: ['oes_standard_derivatives']}); 7 | var camera = require('regl-camera')(regl, { 8 | distance: 20, 9 | center: [0, 5, 0], 10 | theta: Math.PI * 0.5, 11 | phi: 0.0 12 | }); 13 | 14 | var params = { 15 | type: 'barycentric', 16 | standardDeriv: true, 17 | cartesianX: 1.0, 18 | cartesianY: 1.0, 19 | cartesianZ: 1.0, 20 | width: 1.0, 21 | feather: 0.5, 22 | }; 23 | 24 | var panel = require('h')('div'); 25 | document.querySelector('canvas').addEventListener('mousewheel', e => e.preventDefault()); 26 | document.body.appendChild(panel); 27 | panel.addEventListener('mousewheel', e => e.stopPropagation()); 28 | panel.addEventListener('mousemove', e => e.stopPropagation()); 29 | panel.addEventListener('mousedown', e => e.stopPropagation()); 30 | panel.addEventListener('mouseup', e => e.stopPropagation()); 31 | 32 | require('control-panel')([ 33 | {type: 'select', label: 'type', initial: params.type, options: ['barycentric', 'cartesian']}, 34 | {type: 'checkbox', label: 'standardDeriv', initial: params.standardDeriv}, 35 | {type: 'range', label: 'width', initial: params.width, min: 0.0, max: 5.0, step: 0.01}, 36 | {type: 'range', label: 'feather', initial: params.feather, min: 0.0, max: 5.0, step: 0.01}, 37 | {type: 'range', label: 'cartesianX', initial: params.cartesianX, min: 0.0, max: 5.0, step: 0.1}, 38 | {type: 'range', label: 'cartesianY', initial: params.cartesianY, min: 0.0, max: 5.0, step: 0.1}, 39 | {type: 'range', label: 'cartesianZ', initial: params.cartesianZ, min: 0.0, max: 5.0, step: 0.1}, 40 | ], {root: panel}).on('input', data => extend(params, data)); 41 | 42 | var draw = makeDrawCmd(regl) 43 | regl.frame(function () { 44 | regl.clear({color: [0.2, 0.2, 0.2, 1], depth: 1}); 45 | setParams(params, () => { 46 | camera(() => { 47 | draw[params.type][params.standardDeriv ? 'scaled' : 'unscaled'](); 48 | }); 49 | }); 50 | }) 51 | 52 | var setParams = regl({ 53 | uniforms: { 54 | cartesianX: (c, p) => p.cartesianX, 55 | cartesianY: (c, p) => p.cartesianY, 56 | cartesianZ: (c, p) => p.cartesianZ, 57 | width: (c, p) => p.width, 58 | feather: (c, p) => Math.max(p.feather, 1e-4), 59 | feather: (c, p) => p.feather, 60 | } 61 | }); 62 | 63 | function makeDrawCmd (regl) { 64 | var mesh = wireframe(bunny, { 65 | attributes: { 66 | positions: bunny.positions 67 | } 68 | }); 69 | 70 | function makeCmd(frag) { 71 | return regl({ 72 | frag: frag, 73 | vert: ` 74 | precision mediump float; 75 | uniform mat4 projection, view; 76 | attribute vec3 position; 77 | attribute vec2 barycentric; 78 | varying vec3 p; 79 | varying vec2 b; 80 | void main () { 81 | b = barycentric; 82 | p = position; 83 | gl_Position = projection * view * vec4(position, 1); 84 | } 85 | `, 86 | attributes: { 87 | position: mesh.positions, 88 | barycentric: mesh.barycentric 89 | }, 90 | elements: mesh.cells, 91 | }) 92 | } 93 | 94 | return { 95 | barycentric: { 96 | scaled: makeCmd(glsl` 97 | #extension GL_OES_standard_derivatives : enable 98 | precision mediump float; 99 | #pragma glslify: grid=require(../barycentric/scaled) 100 | varying vec2 b; 101 | uniform float width, feather; 102 | void main () { 103 | float g = grid(b, width, feather); 104 | gl_FragColor = vec4(mix(vec3(0), vec3(0.8), g), 1); 105 | } 106 | `), 107 | unscaled: makeCmd(glsl` 108 | precision mediump float; 109 | #pragma glslify: grid=require(../barycentric/unscaled) 110 | varying vec2 b; 111 | uniform float width, feather; 112 | void main () { 113 | float g = grid(b, width, feather); 114 | gl_FragColor = vec4(mix(vec3(0), vec3(0.8), g), 1); 115 | } 116 | `), 117 | }, 118 | cartesian: { 119 | scaled: makeCmd(glsl` 120 | #extension GL_OES_standard_derivatives : enable 121 | precision mediump float; 122 | #pragma glslify: grid=require(../cartesian/scaled) 123 | varying vec2 b; 124 | varying vec3 p; 125 | uniform float cartesianX, cartesianY, cartesianZ; 126 | uniform float width, feather; 127 | void main () { 128 | float g = grid( 129 | vec3( 130 | cartesianX > 0.0 ? p.x * cartesianX : 0.5, 131 | cartesianY > 0.0 ? p.y * cartesianY : 0.5, 132 | cartesianZ > 0.0 ? p.z * cartesianZ : 0.5 133 | ), width, feather); 134 | gl_FragColor = vec4(mix(vec3(0), vec3(0.8), g), 1); 135 | } 136 | `), 137 | unscaled: makeCmd(glsl` 138 | #extension GL_OES_standard_derivatives : enable 139 | precision mediump float; 140 | #pragma glslify: grid=require(../cartesian/unscaled) 141 | varying vec2 b; 142 | varying vec3 p; 143 | uniform float cartesianX, cartesianY, cartesianZ; 144 | uniform float width, feather; 145 | void main () { 146 | float g = grid( 147 | vec3( 148 | cartesianX > 0.0 ? p.x * cartesianX : 0.5, 149 | cartesianY > 0.0 ? p.y * cartesianY : 0.5, 150 | cartesianZ > 0.0 ? p.z * cartesianZ : 0.5 151 | ), width, feather); 152 | gl_FragColor = vec4(mix(vec3(0), vec3(0.8), g), 1); 153 | } 154 | `), 155 | } 156 | }; 157 | } 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glsl-solid-wireframe 2 | 3 | > draw wireframes on a triangular mesh using a fragment shader 4 | 5 | This module uses barycentric coordinates to draw a wireframe on a solid triangular mesh. Alternatively, it will simply draw grid lines at integer component values of a float- or vector-valued variable which you give it. 6 | 7 | You can see a detailed explanation of the technique [here](http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/). It uses [`OES_standard_derivatives`](https://www.khronos.org/registry/gles/extensions/OES/OES_standard_derivatives.txt) to scale the lines to a uniform width, but also exposes a basic fallback that scales lines relative to the size of the triangle in case `OES_standard_derivatives` is not available. (The fallback is pretty bad.) [Support for `OES_standard_derivatives` is 96% for mobile and 99% across the board](http://webglstats.com/webgl/extension/OES_standard_derivatives). 8 | 9 | Please ***do not*** confuse it with nVidia's [Solid Wireframe technique](http://developer.download.nvidia.com/SDK/10/direct3d/Source/SolidWireframe/Doc/SolidWireframe.pdf). That technique uses a geometry shader so will not be available in WebGL any time soon. The convenience function used here to generate barycentric coordinates produces a similar result but duplicates and expands the geometry to achieve it. It's real wasteful. Not perfect. 10 | 11 | ## Example 12 | 13 |
14 |
15 |
16 |
17 |
18 | Barycentric coordinate based mesh. See demo →
19 |
22 |
23 |
24 |
25 |
26 | Cartesian coordinate based mesh using 5 * position.xy as the input. See demo →
27 |