├── .DS_Store
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── dist
├── assets
│ ├── favicon-273be61c.ico
│ ├── index-dfc8c28f.js
│ ├── index-f170363e.js
│ └── index-fcc81f05.css
└── index.html
├── package.json
├── preview.jpg
├── src
├── .DS_Store
├── css
│ └── base.css
├── favicon.ico
├── index.html
└── js
│ ├── Experience.jsx
│ ├── LevaWrapper.tsx
│ ├── script.jsx
│ └── shaders
│ ├── fragment.glsl
│ └── vertex.glsl
└── vite.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavel-mazhuga/codrops-tutorial-distorted-sphere-custom-material/83bb3d2639460e4a5c478e23414356957968a8d8/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | .parcel-cache
4 | package-lock.json
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | webpack.config.js
2 | node_modules/*
3 | build/*
4 | postcss*
5 | *.html
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true,
6 | "arrowParens": "always",
7 | "printWidth": 120
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2009 - 2024 [Codrops](https://tympanus.net/codrops)
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Creating an Animated Displaced Sphere with a Custom Three.js Material
2 |
3 | Exploring the possibilities of creating a custom material using shaders based on built-in Three.js materials with React Three Fiber.
4 |
5 | 
6 |
7 | [Article on Codrops](https://tympanus.net/codrops/?p=78913)
8 |
9 | [Demo](http://tympanus.net/Tutorials/DistortedSphereCustomMaterial/)
10 |
11 | ## Installation
12 |
13 | ```
14 | npm install
15 | npm run dev
16 | ```
17 |
18 | ## Misc
19 |
20 | Follow _Pavel Mazhuga_: [Twitter](https://x.com/PMazhuga), [Instagram](https://www.instagram.com/mazhuga.gl), [GitHub](https://github.com/pavel-mazhuga/), [Website](https://pavelmazhuga.com)
21 |
22 | Follow Codrops: [X](http://www.X.com/codrops), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/)
23 |
24 | ## License
25 |
26 | [MIT](LICENSE)
27 |
28 | Made with :blue_heart: by [Codrops](http://www.codrops.com) and [Pavel Mazhuga](https://x.com/PMazhuga)
29 |
--------------------------------------------------------------------------------
/dist/assets/favicon-273be61c.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavel-mazhuga/codrops-tutorial-distorted-sphere-custom-material/83bb3d2639460e4a5c478e23414356957968a8d8/dist/assets/favicon-273be61c.ico
--------------------------------------------------------------------------------
/dist/assets/index-fcc81f05.css:
--------------------------------------------------------------------------------
1 | *,*:after,*:before{box-sizing:border-box}:root{font-size:12px;--color-text: #fff;--color-bg: #000;--color-link: #aaa;--color-link-hover: #fff;--page-padding: 1.5rem}body{margin:0;color:var(--color-text);background-color:var(--color-bg);font-family:ui-monospace,monospace;font-weight:600;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;min-height:100vh;display:flex;flex-direction:column}.js .loading:before,.js .loading:after{content:"";position:fixed;z-index:1000}.js .loading:before{top:0;left:0;width:100%;height:100%;background:var(--color-bg)}.js .loading:after{top:50%;left:50%;width:60px;height:60px;margin:-30px 0 0 -30px;border-radius:50%;opacity:.4;background:var(--color-link);animation:loaderAnim .7s linear infinite alternate forwards}@keyframes loaderAnim{to{opacity:1;transform:scale3d(.5,.5,1)}}a{text-decoration:none;color:var(--color-link);outline:none;cursor:pointer}a:hover{text-decoration:underline;color:var(--color-link-hover);outline:none}a:focus{outline:none;background:lightgrey}a:focus:not(:focus-visible){background:transparent}a:focus-visible{outline:2px solid red;background:transparent}.unbutton{background:none;border:0;padding:0;margin:0;font:inherit;cursor:pointer}.unbutton:focus{outline:none}.frame{padding:var(--page-padding);position:relative;display:grid;z-index:1000;width:100%;height:100%;grid-row-gap:1rem;grid-column-gap:2rem;pointer-events:none;justify-items:start;grid-template-columns:auto auto;grid-template-areas:"title" "archive" "back" "github" "sponsor" "tags"}.frame #cdawrap{justify-self:start}.frame a{pointer-events:auto}.frame__title{grid-area:title;font-size:inherit;margin:0}.frame__back{grid-area:back;justify-self:start}.frame__archive{grid-area:archive;justify-self:start}.frame__sub{grid-area:sub}.frame__github{grid-area:github}.frame__tags{grid-area:tags;display:flex;gap:1rem}.frame__hire{grid-area:hire}.frame__demos{grid-area:demos;display:flex;gap:1rem}.content{padding:var(--page-padding);display:flex;flex-direction:column;width:100vw;position:relative}.main{display:flex;flex-direction:column;flex:1 1 auto}@media screen and (min-width: 53em){body{--page-padding: 2rem 3rem}.frame{position:fixed;top:0;left:0;width:100%;height:100%;grid-template-columns:auto auto auto auto 1fr;grid-template-rows:auto auto;align-content:space-between;grid-template-areas:"title title back archive github" "tags tags tag sponsor sponsor"}.frame #cdawrap,.frame__sub{justify-self:end}.content{min-height:100vh;justify-content:center;align-items:center}}.canvas-wrapper{position:fixed;top:0;left:0;width:100%;height:100%}.leva-wrapper{position:fixed;z-index:3;top:10px;right:10px;max-height:calc(100vh - 100px);max-height:calc(100svh - 100px);overflow-y:auto}@media (max-width: 1150px){.frame{display:flex;flex-direction:column;align-items:flex-start;flex:1 1 auto}.frame__tags{margin-top:auto}}@media (max-width: 850px){.leva-wrapper{display:none}}
2 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Animated Displaced Sphere with Extended Three.js Material | Codrops
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three-template",
3 | "private": true,
4 | "version": "0.0.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build --target es2020",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "@vitejs/plugin-react": "^4.3.1",
13 | "vite": "^4.5.3"
14 | },
15 | "dependencies": {
16 | "@react-three/drei": "^9.107.0",
17 | "@react-three/fiber": "^8.16.8",
18 | "leva": "^0.9.34",
19 | "react": "^18.3.1",
20 | "react-dom": "^18.3.1",
21 | "three": "^0.165.0",
22 | "three-custom-shader-material": "^5.4.0",
23 | "usehooks-ts": "^3.1.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavel-mazhuga/codrops-tutorial-distorted-sphere-custom-material/83bb3d2639460e4a5c478e23414356957968a8d8/preview.jpg
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavel-mazhuga/codrops-tutorial-distorted-sphere-custom-material/83bb3d2639460e4a5c478e23414356957968a8d8/src/.DS_Store
--------------------------------------------------------------------------------
/src/css/base.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | font-size: 12px;
9 | --color-text: #fff;
10 | --color-bg: #000;
11 | --color-link: #aaa;
12 | --color-link-hover: #fff;
13 | --page-padding: 1.5rem;
14 | }
15 |
16 | body {
17 | margin: 0;
18 | color: var(--color-text);
19 | background-color: var(--color-bg);
20 | font-family: ui-monospace, monospace;
21 | font-weight: 600;
22 | -webkit-font-smoothing: antialiased;
23 | -moz-osx-font-smoothing: grayscale;
24 | min-height: 100vh;
25 | display: flex;
26 | flex-direction: column;
27 | }
28 |
29 | /* Page Loader */
30 | .js .loading::before,
31 | .js .loading::after {
32 | content: '';
33 | position: fixed;
34 | z-index: 1000;
35 | }
36 |
37 | .js .loading::before {
38 | top: 0;
39 | left: 0;
40 | width: 100%;
41 | height: 100%;
42 | background: var(--color-bg);
43 | }
44 |
45 | .js .loading::after {
46 | top: 50%;
47 | left: 50%;
48 | width: 60px;
49 | height: 60px;
50 | margin: -30px 0 0 -30px;
51 | border-radius: 50%;
52 | opacity: 0.4;
53 | background: var(--color-link);
54 | animation: loaderAnim 0.7s linear infinite alternate forwards;
55 | }
56 |
57 | @keyframes loaderAnim {
58 | to {
59 | opacity: 1;
60 | transform: scale3d(0.5, 0.5, 1);
61 | }
62 | }
63 |
64 | a {
65 | text-decoration: none;
66 | color: var(--color-link);
67 | outline: none;
68 | cursor: pointer;
69 | }
70 |
71 | a:hover {
72 | text-decoration: underline;
73 | color: var(--color-link-hover);
74 | outline: none;
75 | }
76 |
77 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
78 | a:focus {
79 | /* Provide a fallback style for browsers
80 | that don't support :focus-visible */
81 | outline: none;
82 | background: lightgrey;
83 | }
84 |
85 | a:focus:not(:focus-visible) {
86 | /* Remove the focus indicator on mouse-focus for browsers
87 | that do support :focus-visible */
88 | background: transparent;
89 | }
90 |
91 | a:focus-visible {
92 | /* Draw a very noticeable focus style for
93 | keyboard-focus on browsers that do support
94 | :focus-visible */
95 | outline: 2px solid red;
96 | background: transparent;
97 | }
98 |
99 | .unbutton {
100 | background: none;
101 | border: 0;
102 | padding: 0;
103 | margin: 0;
104 | font: inherit;
105 | cursor: pointer;
106 | }
107 |
108 | .unbutton:focus {
109 | outline: none;
110 | }
111 |
112 | .frame {
113 | padding: var(--page-padding);
114 | position: relative;
115 | display: grid;
116 | z-index: 1000;
117 | width: 100%;
118 | height: 100%;
119 | grid-row-gap: 1rem;
120 | grid-column-gap: 2rem;
121 | pointer-events: none;
122 | justify-items: start;
123 | grid-template-columns: auto auto;
124 | grid-template-areas: 'title' 'archive' 'back' 'github' 'sponsor' 'tags';
125 | }
126 |
127 | .frame #cdawrap {
128 | justify-self: start;
129 | }
130 |
131 | .frame a {
132 | pointer-events: auto;
133 | }
134 |
135 | .frame__title {
136 | grid-area: title;
137 | font-size: inherit;
138 | margin: 0;
139 | }
140 |
141 | .frame__back {
142 | grid-area: back;
143 | justify-self: start;
144 | }
145 |
146 | .frame__archive {
147 | grid-area: archive;
148 | justify-self: start;
149 | }
150 |
151 | .frame__sub {
152 | grid-area: sub;
153 | }
154 |
155 | .frame__github {
156 | grid-area: github;
157 | }
158 |
159 | .frame__tags {
160 | grid-area: tags;
161 | display: flex;
162 | gap: 1rem;
163 | }
164 |
165 | .frame__hire {
166 | grid-area: hire;
167 | }
168 |
169 | .frame__demos {
170 | grid-area: demos;
171 | display: flex;
172 | gap: 1rem;
173 | }
174 |
175 | .content {
176 | padding: var(--page-padding);
177 | display: flex;
178 | flex-direction: column;
179 | width: 100vw;
180 | position: relative;
181 | }
182 |
183 | .main {
184 | display: flex;
185 | flex-direction: column;
186 | flex: 1 1 auto;
187 | }
188 |
189 | @media screen and (min-width: 53em) {
190 | body {
191 | --page-padding: 2rem 3rem;
192 | }
193 | .frame {
194 | position: fixed;
195 | top: 0;
196 | left: 0;
197 | width: 100%;
198 | height: 100%;
199 | grid-template-columns: auto auto auto auto 1fr;
200 | grid-template-rows: auto auto;
201 | align-content: space-between;
202 | grid-template-areas: 'title title back archive github' 'tags tags tag sponsor sponsor';
203 | }
204 | .frame #cdawrap,
205 | .frame__sub {
206 | justify-self: end;
207 | }
208 | .content {
209 | min-height: 100vh;
210 | justify-content: center;
211 | align-items: center;
212 | }
213 | }
214 |
215 | /* Styles */
216 |
217 | .canvas-wrapper {
218 | position: fixed;
219 | top: 0;
220 | left: 0;
221 | width: 100%;
222 | height: 100%;
223 | }
224 |
225 | .leva-wrapper {
226 | position: fixed;
227 | z-index: 3;
228 | top: 10px;
229 | right: 10px;
230 | max-height: calc(100vh - 100px);
231 | max-height: calc(100svh - 100px);
232 | overflow-y: auto;
233 | }
234 |
235 | @media (max-width: 1150px) {
236 | .frame {
237 | display: flex;
238 | flex-direction: column;
239 | align-items: flex-start;
240 | flex: 1 1 auto;
241 | }
242 |
243 | .frame__tags {
244 | margin-top: auto;
245 | }
246 | }
247 |
248 | @media (max-width: 850px) {
249 | .leva-wrapper {
250 | display: none;
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pavel-mazhuga/codrops-tutorial-distorted-sphere-custom-material/83bb3d2639460e4a5c478e23414356957968a8d8/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Animated Displaced Sphere with Extended Three.js Material | Codrops
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/js/Experience.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { OrbitControls } from '@react-three/drei';
3 | import { Canvas, useFrame } from '@react-three/fiber';
4 | import { useControls } from 'leva';
5 | import { Suspense, useMemo, useRef } from 'react';
6 | import { Color, IcosahedronGeometry, MeshDepthMaterial, MeshPhysicalMaterial, RGBADepthPacking } from 'three';
7 | import CustomShaderMaterial from 'three-custom-shader-material';
8 | import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils';
9 | import { useMediaQuery } from 'usehooks-ts';
10 | import vertexShader from './shaders/vertex.glsl?raw';
11 | import fragmentShader from './shaders/fragment.glsl?raw';
12 | import LevaWrapper from './LevaWrapper';
13 |
14 | const Experiment = ({ shouldReduceQuality, isMobile, onLoaded }) => {
15 | const materialRef = useRef(null);
16 | const depthMaterialRef = useRef(null);
17 |
18 | useFrame(({ clock }) => {
19 | const elapsedTime = clock.getElapsedTime();
20 |
21 | if (materialRef.current) {
22 | materialRef.current.uniforms.uTime.value = elapsedTime;
23 | }
24 |
25 | if (depthMaterialRef.current) {
26 | depthMaterialRef.current.uniforms.uTime.value = elapsedTime;
27 | }
28 | });
29 |
30 | const {
31 | gradientStrength,
32 | color,
33 | speed,
34 | noiseStrength,
35 | displacementStrength,
36 | fractAmount,
37 | roughness,
38 | metalness,
39 | clearcoat,
40 | reflectivity,
41 | ior,
42 | iridescence,
43 | } = useControls({
44 | gradientStrength: {
45 | value: 1,
46 | min: 1,
47 | max: 3,
48 | step: 0.001,
49 | },
50 | color: '#af00ff',
51 | speed: {
52 | value: 1.1,
53 | min: 0,
54 | max: 20,
55 | step: 0.001,
56 | },
57 | noiseStrength: {
58 | value: 0.3,
59 | min: 0,
60 | max: 3,
61 | step: 0.001,
62 | },
63 | displacementStrength: {
64 | value: 0.57,
65 | min: 0,
66 | max: 1,
67 | step: 0.001,
68 | },
69 | fractAmount: {
70 | value: 4,
71 | min: 0,
72 | max: 10,
73 | step: 1,
74 | },
75 | roughness: {
76 | min: 0,
77 | max: 1,
78 | step: 0.001,
79 | value: 0.56,
80 | },
81 | metalness: {
82 | min: 0,
83 | max: 1,
84 | step: 0.001,
85 | value: 0.76,
86 | },
87 | clearcoat: {
88 | min: 0,
89 | max: 1,
90 | step: 0.001,
91 | value: 0,
92 | },
93 | reflectivity: {
94 | min: 0,
95 | max: 1,
96 | step: 0.001,
97 | value: 0.46,
98 | },
99 | ior: {
100 | min: 0.001,
101 | max: 5,
102 | step: 0.001,
103 | value: 2.81,
104 | },
105 | iridescence: {
106 | min: 0,
107 | max: 1,
108 | step: 0.001,
109 | value: 0.96,
110 | },
111 | });
112 |
113 | const { intensity: ambientLightIntensity, color: ambientLightColor } = useControls('Ambient light', {
114 | color: '#fff',
115 | intensity: {
116 | value: 1,
117 | min: 0,
118 | max: 1,
119 | step: 0.001,
120 | },
121 | });
122 |
123 | const {
124 | intensity: directionalLightIntensity,
125 | color: directionalLightColor,
126 | positionX: directionalLightPositionX,
127 | positionY: directionalLightPositionY,
128 | positionZ: directionalLightPositionZ,
129 | } = useControls('Directional light', {
130 | color: '#fff',
131 | intensity: {
132 | value: 5,
133 | min: 0,
134 | max: 5,
135 | step: 0.001,
136 | },
137 | positionX: {
138 | value: -2,
139 | min: -10,
140 | max: 10,
141 | step: 0.001,
142 | },
143 | positionY: {
144 | value: 2,
145 | min: -10,
146 | max: 10,
147 | step: 0.001,
148 | },
149 | positionZ: {
150 | value: 3.5,
151 | min: -10,
152 | max: 10,
153 | step: 0.001,
154 | },
155 | });
156 |
157 | const geometry = useMemo(() => {
158 | const geometry = mergeVertices(new IcosahedronGeometry(1.3, shouldReduceQuality ? 128 : 200));
159 | geometry.computeTangents();
160 | return geometry;
161 | }, [shouldReduceQuality]);
162 |
163 | const uniforms = {
164 | uTime: { value: 0 },
165 | uColor: { value: new Color(color) },
166 | uGradientStrength: { value: gradientStrength },
167 | uSpeed: { value: speed },
168 | uNoiseStrength: { value: noiseStrength },
169 | uDisplacementStrength: { value: displacementStrength },
170 | uFractAmount: { value: fractAmount },
171 | };
172 |
173 | useEffect(() => {
174 | onLoaded();
175 | }, [onLoaded]);
176 |
177 | return (
178 | <>
179 |
180 |
194 |
203 |
204 |
205 |
210 | >
211 | );
212 | };
213 |
214 | const Experience = () => {
215 | const isTablet = useMediaQuery('(max-width: 1199px)');
216 | const isMobile = useMediaQuery('(max-width: 767px)');
217 | const [isLoaded, setIsLoaded] = useState(false);
218 |
219 | useEffect(() => {
220 | if (isLoaded) {
221 | document.body.classList.remove('loading');
222 | }
223 | }, [isLoaded]);
224 |
225 | const handleLoad = () => {
226 | setIsLoaded(true);
227 | };
228 |
229 | return (
230 |
231 |
232 |
246 |
247 | );
248 | };
249 |
250 | export default Experience;
251 |
--------------------------------------------------------------------------------
/src/js/LevaWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Leva } from 'leva';
2 | import React from 'react';
3 |
4 | const LevaWrapper = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
12 | export default LevaWrapper;
13 |
--------------------------------------------------------------------------------
/src/js/script.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import Experience from './Experience';
4 |
5 | const rootNode = document.querySelector('.js-root');
6 |
7 | if (rootNode) {
8 | createRoot(rootNode).render();
9 | }
--------------------------------------------------------------------------------
/src/js/shaders/fragment.glsl:
--------------------------------------------------------------------------------
1 | varying float vPattern;
2 |
3 | uniform vec3 uColor;
4 |
5 | void main() {
6 | vec3 color = vPattern * uColor;
7 |
8 | csm_DiffuseColor = vec4(color, 1.);
9 | }
10 |
--------------------------------------------------------------------------------
/src/js/shaders/vertex.glsl:
--------------------------------------------------------------------------------
1 | attribute vec4 tangent;
2 |
3 | varying float vPattern;
4 |
5 | uniform float uTime;
6 | uniform float uSpeed;
7 | uniform float uNoiseStrength;
8 | uniform float uDisplacementStrength;
9 | uniform float uFractAmount;
10 |
11 | // Classic Perlin 3D Noise
12 | // by Stefan Gustavson (https://github.com/stegu/webgl-noise)
13 | //
14 | vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
15 | vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
16 | vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
17 |
18 | float cnoise(vec3 P){
19 | vec3 Pi0 = floor(P); // Integer part for indexing
20 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
21 | Pi0 = mod(Pi0, 289.0);
22 | Pi1 = mod(Pi1, 289.0);
23 | vec3 Pf0 = fract(P); // Fractional part for interpolation
24 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
25 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
26 | vec4 iy = vec4(Pi0.yy, Pi1.yy);
27 | vec4 iz0 = Pi0.zzzz;
28 | vec4 iz1 = Pi1.zzzz;
29 |
30 | vec4 ixy = permute(permute(ix) + iy);
31 | vec4 ixy0 = permute(ixy + iz0);
32 | vec4 ixy1 = permute(ixy + iz1);
33 |
34 | vec4 gx0 = ixy0 / 7.0;
35 | vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
36 | gx0 = fract(gx0);
37 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
38 | vec4 sz0 = step(gz0, vec4(0.0));
39 | gx0 -= sz0 * (step(0.0, gx0) - 0.5);
40 | gy0 -= sz0 * (step(0.0, gy0) - 0.5);
41 |
42 | vec4 gx1 = ixy1 / 7.0;
43 | vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
44 | gx1 = fract(gx1);
45 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
46 | vec4 sz1 = step(gz1, vec4(0.0));
47 | gx1 -= sz1 * (step(0.0, gx1) - 0.5);
48 | gy1 -= sz1 * (step(0.0, gy1) - 0.5);
49 |
50 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
51 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
52 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
53 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
54 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
55 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
56 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
57 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
58 |
59 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
60 | g000 *= norm0.x;
61 | g010 *= norm0.y;
62 | g100 *= norm0.z;
63 | g110 *= norm0.w;
64 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
65 | g001 *= norm1.x;
66 | g011 *= norm1.y;
67 | g101 *= norm1.z;
68 | g111 *= norm1.w;
69 |
70 | float n000 = dot(g000, Pf0);
71 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
72 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
73 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
74 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
75 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
76 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
77 | float n111 = dot(g111, Pf1);
78 |
79 | vec3 fade_xyz = fade(Pf0);
80 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
81 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
82 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
83 | return 2.2 * n_xyz;
84 | }
85 |
86 | float smoothMod(float axis, float amp, float rad) {
87 | float top = cos(PI * (axis / amp)) * sin(PI * (axis / amp));
88 | float bottom = pow(sin(PI * (axis / amp)), 2.0) + pow(rad, 2.0);
89 | float at = atan(top / bottom);
90 | return amp * (1.0 / 2.0) - (1.0 / PI) * at;
91 | }
92 |
93 | float getDisplacement(vec3 position) {
94 | vec3 pos = position;
95 | pos.y -= uTime * 0.05 * uSpeed;
96 | pos += cnoise(pos * 1.65) * uNoiseStrength;
97 |
98 | return smoothMod(pos.y * uFractAmount, 1., 1.5) * uDisplacementStrength;
99 | }
100 |
101 | void main() {
102 | vec3 biTangent = cross(csm_Normal, tangent.xyz);
103 | float shift = 0.01;
104 | vec3 posA = csm_Position + tangent.xyz * shift;
105 | vec3 posB = csm_Position + biTangent * shift;
106 |
107 | float pattern = getDisplacement(csm_Position);
108 | vPattern = pattern;
109 |
110 | csm_Position += csm_Normal * pattern;
111 | posA += csm_Normal * getDisplacement(posA);
112 | posB += csm_Normal * getDisplacement(posB);
113 |
114 | vec3 toA = normalize(posA - csm_Position);
115 | vec3 toB = normalize(posB - csm_Position);
116 |
117 | csm_Normal = normalize(cross(toA, toB));
118 | }
119 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | root: 'src',
7 | base: './',
8 | build: {
9 | outDir: '../dist'
10 | }
11 | });
--------------------------------------------------------------------------------