├── .gitignore ├── src └── js │ ├── main.js │ └── structure │ ├── tools │ ├── gui.js │ ├── stats.js │ └── orbit.js │ ├── user.js │ ├── config │ ├── tools.js │ ├── settings.js │ └── defaults.js │ ├── environment.js │ ├── render.js │ └── prepare.js ├── .eslintrc ├── .editorconfig ├── index.html ├── package.json ├── webpack.config.js ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import prepare from './structure/prepare'; 2 | 3 | prepare(); 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "env": { 4 | "browser": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true; 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_spaces = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /src/js/structure/tools/gui.js: -------------------------------------------------------------------------------- 1 | import dat from 'dat.gui'; 2 | 3 | import tools from '../config/tools'; 4 | 5 | /** 6 | * Add the GUI. 7 | */ 8 | export default () => { 9 | tools.gui = new dat.GUI(); 10 | tools.gui.close(); 11 | }; 12 | -------------------------------------------------------------------------------- /src/js/structure/tools/stats.js: -------------------------------------------------------------------------------- 1 | import Stats from 'stats.js'; 2 | 3 | import tools from '../config/tools'; 4 | 5 | /** 6 | * Add the FPS meter. 7 | */ 8 | export default () => { 9 | tools.stats = new Stats(); 10 | tools.stats.showPanel(0); 11 | document.body.appendChild(tools.stats.dom); 12 | }; 13 | -------------------------------------------------------------------------------- /src/js/structure/user.js: -------------------------------------------------------------------------------- 1 | import settings from './config/settings'; 2 | 3 | import addGUI from './tools/gui'; 4 | import addOrbitControls from './tools/orbit'; 5 | import addStats from './tools/stats'; 6 | 7 | export default () => { 8 | if (settings.user.gui) addGUI(); 9 | if (settings.user.orbit) addOrbitControls(); 10 | if (settings.user.stats) addStats(); 11 | }; 12 | -------------------------------------------------------------------------------- /src/js/structure/tools/orbit.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import props from '../config/defaults'; 4 | import tools from '../config/tools'; 5 | 6 | const OrbitControls = require('three-orbit-controls')(THREE); 7 | 8 | /** 9 | * Add orbit controls. 10 | */ 11 | export default () => { 12 | tools.orbit = new OrbitControls(props.camera, props.renderer.domElement); 13 | tools.orbit.enableZoom = true; 14 | }; 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGL Structure 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/js/structure/config/tools.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | /** 4 | * A lightweight graphical user interface for changing variables in JavaScript. 5 | * 6 | * @type {dat.GUI} 7 | * @see https://github.com/dataarts/dat.gui 8 | */ 9 | gui: {}, 10 | 11 | /** 12 | * This set of controls performs orbiting, dollying (zooming), and panning. 13 | * 14 | * @type {THREE.OrbitControls} 15 | * @see https://github.com/mrdoob/three.js/blob/master/examples/js/controls/OrbitControls.js 16 | */ 17 | orbit: {}, 18 | 19 | /** 20 | * This class provides a simple info box that will help you monitor your code performance. 21 | * 22 | * @type {Stats} 23 | * @see https://github.com/mrdoob/stats.js/ 24 | */ 25 | stats: {}, 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /src/js/structure/config/settings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * Give the user some powers (or not). 4 | */ 5 | user: { 6 | gui: false, 7 | orbit: false, 8 | stats: false, 9 | }, 10 | 11 | /** 12 | * Set the camera properties. 13 | */ 14 | camera: { 15 | fov: 45, 16 | aspect: window.innerWidth / window.innerHeight, 17 | near: 1, 18 | far: 3500, 19 | }, 20 | 21 | /** 22 | * Background color used on draw (false for transparent). 23 | */ 24 | backgroundColor: 0xEFEFEF, 25 | 26 | /** 27 | * Set a default pixel ratio (1 for more performance). 28 | */ 29 | defaultPixelRatio: window.devicePixelRatio, 30 | 31 | /** 32 | * Check for mobile device (for devicePixelRatio or less intensive stuff). 33 | */ 34 | mobile: (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)), 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl-structure", 3 | "version": "1.0.0", 4 | "description": "A modern structure for WebGL based projects.", 5 | "scripts": { 6 | "development": "webpack --watch | webpack-dev-server --port 3000 --hot --host 0.0.0.0", 7 | "production": "NODE_ENV=production webpack" 8 | }, 9 | "author": "Colin van Eenige", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "babel-core": "^6.21.0", 13 | "babel-loader": "^6.2.10", 14 | "babel-preset-es2015": "^6.18.0", 15 | "eslint": "^3.12.2", 16 | "eslint-config-airbnb-base": "^11.0.0", 17 | "eslint-plugin-import": "^2.2.0", 18 | "webpack": "^2.2.1", 19 | "webpack-dev-server": "^2.3.0" 20 | }, 21 | "dependencies": { 22 | "dat.gui": "^0.6.1", 23 | "stats.js": "^0.17.0", 24 | "three": "^0.84.0", 25 | "three-orbit-controls": "^82.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/js/structure/config/defaults.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | /** 4 | * Scenes allow you to set up what and where is to be rendered by three.js. 5 | * This is where you place objects, lights and cameras. 6 | * 7 | * @type {THREE.Scene} 8 | * @see https://threejs.org/docs/#Reference/Scenes/Scene 9 | */ 10 | scene: {}, 11 | 12 | /** 13 | * The WebGL renderer displays your beautifully crafted scenes using WebGL. 14 | * 15 | * @type {THREE.WebGLRenderer} 16 | * @see https://threejs.org/docs/#Reference/Renderers/WebGLRenderer 17 | */ 18 | renderer: {}, 19 | 20 | /** 21 | * Camera with perspective projection. 22 | * 23 | * @type {THREE.PerspectiveCamera} 24 | * @see https://threejs.org/docs/#Reference/Cameras/PerspectiveCamera 25 | */ 26 | camera: {}, 27 | 28 | /** 29 | * Object to store the environment in. 30 | */ 31 | structure: {}, 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | const production = process.env.NODE_ENV === 'production'; 4 | 5 | module.exports = { 6 | entry: `${__dirname}/src/js/main.js`, 7 | output: { 8 | path: `${__dirname}/dist/js/`, 9 | filename: 'final.js', 10 | }, 11 | module: { 12 | loaders: [ 13 | { 14 | test: /\.js$/, 15 | exclude: `${__dirname}/node_modules/`, 16 | loader: 'babel-loader', 17 | query: { 18 | presets: ['es2015'], 19 | cacheDirectory: true, 20 | }, 21 | }, 22 | ], 23 | }, 24 | plugins: production ? [ 25 | new webpack.optimize.UglifyJsPlugin({ 26 | minimize: true, 27 | compress: { warnings: false }, 28 | }), 29 | ] : [], 30 | resolve: { 31 | modules: [`${__dirname}/node_modules/`], 32 | alias: { 33 | 'dat.gui': 'dat.gui/build/dat.gui', 34 | }, 35 | }, 36 | stats: { 37 | colors: true, 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /src/js/structure/environment.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import props from './config/defaults'; 4 | 5 | /** 6 | * Build the environment. 7 | */ 8 | export default () => { 9 | const lights = []; 10 | lights[0] = new THREE.PointLight(0xffffff, 0.2, 0); 11 | lights[1] = new THREE.PointLight(0xffffff, 0.2, 0); 12 | lights[2] = new THREE.PointLight(0xffffff, 0.2, 0); 13 | 14 | lights[0].position.set(0, 200, 0); 15 | lights[1].position.set(100, 200, 100); 16 | lights[2].position.set(-100, -200, -100); 17 | 18 | props.scene.add(lights[0]); 19 | props.scene.add(lights[1]); 20 | props.scene.add(lights[2]); 21 | 22 | const geometry = new THREE.BoxGeometry(5, 5, 5); 23 | const material = new THREE.MeshPhongMaterial({ 24 | color: 0xFFC107, 25 | emissive: 0x00BFA5, 26 | side: THREE.DoubleSide, 27 | shading: THREE.FlatShading, 28 | }); 29 | 30 | props.structure.cube = new THREE.Mesh(geometry, material); 31 | props.scene.add(props.structure.cube); 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Colin van Eenige 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/js/structure/render.js: -------------------------------------------------------------------------------- 1 | import props from './config/defaults'; 2 | import settings from './config/settings'; 3 | import tools from './config/tools'; 4 | 5 | /** 6 | * Update the environment. 7 | */ 8 | const updateEnvironment = () => { 9 | props.structure.cube.rotation.x += 0.01; 10 | props.structure.cube.rotation.z += 0.01; 11 | }; 12 | 13 | /** 14 | * Render function called on every frame. 15 | */ 16 | export default function render() { 17 | // Begin frame analysis 18 | if (settings.user.stats) tools.stats.begin(); 19 | 20 | // Update optional background color 21 | if (settings.backgroundColor !== false) props.renderer.setClearColor(settings.backgroundColor); 22 | 23 | // Update the environment 24 | updateEnvironment(); 25 | 26 | // End frame analysis 27 | if (settings.user.stats) tools.stats.end(); 28 | 29 | // Request the next frame 30 | requestAnimationFrame(render); 31 | 32 | // Render the current frame 33 | props.renderer.render(props.scene, props.camera); 34 | } 35 | 36 | /** 37 | * Update the camera and renderer based on window size. 38 | */ 39 | const windowResizeHandler = () => { 40 | props.camera.aspect = window.innerWidth / window.innerHeight; 41 | props.camera.updateProjectionMatrix(); 42 | props.renderer.setSize(window.innerWidth, window.innerHeight); 43 | }; 44 | 45 | window.addEventListener('resize', windowResizeHandler, false); 46 | -------------------------------------------------------------------------------- /src/js/structure/prepare.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import props from './config/defaults'; 4 | import settings from './config/settings'; 5 | 6 | import createEnvironment from './environment'; 7 | import createUser from './user'; 8 | import render from './render'; 9 | 10 | /** 11 | * Create scene of type THREE.scene. 12 | */ 13 | const createScene = () => { 14 | props.scene = new THREE.Scene(); 15 | }; 16 | 17 | /** 18 | * Create renderer of type THREE.WebGLRenderer. 19 | */ 20 | const createRenderer = () => { 21 | // Create the renderer 22 | props.renderer = new THREE.WebGLRenderer({ 23 | // Canvas background will be transparent if no backgroundColor is set 24 | alpha: !settings.backgroundColor, 25 | }); 26 | 27 | // Set the pixel ratio for detail or performance 28 | props.renderer.setPixelRatio(settings.mobile ? 1 : settings.defaultPixelRatio); 29 | 30 | // Set the size of the render to fit the window 31 | props.renderer.setSize(window.innerWidth, window.innerHeight); 32 | 33 | // TODO: Check for which cases this is necessary 34 | props.renderer.gammaInput = true; 35 | 36 | // TODO: Check for which cases this is necessary 37 | props.renderer.gammaOutput = true; 38 | 39 | // Append the render canvas to the DOM 40 | document.body.appendChild(props.renderer.domElement); 41 | }; 42 | 43 | /** 44 | * Create camera of type THREE.PerspectiveCamera. 45 | */ 46 | const createCamera = () => { 47 | // Create the camera 48 | props.camera = new THREE.PerspectiveCamera( 49 | settings.camera.fov, 50 | settings.camera.aspect, 51 | settings.camera.near, 52 | settings.camera.far, 53 | ); 54 | 55 | // Set camera position 56 | props.camera.position.set(0, 35, 0); 57 | 58 | // Set camera target position 59 | props.camera.lookAt(new THREE.Vector3(0, 0, 0)); 60 | }; 61 | 62 | /** 63 | * Create all necessary parts of the architecture and start building. 64 | */ 65 | export default () => { 66 | createScene(); 67 | createRenderer(); 68 | createCamera(); 69 | 70 | createEnvironment(); 71 | createUser(); 72 | render(); 73 | }; 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern WebGL Structure 2 | 3 | During recent work related and personal projects I've been playing a lot with WebGL. Getting more and more involved with ES6 code I wanted to create a modern structure to easily configure a WebGL workflow and create a prototype without the need of doing the same setup over and over again. 4 | 5 | In January of 2017 I've been working on expanding my WebGL knowledge and created several experiments on [Codepen](http://codepen.io/cvaneenige/) with [THREE.js](https://threejs.org). Creating these experiments taught me a lot and they have been well-received by the community. 6 | 7 | ## Installation 8 | 9 | 1. Run `npm install`. 10 | This will install all dependencies needed for the application to run. 11 | 12 | 2. Run `npm run development`. This will watch and compile your Javascript files. 13 | 14 | 3. Navigate to `localhost:3000`. You should see a moving cube! 15 | 16 | ## Usage 17 | 18 | This workflow is based on ES6 techniques and useful tools to make development of your THREE.js application as easy as possible! 19 | 20 | #### Global variables 21 | Variables in ES6 can be made easily accessible by using `imports`. Importing one of the objects below gives you the opportunity to change them in one place and use them in another (these will update automatically). 22 | 23 | * **Defaults:** Used to store all THREE.js object: scene, renderer, camera and an object for structure elements like geometries and materials. These can be used in the GUI to update them in real-time. 24 | 25 | * **Settings:** Used to store overall user settings, camera variables, canvas background color, default device pixel ratio and a check for mobile devices. 26 | 27 | * **Tools:** Used to store useful tools like a [GUI](https://github.com/dataarts/dat.gui) for changing variables in real-time, [orbit controls](https://github.com/mattdesl/three-orbit-controls) for user movement in the THREE.js world and a [stats monitor](https://github.com/mrdoob/stats.js/) to keep track of performance in the application. *These can all be turned on and off in the user settings.* 28 | 29 | #### THREE.js 30 | To create amazing things with THREE.js you should only have to focus on two things: Creating the magical environment and bringing it to life with movement. 31 | 32 | * **Create:** After creating the basic requirements to run a THREE.js world the environment is created. This environment can be stored globally and adjusted by the GUI. This can be found in `environment.js`. 33 | 34 | * **Render:** After the environment is created, and the optional tools are enabled, the render function is called and should optimally run at least 60 times a second. Besides the necessary things like updating the stats and setting a background color there's a clean function for updating the environment. This can be found in `render.js`; 35 | 36 | #### Overall Workflow 37 | To enhance the overall workflow these tools are really helpful to stay consistent with your code style and applying new techniques and standards while staying backwards compatible. 38 | 39 | * **Webpack:** Used to compile Javascript (with [Babel](https://babeljs.io)) for backwards compatibility. Running the development script watches and bundles Javascript as you develop and offers a localhost to see your application run on. Running the production script bundles the Javascript once and uglifies it to get the smallest size available. More documentation can be found [here](https://webpack.js.org). 40 | 41 | * **ESLint:** Used to maintain Javascript code standards throughout the project. This workflow uses the [Airbnb Style Guide](https://github.com/airbnb/javascript) and can be adjusted to your liking in the `.eslintrc` file. More documentation can be found [here](http://eslint.org). 42 | 43 | ## Experiments 44 | While creating these Codepen experiments this workflow improved a lot. I'd love to hear your thoughts on how to make it even better and see your creations! 45 | 46 | * [Basic Geometry Animation](http://codepen.io/cvaneenige/full/jyEPPm) 47 | 48 | * [Light Reflecting Donuts](http://codepen.io/cvaneenige/full/egNYmb/) 49 | 50 | * [Light Reflecting Monkeys](http://codepen.io/cvaneenige/full/PWqqRN) 51 | 52 | * [Particle Stream](http://codepen.io/cvaneenige/full/rjVvdM) 53 | 54 | * [Rainbow Sprinkles](http://codepen.io/cvaneenige/full/dNYqbp/) 55 | 56 | * [Combination is key](http://codepen.io/cvaneenige/full/zNqmxv/) 57 | 58 | * [Rainbow Balloons](http://codepen.io/cvaneenige/full/dNXjga/) 59 | 60 | ## License 61 | 62 | MIT 63 | --------------------------------------------------------------------------------