├── .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 | ![Build Status](https://travis-ci.org/migalooo/glsl-shader-loader.svg?branch=master) ![Build Status](https://img.shields.io/badge/node-%3E%3D%20v6.0.0-blue.svg) 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 | --------------------------------------------------------------------------------