glslify

:sparkles: A node.js-style module system for GLSL!

  • Owner: glslify/glslify
  • Platform:
  • License:: Other
  • Category::
  • Topic:
  • Like:
    0
      Compare:

Github stars Tracking Chart

glslify stability

npm version downloads travis

A node.js-style module system for GLSL!

This module contains:

  • glslify's command-line interface (CLI)
  • glslify node/electron interface
  • browserify transform

It forms one of the core components of the stack.gl
ecosystem, allowing you to install GLSL modules from npm and
use them in your shaders. This makes it trivial to piece together different
effects and techniques from the community, including but certainly not limited
to
fog,
noise,
film grain,
raymarching helpers,
easing functions and
lighting models.

A full list can be found on the stack.gl packages list
under the "Shader Components" category.

Because glslify just outputs a single shader file as a string, it's easy to use
it with any WebGL framework of your choosing,
provided they accept custom shaders. Integration is planned for
three.js and
pex, with more on the way!
Open an issue here if you'd like to
discuss integrating glslify with your platform of choice.

If you're interested in playing around with glslify, you should check out
glslb.in: it's a fragment shader sandbox similar to
Shadertoy and
GLSL Sandbox
with built in support for glslify.

Example

var glsl = require('glslify')
console.log(glsl(`
  #pragma glslify: noise = require('glsl-noise/simplex/3d')

  precision mediump float;
  varying vec3 vpos;
  void main () {
    gl_FragColor = vec4(noise(vpos*25.0),1);
  }
`))

Module API

var glsl = require('glslify')

var src = glsl`shader source...`

Compile a shader inline using glsl as a
tagged template string function.

var src = glsl(file, opts)

var src = glsl(shaderSource, opts)

Compile a shader using an inline shader string or a file name.

These are convencience methods provided that call glsl.compile() or
glsl.file() accordingly. These methods are also provided for backwards
compatibility with the previous < 6 interface.

Optionally provide:

  • opts.basedir - directory to resolve relative paths
  • opts.transform - an array of transform functions, transform module name

var src = glsl.compile(src, opts)

Compile a shader string from a string src.

  • opts.basedir - directory to resolve relative paths in src
  • opts.transform - an array of transform functions, transform module name
    strings, or [trname,tropts] pairs

var src = glsl.file(filename, opts)

Compile a shader from a filename.

  • opts.basedir - directory to resolve relative paths in src
  • opts.transform - an array of transform functions, transform module name
    strings, or [trname,tropts] pairs

Installation

NPM

To install the command-line interface, install glslify globally like
so:

npm install -g glslify

To install glslify for use as a browserify transform, you should
install it locally instead:

npm install glslify

Getting Started

CLI

The CLI can take a file as its first argument, and output to a file
using the -o flag:

glslify index.glsl -o output.glsl

It can also read input from stdin and output to stdout:

cat index.glsl, glslify > output.glsl

Browserify Transform

If using browserify from the command-line, simply pass glslify
in as a transform using the -t/--transform flag:

browserify -t glslify index.js -o bundle.js

Alternatively, you may include glslify as a browserify.transform
in your package.json file:

{
  "name": "my-app",
  "dependencies": {
    "glslify": "^2.0.0"
  },
  "browserify": {
    "transform": ["glslify"]
  }
}

When writing your app, you should be able to require and call
glslify the same as the node/electron interface, like so:

// index.js
var glsl = require('glslify')

var src = glsl.file('./shader.glsl')
console.log(src)

or using tagged template strings:

var glsl = require('glslify')
console.log(glsl`
  #pragma glslify: noise = require('glsl-noise/simplex/3d')

  precision mediump float;
  varying vec3 vpos;
  void main () {
    gl_FragColor = vec4(noise(vpos*25.0),1);
  }
`)

Your glslify calls will be replaced with bundled GLSL strings
at build time automatically for you!

// index.js
var src = "#define GLSLIFY 1\n\nprecision mediump float; ..."

console.log(src)

Webpack Loader

You can use the
glslify-loader
module to bundle shaders through glslify with Webpack. Check out
the repository
for further information.

Babel Plugin

You can use glslify-babel as a Babel plugin. This allows you to use all ES6 features with glslify, including import statements and tagged template strings. Check out the repository to learn more.

:bulb: A Note on Babel Import/Export

If you are using Babel presets to transpile ES6 import/export to CommonJS require() statements, you may run into issues with glslify. This is because Babel mangles the output into source code that isn't easy to statically analyze. One solution is to directly map glslify to CommonJS statements, using babel-plugin-import-to-require in your .babelrc.

Usage

Installing a GLSL Module

Much like plain JavaScript modules, GLSL modules are stored on npm.
The main difference is that GLSL modules contain an index.glsl file
instead of an index.js. Generally, these modules start with glsl-
in their name.

To install glsl-noise in
your current directory:

npm install glsl-noise

This will download glsl-noise and any of its dependencies, placing
them in a node_modules directory for glslify to use.

Importing a GLSL Module

You can import a module using the following #pragma syntax:

#pragma glslify: noise = require(glsl-noise/simplex/2d)

void main() {
  float brightness = noise(gl_FragCoord.xy);

  gl_FragColor = vec4(vec3(brightness), 1.);
}

Shader dependencies are resolved using the same algorithm
as node, so the above will load ./node_modules/simplex/2d.glsl
from the shader's directory.

The above example would result in the following output:

#define GLSLIFY 1

//
// Description : Array and textureless GLSL 2D simplex noise function.
//      Author : Ian McEwan, Ashima Arts.
//  Maintainer : ijm
//     Lastmod : 20110822 (ijm)
//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
//               Distributed under the MIT License. See LICENSE file.
//               https://github.com/ashima/webgl-noise
//

vec3 mod289_1_0(vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec2 mod289_1_0(vec2 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec3 permute_1_1(vec3 x) {
  return mod289_1_0(((x*34.0)+1.0)*x);
}

float snoise_1_2(vec2 v)
  {
  const vec4 C = vec4(0.211324865405187,  // (3.0-sqrt(3.0))/6.0
                      0.366025403784439,  // 0.5*(sqrt(3.0)-1.0)
                     -0.577350269189626,  // -1.0 + 2.0 * C.x
                      0.024390243902439); // 1.0 / 41.0
// First corner
  vec2 i  = floor(v + dot(v, C.yy) );
  vec2 x0 = v -   i + dot(i, C.xx);

// Other corners
  vec2 i1;
  //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
  //i1.y = 1.0 - i1.x;
  i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
  // x0 = x0 - 0.0 + 0.0 * C.xx ;
  // x1 = x0 - i1 + 1.0 * C.xx ;
  // x2 = x0 - 1.0 + 2.0 * C.xx ;
  vec4 x12 = x0.xyxy + C.xxzz;
  x12.xy -= i1;

// Permutations
  i = mod289_1_0(i); // Avoid truncation effects in permutation
  vec3 p = permute_1_1( permute_1_1( i.y + vec3(0.0, i1.y, 1.0 ))
    + i.x + vec3(0.0, i1.x, 1.0 ));

  vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
  m = m*m ;
  m = m*m ;

// Gradients: 41 points uniformly over a line, mapped onto a diamond.
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)

  vec3 x = 2.0 * fract(p * C.www) - 1.0;
  vec3 h = abs(x) - 0.5;
  vec3 ox = floor(x + 0.5);
  vec3 a0 = x - ox;

// Normalise gradients implicitly by scaling m
// Approximation of: m *= inversesqrt( a0*a0 + h*h );
  m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );

// Compute final noise value at P
  vec3 g;
  g.x  = a0.x  * x0.x  + h.x  * x0.y;
  g.yz = a0.yz * x12.xz + h.yz * x12.yw;
  return 130.0 * dot(m, g);
}




void main() {
  float brightness = snoise_1_2(gl_FragCoord.xy);

  gl_FragColor = vec4(vec3(brightness), 1.);
}

Exporting a GLSL Module

You can export a token from a module using the glslify: export
pragma, like so:

float myFunction(vec3 normal) {
  return dot(vec3(0, 1, 0), normal);
}

#pragma glslify: export(myFunction)

This means that when you import this module file elsewhere, you'll
get myFunction in return:

#pragma glslify: topDot = require(./my-function.glsl)

topDot(vec3(0, 1, 0)); // 1

If you check the output shader source, you'll notice that variables
have been renamed to avoid conflicts between multiple shader files.

You're not limited to exporting functions either: you should be able
to export any GLSL token, such as a struct for reuse between your
modules:

struct Light {
  vec3 position;
  vec3 color;
};

#pragma glslify: export(Light)

Passing references between modules

Normally, glslify renames tokens to avoid conflicts across contexts. Sometimes, however, you want to reference the same thing from different contexts. The require function lets you explicitly fix reference names in order to guarantee that two different modules are talking about the same reference.

Give some-module access to locally declared bar whenever it looks for foo internally:

int bar;
#pragma glslify: require('some-module',foo=bar,...)

It's important to make sure that bar has already been declared when you invoke #pragma glslify: require(...).

Now time for some imagination. Let's pretend that we have some float[500] arrays that we'd like to be summed up.

Here's a module that performs a reduction using a function map.

float accumulate(float list[N]) {
  float z = 0;
  for (int i = 0; i<N; i++) {
    z = map(z,list[i]);
  }
  return z;
}
#pragma glslify: export(accumulate)

But notice that this module doesn't actually declare const int N; or define a function map anywhere. We have to make sure they are already defined when we require the module, and pass their names along with the require function:

const int M = 500;
float add(float a, float b){ return a+b; }

#pragma glslify: sum500 = require('./accumulator.glsl',N=M,map=add)

The accumulator has been imported and glslified into a sum function. We can also multiply all of the floats in some float[17] arrays the same way:

const int M = 500;
const int L = 17;
float add(float a, float b){ return a+b; }
float mul(float a, float b){ return a*b; }

#pragma glslify: sum500 = require('./accumulator.glsl',N=M,map=add)
#pragma glslify: product17 = require('./accumulator.glsl',N=L,map=mul)

Glsl-hash-blur is an example of a module that uses this feature.

Source Transforms

Source transforms are a feature inspired by browserify, allowing you to
modify your GLSL source at build time on a per-package basis. This is
useful both for transpilation (e.g. converting from or to
HLSL) or for
making incremental improvements to GLSL syntax. (e.g. you can use
glslify-hex to include CSS-style
hex strings for colors in place of vec3s).

There are three kinds of source transform:

  • Local transforms, the default. These are applied per-file, and only
    applied to a single package. If you're defining it via the CLI using -t
    it'll only apply itself to files outside of node_modules, but you
    can include it in package.json too: these will be applied only to that
    package without interfering with any of the package's parents or children.
  • Global transforms are applied after local transforms to every file,
    regardless of whether or not it's a dependency.
  • Post transforms are applied to the entire output file once it's been
    bundled. Generally, you want to reserve this for very specific use cases
    such as whole-shader optimisation.

There are a number of ways to use a transform. Start by
installing it in your project:

npm install --save glslify-hex

The preferred way to enable a transform is through your project's
package.json file's glslify.transform property, like so:

{
  "name": "my-project",
  "dependencies": {
    "glslify-hex": "^2.0.0",
    "glslify": "^2.0.0"
  },
  "glslify": {
    "transform": ["glslify-hex"]
  }
}

You may also include arguments to your transform as you would
with browserify:

{
  "name": "my-project",
  "dependencies": {
    "glslify-hex": "^2.0.0",
    "glslify": "^2.0.0"
  },
  "glslify": {
    "transform": [
      ["glslify-hex", {
        "option-1": true,
        "option-2": 42
      }]
    ]
  }
}

Note that this method is only available for local transforms.

You may also specify transforms via the CLI:

glslify -t 'local-transform' -g 'global-transform' -p 'post-transform'

Or when using the browserify transform by including them as
options like so:

var glslify = require('glslify')

glslify.file(__dirname + '/shader.glsl', {
  transform: [
    ["glslify-hex", {
      "option-1": true,
      "option-2": 42
    }],
    ["global-transform", { global: true }],
    ["post-transform", { post: true }]
  ]
})

Further Reading

glslify in the Wild

Contributing

See stackgl/contributing for details.

License

MIT. See LICENSE.md for details.

Main metrics

Overview
Name With Ownerglslify/glslify
Primary LanguageJavaScript
Program languageJavaScript (Language Count: 3)
Platform
License:Other
所有者活动
Created At2012-12-04 06:39:27
Pushed At2022-06-29 02:02:58
Last Commit At2020-09-02 17:13:58
Release Count41
Last Release Namev7.1.1 (Posted on 2020-09-02 17:14:15)
First Release Namev1.0.0 (Posted on 2014-02-05 22:01:01)
用户参与
Stargazers Count2.2k
Watchers Count40
Fork Count83
Commits Count202
Has Issues Enabled
Issues Count101
Issue Open Count55
Pull Requests Count40
Pull Requests Open Count8
Pull Requests Close Count2
项目设置
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private