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