├── .gitignore ├── LICENSE.md ├── README.md ├── circle.yml ├── demo.js ├── example.js ├── frag.js ├── include-precision.js ├── index.html ├── index.js ├── package.json ├── remove-attributes.js ├── remove-unused.js ├── test ├── aastep.js ├── frag.js ├── index.js └── vert.js └── vert.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | demo-bundle.js 4 | *-bundle.js 5 | bundle.js 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | * Copyright © 2016 [Hugh Kennedy](https://github.com/hughsk/) 5 | * Copyright © 2016 [kindred](https://github.com/kindredjs/) contributors 6 | 7 | *kindred contributors listed at * 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 25 | IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kindred-shader-formatter 2 | 3 | [![](https://img.shields.io/badge/stability-experimental-ffa100.svg?style=flat-square)](https://nodejs.org/api/documentation.html#documentation_stability_index) 4 | [![](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/) 5 | [![](https://img.shields.io/npm/v/kindred-shader-formatter.svg?style=flat-square)](https://npmjs.com/package/kindred-shader-formatter) 6 | [![](https://img.shields.io/circleci/project/kindredjs/kindred-shader-formatter/master.svg?style=flat-square)](https://circleci.com/gh/kindredjs/kindred-shader-formatter) 7 | 8 | Simplify authoring GLSL shaders and reduce boilerplate with a few helpful adjustments. 9 | 10 | ## Usage 11 | 12 | For example, here's a kindred-style shader: 13 | 14 | ``` glsl 15 | attribute vec3 position; 16 | attribute vec3 normal; 17 | 18 | varying vec3 vNormal; 19 | 20 | void vert() { 21 | vNormal = normal; 22 | gl_Position = vec4(position, 1); 23 | } 24 | 25 | void frag() { 26 | gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1); 27 | } 28 | ``` 29 | 30 | Both the vertex and fragment shaders are written together — removing the need to keep a duplicate list of uniforms/varyings/attributes in each file. You can pass this shader into `kindred-shader-formatter` to get two separate fragment and vertex shaders to pass into your WebGL library of choice. 31 | 32 | ``` javascript 33 | var format = require('kindred-shader-formatter') 34 | var glslify = require('glslify') 35 | 36 | var formatted = format(glslify('./shader.glsl')) 37 | 38 | console.log(formatted.vert) 39 | console.log(formatted.frag) 40 | ``` 41 | 42 | This leaves you with two shaders like the following: 43 | 44 | ``` glsl 45 | precision highp float; 46 | 47 | attribute vec3 position; 48 | attribute vec3 normal; 49 | 50 | varying vec3 vNormal; 51 | 52 | void vert() { 53 | vNormal = normal; 54 | gl_Position = vec4(position, 1); 55 | } 56 | 57 | void main() { vert(); } 58 | ``` 59 | ``` glsl 60 | precision highp float; 61 | 62 | varying vec3 vNormal; 63 | 64 | void frag() { 65 | gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1); 66 | } 67 | 68 | void main() { frag(); } 69 | ``` 70 | 71 | Under the hood, we're making a few changes to your shaders: 72 | 73 | * Vertex and fragment shaders are written as a single shader, using `void vert()` and `void frag()` respectively in place of `void main()`. 74 | * Attribute declarations are removed from fragment shaders. 75 | * `precision highp float;` is automatically added to your shaders if not otherwise specified. 76 | * Unused functions are automatically removed using [glsl-token-function-shaker](https://github.com/stackgl/glsl-token-function-shaker). 77 | 78 | ## API 79 | 80 | ### `format(source)` 81 | 82 | Formats a combined shader source, returning an object with the vertex and fragment shaders as strings in `vert` and `frag` respectively. 83 | 84 | ``` javascript 85 | const format = require('kindred-shader-formatter') 86 | const Shader = require('gl-shader') 87 | 88 | const src = format(` 89 | // GLSL shader goes here 90 | `) 91 | 92 | const shader = Shader(gl, src.vert, src.frag) 93 | ``` 94 | 95 | ### `format.vert(source)` 96 | 97 | Extracts and returns only the vertex shader from `source`. 98 | 99 | ### `format.frag(source)` 100 | 101 | Extracts and returns only the fragment shader from `source`. 102 | 103 | ## License 104 | 105 | MIT. See [LICENSE.md](LICENSE.md) for details. 106 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: '4' 4 | test: 5 | pre: 6 | - npm rebuild 7 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | var Editor = require('glsl-editor') 2 | var format = require('./') 3 | 4 | var outFrag = Editor({ container: document.querySelector('#out-frag'), readOnly: true }) 5 | var outVert = Editor({ container: document.querySelector('#out-vert'), readOnly: true }) 6 | var source = Editor({ 7 | container: document.querySelector('#source'), 8 | value: ` 9 | 10 | attribute vec2 position; 11 | uniform float time; 12 | varying vec2 uv; 13 | 14 | void vert() { 15 | uv = position * 0.5 + 0.5; 16 | gl_Position = vec4(position, 1, 1); 17 | } 18 | 19 | void frag() { 20 | gl_FragColor.rg = uv; 21 | gl_FragColor.b = sin(time) * 0.5 + 0.5; 22 | gl_FragColor.a = 1.0; 23 | } 24 | `.trim() 25 | }) 26 | 27 | setTimeout(function () { 28 | update() 29 | source.editor.on('change', update) 30 | }) 31 | 32 | require('glsl-editor/css') 33 | require('glsl-editor/theme') 34 | window.addEventListener('resize', function () { 35 | source.resize() 36 | outFrag.resize() 37 | outVert.resize() 38 | }, false) 39 | 40 | function update () { 41 | var value = source.getValue() 42 | var result = format(value) 43 | 44 | outFrag.setValue(result.frag) 45 | outVert.setValue(result.vert) 46 | } 47 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var canvas = document.body.appendChild(document.createElement('canvas')) 2 | var gl = canvas.getContext('webgl') 3 | 4 | var triangle = require('a-big-triangle') 5 | var Shader = require('gl-shader') 6 | var Fit = require('canvas-fit') 7 | var sh = require('./') 8 | 9 | var start = Date.now() 10 | var source = sh` 11 | attribute vec2 position; 12 | uniform float time; 13 | varying vec2 uv; 14 | 15 | void vert() { 16 | gl_Position = vec4(uv = position, 1, 1); 17 | } 18 | 19 | vec2 pow2(vec2 a, float b) { 20 | return vec2(pow(a.x, b), pow(a.y, b)); 21 | } 22 | 23 | void frag() { 24 | gl_FragColor = vec4(1.0 - pow2(abs(uv), 5.0), sin(time) * 0.5 + 0.5, 1); 25 | } 26 | ` 27 | 28 | var shader = Shader(gl, source.vert, source.frag) 29 | 30 | render() 31 | function render () { 32 | window.requestAnimationFrame(render) 33 | 34 | var width = canvas.width 35 | var height = canvas.height 36 | 37 | gl.viewport(0, 0, width, height) 38 | shader.bind() 39 | shader.uniforms.time = (Date.now() - start) / 1000 40 | 41 | triangle(gl) 42 | } 43 | 44 | window.addEventListener('resize', Fit(canvas), false) 45 | -------------------------------------------------------------------------------- /frag.js: -------------------------------------------------------------------------------- 1 | var removeAttributes = require('./remove-attributes') 2 | var includePrecision = require('./include-precision') 3 | var removeUnused = require('./remove-unused') 4 | 5 | module.exports = function kindredFragmentShaderFormat (src, params) { 6 | src = includePrecision(src) 7 | src += '\nvoid main () { frag(); }' 8 | src = removeAttributes(src) 9 | src = removeUnused(src, { keep: 'frag' }) 10 | return src 11 | } 12 | -------------------------------------------------------------------------------- /include-precision.js: -------------------------------------------------------------------------------- 1 | var tokenize = require('glsl-tokenizer') 2 | 3 | module.exports = includePrecison 4 | 5 | function includePrecison (src) { 6 | var tokens = tokenize(src) 7 | var needsPrecision = true 8 | 9 | for (var i = 0; i < tokens.length; i++) { 10 | if (tokens[i].type !== 'keyword') continue 11 | if (tokens[i].data !== 'precision') continue 12 | needsPrecision = false 13 | break 14 | } 15 | 16 | return needsPrecision 17 | ? 'precision highp float;\n' + src 18 | : src 19 | } 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | kindred-shader-formatter 6 | 63 | 64 | 65 |
66 |
67 |

Kindred Shader Input

68 |
69 |
70 |

Output Vertex Shader

71 |
72 |
73 |

Output Fragment Shader

74 |
75 |
76 | 77 | 78 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var convertVert = require('./vert') 2 | var convertFrag = require('./frag') 3 | 4 | module.exports = format 5 | module.exports.vert = convertVert 6 | module.exports.frag = convertFrag 7 | 8 | function format (src) { 9 | return { 10 | vert: convertVert(src), 11 | frag: convertFrag(src) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kindred-shader-formatter", 3 | "version": "1.0.1", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/kindredjs/kindred-shader-formatter" 8 | }, 9 | "scripts": { 10 | "test": "node test | tspec", 11 | "posttest": "standard", 12 | "bundle": "browserify demo.js -o demo-bundle.js", 13 | "start": "budo demo.js:demo-bundle.js" 14 | }, 15 | "dependencies": { 16 | "glsl-token-function-shaker": "^1.0.0", 17 | "glsl-token-string": "^1.0.1", 18 | "glsl-tokenizer": "^2.1.2" 19 | }, 20 | "devDependencies": { 21 | "browserify": "^13.0.1", 22 | "budo": "^8.3.0", 23 | "gl": "^4.0.2", 24 | "gl-shader": "^4.2.0", 25 | "glsl-aastep": "^1.0.1", 26 | "glsl-editor": "^1.0.0", 27 | "glslify": "^5.1.0", 28 | "standard": "^7.1.2", 29 | "tap-spec": "^4.1.1", 30 | "tape": "^4.6.0" 31 | }, 32 | "keywords": [ 33 | "ecosystem:kindred", 34 | "kindred", 35 | "shader", 36 | "format", 37 | "glsl", 38 | "combined" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /remove-attributes.js: -------------------------------------------------------------------------------- 1 | var stringify = require('glsl-token-string') 2 | var tokenize = require('glsl-tokenizer') 3 | 4 | module.exports = removeAttributes 5 | 6 | function removeAttributes (src) { 7 | var tokens = tokenize(src) 8 | var attOpen = null 9 | 10 | for (var i = 0; i < tokens.length; i++) { 11 | var token = tokens[i] 12 | 13 | if (attOpen === null) { 14 | if (token.type !== 'keyword') continue 15 | if (token.data !== 'attribute') continue 16 | attOpen = i 17 | } 18 | 19 | if (token.data !== ';') { 20 | token.data = '' 21 | continue 22 | } 23 | 24 | token.data = '' 25 | attOpen = null 26 | 27 | var next = tokens[i + 1] 28 | if (!next) break 29 | 30 | if (next.type === 'whitespace') { 31 | next.data = '' 32 | i++ 33 | } 34 | } 35 | 36 | return stringify(tokens) 37 | } 38 | -------------------------------------------------------------------------------- /remove-unused.js: -------------------------------------------------------------------------------- 1 | var shake = require('glsl-token-function-shaker') 2 | var stringify = require('glsl-token-string') 3 | var tokenize = require('glsl-tokenizer') 4 | 5 | module.exports = removeUnused 6 | 7 | function removeUnused (src, options) { 8 | var tokens = tokenize(src) 9 | 10 | shake(tokens, { ignore: options.keep }) 11 | 12 | return stringify(tokens) 13 | } 14 | -------------------------------------------------------------------------------- /test/aastep.js: -------------------------------------------------------------------------------- 1 | const Shader = require('gl-shader') 2 | const glslify = require('glslify') 3 | const format = require('../') 4 | const test = require('tape') 5 | const GL = require('gl') 6 | 7 | /** 8 | * GL_OES_standard_derivatives's dFx and dFy functions are unavailable 9 | * in vertex shaders. As such, including glsl-aastep would cause compliation 10 | * to fail. 11 | * 12 | * By employing tree shaking, we can safely remove aastep from the vertex 13 | * shader because it's unused there. 14 | */ 15 | const src = ` 16 | #extension GL_OES_standard_derivatives : enable 17 | 18 | attribute vec3 position; 19 | attribute vec3 normal; 20 | 21 | varying vec3 vNormal; 22 | 23 | #pragma glslify: aastep = require('glsl-aastep') 24 | 25 | void vert() { 26 | vNormal = normal; 27 | gl_Position = vec4(position, 1); 28 | } 29 | 30 | void frag() { 31 | float flag = aastep(vNormal.x, 0.0); 32 | gl_FragColor = vec4(vNormal * 0.5 + 0.5, flag); 33 | } 34 | `.trim() 35 | 36 | test('kindred-shader-formatter: aastep', function (t) { 37 | const gl = GL(256, 256) 38 | 39 | glslify.bundle(src, { inline: true }, function (err, result) { 40 | if (err) return t.ifError(err) 41 | 42 | var formatted = format(result) 43 | 44 | t.ok(formatted.vert.indexOf('aastep') === -1, 'vertex shader is excluding aastep') 45 | t.ok(formatted.frag.indexOf('aastep') !== -1, 'fragment shader contains aastep') 46 | t.doesNotThrow(function () { 47 | Shader(gl, formatted.vert, formatted.frag) 48 | }, 'shader compiles successfully') 49 | 50 | gl.destroy() 51 | 52 | t.end() 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/frag.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const frag = require('../frag') 3 | 4 | test('kindred-shader-formatter: frag', function (t) { 5 | const converted = frag(` 6 | attribute vec2 position; 7 | attribute vec2 uv; 8 | 9 | varying vec2 vUV; 10 | 11 | void vert() { 12 | vUV = uv; 13 | gl_Position = vec4(position, 0, 1); 14 | } 15 | 16 | void frag() { 17 | gl_FragColor = vec4(1, 0, 1, 1); 18 | } 19 | `) 20 | 21 | t.ok(converted.indexOf('vert') === -1, 'vert() was removed') 22 | t.ok(converted.indexOf('attribute') === -1, 'attributes were removed') 23 | t.ok(converted.indexOf('varying') !== -1, 'varyings were not removed') 24 | t.ok(converted.indexOf('main') !== -1, 'main() was added') 25 | t.ok(converted.indexOf('precision highp float') !== -1, 'precision was added') 26 | 27 | t.end() 28 | }) 29 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('./vert') 2 | require('./frag') 3 | require('./aastep') 4 | -------------------------------------------------------------------------------- /test/vert.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const vert = require('../vert') 3 | 4 | test('kindred-shader-formatter: vert', function (t) { 5 | const converted = vert(` 6 | void vert() { 7 | gl_Position = vec4(1, 0, 0, 1); 8 | } 9 | 10 | void frag() { 11 | gl_FragColor = vec4(1, 0, 1, 1); 12 | } 13 | `) 14 | 15 | t.ok(converted.indexOf('frag') === -1, 'frag() was removed') 16 | t.ok(converted.indexOf('main') !== -1, 'main() was added') 17 | t.ok(converted.indexOf('precision highp float') !== -1, 'precision was added') 18 | 19 | t.end() 20 | }) 21 | -------------------------------------------------------------------------------- /vert.js: -------------------------------------------------------------------------------- 1 | var includePrecision = require('./include-precision') 2 | var removeUnused = require('./remove-unused') 3 | 4 | module.exports = function kindredVertexShaderFormat (src, params) { 5 | src = includePrecision(src) 6 | src += '\nvoid main () { vert(); }' 7 | src = removeUnused(src, { keep: 'vert' }) 8 | return src 9 | } 10 | --------------------------------------------------------------------------------