├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .nvmrc ├── .prettierrc ├── README.md ├── favicon.ico ├── package-lock.json ├── package.json ├── src ├── assets │ ├── fonts │ │ ├── FuturaStd-Bold.woff │ │ ├── FuturaStd-Bold.woff2 │ │ ├── FuturaStd-Book.woff │ │ ├── FuturaStd-Book.woff2 │ │ ├── FuturaStd-ExtraBold.woff │ │ ├── FuturaStd-ExtraBold.woff2 │ │ ├── FuturaStd-Heavy.woff │ │ ├── FuturaStd-Heavy.woff2 │ │ ├── FuturaStd-Light.woff │ │ ├── FuturaStd-Light.woff2 │ │ ├── FuturaStd-Medium.woff │ │ └── FuturaStd-Medium.woff2 │ └── images │ │ └── clouds │ │ ├── 1.jpg │ │ └── 2.jpg ├── css │ ├── fonts.css │ └── main.css ├── data │ ├── colors.json │ └── index.js ├── js │ ├── components │ │ ├── Canvas │ │ │ ├── Camera │ │ │ │ ├── Controls.js │ │ │ │ └── index.js │ │ │ ├── Environment │ │ │ │ ├── Background │ │ │ │ │ └── index.js │ │ │ │ ├── Cloud │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── Sphere │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── UI │ │ │ └── Credits │ │ │ └── index.js │ ├── helpers │ │ ├── gui.js │ │ └── loadingManager.js │ ├── hooks │ │ ├── index.js │ │ ├── useAssets.js │ │ ├── useDebugMode.js │ │ ├── useImage.js │ │ ├── useRawData.js │ │ └── useTexture.js │ └── index.js ├── pages │ └── index.html └── shaders │ ├── cloud.frag │ ├── cloud.vert │ ├── default.vert │ ├── environment.frag │ └── levels.glsl └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = false -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | extends: ["google"], 7 | globals: { 8 | Atomics: "readonly", 9 | SharedArrayBuffer: "readonly", 10 | }, 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | ecmaVersion: 2018, 16 | sourceType: "module", 17 | }, 18 | plugins: ["react", "react-hooks"], 19 | rules: { 20 | indent: ["error", 2, { SwitchCase: 1 }], 21 | "valid-jsdoc": 0, 22 | "require-jsdoc": 0, 23 | "no-unused-vars": 1, 24 | "new-cap": 0, 25 | "prefer-template": 2, 26 | semi: ["error", "never"], 27 | "react/jsx-uses-react": "error", 28 | "react/jsx-uses-vars": "error", 29 | "react/jsx-first-prop-new-line": "error", 30 | "react/jsx-max-props-per-line": ["error", { maximum: 1 }], 31 | "react-hooks/rules-of-hooks": "error", 32 | "react-hooks/exhaustive-deps": 0, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.15.3 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Procedural Clouds Extending Three.js' Sprite 3 | 4 | *Learn how to simulate a cloud on a Threejs' Sprite using React-Three-Fiber*, *by Robert Borghesi* 5 | 6 | ![Procedural Clouds featured image](https://tympanus.net/codrops/wp-content/uploads/2020/01/ProceduralClouds_featured.jpg) 7 | 8 | [Article on Codrops](https://tympanus.net/codrops/?p=46546) 9 | 10 | [Demo](http://tympanus.net/Tutorials/ProceduralClouds/) 11 | 12 | 13 | ## Instructions 14 | 15 | - `npm install` to install dependencies 16 | - `npm run dev` to start the server 17 | - `npm run build` to create the build 18 | 19 | If you want to understand why I used the `Sprite` material just add `/?debug=true` in the URL, it will enable the Orbit Controls and it will add some meshes. 20 | 21 | 22 | ## Credits 23 | 24 | [Threejs](https://threejs.org/) — [React Three Fiber](https://github.com/react-spring/react-three-fiber) 25 | 26 | This is part of two bigger projects made at [LOW](http://low.thebignow.it/), you can see them online on [LetGirlsDream](https://www.letgirlsdream.org/) and [1955Horsebit](http://1955horsebit.gucci.com/) 27 | 28 | 29 | ## License 30 | This resource can be used freely if integrated or build upon in personal or commercial projects such as websites, web apps and web templates intended for sale. It is not allowed to take the resource "as-is" and sell it, redistribute, re-publish it, or sell "pluginized" versions of it. Free plugins built using this resource should have a visible mention and link to the original work. Always consider the licenses of all included libraries, scripts and images used. 31 | 32 | 33 | ## Misc 34 | 35 | Follow **Robert**: [Twitter](https://twitter.com/dghez_), 36 | 37 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [Google+](https://plus.google.com/101095823814290637419), [GitHub](https://github.com/codrops), [Pinterest](http://www.pinterest.com/codrops/), [Instagram](https://www.instagram.com/codropsss/) 38 | 39 | 40 | [© Codrops 2019](http://www.codrops.com) 41 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/favicon.ico -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "procedural-clouds", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=dev webpack-dev-server --open --host 0.0.0.0 --port 3000", 8 | "build": "cross-env NODE_ENV=test webpack", 9 | "build-prod": "cross-env NODE_ENV=prod webpack" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:dghez/THREEJS_Procedural-cloud.git" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "@babel/core": "^7.5.5", 20 | "@babel/plugin-proposal-class-properties": "^7.5.5", 21 | "@babel/preset-env": "^7.5.5", 22 | "@babel/preset-react": "^7.0.0", 23 | "babel-loader": "^8.0.6", 24 | "babel-polyfill": "^6.26.0", 25 | "clean-webpack-plugin": "^3.0.0", 26 | "copy-webpack-plugin": "^5.0.4", 27 | "cross-env": "^7.0.0", 28 | "css-loader": "^3.1.0", 29 | "eslint": "^6.1.0", 30 | "eslint-config-google": "^0.13.0", 31 | "eslint-plugin-react": "^7.14.3", 32 | "eslint-plugin-react-hooks": "^1.6.1", 33 | "file-loader": "^4.1.0", 34 | "glslify-loader": "^2.0.0", 35 | "html-webpack-plugin": "^3.2.0", 36 | "raw-loader": "^3.1.0", 37 | "sass-loader": "^8.0.0", 38 | "style-loader": "^1.0.0", 39 | "webpack": "^4.39.1", 40 | "webpack-cli": "^3.3.6", 41 | "webpack-dev-server": "^3.7.2" 42 | }, 43 | "dependencies": { 44 | "core-js": "^3.1.4", 45 | "dat.gui": "^0.7.6", 46 | "glsl-fractal-brownian-noise": "^1.1.0", 47 | "glsl-noise": "0.0.0", 48 | "interpolation": "^1.0.0", 49 | "lodash": "^4.17.15", 50 | "mobile-detect": "^1.4.3", 51 | "normalize.css": "^8.0.1", 52 | "pepjs": "^0.5.2", 53 | "query-string": "^6.8.2", 54 | "react": "^16.9", 55 | "react-dom": "^16.9.0", 56 | "react-three-fiber": "^4.0.7", 57 | "string": "^3.3.3", 58 | "styled-jsx": "^3.2.1", 59 | "three": "^0.112.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Bold.woff -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Bold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Book.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Book.woff -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Book.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Book.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-ExtraBold.woff -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-ExtraBold.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Heavy.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Heavy.woff -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Heavy.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Heavy.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Light.woff -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Light.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Medium.woff -------------------------------------------------------------------------------- /src/assets/fonts/FuturaStd-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/fonts/FuturaStd-Medium.woff2 -------------------------------------------------------------------------------- /src/assets/images/clouds/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/images/clouds/1.jpg -------------------------------------------------------------------------------- /src/assets/images/clouds/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghez/THREEJS_Procedural-clouds/029dce52c5027d2f29753ce8ae5083b8a593374a/src/assets/images/clouds/2.jpg -------------------------------------------------------------------------------- /src/css/fonts.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap'); -------------------------------------------------------------------------------- /src/css/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | box-sizing: border-box; 4 | background: #ababab; 5 | overscroll-behavior: none; 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | *, 11 | *:before, 12 | *:after { 13 | box-sizing: inherit; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-font-smoothing: antialiased; 16 | -o-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | text-rendering: optimizeLegibility; 19 | -webkit-tap-highlight-color: transparent; 20 | } 21 | 22 | ::selection { 23 | background: #e7e3f1; 24 | } 25 | 26 | ::-moz-selection { 27 | background: #e7e3f1; 28 | } 29 | 30 | a { 31 | color: inherit; 32 | text-decoration: none; 33 | } 34 | 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6, 41 | p { 42 | margin: 0; 43 | } 44 | 45 | #app { 46 | position: fixed; 47 | top: 0; 48 | left: 0; 49 | width: 100%; 50 | height: 100%; 51 | } 52 | -------------------------------------------------------------------------------- /src/data/colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "gradients": [ 3 | { 4 | "top": "#0747ab", 5 | "bottom": "#6796b5", 6 | "spot1": "#a7b9c7", 7 | "spot2": "#a7b9c7" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/data/index.js: -------------------------------------------------------------------------------- 1 | import colors from './colors.json' 2 | 3 | export default { 4 | colors, 5 | } 6 | -------------------------------------------------------------------------------- /src/js/components/Canvas/Camera/Controls.js: -------------------------------------------------------------------------------- 1 | import React, {useRef, useEffect} from 'react' 2 | import {extend, useFrame, useThree} from 'react-three-fiber' 3 | 4 | import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls' 5 | extend({OrbitControls}) 6 | 7 | export default () => { 8 | const ref = useRef() 9 | const {gl, camera} = useThree() 10 | 11 | useFrame(() => ref.current.update()) 12 | 13 | useEffect(()=>{ 14 | ref.current.object = camera 15 | }, [camera]) 16 | 17 | return ( 18 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/js/components/Canvas/Camera/index.js: -------------------------------------------------------------------------------- 1 | import React, {useRef, useEffect} from 'react' 2 | import {useThree} from 'react-three-fiber' 3 | 4 | import {useDebugMode} from '~js/hooks' 5 | 6 | import Controls from './Controls' 7 | 8 | export default () => { 9 | const camera = useRef() 10 | const {size, setDefaultCamera} = useThree() 11 | const {width, height} = size 12 | const debugMode = useDebugMode() 13 | 14 | useEffect( () => { 15 | setDefaultCamera(camera.current) 16 | }, []) 17 | 18 | return ( 19 | <> 20 | 25 | {debugMode && } 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/js/components/Canvas/Environment/Background/index.js: -------------------------------------------------------------------------------- 1 | import React, {useRef, useMemo} from 'react' 2 | import {BackSide, Color} from 'three' 3 | import {useRawData} from '~js/hooks' 4 | 5 | import vertex from '~shaders/default.vert' 6 | import fragment from '~shaders/environment.frag' 7 | 8 | export default () => { 9 | const mesh = useRef() 10 | const colorSteps = useRawData('colors.gradients') 11 | const radius = 8 12 | 13 | const uniforms = useMemo(() => { 14 | return { 15 | uTopColor: {value: new Color(colorSteps[0].top)}, 16 | uBottomColor: {value: new Color(colorSteps[0].bottom)}, 17 | uSpot1Color: {value: new Color(colorSteps[0].spot1)}, 18 | uSpot1Position: {value: [0.4, 0.7]}, 19 | uSpot2Color: {value: new Color(colorSteps[0].spot2)}, 20 | uSpot2Position: {value: [0.6, 0.4]}, 21 | } 22 | }, []) 23 | 24 | return ( 25 | <> 26 | 30 | 33 | 42 | 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/js/components/Canvas/Environment/Cloud/index.js: -------------------------------------------------------------------------------- 1 | import React, {useRef, useEffect, useMemo} from 'react' 2 | import {useFrame} from 'react-three-fiber' 3 | import {ShaderMaterial, UniformsUtils, ShaderLib} from 'three' 4 | 5 | import {useAssets, useTexture} from '~js/hooks' 6 | import gui from '~js/helpers/gui' 7 | import fragment from '~shaders/cloud.frag' 8 | import vertex from '~shaders/cloud.vert' 9 | 10 | 11 | export default ({size}) => { 12 | const group = useRef() 13 | const mesh = useRef() 14 | const [width, height] = size 15 | 16 | const src1 = useAssets('images/clouds/1.jpg') 17 | const t1 = useTexture(src1) 18 | 19 | const src2 = useAssets('images/clouds/2.jpg') 20 | const t2 = useTexture(src2) 21 | 22 | const myUniforms = useMemo(() => ({ 23 | uTime: {value: 0}, 24 | uTxtShape: {value: t1}, 25 | uTxtCloudNoise: {value: t2}, 26 | uFac1: {value: 17.8}, 27 | uFac2: {value: 2.7}, 28 | uTimeFactor1: {value: 0.002}, 29 | uTimeFactor2: {value: 0.0015}, 30 | uDisplStrenght1: {value: 0.04}, 31 | uDisplStrenght2: {value: 0.08}, 32 | }), [t1]) 33 | 34 | const material = useMemo(() => { 35 | const mat = new ShaderMaterial({ 36 | uniforms: {...UniformsUtils.clone(ShaderLib.sprite.uniforms), ...myUniforms}, 37 | vertexShader: vertex, 38 | fragmentShader: fragment, 39 | transparent: true, 40 | }) 41 | 42 | return mat 43 | }, []) 44 | 45 | useEffect( () => { 46 | if (material) { 47 | material.uniforms.uTxtShape.value = t1 48 | } 49 | }, [t1]) 50 | 51 | useEffect( () => { 52 | if (material) { 53 | material.uniforms.uTxtCloudNoise.value = t2 54 | } 55 | }, [t2]) 56 | 57 | useFrame(()=> { 58 | if (material) { 59 | material.uniforms.uTime.value += 1 60 | } 61 | }) 62 | 63 | /** 64 | * DAT GUI 65 | */ 66 | useEffect(() => { 67 | if (material) { 68 | gui.get((gui) => { 69 | gui.add(material.uniforms.uFac1, 'value', 0.00001, 30).step(0.1).name('1-ScaleFactor') 70 | gui.add(material.uniforms.uTimeFactor1, 'value', 0.00001, 0.009).step(0.0001).name('1-TimeFactor') 71 | gui.add(material.uniforms.uDisplStrenght1, 'value', 0.00001, 0.3).step(0.01).name('1-Strength') 72 | gui.add(material.uniforms.uTimeFactor2, 'value', 0.00001, 0.009).step(0.0001).name('2-TimeFactor') 73 | gui.add(material.uniforms.uFac2, 'value', 0.00001, 100).name('2-ScaleFactor') 74 | gui.add(material.uniforms.uDisplStrenght2, 'value', 0.00001, 0.3).step(0.01).name('2-Strength') 75 | }) 76 | } 77 | }, [material]) 78 | 79 | 80 | return ( 81 | 82 | 87 | 90 | 94 | 95 | 96 | ) 97 | } 98 | -------------------------------------------------------------------------------- /src/js/components/Canvas/Environment/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Background from './Background' 4 | import Cloud from './Cloud' 5 | 6 | export default () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/js/components/Canvas/Sphere/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {map} from 'lodash' 3 | 4 | const positions = [ 5 | [1, 0, 0], 6 | [-1, 0, 0], 7 | [0, 1, 0], 8 | [0, -1, 0], 9 | [0, 0, 1], 10 | [0, 0, -1], 11 | ] 12 | 13 | const Sphere = ({position}) => { 14 | return ( 15 | 18 | 22 | 26 | 27 | ) 28 | } 29 | 30 | export default () => { 31 | const sphereMeshes = map(positions, (el, i) => { 32 | return 35 | }) 36 | 37 | return ( 38 | <> 39 | {sphereMeshes} 40 | 41 | 45 | 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/js/components/Canvas/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Canvas} from 'react-three-fiber' 3 | 4 | import {useDebugMode} from '~js/hooks' 5 | 6 | export default ({children}) => { 7 | return ( 8 |
9 | 12 | {children} 13 | 14 | 15 | 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/js/components/UI/Credits/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default () => { 4 | return ( 5 |
TW: @dghez_ 8 | 9 | 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/js/helpers/gui.js: -------------------------------------------------------------------------------- 1 | import * as dat from 'dat.gui' 2 | 3 | let gui 4 | const callbacks = [] 5 | 6 | const init = () => { 7 | if (!gui) { 8 | gui = new dat.GUI({width: 300}) 9 | 10 | while (callbacks.length) { 11 | callbacks.shift()(gui) 12 | } 13 | } 14 | } 15 | 16 | export default { 17 | init, 18 | get: (callback) => { 19 | if (gui) { 20 | callback(gui) 21 | } else { 22 | callbacks.push(callback) 23 | } 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /src/js/helpers/loadingManager.js: -------------------------------------------------------------------------------- 1 | import {LoadingManager, TextureLoader, RepeatWrapping} from 'three' 2 | 3 | import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader' 4 | import {DRACOLoader} from 'three/examples/jsm/loaders/DRACOLoader' 5 | 6 | const loadingManager = new LoadingManager() 7 | const textureLoader = new TextureLoader(loadingManager) 8 | 9 | const gltfLoader = new GLTFLoader(loadingManager) 10 | const dracoLoader = new DRACOLoader() 11 | dracoLoader.setDecoderPath('/static-threejs/draco/') 12 | gltfLoader.setDRACOLoader(dracoLoader) 13 | 14 | const onLoadCallbacks = [] 15 | const onProgressCallbacks = [] 16 | 17 | loadingManager.onLoad = () => { 18 | while (onLoadCallbacks.length) { 19 | onLoadCallbacks.shift().call() 20 | } 21 | } 22 | 23 | loadingManager.onProgress = (item, loaded, total) => { 24 | const _onProgressCallbacks = [...onProgressCallbacks] 25 | 26 | while (_onProgressCallbacks.length) { 27 | _onProgressCallbacks.shift()(loaded / total) 28 | } 29 | } 30 | 31 | const loadTexture = (src, callback) => { 32 | const texture = textureLoader.load(src, () => { 33 | typeof callback === 'function' && callback(texture) 34 | }) 35 | 36 | texture.wrapS = texture.wrapT = RepeatWrapping 37 | 38 | return texture 39 | } 40 | 41 | const loadModelGLTF = (src) => { 42 | return new Promise((resolve, reject) => { 43 | gltfLoader.load(src, (resources) => { 44 | resolve(resources.scene) 45 | }) 46 | }) 47 | } 48 | 49 | const loadGLTF = async (modelSrc, diffuseSrc, normalSrc, aoSrc) => { 50 | const mesh = await loadModelGLTF(modelSrc) 51 | const diffuseMap = loadTexture(diffuseSrc) 52 | const normalMap = loadTexture(normalSrc) 53 | const aoMap = loadTexture(aoSrc) 54 | 55 | return {mesh, diffuseMap, normalMap, aoMap} 56 | } 57 | 58 | const onLoad = (callback) => { 59 | onLoadCallbacks.push(callback) 60 | 61 | window.aa = loadingManager 62 | } 63 | 64 | const onProgress = (callback) => { 65 | onProgressCallbacks.push(callback) 66 | } 67 | 68 | export {loadGLTF, loadTexture, onLoad, onProgress} 69 | -------------------------------------------------------------------------------- /src/js/hooks/index.js: -------------------------------------------------------------------------------- 1 | import useDebugMode from './useDebugMode' 2 | import useRawData from './useRawData' 3 | import useAssets from './useAssets' 4 | import useImage from './useImage' 5 | import useTexture from './useTexture' 6 | 7 | export { 8 | useDebugMode, 9 | useRawData, 10 | useAssets, 11 | useImage, 12 | useTexture, 13 | } 14 | -------------------------------------------------------------------------------- /src/js/hooks/useAssets.js: -------------------------------------------------------------------------------- 1 | export default function useAssets(path) { 2 | const src = '' 3 | 4 | if (path) { 5 | try { 6 | return require(`~assets/${path}`) 7 | } catch (err) { 8 | console.warn(err) 9 | } 10 | } 11 | 12 | return src 13 | } 14 | -------------------------------------------------------------------------------- /src/js/hooks/useDebugMode.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | import {get} from 'lodash' 3 | 4 | function parseQueryString(url) { 5 | const vars = {} 6 | 7 | url.replace(/[?&]+([^=&]+)=([^&]*)/gi, (m, key, value) => { 8 | vars[key] = value 9 | }) 10 | 11 | return vars 12 | } 13 | 14 | export default function useDebugMode() { 15 | const [debugMode, setDebugMode] = useState(false) 16 | const {search} = window.location 17 | 18 | useEffect(() => { 19 | const parsed = parseQueryString(search) 20 | 21 | setDebugMode(get(parsed, 'debug') === 'true') 22 | }, [location]) 23 | 24 | return debugMode 25 | } 26 | -------------------------------------------------------------------------------- /src/js/hooks/useImage.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | 3 | export default (src) => { 4 | const [image, setImage] = useState(null) 5 | 6 | useEffect(() => { 7 | const image = new Image() 8 | 9 | image.src = src 10 | 11 | image.onload = () => setImage(image) 12 | }, [src]) 13 | 14 | return image 15 | } 16 | -------------------------------------------------------------------------------- /src/js/hooks/useRawData.js: -------------------------------------------------------------------------------- 1 | import {useMemo} from 'react' 2 | import data from '~data' 3 | import {get} from 'lodash' 4 | 5 | export default function useRawData(key) { 6 | const response = useMemo(() => { 7 | return key ? get(data, key, '') : data 8 | }, [key]) 9 | 10 | return response 11 | } 12 | -------------------------------------------------------------------------------- /src/js/hooks/useTexture.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | import {Texture} from 'three' 3 | 4 | import {loadTexture} from '~js/helpers/loadingManager' 5 | 6 | export default function useTexture(src) { 7 | const [texture, setTexture] = useState(new Texture()) 8 | 9 | useEffect(()=> { 10 | loadTexture(src, setTexture) 11 | }, []) 12 | 13 | return texture 14 | } 15 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import 'normalize.css' 2 | import '~css/fonts.css' 3 | import '~css/main.css' 4 | import 'core-js/stable' 5 | import 'regenerator-runtime/runtime' 6 | 7 | import React, {useEffect} from 'react' 8 | import {render} from 'react-dom' 9 | 10 | import gui from '~js/helpers/gui' 11 | import {useDebugMode} from '~js/hooks' 12 | 13 | import Canvas from '~js/components/Canvas' 14 | import Camera from '~js/components/Canvas/Camera' 15 | import Sphere from '~js/components/Canvas/Sphere' 16 | import Environment from '~js/components/Canvas/Environment' 17 | import Credits from '~js/components/UI/Credits' 18 | 19 | /** 20 | * app 21 | */ 22 | const App = () => { 23 | const debugMode = useDebugMode() 24 | 25 | useEffect(() => { 26 | gui.init() 27 | }, []) 28 | 29 | return ( 30 | <> 31 | 32 | 33 | 34 | 35 | {debugMode && } 36 | 37 | 38 | ) 39 | } 40 | 41 | /** 42 | * render app 43 | */ 44 | render( 45 | , 46 | document.getElementById('app') 47 | ) 48 | -------------------------------------------------------------------------------- /src/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /src/shaders/cloud.frag: -------------------------------------------------------------------------------- 1 | #pragma glslify: fbm3d = require('glsl-fractal-brownian-noise/3d') 2 | #pragma glslify: snoise3 = require(glsl-noise/simplex/3d) 3 | #pragma glslify: levels = require('./levels') 4 | 5 | 6 | uniform sampler2D uTxtShape; 7 | uniform sampler2D uTxtCloudNoise; 8 | uniform float uTime; 9 | 10 | uniform float uFac1; 11 | uniform float uFac2; 12 | uniform float uTimeFactor1; 13 | uniform float uTimeFactor2; 14 | uniform float uDisplStrenght1; 15 | uniform float uDisplStrenght2; 16 | 17 | varying vec2 vUv; 18 | 19 | void main() { 20 | vec2 newUv = vUv; 21 | 22 | vec4 txtNoise1 = texture2D(uTxtCloudNoise, vec2(vUv.x + uTime * 0.0001, vUv.y - uTime * 0.00014)); // noise txt 23 | vec4 txtNoise2 = texture2D(uTxtCloudNoise, vec2(vUv.x - uTime * 0.00002, vUv.y + uTime * 0.000017 + 0.2)); // noise txt 24 | 25 | float noiseBig = fbm3d(vec3(vUv * uFac1, uTime * uTimeFactor1), 4)+ 1.0 * 0.5; 26 | newUv += noiseBig * uDisplStrenght1; 27 | 28 | float noiseSmall = snoise3(vec3(newUv * uFac2, uTime * uTimeFactor2)); 29 | 30 | newUv += noiseSmall * uDisplStrenght2; 31 | 32 | vec4 txtShape = texture2D(uTxtShape, newUv); 33 | 34 | float alpha = levels((txtNoise1 + txtNoise2) * 0.6, 0.2, 0.4, 0.7).r; 35 | alpha *= txtShape.r; 36 | 37 | gl_FragColor = vec4(vec3(0.95,0.95,0.95), alpha); 38 | } 39 | -------------------------------------------------------------------------------- /src/shaders/cloud.vert: -------------------------------------------------------------------------------- 1 | uniform float rotation; 2 | uniform vec2 center; 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | varying vec2 vUv; 10 | 11 | void main() { 12 | // #include 13 | vUv = uv; 14 | 15 | vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 ); 16 | vec2 scale; 17 | scale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) ); 18 | scale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) ); 19 | 20 | vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale; 21 | vec2 rotatedPosition; 22 | rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y; 23 | rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y; 24 | mvPosition.xy += rotatedPosition; 25 | gl_Position = projectionMatrix * mvPosition; 26 | #include 27 | #include 28 | #include 29 | } -------------------------------------------------------------------------------- /src/shaders/default.vert: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() { 4 | 5 | vUv = uv; 6 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); 7 | } -------------------------------------------------------------------------------- /src/shaders/environment.frag: -------------------------------------------------------------------------------- 1 | uniform vec3 uTopColor; 2 | uniform vec3 uBottomColor; 3 | uniform vec3 uSpot1Color; 4 | uniform vec3 uSpot2Color; 5 | uniform vec2 uSpot1Position; 6 | uniform vec2 uSpot2Position; 7 | 8 | varying vec2 vUv; 9 | 10 | float distanceFromPoint(vec2 uv, vec2 point, float max){ 11 | float d = distance(uv, point); 12 | d = smoothstep(0.0, max, d); 13 | d = 1.0 - d; 14 | return d; 15 | } 16 | 17 | void main() { 18 | 19 | float d1 = distanceFromPoint(vUv, vec2(uSpot1Position), 0.3); 20 | float d2 = distanceFromPoint(vUv, vec2(uSpot2Position), 0.4); 21 | 22 | vec4 colorSpot1 = vec4(uSpot1Color, 1.0 * d1 * 0.8); 23 | vec4 colorSpot2 = vec4(uSpot2Color, 1.0 * d2 * 0.8); 24 | vec4 verticalGradient = vec4(mix(uBottomColor,uTopColor, vUv.y), 1.0); 25 | vec4 mixVS1 = mix(verticalGradient, colorSpot1, colorSpot1.a); 26 | vec4 final = mix(mixVS1, colorSpot2, colorSpot2.a); 27 | 28 | gl_FragColor = vec4(final.rgb, 1.0); 29 | } 30 | -------------------------------------------------------------------------------- /src/shaders/levels.glsl: -------------------------------------------------------------------------------- 1 | vec4 gammaCorrect(vec4 color, float gamma){ 2 | return pow(color, vec4(1.0 / gamma)); 3 | } 4 | 5 | vec4 levelRange(vec4 color, float minInput, float maxInput){ 6 | return min(max(color - vec4(minInput), vec4(0.0)) / (vec4(maxInput) - vec4(minInput)), vec4(1.0)); 7 | } 8 | 9 | vec4 levels(vec4 color, float minInput, float gamma, float maxInput){ 10 | return gammaCorrect(levelRange(color, minInput, maxInput), gamma); 11 | } 12 | 13 | #pragma glslify: export(levels); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const {CleanWebpackPlugin} = require('clean-webpack-plugin') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const CopyPlugin = require('copy-webpack-plugin') 5 | 6 | const env = process.env.NODE_ENV 7 | const devMode = env === 'dev' 8 | 9 | module.exports = { 10 | mode: devMode ? 'development' : 'production', 11 | entry: './src/js', 12 | output: { 13 | filename: devMode === 'development' ? 'assets/js/main.js' : 'assets/js/main.[contenthash].js', 14 | publicPath: '/', 15 | }, 16 | resolve: { 17 | alias: { 18 | '~js': path.resolve(__dirname, 'src/js'), 19 | '~css': path.resolve(__dirname, 'src/css'), 20 | '~data': path.resolve(__dirname, 'src/data'), 21 | '~assets': path.resolve(__dirname, 'src/assets'), 22 | '~shaders': path.resolve(__dirname, 'src/shaders'), 23 | }, 24 | }, 25 | plugins: [ 26 | new CleanWebpackPlugin(), 27 | new CopyPlugin([ 28 | { 29 | from: './src/static/**/*', 30 | to: './', 31 | transformPath(targetPath) { 32 | return targetPath.replace('src/static/', '') 33 | }, 34 | }, 35 | ]), 36 | new HtmlWebpackPlugin({ 37 | template: './src/pages/index.html', 38 | filename: 'index.html', 39 | env, 40 | }), 41 | ], 42 | module: { 43 | rules: [ 44 | { 45 | test: /\.pug$/, 46 | loader: 'pug-loader', 47 | options: { 48 | pretty: true, 49 | root: path.resolve(__dirname, 'src'), 50 | }, 51 | }, 52 | { 53 | test: /\.(jpg|png|svg|gif|mp4)$/, 54 | loader: 'file-loader', 55 | options: { 56 | name: 'assets/images/[hash].[ext]', 57 | }, 58 | }, 59 | { 60 | test: /\.(dae|obj|gltf)$/, 61 | loader: 'file-loader', 62 | options: { 63 | name: 'assets/objects/[name].[ext]', 64 | }, 65 | }, 66 | { 67 | test: /\.(woff|woff2|eot|ttf|otf)$/, 68 | loader: 'file-loader', 69 | options: { 70 | name: 'assets/fonts/[name].[ext]', 71 | }, 72 | }, 73 | { 74 | test: /\.(sa|sc|c)ss$/, 75 | use: ['style-loader', 'css-loader'], 76 | }, 77 | { 78 | test: /\.(frag|vert|glsl)$/, 79 | exclude: /node_modules/, 80 | use: ['raw-loader', 'glslify-loader'], 81 | }, 82 | { 83 | test: /\.m?js$/, 84 | exclude: /node_modules\/(?!(swiper|dom7)\/).*/, 85 | use: { 86 | loader: 'babel-loader', 87 | options: { 88 | presets: ['@babel/preset-env', '@babel/preset-react'], 89 | plugins: ['styled-jsx/babel', ['@babel/plugin-proposal-class-properties', {loose: true}]], 90 | }, 91 | }, 92 | }, 93 | ], 94 | }, 95 | } 96 | --------------------------------------------------------------------------------