├── .gitignore ├── src ├── fonts │ └── Inter.ttf ├── images │ └── favicon.ico ├── styles │ ├── index.scss │ ├── _variables.scss │ └── _scaffolding.scss ├── index.js ├── template.html ├── render │ ├── tick-manager.js │ └── init.js └── app.js ├── .prettierrc.json ├── postcss.config.js ├── jsconfig.json ├── .babelrc.json ├── config ├── paths.js ├── webpack.dev.js ├── webpack.prod.js └── webpack.common.js ├── .eslintrc.json ├── README.md ├── LICENSE └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | /node_modules 3 | /dist -------------------------------------------------------------------------------- /src/fonts/Inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visionary-3d/three-boilerplate/HEAD/src/fonts/Inter.ttf -------------------------------------------------------------------------------- /src/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/visionary-3d/three-boilerplate/HEAD/src/images/favicon.ico -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "printWidth": 100, 6 | "semi": false 7 | } 8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-preset-env': { 4 | browsers: 'last 2 versions', 5 | }, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Inter'; 3 | src: url('../fonts/Inter.ttf'); 4 | } 5 | 6 | @import 'variables'; 7 | @import 'scaffolding'; 8 | -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | $font-size: 1rem; 2 | $font-family: 'Inter', sans-serif; 3 | $background-color: #121212; 4 | $font-color: #dae0e0; 5 | $page-width: 650px; 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | }, 7 | "allowJs": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [ 4 | "@babel/plugin-proposal-class-properties", 5 | ["@babel/transform-runtime"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import '@/styles/index.scss'; 2 | import startApp from './app'; 3 | import { initEngine } from './render/init'; 4 | 5 | (async () => { 6 | await initEngine() 7 | startApp() 8 | })() -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | // Source files 5 | src: path.resolve(__dirname, '../src'), 6 | 7 | // Production build files 8 | build: path.resolve(__dirname, '../dist'), 9 | 10 | // Static files that get copied to build folder 11 | public: path.resolve(__dirname, '../public'), 12 | } 13 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/styles/_scaffolding.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: $font-size; 3 | font-family: $font-family; 4 | background: $background-color; 5 | color: $font-color; 6 | line-height: 1.4; 7 | overflow: hidden; 8 | } 9 | 10 | body { 11 | margin: 0; 12 | padding: 0; 13 | text-align: center; 14 | max-width: $page-width; 15 | } 16 | 17 | canvas { 18 | width: 100vw; 19 | height: 100vh; 20 | position: absolute; 21 | padding: 0; 22 | margin: 0; 23 | } 24 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "prefer-template": "off", 4 | "no-var": 1, 5 | "no-unused-vars": 1, 6 | "camelcase": 1, 7 | "no-nested-ternary": 1, 8 | "no-console": 1, 9 | "no-template-curly-in-string": 1, 10 | "no-self-compare": 1, 11 | "import/prefer-default-export": 0, 12 | "arrow-body-style": 1, 13 | "import/no-extraneous-dependencies": ["off", { "devDependencies": false }] 14 | }, 15 | "ignorePatterns": ["dist", "node_modules", "webpack.*", "config/paths.js"], 16 | "env": { 17 | "browser": true, 18 | "es6": true 19 | }, 20 | "extends": ["eslint:recommended", "prettier"], 21 | "parserOptions": { 22 | "ecmaVersion": 2021, 23 | "sourceType": "module" 24 | }, 25 | "plugins": ["prettier"], 26 | "settings": { 27 | "import/resolver": { 28 | "webpack": { 29 | "config": "config/webpack.common.js" 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | 3 | const common = require('./webpack.common') 4 | 5 | module.exports = merge(common, { 6 | // Set the mode to development or production 7 | mode: 'development', 8 | 9 | // Control how source maps are generated 10 | devtool: 'inline-source-map', 11 | 12 | // Spin up a server for quick development 13 | devServer: { 14 | historyApiFallback: true, 15 | open: true, 16 | compress: true, 17 | hot: true, 18 | port: 8080, 19 | }, 20 | 21 | module: { 22 | rules: [ 23 | // Styles: Inject CSS into the head with source maps 24 | { 25 | test: /\.(sass|scss|css)$/, 26 | use: [ 27 | 'style-loader', 28 | { 29 | loader: 'css-loader', 30 | options: { sourceMap: true, importLoaders: 1, modules: false }, 31 | }, 32 | { loader: 'postcss-loader', options: { sourceMap: true } }, 33 | { loader: 'sass-loader', options: { sourceMap: true } }, 34 | ], 35 | }, 36 | ], 37 | }, 38 | }) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Three Webpack Boilerplate 2 | 3 | Three webpack 5 boilerplate using Babel, PostCSS, Sass and Gsap. 4 | 5 | ## Installation 6 | 7 | Clone this repo and npm install. 8 | 9 | ```bash 10 | npm i 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Development server 16 | 17 | ```bash 18 | npm start 19 | ``` 20 | 21 | You can view the development server at `localhost:8080`. 22 | 23 | ### Production build 24 | 25 | ```bash 26 | npm run build 27 | ``` 28 | 29 | > Note: Install [http-server](https://www.npmjs.com/package/http-server) globally to deploy a simple server. 30 | 31 | ```bash 32 | npm i -g http-server 33 | ``` 34 | 35 | You can view the deploy by creating a server in `dist`. 36 | 37 | ```bash 38 | cd dist && http-server 39 | ``` 40 | 41 | ## Features 42 | 43 | - [Three](https://threejs.org) 44 | - [Webpack](https://webpack.js.org/) 45 | - [Babel](https://babeljs.io/) 46 | - [Sass](https://sass-lang.com/) 47 | - [PostCSS](https://postcss.org/) 48 | - [Gsap](https://greensock.com/gsap/) 49 | 50 | ## License 51 | 52 | This project is open source and available under the [MIT License](LICENSE). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Tania Rascia 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. -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 2 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') 3 | const { merge } = require('webpack-merge') 4 | 5 | const paths = require('./paths') 6 | const common = require('./webpack.common') 7 | 8 | module.exports = merge(common, { 9 | mode: 'production', 10 | devtool: false, 11 | output: { 12 | path: paths.build, 13 | publicPath: '/', 14 | filename: 'js/[name].[contenthash].bundle.js', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.(sass|scss|css)$/, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | options: { 25 | importLoaders: 2, 26 | sourceMap: false, 27 | modules: false, 28 | }, 29 | }, 30 | 'postcss-loader', 31 | 'sass-loader', 32 | ], 33 | }, 34 | ], 35 | }, 36 | plugins: [ 37 | // Extracts CSS into separate files 38 | new MiniCssExtractPlugin({ 39 | filename: 'styles/[name].[contenthash].css', 40 | chunkFilename: '[id].css', 41 | }), 42 | ], 43 | optimization: { 44 | minimize: true, 45 | minimizer: [new CssMinimizerPlugin(), '...'], 46 | runtimeChunk: { 47 | name: 'runtime', 48 | }, 49 | }, 50 | performance: { 51 | hints: false, 52 | maxEntrypointSize: 512000, 53 | maxAssetSize: 512000, 54 | }, 55 | }) 56 | -------------------------------------------------------------------------------- /src/render/tick-manager.js: -------------------------------------------------------------------------------- 1 | import { useCamera, useComposer, useControls, useRenderer, useScene, useStats } from './init.js'; 2 | 3 | // animation params 4 | const localData = { 5 | timestamp: 0, 6 | timeDiff: 0, 7 | frame: null, 8 | }; 9 | const localFrameOpts = { 10 | data: localData, 11 | }; 12 | 13 | const frameEvent = new MessageEvent('tick', localFrameOpts); 14 | 15 | class TickManager extends EventTarget { 16 | constructor({ timestamp, timeDiff, frame } = localData) { 17 | super(); 18 | 19 | this.timestamp = timestamp; 20 | this.timeDiff = timeDiff; 21 | this.frame = frame; 22 | } 23 | startLoop() { 24 | const composer = useComposer(); 25 | const renderer = useRenderer(); 26 | const scene = useScene(); 27 | const camera = useCamera(); 28 | const controls = useControls(); 29 | const stats = useStats(); 30 | 31 | if (!renderer) { 32 | throw new Error('Updating Frame Failed : Uninitialized Renderer'); 33 | } 34 | 35 | let lastTimestamp = performance.now(); 36 | 37 | const animate = (timestamp, frame) => { 38 | this.timestamp = timestamp ?? performance.now(); 39 | this.timeDiff = timestamp - lastTimestamp; 40 | 41 | const timeDiffCapped = Math.min(Math.max(this.timeDiff, 0), 100); 42 | 43 | // performance tracker start 44 | 45 | controls.update(); 46 | 47 | composer.render(); 48 | // renderer.render(scene, camera); 49 | 50 | this.tick(timestamp, timeDiffCapped, frame); 51 | 52 | stats.update(); 53 | 54 | // performance tracker end 55 | }; 56 | 57 | renderer.setAnimationLoop(animate); 58 | } 59 | tick(timestamp, timeDiff, frame) { 60 | localData.timestamp = timestamp; 61 | localData.frame = frame; 62 | localData.timeDiff = timeDiff; 63 | this.dispatchEvent(frameEvent); 64 | } 65 | } 66 | 67 | export default TickManager; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-boilerplate", 3 | "version": "3.0.1", 4 | "description": "Three.js webpack 5 boilerplate using Babel and PostCSS and Gsap.", 5 | "main": "index.js", 6 | "author": "Arya", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "cross-env NODE_ENV=development webpack serve --config config/webpack.dev.js", 10 | "build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js", 11 | "lint": "eslint 'src/**/*.js' || true", 12 | "prettify": "prettier --write 'src/**/*.js'" 13 | }, 14 | "keywords": [ 15 | "three", 16 | "three webpack", 17 | "three webpack 5", 18 | "three webpack boilerplate", 19 | "webpack", 20 | "webpack 5" 21 | ], 22 | "devDependencies": { 23 | "@babel/core": "^7.15.8", 24 | "@babel/plugin-proposal-class-properties": "^7.14.5", 25 | "@babel/plugin-transform-runtime": "^7.19.6", 26 | "@babel/preset-env": "^7.15.8", 27 | "babel-loader": "^8.2.2", 28 | "clean-webpack-plugin": "^4.0.0", 29 | "copy-webpack-plugin": "^9.0.1", 30 | "cross-env": "^7.0.3", 31 | "css-loader": "^6.4.0", 32 | "css-minimizer-webpack-plugin": "^3.1.1", 33 | "eslint": "^7.28.0", 34 | "eslint-config-prettier": "^8.3.0", 35 | "eslint-import-resolver-webpack": "^0.13.1", 36 | "eslint-plugin-prettier": "^4.0.0", 37 | "html-webpack-plugin": "^5.3.2", 38 | "mini-css-extract-plugin": "^2.4.2", 39 | "postcss-loader": "^6.2.0", 40 | "postcss-preset-env": "^6.7.0", 41 | "prettier": "^2.4.1", 42 | "raw-loader": "^4.0.2", 43 | "sass": "^1.43.5", 44 | "sass-loader": "^12.2.0", 45 | "style-loader": "^3.3.0", 46 | "webpack": "^5.58.2", 47 | "webpack-cli": "^4.9.0", 48 | "webpack-dev-server": "^4.3.1", 49 | "webpack-merge": "^5.8.0" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "git@github.com:visionary-studio/three-boilerplate" 54 | }, 55 | "dependencies": { 56 | "@babel/runtime": "^7.20.7", 57 | "caniuse-lite": "^1.0.30001441", 58 | "gsap": "^3.11.4", 59 | "three": "^0.148.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { 3 | addPass, 4 | useCamera, 5 | useGui, 6 | useRenderSize, 7 | useScene, 8 | useTick 9 | } from './render/init.js' 10 | // import postprocessing passes 11 | import { SavePass } from 'three/examples/jsm/postprocessing/SavePass.js' 12 | import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js' 13 | import { BlendShader } from 'three/examples/jsm/shaders/BlendShader.js' 14 | import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js' 15 | 16 | const startApp = () => { 17 | const scene = useScene() 18 | const camera = useCamera() 19 | const gui = useGui() 20 | const { width, height } = useRenderSize() 21 | 22 | const ROTATION_SPEED = 0.02 23 | const MOTION_BLUR_AMOUNT = 0.725 24 | 25 | const dirLight = new THREE.DirectionalLight('#ffffff', 1) 26 | const ambientLight = new THREE.AmbientLight('#ffffff', 0.5) 27 | scene.add(dirLight, ambientLight) 28 | 29 | const geometry = new THREE.IcosahedronGeometry() 30 | const material = new THREE.MeshStandardMaterial({ 31 | color: '#4e62f9', 32 | }) 33 | const ico = new THREE.Mesh(geometry, material) 34 | scene.add(ico) 35 | 36 | // GUI 37 | const cameraFolder = gui.addFolder('Camera') 38 | cameraFolder.add(camera.position, 'z', 0, 10) 39 | cameraFolder.open() 40 | 41 | // postprocessing 42 | const renderTargetParameters = { 43 | minFilter: THREE.LinearFilter, 44 | magFilter: THREE.LinearFilter, 45 | stencilBuffer: false, 46 | } 47 | 48 | // save pass 49 | const savePass = new SavePass(new THREE.WebGLRenderTarget(width, height, renderTargetParameters)) 50 | 51 | // blend pass 52 | const blendPass = new ShaderPass(BlendShader, 'tDiffuse1') 53 | blendPass.uniforms['tDiffuse2'].value = savePass.renderTarget.texture 54 | blendPass.uniforms['mixRatio'].value = MOTION_BLUR_AMOUNT 55 | 56 | // output pass 57 | const outputPass = new ShaderPass(CopyShader) 58 | outputPass.renderToScreen = true 59 | 60 | // adding passes to composer 61 | addPass(blendPass) 62 | addPass(savePass) 63 | addPass(outputPass) 64 | 65 | const animateIco = () => { 66 | ico.rotation.x += ROTATION_SPEED 67 | ico.rotation.y += ROTATION_SPEED 68 | } 69 | 70 | useTick(({ timestamp, timeDiff }) => { 71 | animateIco() 72 | }) 73 | } 74 | 75 | export default startApp; 76 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 2 | const CopyWebpackPlugin = require('copy-webpack-plugin') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | 5 | const paths = require('./paths') 6 | 7 | module.exports = { 8 | // Where webpack looks to start building the bundle 9 | entry: [paths.src + '/index.js'], 10 | 11 | // Where webpack outputs the assets and bundles 12 | output: { 13 | path: paths.build, 14 | filename: '[name].bundle.js', 15 | publicPath: '/', 16 | hashFunction: 'xxhash64', 17 | }, 18 | 19 | // Customize the webpack build process 20 | plugins: [ 21 | // Removes/cleans build folders and unused assets when rebuilding 22 | new CleanWebpackPlugin(), 23 | 24 | // Copies files from target to destination folder 25 | new CopyWebpackPlugin({ 26 | patterns: [ 27 | { 28 | from: paths.public, 29 | to: 'assets', 30 | globOptions: { 31 | ignore: ['*.DS_Store'], 32 | }, 33 | noErrorOnMissing: true, 34 | }, 35 | ], 36 | }), 37 | 38 | // Generates an HTML file from a template 39 | // Generates deprecation warning: https://github.com/jantimon/html-webpack-plugin/issues/1501 40 | new HtmlWebpackPlugin({ 41 | title: 'Three Webpack Boilerplate', 42 | favicon: paths.src + '/images/favicon.ico', 43 | template: paths.src + '/template.html', // template file 44 | filename: 'index.html', // output file 45 | }), 46 | ], 47 | 48 | // Determine how modules within the project are treated 49 | module: { 50 | rules: [ 51 | // JavaScript: Use Babel to transpile JavaScript files 52 | { test: /\.js$/, use: ['babel-loader'] }, 53 | 54 | // Images: Copy image files to build folder 55 | { test: /\.(?:ico|gif|png|jpg|jpeg|mp3|wav)$/i, type: 'asset/resource' }, 56 | 57 | // Fonts and SVGs: Inline files 58 | { test: /\.(woff(2)?|eot|ttf|otf|svg|)$/, type: 'asset/inline' }, 59 | 60 | // GLSL: Raw loader 61 | { test: /\.(glsl|vs|fs|vert|frag)$/, exclude: /node_modules/, use: 'raw-loader' } 62 | ], 63 | }, 64 | 65 | resolve: { 66 | modules: [paths.src, 'node_modules'], 67 | extensions: ['.js', '.jsx', '.json'], 68 | alias: { 69 | '@': paths.src, 70 | assets: paths.public, 71 | }, 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /src/render/init.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' 3 | import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js' 4 | import Stats from 'three/examples/jsm/libs/stats.module.js' 5 | import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' 6 | import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js' 7 | import TickManager from './tick-manager.js' 8 | 9 | let scene, 10 | camera, 11 | renderer, 12 | composer, 13 | controls, 14 | stats, 15 | gui, 16 | renderWidth, 17 | renderHeight, 18 | renderAspectRatio 19 | const renderTickManager = new TickManager() 20 | 21 | export const initEngine = async () => { 22 | scene = new THREE.Scene() 23 | 24 | renderWidth = window.innerWidth 25 | renderHeight = window.innerHeight 26 | 27 | renderAspectRatio = renderWidth / renderHeight 28 | 29 | camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.1, 100) 30 | camera.position.z = 2 31 | 32 | renderer = new THREE.WebGLRenderer({ antialias: true }) 33 | renderer.setSize(renderWidth, renderHeight) 34 | renderer.setPixelRatio(window.devicePixelRatio * 1.5) 35 | 36 | // shadow 37 | renderer.shadowMap.enabled = true 38 | renderer.shadowMap.type = THREE.PCFSoftShadowMap 39 | 40 | document.body.appendChild(renderer.domElement) 41 | 42 | const target = new THREE.WebGLRenderTarget(renderWidth, renderHeight, { 43 | samples: 8, 44 | }) 45 | composer = new EffectComposer(renderer, target) 46 | composer.setPixelRatio(renderer.getPixelRatio()) 47 | 48 | const renderPass = new RenderPass(scene, camera) 49 | composer.addPass(renderPass) 50 | 51 | stats = Stats() 52 | document.body.appendChild(stats.dom) 53 | 54 | gui = new GUI() 55 | 56 | controls = new OrbitControls(camera, renderer.domElement) 57 | controls.enableDamping = true 58 | 59 | window.addEventListener( 60 | 'resize', 61 | () => { 62 | renderWidth = window.innerWidth 63 | renderHeight = window.innerHeight 64 | renderAspectRatio = renderWidth / renderHeight 65 | 66 | renderer.setPixelRatio(window.devicePixelRatio * 1.5) 67 | 68 | camera.aspect = renderAspectRatio 69 | camera.updateProjectionMatrix() 70 | 71 | renderer.setSize(renderWidth, renderHeight) 72 | composer.setSize(renderWidth, renderHeight) 73 | }, 74 | false 75 | ) 76 | 77 | renderTickManager.startLoop() 78 | } 79 | 80 | export const useRenderer = () => renderer 81 | 82 | export const useRenderSize = () => ({ width: renderWidth, height: renderHeight }) 83 | 84 | export const useScene = () => scene 85 | 86 | export const useCamera = () => camera 87 | 88 | export const useControls = () => controls 89 | 90 | export const useStats = () => stats 91 | 92 | export const useComposer = () => composer 93 | 94 | export const useGui = () => gui 95 | 96 | export const addPass = (pass) => { 97 | composer.addPass(pass) 98 | } 99 | 100 | export const useTick = (fn) => { 101 | if (renderTickManager) { 102 | const _tick = (e) => { 103 | fn(e.data) 104 | } 105 | renderTickManager.addEventListener('tick', _tick) 106 | } 107 | } 108 | --------------------------------------------------------------------------------