├── .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 |
--------------------------------------------------------------------------------