├── .npmignore ├── .gitignore ├── .eslintrc ├── example ├── .eslintrc ├── Reflection_Cube_Map.json ├── Water_or_Oil.json ├── index.html └── example.js ├── package.json ├── webpack.config.cdn.js ├── THREE_SHADER_FORMAT.md ├── README.md └── src └── ShaderRuntime.js /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | npm-debug.log 4 | dist/ 5 | shaderfrog-runtime.min.js 6 | lib/ 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true, 9 | "es6": true 10 | }, 11 | "rules": { 12 | "camelcase": 0, 13 | "consistent-return": 0, 14 | "strict": 0, 15 | "no-underscore-dangle": 0, 16 | "no-unused-vars": 0, 17 | "no-unused-expressions": 0, 18 | "no-return-assign": 0, // for return ( fn = cond ) 19 | "no-cond-assign": 0, // for shorthand for(;x = y[z++]) loops 20 | "no-multi-spaces": 0, 21 | "quotes": [ 2 ], 22 | "new-cap": 0, 23 | "comma-spacing": 0, 24 | "no-use-before-define": 0, 25 | "curly": 0, 26 | "no-trailing-spaces": 0 27 | }, 28 | "plugins": [ 29 | "react" 30 | ], 31 | "globals": { 32 | "app": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true, 9 | "es6": true 10 | }, 11 | "rules": { 12 | "camelcase": 0, 13 | "consistent-return": 0, 14 | "strict": 0, 15 | "no-underscore-dangle": 0, 16 | "no-unused-vars": 0, 17 | "no-unused-expressions": 0, 18 | "no-return-assign": 0, // for return ( fn = cond ) 19 | "no-cond-assign": 0, // for shorthand for(;x = y[z++]) loops 20 | "no-multi-spaces": 0, 21 | "quotes": [ 2 ], 22 | "new-cap": 0, 23 | "comma-spacing": 0, 24 | "no-use-before-define": 0, 25 | "curly": 0, 26 | "no-trailing-spaces": 0 27 | }, 28 | "plugins": [ 29 | "react" 30 | ], 31 | "globals": { 32 | "THREE": true, 33 | "ShaderFrogRuntime": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shaderfrog-runtime", 3 | "version": "0.2.0", 4 | "description": "ShaderFrog runtime library", 5 | "main": "lib/shaderfrog-runtime.js", 6 | "scripts": { 7 | "compile": "babel --out-file lib/shaderfrog-runtime.js src/ShaderRuntime.js", 8 | "test": "echo 'Error: no test specified' && exit 1", 9 | "prepublish": "npm run compile" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/AndrewRayCode/ShaderFrog-Runtime.git" 14 | }, 15 | "author": "Andrew Ray", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/AndrewRayCode/ShaderFrog-Runtime/issues" 19 | }, 20 | "homepage": "https://github.com/AndrewRayCode/ShaderFrog-Runtime#readme", 21 | "dependencies": { 22 | "babel": "^5.8.35", 23 | "babel-core": "^5.8.21", 24 | "babel-loader": "^5.3.2", 25 | "webpack": "^1.11.0", 26 | "webpack-notifier": "^1.2.1" 27 | }, 28 | "peerDependencies": { 29 | "three": "~0.84.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /webpack.config.cdn.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var WebpackNotifierPlugin = require('webpack-notifier'); 4 | 5 | var config = { 6 | entry: { 7 | runtime: './ShaderRuntime.js' 8 | }, 9 | output: { 10 | filename: 'shaderfrog-runtime.min.js', 11 | library: 'ShaderFrogRuntime', 12 | libraryTarget: 'var', 13 | pubicPath: 'https://s3-us-west-1.amazonaws.com/shader-frog/example/' 14 | }, 15 | resolveLoader: { 16 | root: path.join(__dirname, 'scripts/loaders') 17 | }, 18 | module: { 19 | loaders: [{ 20 | test: /\.js$/, 21 | loader: 'babel-loader' 22 | }] 23 | }, 24 | externals: { 25 | THREE: 'THREE', 26 | three: 'THREE' 27 | }, 28 | plugins: [ 29 | new webpack.NoErrorsPlugin(), 30 | new webpack.optimize.UglifyJsPlugin({ 31 | compress: { 32 | warnings: false 33 | } 34 | }) 35 | ] 36 | }; 37 | 38 | module.exports = config; 39 | -------------------------------------------------------------------------------- /example/Reflection_Cube_Map.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 84, 3 | "name": "Reflection Cube Map", 4 | "fragment": "precision highp float;\nprecision highp int;\n\nvarying vec3 vReflect;\n\nuniform float mirrorReflection;\nuniform samplerCube reflectionSampler;\n\nvoid main() {\n vec4 cubeColor = textureCube( reflectionSampler, vec3( mirrorReflection * vReflect.x, vReflect.yz ) );\n cubeColor.w = 1.0;\n gl_FragColor = cubeColor;\n\n}", 5 | "vertex": "precision highp float;\nprecision highp int;\n\nuniform mat4 modelMatrix;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat4 viewMatrix;\nuniform mat3 normalMatrix;\nuniform vec3 cameraPosition;\n\nattribute vec3 position;\nattribute vec3 normal;\nattribute vec2 uv;\nattribute vec2 uv2;\n\nvarying vec3 vReflect;\n\nvoid main() {\n vec3 worldPosition = ( modelMatrix * vec4( position, 1.0 )).xyz;\n vec3 cameraToVertex = normalize( worldPosition - cameraPosition );\n vec3 worldNormal = normalize(\n mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * normal\n );\n vReflect = reflect( cameraToVertex, worldNormal );\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}", 6 | "uniforms": { 7 | "cameraPosition": { 8 | "name": "cameraPosition", 9 | "type": "v3", 10 | "glslType": "vec3", 11 | "description": "" 12 | }, 13 | "mirrorReflection": { 14 | "name": "mirrorReflection", 15 | "type": "f", 16 | "glslType": "float", 17 | "value": "1", 18 | "description": "" 19 | }, 20 | "reflectionSampler": { 21 | "name": "reflectionSampler", 22 | "type": "t", 23 | "glslType": "samplerCube", 24 | "value": { 25 | "id": 2, 26 | "image": "nissi_beach.jpg", 27 | "thumbnail": "thumb_nissi_beach.jpg", 28 | "name": "Nissi Beach", 29 | "description": "Nissi Beach", 30 | "isSamplerCube": true 31 | }, 32 | "description": "I am a description of a uniform!" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /example/Water_or_Oil.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 40, 3 | "name": "Water or Oil", 4 | "fragment": "// http://glslsandbox.com/e#11554.0\n\n#ifdef GL_ES\nprecision mediump float;\n#endif\n\nuniform float time;\n\nvarying vec2 vUv;\nvarying vec3 vPosition;\nvarying vec3 vNormal;\n\nuniform vec3 color1;\nuniform vec3 color2;\nuniform vec3 color3;\nuniform vec3 color4;\n\n// by @301z\n\nfloat rand(vec2 n) { \n\treturn fract(cos(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);\n}\n\nfloat noise(vec2 n) {\n\tconst vec2 d = vec2(0.0, 1.0);\n\tvec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n));\n\treturn mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y);\n}\n\nfloat fbm(vec2 n) {\n\tfloat total = 0.0, amplitude = 1.0;\n\tfor (int i = 0; i < 7; i++) {\n\t\ttotal += noise(n) * amplitude;\n\t\tn += n;\n\t\tamplitude *= 0.5;\n\t}\n\treturn total;\n}\n\nvoid main() {\n\tconst vec3 c5 = vec3(0.1);\n\tconst vec3 c6 = vec3(0.9);\n\tvec2 p = vUv.xy * 8.0;\n\tfloat q = fbm(p - time * 0.1);\n\tvec2 r = vec2(fbm(p + q + time * 0.7 - p.x - p.y), fbm(p + q - time * 0.4));\n\tvec3 c = mix(color1, color2, fbm(p + r)) +\n\t mix(color3, color4, r.x) - mix(c5, c6, r.y);\n\tgl_FragColor = vec4(c * cos(1.57 * vUv.y), 1.0);\n}\n", 5 | "vertex": "precision highp float;\r\nprecision highp int;\r\n\r\nuniform mat4 modelMatrix;\r\nuniform mat4 modelViewMatrix;\r\nuniform mat4 projectionMatrix;\r\nuniform mat4 viewMatrix;\r\nuniform mat3 normalMatrix;\r\n\r\nattribute vec3 position;\r\nattribute vec3 normal;\r\nattribute vec2 uv;\r\nattribute vec2 uv2;\r\n\r\nvarying vec2 vUv;\r\nvarying vec3 vPosition;\r\nvarying vec3 vNormal;\r\n\r\nvoid main() {\r\n vUv = uv;\r\n vPosition = position;\r\n vNormal = normal;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}", 6 | "uniforms": { 7 | "time": { 8 | "type": "f", 9 | "glslType": "float", 10 | "description": "", 11 | "textureId": null, 12 | "runtime": { 13 | "texture": null 14 | } 15 | }, 16 | "color1": { 17 | "type": "c", 18 | "glslType": "vec3", 19 | "value": { 20 | "r": 0, 21 | "g": 0.9450980392156862, 22 | "b": 0.00784313725490196 23 | }, 24 | "description": "", 25 | "textureId": null, 26 | "runtime": { 27 | "texture": null 28 | } 29 | }, 30 | "color2": { 31 | "type": "c", 32 | "glslType": "vec3", 33 | "value": { 34 | "r": 1, 35 | "g": 0.1607843137254902, 36 | "b": 0.2627450980392157 37 | }, 38 | "description": "", 39 | "textureId": null, 40 | "runtime": { 41 | "texture": null 42 | } 43 | }, 44 | "color3": { 45 | "type": "c", 46 | "glslType": "vec3", 47 | "value": { 48 | "r": 0.9921568627450981, 49 | "g": 0.984313725490196, 50 | "b": 0 51 | }, 52 | "description": "", 53 | "textureId": null, 54 | "runtime": { 55 | "texture": null 56 | } 57 | }, 58 | "color4": { 59 | "type": "c", 60 | "glslType": "vec3", 61 | "value": { 62 | "r": 0.5019607843137255, 63 | "g": 0.9725490196078431, 64 | "b": 0 65 | }, 66 | "description": "", 67 | "textureId": null, 68 | "runtime": { 69 | "texture": null 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 48 | 49 | 50 |
51 |

ShaderFrog.com Runtime Demo

52 | Runtime demo for ShaderFrog.com online WebGL shader editor. View source of this page or see example.js. 53 |

Repository: Github source 54 |
55 | 56 |

Relevant Source

57 |
// Load multiple ShaderFrog shaders
58 | runtime.load([
59 |     'Copy_of_Gems.json',
60 |     'Water_or_Oil.json'
61 | ], function( shaders ) {
62 | 
63 |     // `shaders` will be an array with the material data in the same order you
64 |     // specified when calling `load
65 | 
66 |     // Get the Three.js material you can assign to your objects
67 |     // ShaderFrog shader 1 (reflection effect)
68 |     var materialTop = runtime.get( shaders[ 0 ].name );
69 | 
70 |     // You set uniforms the same way as a regular THREE.js shader. In this
71 |     // case, the shader uses a cube camera for reflection, so we have to set
72 |     // its value to the renderTarget of a cubeCamera we create
73 |     materialTop.uniforms.reflectionSampler.value = cubeCamera.renderTarget;
74 |     meshTop.material = materialTop;
75 | 
76 |     // ShaderFrog shader 2 (oily effect)
77 |     var materialBottom = runtime.get( shaders[ 1 ].name );
78 |     meshBottom.material = materialBottom;
79 | });
80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | var runtime = new ShaderFrogRuntime(), 2 | width = 800, height = 600, 3 | clock = new THREE.Clock(), 4 | camera, cubeCamera, scene, renderer, meshTop, meshBottom, cubeCamera, leftSphere, rightSphere; 5 | 6 | // Load multiple ShaderFrog shaders 7 | runtime.load([ 8 | 'http://andrewray.me/stuff/Reflection_Cube_Map.json', 9 | 'https://s3-us-west-1.amazonaws.com/shader-frog/example/Water_or_Oil.json' 10 | ], function( shaders ) { 11 | 12 | // `shaders` will be an array with the material data in the same order you 13 | // specified when calling `load 14 | 15 | // Get the Three.js material you can assign to your objects 16 | // ShaderFrog shader 1 (reflection effect) 17 | var materialTop = runtime.get( shaders[ 0 ].name ); 18 | 19 | // You set uniforms the same way as a regular THREE.js shader. In this 20 | // case, the shader uses a cube camera for reflection, so we have to set 21 | // its value to the renderTarget of a cubeCamera we create 22 | THREE.ImageUtils.crossOrigin = ''; 23 | var urls = [ 'posx', 'negx', 'posy', 'negy', 'posz', 'negz' ].map( ( url ) => { 24 | return 'https://s3-us-west-1.amazonaws.com/shader-frog/' + url + '_lanc_chapel.jpg'; 25 | }); 26 | materialTop.uniforms.iChannel0.value = THREE.ImageUtils.loadTextureCube( urls ); 27 | meshTop.material = materialTop; 28 | 29 | // ShaderFrog shader 2 (oily effect) 30 | var materialBottom = runtime.get( shaders[ 1 ].name ); 31 | meshBottom.material = materialBottom; 32 | }); 33 | 34 | init(); 35 | animate(); 36 | 37 | function init() { 38 | 39 | scene = new THREE.Scene(); 40 | 41 | // Cameras 42 | camera = new THREE.PerspectiveCamera( 50, width / height, 1, 10000 ); 43 | camera.position.z = 100; 44 | scene.add( camera ); 45 | runtime.registerCamera( camera ); 46 | 47 | cubeCamera = new THREE.CubeCamera( 0.1, 10000, 128 ); 48 | scene.add( cubeCamera ); 49 | 50 | // Main object 51 | var topGeometry = new THREE.SphereGeometry( 20, 10, 10 ); 52 | meshTop = new THREE.Mesh( topGeometry ); 53 | meshTop.position.y = 20; 54 | scene.add( meshTop ); 55 | 56 | // Second main object 57 | var bottomGeometry = new THREE.SphereGeometry( 20, 10, 10 ); 58 | meshBottom = new THREE.Mesh( bottomGeometry ); 59 | meshBottom.position.y = -20; 60 | scene.add( meshBottom ); 61 | 62 | // Decorative objects 63 | var other = new THREE.SphereGeometry( 10, 20, 50, 50 ); 64 | leftSphere = new THREE.Mesh(other, new THREE.MeshBasicMaterial({ 65 | color: 0xff0000 66 | })); 67 | leftSphere.position.x -= 45; 68 | scene.add(leftSphere); 69 | 70 | var cyl = new THREE.CylinderGeometry( 1, 1, 100, 5 ); 71 | var cylMesh = new THREE.Mesh(cyl, new THREE.MeshBasicMaterial({ 72 | color: 0x0044ff 73 | })); 74 | cylMesh.position.x += 45; 75 | scene.add(cylMesh); 76 | 77 | var cyl2 = new THREE.CylinderGeometry( 1, 1, 100, 5 ); 78 | var cylMesh2 = new THREE.Mesh(cyl2, new THREE.MeshBasicMaterial({ 79 | color: 0x0044ff 80 | })); 81 | cylMesh2.position.x -= 45; 82 | scene.add(cylMesh2); 83 | 84 | var other2 = new THREE.SphereGeometry( 10, 20, 50, 50 ); 85 | rightSphere = new THREE.Mesh(other2, new THREE.MeshBasicMaterial({ 86 | color: 0x00ff00 87 | })); 88 | rightSphere.position.x += 45; 89 | scene.add(rightSphere); 90 | 91 | renderer = new THREE.WebGLRenderer(); 92 | renderer.setSize( width, height ); 93 | 94 | document.getElementById( 'canvas' ).appendChild( renderer.domElement ); 95 | 96 | } 97 | 98 | function animate() { 99 | 100 | requestAnimationFrame( animate ); 101 | render(); 102 | 103 | } 104 | 105 | function render() { 106 | 107 | meshTop.rotation.x += 0.01; 108 | meshTop.rotation.y += 0.02; 109 | 110 | var time = clock.getElapsedTime(); 111 | 112 | runtime.updateShaders( time ); 113 | 114 | leftSphere.position.y = 40 * Math.sin( new Date() * 0.001 ); 115 | rightSphere.position.y = -40 * Math.sin( new Date() * 0.001 ); 116 | 117 | meshTop.visible = false; 118 | cubeCamera.updateCubeMap( renderer, scene ); 119 | meshTop.visible = true; 120 | 121 | renderer.render( scene, camera ); 122 | 123 | } 124 | -------------------------------------------------------------------------------- /THREE_SHADER_FORMAT.md: -------------------------------------------------------------------------------- 1 | # Problem 2 | 3 | THREE.js does not have a defined shader format for sharing raw GLSL shader code to load into a [`RawShaderMaterial`](http://threejs.org/docs/#Reference/Materials/RawShaderMaterial). This format is also what is currently used by the [ShaderFrog](http://shaderfrog.com) runtime, found in this repository. [ShaderFrog](http://shaderfrog.com) requires a format to make shaders portable to the end user. This document suggests such a format. 4 | 5 | This proposed format has some main pragmatic goals: 6 | - Provide a way to identify a shader when used in the end user's application; 7 | - Document the uniforms and their types used in the shader. This prevents the end user needing to duplicate uniform types in their implementation of the shader. For example, a user having to type `material.uniforms.color = { type: 'c' }` is redundant, since [ShaderFrog](http://shaderfrog.com) already knows that the `color` uniform is a [`THREE.Color`](http://threejs.org/docs/#Reference/Math/Color). 8 | 9 | # Proposed Format 10 | 11 | An incomplete example can be found at [Reflection_Cube_Map.json](https://github.com/AndrewRayCode/ShaderFrog-Runtime/blob/master/example/Reflection_Cube_Map.json). The format is described with comments below. 12 | 13 | The suggested format is a valid JSON file. It contains no executable Javascript as is not loaded via JSONP. Example data is included with each key as well as if the key is required or not. 14 | 15 | #### Identifier 16 | 17 | (optional int) "id": 84, 18 | (required string) "name": "Reflection Cube Map", 19 | 20 | At runtime, [ShaderFrog](http://shaderfrog.com) needs a way to identifier shaders. The `id` is provided for convenience in case the user has to load different shaders that have the same name (unlikely). 21 | 22 | #### Raw GLSL code 23 | 24 | (required string) "fragment": "precision highp float;\n...", 25 | (required string) "vertex": "precision highp float;\n...", 26 | 27 | Three.js [`RawShaderMaterial`](http://threejs.org/docs/#Reference/Materials/RawShaderMaterial) does not have a default fragment nor vertex program, so both programs are required. 28 | 29 | #### Uniforms 30 | 31 | Uniforms are stored as a dictionary with the uniform name as the key. 32 | 33 | "uniforms": { ... } 34 | 35 | An example uniform: 36 | 37 | "cameraPosition": { 38 | (required string) "type": "v3", 39 | (required any) "value": serialized default value, 40 | (optional string) "glslType": "vec3", 41 | (optional string) "name": "cameraPosition", 42 | (optional string) "description": "A description" 43 | }, 44 | 45 | Both the `type` and the `glslType` are known to [ShaderFrog](http://shaderfrog.com) when editing the shader. Three.js requires passing `glslType` in the [`RawShaderMaterial`](http://threejs.org/docs/#Reference/Materials/RawShaderMaterial) uniform, as in `material.uniforms.num = { type: 'f', value: 0 };` 46 | 47 | The `type` is provided as a convenience, and is specific to Three.js. For example, in a shader, a `vec3` is always a `vec3`. In Three.js you can pass in either a [Three.Color](http://threejs.org/docs/#Reference/Math/Color) or a [Three.Vector3](http://threejs.org/docs/#Reference/Math/Vector3). In the user's application, this matters because if you need to manipulate the uniform at runtime, colors and vector3s have different convenience methods for manipulation. 48 | 49 | #### Uniform Value 50 | 51 | Shaders should ship with default values to minimize the amount of code the user has to write. Coming from [ShaderFrog](http://shaderfrog.com), default values exist for every uniform. **This requires serialization of Three.js types**. Below is an incomplete list of mappings to Three.js types. 52 | 53 | **vec3 (THREE.Color)** 54 | 55 | "value": { 56 | "r": 1, 57 | "g": 1, 58 | "b": 1 59 | }, 60 | 61 | **vec2 (THREE.Vector2)** 62 | 63 | "value": { 64 | "x": 0, 65 | "y": 0 66 | }, 67 | 68 | **vec3 (THREE.Vector3)** 69 | 70 | "value": { 71 | "x": 0, 72 | "y": 0, 73 | "z": 0 74 | }, 75 | 76 | **vec4 (THREE.Vector4)** 77 | 78 | "value": { 79 | "x": 0, 80 | "y": 0, 81 | "z": 0, 82 | "w": 0 83 | }, 84 | 85 | **samplerCube (Three.TextureCube)** often used for reflection shaders (incomplete) 86 | 87 | "value": { 88 | "image": "nissi_beach.jpg", 89 | "name": "Nissi Beach", 90 | "description": "Nissi Beach", 91 | "isSamplerCube": true 92 | }, 93 | 94 | **samplerCube (Three.CubeCamera)** for real-time reflections 95 | 96 | "value": { 97 | "isCubeCamera": true 98 | }, 99 | 100 | **sampler2D (Three.Texture)** 101 | 102 | "value": { 103 | "image": "http://full-path-to-img.extension", 104 | }, 105 | 106 | **mat3, mat4** 107 | 108 | TODO 109 | 110 | The following use literals in JSON to represent their values: 111 | 112 | - float (Number) 113 | - int (Number) 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ShaderFrog logo](https://s3-us-west-1.amazonaws.com/shader-frog/shader-frog-matte-black.png) 2 | 3 | **Please file [ShaderFrog.com](http://shaderfrog.com) bugs here!** 4 | 5 | # Deprecation Notice: State of ShaderFrog 6 | 7 | The current ShaderFrog site and engine is in maintenence mode only. The core 8 | engine and compiler are buggy and I haven't actively developed on the current 9 | version of ShaderFrog in several years now. Due to buggy and fundamentally 10 | flawed core nature of ShaderFrog, I'm no longer actively working on it. I 11 | apologize for any frustration the bugs or lack of support have caused to your 12 | development process. 13 | 14 | Instead, I'm working on a new version of the algorithm, Shaderfrog 2.0, starting with a 15 | ground up build of a [GLSL compiler](https://www.npmjs.com/package/@shaderfrog/glsl-parser). I post updates when I have them [as @andrewray on Twitter](https://twitter.com/andrewray) 16 | if you want to follow along. There is no ETA on a new launch date. As of October 17 | 2021, I've made significant progress on ShaderFrog 2.0, and I hope to keep 18 | posting regular updates and share demos as soon as I have them. 19 | 20 | # ShaderFrog Runtime Library 21 | 22 | This is a utility library to load and update [ShaderFrog.com](http://shaderfrog.com) shaders into your THREE.js scene or application. 23 | 24 | ## Demo 25 | 26 | [**Online Demo**](http://shaderfrog.com/runtime/index.html) 27 | 28 | Demo source found in the [example/](https://github.com/AndrewRayCode/ShaderFrog-Runtime/tree/master/example) folder. 29 | 30 | ## Usage 31 | 32 | ### npm 33 | 34 | npm install --save shaderfrog-runtime 35 | 36 | var ShaderFrogRuntime = require( 'shaderfrog-runtime' ): 37 | 38 | ### Vanilla JavaScript 39 | 40 | Download the [built Javascript file](http://shaderfrog.com/runtime/shaderfrog-runtime.min.js) and include it in your project *after* THREE.js: 41 | 42 | 43 | 44 | ### Instantiation 45 | 46 | Instantiate a new runtime: 47 | 48 | var runtime = new ShaderFrogRuntime(); 49 | 50 | Instantiate a new clock: 51 | 52 | var clock = new THREE.Clock(); 53 | 54 | Load your desired shader, and assign it to a material: 55 | 56 | runtime.load( 'Your_Shader.json', function( shaderData ) { 57 | 58 | // Get the Three.js material you can assign to your objects 59 | var material = runtime.get( shaderData.name ); 60 | 61 | // Assign it to your objects 62 | mesh.material = material; 63 | }); 64 | 65 | In your initialization code, register the main camera, which is the one that you call `renderer.render( scene, camera )` with: 66 | 67 | runtime.registerCamera( camera ); 68 | 69 | This tells the ShaderFrog runtime how to update the `cameraPosition` uniform, which some shaders use to calculate things based on the camera, like reflection. 70 | 71 | In your animation loop, update the running shaders that the ShaderFrog runtime knows about: 72 | 73 | runtime.updateShaders( clock.getElapsedTime() ); 74 | 75 | A full example can be found in the [example/](https://github.com/AndrewRayCode/ShaderFrog-Runtime/tree/master/example) folder. 76 | 77 | ## API 78 | 79 | **Warning:** This API is volatile and subject to change in future versions. 80 | 81 | #### `runtime.registerCamera( THREE.Camera camera )` 82 | 83 | Tells the runtime to use this camera's position for the default `cameraPosition` uniform. This uniform is normally passed by default in THREE.js to shader materials, but ShaderFrog shaders use the RawSahderMaterial class. 84 | 85 | #### `runtime.updateShaders( Float time )` 86 | 87 | Update uniform values for shaders, specifically `float time`, `vec3 cameraPosition`, and `mat4 viewMatrix`. The only uniform the runtime cannot define is `time` which should be provided by the elapsed time in milliseconds. [THREE.Clock.getElapsedTime()](http://threejs.org/docs/#Reference/Core/Clock) provies this value. 88 | 89 | #### `runtime.load( [ String source | Array sources ], Function callback )` 90 | 91 | Call this function with either: 92 | 93 | runtime.load( 'material.json', function( material ) ) { 94 | var shader = runtime.get( material.name ); ... 95 | } 96 | 97 | ...for one material, or... 98 | 99 | runtime.load( [ 'material1.json', 'material2.json' ], function( materials ) ) { 100 | var shader = runtime.get( materials[ 0 ].name ); ... 101 | } 102 | 103 | Load the specified URLs and parse them into materials. If you pass in an array of URLs, the callback receives an array of materials in the same order you specified. 104 | 105 | #### `runtime.add( String name, Object shader )` 106 | 107 | If your shader data is already loaded in JSON form by some other means, you can add it to the runtime's repository of known shaders with this method. 108 | 109 | #### `runtime.get( String name )` 110 | 111 | The ShaderFrog runtime stores materials by name. This function returns a **new instance** of the material you have loaded. You can assign this new material to your object, update uniforms on it, etc. 112 | 113 | ## Proposed Shader Format 114 | 115 | [ShaderFrog](http://shaderfrog.com) requires a shader file format to transfer all neccessary shader data from the editor to the end user. A proposed JSON format is discussed in [THREE_SHADER_FORMAT.md](https://github.com/AndrewRayCode/ShaderFrog-Runtime/blob/master/THREE_SHADER_FORMAT.md). 116 | 117 | ## Development 118 | 119 | To install the dependencies: 120 | 121 | git clone https://github.com/AndrewRayCode/ShaderFrog-Runtime 122 | npm install 123 | 124 | To build the distributable Javascript file: 125 | 126 | npm run build 127 | -------------------------------------------------------------------------------- /src/ShaderRuntime.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | let defaultThreeUniforms = [ 4 | 'normalMatrix', 'viewMatrix', 'projectionMatrix', 'position', 'normal', 5 | 'modelViewMatrix', 'uv', 'uv2', 'modelMatrix' 6 | ]; 7 | 8 | function ShaderRuntime() {} 9 | 10 | ShaderRuntime.prototype = { 11 | 12 | mainCamera: null, 13 | cubeCameras: {}, 14 | 15 | reserved: { time: null, cameraPosition: null }, 16 | 17 | umap: { 18 | float: { type: 'f', value: 0 }, 19 | int: { type: 'i', value: 0 }, 20 | vec2: { type: 'v2', value() { return new THREE.Vector2(); } }, 21 | vec3: { type: 'v3', value() { return new THREE.Vector3(); } }, 22 | vec4: { type: 'v4', value() { return new THREE.Vector4(); } }, 23 | samplerCube: { type: 't' }, 24 | sampler2D: { type: 't' } 25 | }, 26 | 27 | getUmap( type ) { 28 | let value = this.umap[ type ].value; 29 | return typeof value === 'function' ? value() : value; 30 | }, 31 | 32 | load( sourceOrSources, callback ) { 33 | 34 | let sources = sourceOrSources, 35 | onlyOneSource = typeof sourceOrSources === 'string'; 36 | 37 | if( onlyOneSource ) { 38 | sources = [ sourceOrSources ]; 39 | } 40 | 41 | let loadedShaders = new Array( sources.length ), 42 | itemsLoaded = 0; 43 | 44 | let loadSource = ( index, source ) => { 45 | 46 | let loader = new THREE.XHRLoader(); 47 | loader.load( source, ( json ) => { 48 | 49 | let parsed; 50 | try { 51 | parsed = JSON.parse( json ); 52 | delete parsed.id; // Errors if passed to rawshadermaterial :( 53 | } catch( e ) { 54 | throw new Error( 'Could not parse shader' + source + '! Please verify the URL is correct.' ); 55 | } 56 | this.add( parsed.name, parsed ); 57 | loadedShaders[ index ] = parsed; 58 | 59 | if( ++itemsLoaded === sources.length ) { 60 | callback( onlyOneSource ? loadedShaders[ 0 ] : loadedShaders ); 61 | } 62 | 63 | }); 64 | }; 65 | 66 | for( let x = 0; x < sources.length; x++ ) { 67 | loadSource( x, sources[ x ] ); 68 | } 69 | 70 | }, 71 | 72 | registerCamera( camera ) { 73 | 74 | if( !( camera instanceof THREE.Camera ) ) { 75 | throw new Error( 'Cannot register a non-camera as a camera!' ); 76 | } 77 | 78 | this.mainCamera = camera; 79 | 80 | }, 81 | 82 | registerCubeCamera( name, camera ) { 83 | 84 | if( !camera.renderTarget ) { 85 | throw new Error( 'Cannot register a non-camera as a camera!' ); 86 | } 87 | 88 | this.cubeCameras[ name ] = camera; 89 | 90 | }, 91 | 92 | unregisterCamera( name ) { 93 | 94 | if( name in this.cubeCameras ) { 95 | 96 | delete this.cubeCameras[ name ]; 97 | 98 | } else if( name === this.mainCamera ) { 99 | 100 | delete this.mainCamera; 101 | 102 | } else { 103 | 104 | throw new Error( 'You never registered camera ' + name ); 105 | 106 | } 107 | 108 | }, 109 | 110 | updateSource( identifier, config, findBy ) { 111 | 112 | findBy = findBy || 'name'; 113 | 114 | if( !this.shaderTypes[ identifier ] ) { 115 | throw new Error( 'Runtime Error: Cannot update shader ' + identifier + ' because it has not been added.' ); 116 | } 117 | 118 | let newShaderData = this.add( identifier, config ), 119 | shader, x; 120 | 121 | for( x = 0; shader = this.runningShaders[ x++ ]; ) { 122 | if( shader[ findBy ] === identifier ) { 123 | extend( shader.material, omit( newShaderData, 'id' ) ); 124 | shader.material.needsUpdate = true; 125 | } 126 | } 127 | 128 | }, 129 | 130 | renameShader( oldName, newName ) { 131 | 132 | let x, shader; 133 | 134 | if( !( oldName in this.shaderTypes ) ) { 135 | throw new Error('Could not rename shader ' + oldName + ' to ' + newName + '. It does not exist.'); 136 | } 137 | 138 | this.shaderTypes[ newName ] = this.shaderTypes[ oldName ]; 139 | delete this.shaderTypes[ oldName ]; 140 | 141 | for( x = 0; shader = this.runningShaders[ x++ ]; ) { 142 | if( shader.name === oldName ) { 143 | shader.name = newName; 144 | } 145 | } 146 | 147 | }, 148 | 149 | get( identifier ) { 150 | 151 | let shaderType = this.shaderTypes[ identifier ]; 152 | 153 | if( !shaderType.initted ) { 154 | 155 | this.create( identifier ); 156 | } 157 | 158 | return shaderType.material; 159 | 160 | }, 161 | 162 | add( shaderName, config ) { 163 | 164 | let newData = clone( config ), 165 | uniform; 166 | newData.fragmentShader = config.fragment; 167 | newData.vertexShader = config.vertex; 168 | delete newData.fragment; 169 | delete newData.vertex; 170 | 171 | for( var uniformName in newData.uniforms ) { 172 | uniform = newData.uniforms[ uniformName ]; 173 | if( uniform.value === null ) { 174 | newData.uniforms[ uniformName ].value = this.getUmap( uniform.glslType ); 175 | } 176 | } 177 | 178 | if( shaderName in this.shaderTypes ) { 179 | // maybe not needed? too sleepy, need document 180 | extend( this.shaderTypes[ shaderName ], newData ); 181 | } else { 182 | this.shaderTypes[ shaderName ] = newData; 183 | } 184 | 185 | return newData; 186 | 187 | }, 188 | 189 | create( identifier ) { 190 | 191 | let shaderType = this.shaderTypes[ identifier ]; 192 | let keys = Object.keys( shaderType ); 193 | 194 | // Three's shadermaterial id is not assignable, so filter it out 195 | let withoutId = {}; 196 | for( let i = 0; i < keys.length; i++ ) { 197 | if( keys[ i ] !== 'id' ) { 198 | withoutId[ keys[ i ] ] = shaderType[ keys[ i ] ]; 199 | } 200 | } 201 | 202 | shaderType.material = new THREE.RawShaderMaterial( withoutId ); 203 | 204 | this.runningShaders.push( shaderType ); 205 | 206 | shaderType.init && shaderType.init( shaderType.material ); 207 | shaderType.material.needsUpdate = true; 208 | 209 | shaderType.initted = true; 210 | 211 | return shaderType.material; 212 | 213 | }, 214 | 215 | updateRuntime( identifier, data, findBy ) { 216 | 217 | findBy = findBy || 'name'; 218 | 219 | let shader, x, uniformName, uniform; 220 | 221 | // This loop does not appear to be a slowdown culprit 222 | for( x = 0; shader = this.runningShaders[ x++ ]; ) { 223 | if( shader[ findBy ] === identifier ) { 224 | for( uniformName in data.uniforms ) { 225 | 226 | if( uniformName in this.reserved ) { 227 | continue; 228 | } 229 | 230 | if( uniformName in shader.material.uniforms ) { 231 | 232 | uniform = data.uniforms[ uniformName ]; 233 | 234 | // this is nasty, since the shader serializes 235 | // CubeCamera model to string. Maybe not update it at 236 | // all? 237 | if( uniform.type === 't' && typeof uniform.value === 'string' ) { 238 | uniform.value = this.cubeCameras[ uniform.value ].renderTarget; 239 | } 240 | 241 | shader.material.uniforms[ uniformName ].value = data.uniforms[ uniformName ].value; 242 | } 243 | } 244 | } 245 | } 246 | 247 | }, 248 | 249 | // Update global shader uniform values 250 | updateShaders( time, obj ) { 251 | 252 | let shader, x; 253 | 254 | obj = obj || {}; 255 | 256 | for( x = 0; shader = this.runningShaders[ x++ ]; ) { 257 | 258 | for( let uniform in obj.uniforms ) { 259 | if( uniform in shader.material.uniforms ) { 260 | shader.material.uniforms[ uniform ].value = obj.uniforms[ uniform ]; 261 | } 262 | } 263 | 264 | if( 'cameraPosition' in shader.material.uniforms && this.mainCamera ) { 265 | 266 | shader.material.uniforms.cameraPosition.value = this.mainCamera.position.clone(); 267 | 268 | } 269 | 270 | if( 'viewMatrix' in shader.material.uniforms && this.mainCamera ) { 271 | 272 | shader.material.uniforms.viewMatrix.value = this.mainCamera.matrixWorldInverse; 273 | 274 | } 275 | 276 | if( 'time' in shader.material.uniforms ) { 277 | 278 | shader.material.uniforms.time.value = time; 279 | 280 | } 281 | 282 | } 283 | 284 | }, 285 | 286 | shaderTypes: {}, 287 | 288 | runningShaders: [] 289 | 290 | }; 291 | 292 | // Convenience methods so we don't have to include underscore 293 | function extend() { 294 | let length = arguments.length, 295 | obj = arguments[ 0 ]; 296 | 297 | if( length < 2 ) { 298 | return obj; 299 | } 300 | 301 | for( let index = 1; index < length; index++ ) { 302 | let source = arguments[ index ], 303 | keys = Object.keys( source || {} ), 304 | l = keys.length; 305 | for( let i = 0; i < l; i++ ) { 306 | let key = keys[i]; 307 | obj[ key ] = source[ key ]; 308 | } 309 | } 310 | 311 | return obj; 312 | } 313 | 314 | function clone( obj ) { 315 | return extend( {}, obj ); 316 | } 317 | 318 | function omit( obj, ...keys ) { 319 | let cloned = clone( obj ), x, key; 320 | for( x = 0; key = keys[ x++ ]; ) { 321 | delete cloned[ key ]; 322 | } 323 | return cloned; 324 | } 325 | 326 | export default ShaderRuntime; 327 | --------------------------------------------------------------------------------