├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── dist
└── index.html
├── index.js
├── package-lock.json
├── package.json
├── src
├── index.js
├── parser.js
└── utils
│ ├── ast-generator.js
│ ├── cache-import-snippets.js
│ ├── constructor-mask.js
│ ├── error-handler.js
│ ├── extract-shader-source.js
│ ├── parse-import-string.js
│ ├── select-function-calls.js
│ └── selector.js
├── test
├── app.js
├── shaders
│ ├── collections
│ │ ├── draw.glsl
│ │ ├── fbm.glsl
│ │ ├── noise.glsl
│ │ └── random.glsl
│ └── fragments.glsl
├── webpack.debug.js
└── webpack.test.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/*.js
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | .DS_Store
8 | .tern-project
9 | .*swp
10 | .*swn
11 | .*swo
12 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | test/
3 | dist/
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | before_install:
4 | - curl -o- -L https://yarnpkg.com/install.sh | bash
5 | - export PATH=$HOME/.yarn/bin:$PATH
6 | before_script:
7 | - yarn add webpack@3.8.1
8 | - yarn install
9 | - yarn run test
10 | node_js:
11 | - "6"
12 | - "7"
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GLSL Shader Loader
2 |  
3 |
4 | This is a static shader source bundler for WebGL program, provide a possibility for management shader source by creating separate files.
5 | > glsl-shader-loader for [Webpack](https://webpack.js.org/concepts/), supports for import **GLSL functions** from file and generates a shader string for WebGL program.
6 |
7 | **GLSL Shader Loader** provids many features as following.
8 | - Allow import `.glsl` source file as a Javacript string for WebGL program
9 | - Support `import` [statement](#usage) in `.glsl` file that can extract GLSL functions from other files (includes nested reference)
10 | - Remove invalid import if the function will not be called
11 | - Repeated functions are imported only once
12 | - Syntax tree analysis and error detection
13 |
14 | ---
15 |
16 | ### Install
17 | ```bash
18 | npm install --save-dev glsl-shader-loader
19 | ```
20 |
21 | ### Configuration
22 | In your webpack configuration:
23 | ```js
24 | module: {
25 | rules: [{
26 | test: /\.(frag|vert|glsl)$/,
27 | use: [
28 | {
29 | loader: 'glsl-shader-loader',
30 | options: {}
31 | }
32 | ]
33 | }]
34 | }
35 | ```
36 |
37 | ### Usage
38 | You can import GLSL functions with `#pragma loader:` statements in `.glsl` file
39 | - Import specified function by name
40 | **`#pragma loader: import {nameA, nameB} from './file.glsl';`**
41 |
42 | - Import the only function in file by a new name
43 | **`#pragma loader: import rename from './file.glsl';`**
44 |
45 | - **NOTE**:
46 | - Only if there is a single function in `.glsl` file will you be able to rename it
47 | - If the imported function is not called, the function source will not insert in shader source
48 | - In case two functions have the same name, only import once
49 | - Imported function will replace the position of import statement in order
50 |
51 | ### Options
52 | | Name | Type | Default | Description |
53 | |------|:----:|:--------:|:-----------|
54 | | [root](#root) | {String} | undefined | Specify the root path of source |
55 |
56 | ### `root`
57 |
58 | configuration:
59 | ```javascript
60 | {
61 | loader: 'glsl-shader-loader',
62 | options: {
63 | root: '/src/shaders'
64 | }
65 | }
66 | ```
67 | Use `/` redirect to the specified directory.
68 | > e.g. `#pragma loader: import {light} from '/lights.glsl';` will search `lights.glsl` under the path `projectRoot/src/shaders/`
69 |
70 | ### Example
71 |
72 | A directory structured like this:
73 | ```
74 | .
75 | ├─ app.js
76 | ├─ fragmentShaderSource.glsl
77 | └─ /collections/
78 | ├─ light.glsl
79 | └─ random.glsl
80 | ```
81 |
82 | Setting up shaders in **app.js** you might write code like this:
83 | ```js
84 | import fragmentShaderSource from './fragmentShaderSource.glsl'
85 |
86 | gl.shaderSource(fragmentShader, fragmentShaderSource)
87 | ...
88 | ```
89 |
90 |
91 | In shader code **fragmentShaderSource.glsl**, import `randomDirection` and `spotLight` from file:
92 | ```glsl
93 | precision mediump float;
94 |
95 | varying vec4 v_color;
96 | varying vec3 v_normal;
97 |
98 | #pragma loader: import randomDirection from './collections/random.glsl';
99 |
100 | #pragma loader: import {spotLight} from './collections/spotLight.glsl';
101 |
102 | ...
103 |
104 | void main() {
105 | vec3 direction = randomDirection(range);
106 | vec3 spot = spotLight(direction, v_normal);
107 | ...
108 | gl_FragColor = v_result_color;
109 | }
110 | ```
111 |
112 | **light.glsl**
113 | ```glsl
114 | vec3 spotLight (vec3 direction vec3 normal) {
115 | ...
116 | return spot;
117 | }
118 |
119 | vec3 ambientLight (vec3 direction vec3 normal) {
120 | ...
121 | return ambient;
122 | }
123 | ```
124 |
125 | **random.glsl**
126 | ```glsl
127 | vec3 random(vec2 range) {
128 | ...
129 | return random;
130 | }
131 | ```
132 |
133 | `import fragmentShaderSource from './fragmentShaderSource.glsl'` Will return this JavaScript string:
134 | ```glsl
135 | precision mediump float;
136 |
137 | varying vec4 v_color;
138 | varying vec3 v_normal;
139 |
140 | vec3 randomDirection(vec2 range) {
141 | ...
142 | return random;
143 | }
144 |
145 | vec3 spotLight (vec3 direction vec3 normal) {
146 | ...
147 | return spot;
148 | }
149 |
150 | ...
151 |
152 | void main() {
153 | vec3 direction = randomDirection(range);
154 | vec3 spot = spotLight(direction, v_normal);
155 | ...
156 | gl_FragColor = v_result_color;
157 | }
158 |
159 | ```
160 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Test Running
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./src/index.js')
2 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "glsl-shader-loader",
3 | "version": "0.1.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "async": {
8 | "version": "1.5.2",
9 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
10 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
11 | "dev": true
12 | },
13 | "cssauron": {
14 | "version": "1.4.0",
15 | "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
16 | "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
17 | "dev": true,
18 | "requires": {
19 | "through": "2.3.8"
20 | }
21 | },
22 | "glsl-man": {
23 | "version": "1.1.14",
24 | "resolved": "https://registry.npmjs.org/glsl-man/-/glsl-man-1.1.14.tgz",
25 | "integrity": "sha512-yM/+5DCpHGIDO+qtMTDp2PLgl+g+4WbqmJlA4baCRkNwuOZbpNDyBaS1gfGaJIV/BrMKDuFBr6niOeRHV1iAeg==",
26 | "dev": true,
27 | "requires": {
28 | "cssauron": "1.4.0",
29 | "tree-traversal": "1.1.1"
30 | }
31 | },
32 | "through": {
33 | "version": "2.3.8",
34 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
35 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
36 | "dev": true
37 | },
38 | "tree-traversal": {
39 | "version": "1.1.1",
40 | "resolved": "https://registry.npmjs.org/tree-traversal/-/tree-traversal-1.1.1.tgz",
41 | "integrity": "sha1-cUg7PL4BxcMhT1pYODuqoM8dzL4=",
42 | "dev": true,
43 | "requires": {
44 | "async": "1.5.2"
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "glsl-shader-loader",
3 | "description": "A static shader source bundler for WebGL program, provide a possibility for management shader source by creating separate files.",
4 | "author": "Migaloo ",
5 | "version": "0.1.5",
6 | "keywords": [
7 | "glsl",
8 | "shader",
9 | "webgl",
10 | "module",
11 | "component",
12 | "webpack",
13 | "loader",
14 | "import"
15 | ],
16 | "license": "MIT",
17 | "main": "index.js",
18 | "scripts": {
19 | "test": "webpack --config ./test/webpack.test.js",
20 | "dev": "node --inspect=9047 ./test/webpack.debug.js"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/migalooo/glsl-shader-loader.git"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/migalooo/glsl-shader-loader/issues"
28 | },
29 | "dependencies": {
30 | "glsl-man": "^1.1.14",
31 | "loader-utils": "^1.1.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const glsl = require('glsl-man')
2 | const parser = require('./parser.js')
3 | const selector = require('./utils/selector.js')
4 | const astGenerator = require('./utils/ast-generator.js').astGenerator
5 |
6 | module.exports = function(source){
7 | this.cacheable && this.cacheable()
8 | const callback = this.async()
9 | const cacheNodes = {
10 | // Function names mask array for remove duplicates funcitons
11 | mask: {},
12 | // Save the leaf import shader code
13 | snippets: [],
14 | // Save the root import shader code
15 | anchor: []
16 | }
17 |
18 | const ast = astGenerator(source, this.resourcePath)
19 |
20 | parser(this, this.context, ast, cacheNodes, true, function(err, isRoot) {
21 | if (err) return callback(err)
22 | const {anchor, snippets} = cacheNodes
23 |
24 | // Insert root import shader code
25 | if (anchor.length !== 0) {
26 | anchor.forEach((node, i) => {
27 | const anchor = selector.id(ast, node.anchorId)
28 | // Insert root import shader code
29 | selector.add(anchor, node.nodes.reverse(), true)
30 | })
31 | }
32 |
33 | // No deep import, return root import result
34 | if (snippets.length !== 0) {
35 | const fristAnchorId = anchor.reduce((a, b) => a.anchorId < b.anchorId ? a : b).anchorId
36 | const fristAnchor = selector.id(ast, fristAnchorId)
37 | // Insert leaf import shader code
38 | selector.add(fristAnchor, snippets.reverse(), false)
39 | }
40 |
41 | // Remove #pragma loader:
42 | const removeNodes = []
43 | ast.statements
44 | .forEach(node => {
45 | if (node.directive === '#pragma' && node.value.match(/^loader\:/)) removeNodes.push(node)
46 | })
47 |
48 | selector.remove(removeNodes)
49 |
50 | return callback(null, `module.exports = ${JSON.stringify(glsl.string(ast))}`)
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/src/parser.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const astGenerator = require('./utils/ast-generator.js').astGenerator
4 | const parseImportString = require('./utils/parse-import-string.js')
5 | const extractShaderSource = require('./utils/extract-shader-source.js')
6 | const cacheImportSnippets = require('./utils/cache-import-snippets.js')
7 | const selectFunctionCalls = require('./utils/select-function-calls.js')
8 |
9 | // In root source, mark the position of import and stored in [anchor], we will replace with shader code after import all shader functions
10 | // In leaf source, to remove duplicates, all the shader code will stored in [snippets] and insert before the frist import statements in root
11 | module.exports = function parser(loader, filePath, ast, cacheNodes, isRoot, callback) {
12 |
13 | // Parse function calls in source
14 | const funcCallsMask = selectFunctionCalls(ast)
15 | // Extract --#pragma loader:-- from ast and analyze
16 | const importInfoArr = ast.statements
17 | .filter(node => {
18 | return node.directive === '#pragma' && node.value.match(/^loader\:/)
19 | })
20 | .map(node => {
21 | const importInfo = parseImportString(loader, filePath, node.value, callback)
22 | // Check import statement accessable
23 | fs.accessSync(importInfo.path)
24 |
25 | if (isRoot) importInfo.anchorId = node.id
26 | // Filter unexecuted function
27 | importInfo.names = importInfo.names.filter(name => {
28 | if (funcCallsMask[name]) return true
29 | console.info(`[glsl-shader-loader] Info: glsl function '${name}' not imported because it was not called.\n @ ${node.value}` )
30 | return false
31 | })
32 |
33 | return importInfo
34 | })
35 | .filter(data => data.names.length > 0)
36 |
37 | if (!importInfoArr || importInfoArr.length === 0) return callback(null)
38 |
39 | // Import shader source
40 | Promise.all(importInfoArr.map(data => extractShaderSource(loader, data.path)))
41 | .then(shaderSources => {
42 | return Promise.all(shaderSources.map((source, i) => {
43 | const filePath = importInfoArr[i].path
44 | const dirname = path.dirname(filePath)
45 | const astLeaf = astGenerator(source, filePath)
46 |
47 | cacheImportSnippets(astLeaf, isRoot, importInfoArr[i], cacheNodes, callback)
48 | return deepParser(parser, loader, astLeaf, dirname, cacheNodes)
49 | }))
50 | })
51 | .then(info => {
52 | return callback(null)
53 | })
54 | .catch(err => callback(err))
55 | }
56 |
57 | function deepParser(parser, loader, astLeaf, dirname, cacheNodes) {
58 | return new Promise((resolve, reject) => {
59 | parser(loader, dirname, astLeaf, cacheNodes, false, function(err){
60 | if (err) return reject(err)
61 | resolve()
62 | })
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/src/utils/ast-generator.js:
--------------------------------------------------------------------------------
1 | const glsl = require('glsl-man')
2 | const error = require('./error-handler.js')
3 |
4 | function astGenerator(source, sourcePath) {
5 | let ast
6 | try {
7 | ast = glsl.parse(source)
8 | } catch(err) {
9 | err.message += error.glslSyntaxError(err, sourcePath)
10 | throw err
11 | }
12 | return ast
13 | }
14 |
15 | module.exports = {
16 | astGenerator
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/cache-import-snippets.js:
--------------------------------------------------------------------------------
1 | const selector = require('./selector.js').all
2 |
3 | module.exports = function cacheImportSnippets(ast, isRoot, importInfo, cacheNodes, callback) {
4 | let selectorNodes = []
5 |
6 | // Select function declaration from ast
7 | const funcDeclar = selector(ast, 'type', 'function_declaration')
8 | .filter(node => {
9 | return node.name !== 'main'
10 | })
11 |
12 | if (importInfo.isRename) {
13 | if (funcDeclar.length > 1) callback(new SyntaxError(`There are more than 2 function declarations in ${importInfo.path}, try to use -- import {func} from "./path"; -- instead`))
14 |
15 | const newName = importInfo.names[0]
16 | if (cacheNodes.mask[newName]) return // function has import before
17 |
18 | funcDeclar[0].name = newName
19 | selectorNodes.push(funcDeclar[0])
20 | cacheNodes.mask[newName] = true
21 | } else {
22 | importInfo.names.forEach(name => {
23 | if (cacheNodes.mask[name]) return
24 |
25 | const matchNode = funcDeclar.filter(node => name === node.name)
26 | if (matchNode.length === 0) callback(new SyntaxError(`Can't find function declaration by the name [${name}] in ${importInfo.path}`))
27 |
28 | selectorNodes.push(matchNode[0])
29 | cacheNodes.mask[name] = true
30 | })
31 | }
32 |
33 | // In root, cache import id
34 | isRoot
35 | ? cacheNodes.anchor.push({anchorId: importInfo.anchorId, nodes: selectorNodes})
36 | : cacheNodes.snippets.push(...selectorNodes)
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils/constructor-mask.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // constructor
3 | bool: true,
4 | int: true,
5 | float: true,
6 | vec2: true,
7 | vec3: true,
8 | vec4: true,
9 | ivec2: true,
10 | ivec3: true,
11 | ivec4: true,
12 | bvec2: true,
13 | bvec3: true,
14 | bvec4: true,
15 | mat2: true,
16 | mat3: true,
17 | mat4: true,
18 | // function
19 | radians: true,
20 | degrees: true,
21 | sin: true,
22 | cos: true,
23 | tan: true,
24 | acos: true,
25 | atan: true,
26 | pow: true,
27 | exp: true,
28 | log: true,
29 | exp2: true,
30 | log2: true,
31 | sqrt: true,
32 | inversesqrt: true,
33 | abs: true,
34 | min: true,
35 | max: true,
36 | mod: true,
37 | sign: true,
38 | floor: true,
39 | ceil: true,
40 | clamp: true,
41 | mix: true,
42 | step: true,
43 | smoothstep: true,
44 | fract: true,
45 | length: true,
46 | distance: true,
47 | dot: true,
48 | cross: true,
49 | normalize: true,
50 | reflect: true,
51 | faceforward: true,
52 | matrixCmpMult: true,
53 | lessThan: true,
54 | lessThanEqual: true,
55 | greaterThan: true,
56 | greaterThanEqual: true,
57 | equal: true,
58 | notEqual: true,
59 | any: true,
60 | all: true,
61 | not: true,
62 | texture2D: true,
63 | textureCube: true,
64 | texture2DProj: true,
65 | texture2DLod: true,
66 | textureCubeLod: true,
67 | texture2DProjLod: true
68 | }
69 |
--------------------------------------------------------------------------------
/src/utils/error-handler.js:
--------------------------------------------------------------------------------
1 | function glslSyntaxError(err, path) {
2 | if (err['name'] !== 'SyntaxError') return err
3 | const {line, column} = err.location.start
4 | const codePosition = ` @ ${path} ${line}:${column}`
5 | return codePosition
6 | }
7 |
8 | module.exports = {
9 | glslSyntaxError
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/extract-shader-source.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | module.exports = function extractShaderSource(loader, path) {
4 | return new Promise((resolve, reject) => {
5 | fs.access(path, (err) => {
6 | if (err) return reject(err)
7 |
8 | loader.resolve(loader.context, path, function(err, resolved) {
9 | if (err) return reject(err)
10 | loader.addDependency(resolved)
11 |
12 | fs.readFile(resolved, 'utf-8', function(err, shaderSource) {
13 | if (err) return reject(err)
14 | resolve(shaderSource)
15 | })
16 | })
17 | })
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/parse-import-string.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const loaderUtils = require('loader-utils')
3 |
4 | module.exports = function parseImportString(loader, filePath, importStr, callback) {
5 | // Get options config
6 | const options = loaderUtils.getOptions(loader)
7 | // match [loader:] synatx and extract --funcNames | path-- from importStr
8 | const extract = importStr.match(/^loader\:\s*import\s+((\{.*\})|([\w-]+))\s+from\s+['|"]([./\w-]+)['|"]/)
9 | const namesRaw = extract[1], pathRaw = extract[4]
10 | let isRename, names, fullPath
11 |
12 | if (!namesRaw || !path) return callback(new SyntaxError(importStr))
13 |
14 | // Use syntax --import newName from './file'-- will rename the origin funciton name
15 | // Use syntax --import {funcName} from './file'-- will extract the matched funcion from source
16 | if (namesRaw.match(/^\{/)) {
17 | isRename = false
18 | names = namesRaw.replace(/[{}\s]/g, '').split(',')
19 | } else {
20 | isRename = true
21 | names = [namesRaw]
22 | }
23 |
24 | // If config options root
25 | if (options && options.root && pathRaw.match(/^\//)) {
26 | fullPath = path.join(path.resolve('./'), options.root, pathRaw)
27 | } else {
28 | fullPath = path.join(filePath, pathRaw)
29 | }
30 |
31 | return {
32 | isRename,
33 | names,
34 | path: fullPath
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/select-function-calls.js:
--------------------------------------------------------------------------------
1 | const selector = require('./selector.js').all
2 | const mask = require('./constructor-mask.js')
3 |
4 | module.exports = function selectFunctionCalls(ast) {
5 | const funcCallsMask = {}
6 | selector(ast, 'type', 'function_call')
7 | .forEach(node => {
8 | // Exclude glsl functions
9 | if (!mask[node.function_name]) funcCallsMask[node.function_name] = true
10 | })
11 |
12 | return funcCallsMask
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/selector.js:
--------------------------------------------------------------------------------
1 | const glsl = require('glsl-man')
2 |
3 | function all(ast, attr, value) {
4 | const matches = []
5 | const selector = glsl.query.selector(`[${attr}=${value}]`)
6 | glsl.query.all(ast, selector, matches)
7 | return matches
8 | }
9 |
10 | function id(ast, id) {
11 | const selector = glsl.query.selector(`[id=${id}]`)
12 | const node = glsl.query.firstChild(ast, selector)
13 | return node
14 | }
15 |
16 | function add(anchor, node, after) {
17 | glsl.mod.add(anchor, node, after)
18 | }
19 |
20 | function remove(node) {
21 | if(node instanceof Array) {
22 | node.forEach(d => {
23 | glsl.mod.remove(d)
24 | })
25 | } else {
26 | glsl.mod.remove(node)
27 | }
28 | }
29 |
30 | module.exports = {
31 | all,
32 | id,
33 | add,
34 | remove
35 | }
36 |
--------------------------------------------------------------------------------
/test/app.js:
--------------------------------------------------------------------------------
1 | import fragementShader from './shaders/fragments.glsl'
2 |
3 | console.log(fragementShader)
4 |
--------------------------------------------------------------------------------
/test/shaders/collections/draw.glsl:
--------------------------------------------------------------------------------
1 | float plot (vec2 coord) {
2 | float x = coord.x;
3 | float y = coord.y;
4 | return coord;
5 | }
6 |
--------------------------------------------------------------------------------
/test/shaders/collections/fbm.glsl:
--------------------------------------------------------------------------------
1 | #pragma loader: import {plot} from '/draw.glsl';
2 |
3 | float fbm (in vec2 st) {
4 | float value = 0.0;
5 | float amplitude = .5;
6 | float frequency = 0.;
7 |
8 | vec2 coord = plot(st);
9 | for (int i = 0; i < OCTAVES; i++) {
10 | value += amplitude * noise(st);
11 | st *= 2.;
12 | amplitude *= .5;
13 | }
14 | return value;
15 | }
16 |
--------------------------------------------------------------------------------
/test/shaders/collections/noise.glsl:
--------------------------------------------------------------------------------
1 | float noise (in vec2 st) {
2 | vec2 i = floor(st);
3 | vec2 f = fract(st);
4 |
5 | float a = random(i);
6 | float b = random(i + vec2(1.0, 0.0));
7 | float c = random(i + vec2(0.0, 1.0));
8 | float d = random(i + vec2(1.0, 1.0));
9 |
10 | vec2 u = f * f * (3.0 - 2.0 * f);
11 |
12 | return mix(a, b, u.x) +
13 | (c - a)* u.y * (1.0 - u.x) +
14 | (d - b) * u.x * u.y;
15 | }
16 |
--------------------------------------------------------------------------------
/test/shaders/collections/random.glsl:
--------------------------------------------------------------------------------
1 | float random (in vec2 st) {
2 | return fract(sin(dot(st.xy,
3 | vec2(12.9898,78.233)))*
4 | 43758.5453123);
5 | }
6 |
--------------------------------------------------------------------------------
/test/shaders/fragments.glsl:
--------------------------------------------------------------------------------
1 | #ifdef GL_ES
2 | precision mediump float;
3 | #endif
4 |
5 | struct dirlight
6 | {
7 | vec3 direction;
8 | vec3 color;
9 | };
10 |
11 | uniform vec2 u_resolution;
12 | uniform vec2 u_mouse;
13 | uniform float u_time;
14 |
15 |
16 | #pragma loader: import {random} from './collections/random.glsl';
17 |
18 | #pragma loader: import {noise} from './collections/noise.glsl';
19 |
20 | #define OCTAVES 6
21 | #pragma loader: import freq from './collections/fbm.glsl';
22 |
23 |
24 | void main() {
25 | vec2 st = gl_FragCoord.xy/u_resolution.xy;
26 | st.x *= u_resolution.x/u_resolution.y;
27 |
28 | vec3 no = noise(st);
29 | vec3 color = vec3(0.0);
30 | color += freq(st*3.0);
31 |
32 | gl_FragColor = vec4(color,1.0);
33 | }
34 |
--------------------------------------------------------------------------------
/test/webpack.debug.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 |
4 | webpack({
5 | mode: 'development',
6 | entry: path.resolve(__dirname, './app.js'),
7 | output: {
8 | filename: '[name].js',
9 | path: path.resolve(__dirname, '../dist')
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.(frag|vert|glsl)$/,
15 | use: {
16 | loader: path.resolve(__dirname, '../index.js'),
17 | options: {
18 | root: '/test/shaders/collections'
19 | }
20 | }
21 | }
22 | ]
23 | }
24 | },(err, stats) => {
25 | if (err) {
26 | console.error(err)
27 | return
28 | }
29 | console.log(stats.toString({
30 | colors: true
31 | }))
32 | })
33 |
--------------------------------------------------------------------------------
/test/webpack.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | entry: path.resolve(__dirname, './app.js'),
5 | output: {
6 | filename: '[name].js',
7 | path: path.resolve(__dirname, '../dist')
8 | },
9 | module: {
10 | rules: [
11 | {
12 | test: /\.(frag|vert|glsl)$/,
13 | use: {
14 | loader: path.resolve(__dirname, '../index.js'),
15 | options: {
16 | root: '/test/shaders/collections'
17 | }
18 | }
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | async@^1.4.2:
6 | version "1.5.2"
7 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
8 |
9 | big.js@^3.1.3:
10 | version "3.2.0"
11 | resolved "http://registry.npm.taobao.org/big.js/download/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
12 |
13 | cssauron@^1.4.0:
14 | version "1.4.0"
15 | resolved "https://registry.yarnpkg.com/cssauron/-/cssauron-1.4.0.tgz#a6602dff7e04a8306dc0db9a551e92e8b5662ad8"
16 | dependencies:
17 | through X.X.X
18 |
19 | emojis-list@^2.0.0:
20 | version "2.1.0"
21 | resolved "http://registry.npm.taobao.org/emojis-list/download/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
22 |
23 | glsl-man@^1.1.14:
24 | version "1.1.14"
25 | resolved "https://registry.yarnpkg.com/glsl-man/-/glsl-man-1.1.14.tgz#14acf7969d3cd9113cbff291ea1991a469022630"
26 | dependencies:
27 | cssauron "^1.4.0"
28 | tree-traversal "^1.1.1"
29 |
30 | json5@^0.5.0:
31 | version "0.5.1"
32 | resolved "http://registry.npm.taobao.org/json5/download/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
33 |
34 | loader-utils@^1.1.0:
35 | version "1.1.0"
36 | resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
37 | dependencies:
38 | big.js "^3.1.3"
39 | emojis-list "^2.0.0"
40 | json5 "^0.5.0"
41 |
42 | through@X.X.X:
43 | version "2.3.8"
44 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
45 |
46 | tree-traversal@^1.1.1:
47 | version "1.1.1"
48 | resolved "https://registry.yarnpkg.com/tree-traversal/-/tree-traversal-1.1.1.tgz#71483b3cbe01c5c3214f5a58383baaa0cf1dccbe"
49 | dependencies:
50 | async "^1.4.2"
51 |
--------------------------------------------------------------------------------