├── .codesandbox └── ci.json ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── example ├── index.html ├── index.tsx ├── package.json ├── pages │ ├── distortion-material.tsx │ ├── main.tsx │ ├── more-instances.tsx │ └── voronoi.tsx ├── simplex3d.js ├── style.css ├── tsconfig.json ├── voronoi.js └── yarn.lock ├── logo.jpg ├── make-types.js ├── package.json ├── readme ├── autocomplete.jpg ├── distortion.jpg ├── glslify.jpg ├── multiple-instances.jpg └── voronoi.jpg ├── src ├── component-material.tsx ├── constants.ts ├── create-material.ts ├── generated.ts ├── helpers │ └── objects.ts ├── index.tsx ├── proxies.tsx └── types │ ├── index.ts │ └── internal.ts ├── tsconfig.json └── yarn.lock /.codesandbox/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "sandboxes": ["w1uo1", "ttnet"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marco Ludovico Perego 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://raw.githubusercontent.com/emmelleppi/component-material/master/logo.jpg) 2 | 3 | [![Version](https://img.shields.io/npm/v/component-material?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/component-material) 4 | [![Downloads](https://img.shields.io/npm/dt/component-material.svg?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/component-material) 5 | [![Discord Shield](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=ffffff)](https://discord.gg/ZZjjNvJ) 6 | 7 | # Component Material 8 | 9 | Material is a React utility that helps you compose and modify materials in [react-three-fiber](https://github.com/pmndrs/react-three-fiber) and threejs. 10 | 11 | ### Examples 12 | 13 |

14 | 15 | 16 | 17 |

18 | 19 | ## Quick start 20 | 21 | ```bash 22 | yarn add component-material 23 | ``` 24 | 25 | ```jsx 26 | import Material from 'component-material' 27 | 28 | function CustomMaterial(props) { 29 | return ( 30 | 38 | 42 | 43 | ) 44 | } 45 | 46 | function Sphere() { 47 | return ( 48 | 49 | 50 | 51 | 52 | ``` 53 | 54 | ## Features 55 | 56 | - Create custom shaders on top of existing threejs Shaders and shader-chunks 57 | - Compose your shader code with modular injects 58 | - Auto-completion, intellisense and typechecking 59 | - Syntax-highlighting with either [tagged glsl-literals](https://marketplace.visualstudio.com/items?itemName=boyswan.glsl-literal) or [comment-tagged templates](https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates) 60 | - Glslify and imports via [babel-plugin-glsl](https://github.com/onnovisser/babel-plugin-glsl) 61 | 62 | ## `` 63 | 64 | #### `from` 65 | 66 | By default Material extends three's MeshPhysicalMaterial. If you want to extend a different material just use the `from` prop passing the desired material constructor. 67 | 68 | ```jsx 69 | 70 | ``` 71 | 72 | #### `uniforms` 73 | 74 | Uniforms used inside shaders can be defined via the `uniforms` prop as follows 75 | 76 | ```jsx 77 | 83 | ``` 84 | 85 | This will also create setters and getters for the uniforms automatically, allowing you to mutate them using props and effectively making the material reactive: 86 | 87 | ```jsx 88 | function CustomMaterial({ color }) { 89 | return ( 90 | 94 | ``` 95 | 96 | - The correspondences between glsl and javascript types can be seen [here](https://threejs.org/docs/#api/en/core/Uniform) 97 | - Uniforms cannot be defined twice in the same shader. So be careful not to define the same uniforms inside the `head` tag. 98 | 99 | #### `varyings` 100 | 101 | Varying variables can be defined directly inside the shader `head` tag or they can be declared as prop: 102 | 103 | ```jsx 104 | 110 | ``` 111 | 112 | This is equivalent to adding this code to both your vertex and fragment shaders heads: 113 | 114 | ```glsl 115 | float myVarying1; 116 | vec2 myVarying2; 117 | ``` 118 | 119 | - Varyings don't have an initial value, only a type definition 120 | - As uniforms, varyings cannot be defined twice in the same shader, this will give a glsl error. So be careful not to define the same varyings inside the `head` tag. 121 | 122 | ## Fragment- and vertex-shader composition 123 | 124 | The `Frag` and `Vert` tags have the function of injecting the shader text, passed as children, into the preconfigured shader of the threejs material. Let's see what it means with an example: 125 | 126 | ```jsx 127 | 128 | 135 | 139 | ``` 140 | 141 | In the code above the `Frag.Head` component adds an easing function `quadraticInOut` to the fragment shader of the material, prepending it before the `main` function of the shader. 142 | 143 | The `Frag.Body` component instead adds a line of code that modify the `gl_FragColor` alpha value, appending it after the last operation of the main function. 144 | 145 | In particular, if we take as an example the fragment shader of the `MeshPhysicalMaterial`, `Frag.Head` prepends the code before [this shader line](https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshphysical_frag.glsl.js#L2), `Frag.Body` instead posts the code after [this shader line](https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshphysical_frag.glsl.js#L124) (the `dithering_fragment` chunk). 146 | 147 | The same goes for the `Vert` component, which however acts on the vertex shader. In particular, `Vert.Head` prepends the code to [this shader line](https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshphysical_vert.glsl.js#L2), while `Vert.Body` appends the code to [this shader line](https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshphysical_vert.glsl.js#L60) (the `project_vertex` chunk). 148 | 149 | It is possible to inject the code after a particular chunk just by doing 150 | 151 | ```jsx 152 | 153 | ``` 154 | 155 | where `my_chunk` must be replaced with the name of the chunk concerned. 156 | 157 | If we wanted to insert some code just after the `emissivemap_fragment` chunk ([here the reference for the MeshPhysicalMaterial](https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshphysical_frag.glsl.js#L99)) then just use the following code 158 | 159 | ```jsx 160 | 161 | ``` 162 | 163 | #### `replaceChunk` 164 | 165 | The `replaceChunk` prop is a boolean that allows you to completely replace the chosen chunk, so instead of append the custom shader code after the chunk it will be replaced directly. 166 | 167 | ```jsx 168 | 169 | ``` 170 | 171 | ## Common chunks 172 | 173 | The `Common` tag is useful in case vertex shader and fragment shader share some functions. 174 | 175 | ❌ If both the fragment shader and the vertex shader share the easing function `quadraticInOut`, instead of writing 176 | 177 | ```jsx 178 | 185 | 192 | ``` 193 | 194 | ✅ we will write 195 | 196 | ```jsx 197 | 204 | ``` 205 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { targets: { esmodules: true } } 6 | ], 7 | '@babel/preset-react', 8 | '@babel/preset-typescript' 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Playground 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11' 2 | import * as React from 'react' 3 | import * as ReactDOM from 'react-dom' 4 | 5 | const Main = React.lazy(() => import('./pages/main')) 6 | const DistortionMaterial = React.lazy(() => import('./pages/distortion-material')) 7 | const Voronoi = React.lazy(() => import('./pages/voronoi')) 8 | const MoreInstances = React.lazy(() => import('./pages/more-instances')) 9 | 10 | import { Switch, Route } from 'wouter' 11 | 12 | import './style.css' 13 | 14 | function App() { 15 | return ( 16 | loading.}> 17 | 18 | {() =>
} 19 | {() => } 20 | {() => } 21 | {() => } 22 | 23 | 24 | ) 25 | } 26 | 27 | ReactDOM.render(, document.getElementById('root')) 28 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "parcel index.html", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": { 11 | "@react-three/drei": "^4.0.0", 12 | "react-app-polyfill": "^1.0.0", 13 | "react-use-gesture": "^8.0.1", 14 | "tweakpane": "^1.5.7", 15 | "use-tweaks": "^0.3.1", 16 | "wouter": "^2.6.0" 17 | }, 18 | "alias": { 19 | "react": "../node_modules/react", 20 | "react-dom": "../node_modules/react-dom/profiling", 21 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^17.0.0", 25 | "@types/react-dom": "^17.0.0", 26 | "parcel": "1.12.3", 27 | "typescript": "^4.1.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/pages/distortion-material.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11' 2 | import * as React from 'react' 3 | import { Suspense, useRef } from 'react' 4 | import { Canvas, useFrame } from 'react-three-fiber' 5 | import { Environment, Sphere } from '@react-three/drei' 6 | import { useTweaks } from 'use-tweaks' 7 | import distortion from '../simplex3d' 8 | import M, { GenericMaterial } from '../../dist' 9 | 10 | function Scene() { 11 | const material = useRef() 12 | 13 | const { metalness, clearcoat, roughness, radiusVariationAmplitude, radiusNoiseFrequency } = useTweaks({ 14 | metalness: { value: 1, min: 0, max: 1 }, 15 | clearcoat: { value: 0.6, min: 0, max: 1 }, 16 | roughness: { value: 0.5, min: 0, max: 1 }, 17 | radiusVariationAmplitude: { value: 1.25, min: 0, max: 5 }, 18 | radiusNoiseFrequency: { value: 0.2, min: 0, max: 2 }, 19 | }) 20 | 21 | useFrame(({ clock }) => { 22 | if (material.current) { 23 | material.current.time = clock.getElapsedTime() 24 | } 25 | }) 26 | 27 | const RADIUS = 4 28 | 29 | return ( 30 | 31 | 43 | {/*glsl*/ ` 44 | ${distortion} 45 | 46 | float fsnoise(float val1, float val2, float val3){ 47 | return snoise(vec3(val1,val2,val3)); 48 | } 49 | 50 | vec3 distortFunct(vec3 transformed, float factor) { 51 | float radiusVariation = -fsnoise( 52 | transformed.x * radiusNoiseFrequency + time, 53 | transformed.y * radiusNoiseFrequency + time, 54 | transformed.z * radiusNoiseFrequency + time 55 | ) * radiusVariationAmplitude * factor; 56 | return normalize(transformed) * (radiusVariation + radius); 57 | } 58 | 59 | vec3 orthogonal(vec3 v) { 60 | return normalize(abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) 61 | : vec3(0.0, -v.z, v.y)); 62 | } 63 | 64 | vec3 distortNormal(vec3 position, vec3 distortedPosition, vec3 normal){ 65 | vec3 tangent1 = orthogonal(normal); 66 | vec3 tangent2 = normalize(cross(normal, tangent1)); 67 | vec3 nearby1 = position + tangent1 * 0.1; 68 | vec3 nearby2 = position + tangent2 * 0.1; 69 | vec3 distorted1 = distortFunct(nearby1, 1.0); 70 | vec3 distorted2 = distortFunct(nearby2, 1.0); 71 | return normalize(cross(distorted1 - distortedPosition, distorted2 - distortedPosition)); 72 | } 73 | `} 74 | {/*glsl*/ ` 75 | float updateTime = time / 10.0; 76 | transformed = distortFunct(transformed, 1.0); 77 | vec3 distortedNormal = distortNormal(position, transformed, normal); 78 | vNormal = normal + distortedNormal; 79 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed,1.); 80 | `} 81 | 82 | 83 | ) 84 | } 85 | 86 | function App() { 87 | return ( 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ) 99 | } 100 | 101 | export default App 102 | -------------------------------------------------------------------------------- /example/pages/main.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11' 2 | import * as React from 'react' 3 | import { Suspense, useRef } from 'react' 4 | import { Canvas, useFrame } from 'react-three-fiber' 5 | import { Environment, Sphere } from '@react-three/drei' 6 | import { useTweaks } from 'use-tweaks' 7 | 8 | import M, { GenericMaterial } from '../../dist' 9 | 10 | function Scene() { 11 | const material = useRef(null!) 12 | 13 | const { metalness, roughness } = useTweaks({ 14 | metalness: { value: 0.5, min: 0, max: 1 }, 15 | roughness: { value: 0.5, min: 0, max: 1 }, 16 | }) 17 | 18 | useFrame(({ clock }) => { 19 | if (material.current) { 20 | material.current.time = clock.getElapsedTime() * 2 21 | } 22 | }) 23 | 24 | return ( 25 | 26 | 34 | {/*glsl*/ ` 35 | float quadraticInOut(float t) { 36 | float p = 2.0 * t * t; 37 | return t < 0.5 ? p : -p + (4.0 * t) - 1.0; 38 | } 39 | `} 40 | {/*glsl*/ ` 41 | gl_FragColor = vec4(gl_FragColor.rgb, quadraticInOut((sin(time)+1.0)/2.0)); 42 | `} 43 | 44 | 45 | ) 46 | } 47 | 48 | function App() { 49 | return ( 50 | <> 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ) 63 | } 64 | 65 | export default App 66 | -------------------------------------------------------------------------------- /example/pages/more-instances.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11' 2 | import * as React from 'react' 3 | import { Suspense, useRef } from 'react' 4 | import { Canvas, useFrame } from 'react-three-fiber' 5 | import { Environment, Sphere } from '@react-three/drei' 6 | import { useTweaks } from 'use-tweaks' 7 | import distortion from '../simplex3d' 8 | import M, { GenericMaterial } from '../../dist' 9 | 10 | const RADIUS = 4 11 | 12 | function CustomMaterial(props) { 13 | const material = useRef() 14 | const { metalness, clearcoat, roughness, radiusVariationAmplitude, radiusNoiseFrequency } = useTweaks({ 15 | metalness: { value: 1, min: 0, max: 1 }, 16 | clearcoat: { value: 0.6, min: 0, max: 1 }, 17 | roughness: { value: 0.5, min: 0, max: 1 }, 18 | radiusVariationAmplitude: { value: 1.25, min: 0, max: 5 }, 19 | radiusNoiseFrequency: { value: 0.2, min: 0, max: 2 }, 20 | }) 21 | useFrame(({ clock }) => { 22 | if (material.current) { 23 | material.current.time = clock.getElapsedTime() 24 | } 25 | }) 26 | return ( 27 | 39 | {/*glsl*/ ` 40 | ${distortion} 41 | 42 | float fsnoise(float val1, float val2, float val3){ 43 | return snoise(vec3(val1,val2,val3)); 44 | } 45 | 46 | vec3 distortFunct(vec3 transformed, float factor) { 47 | float radiusVariation = -fsnoise( 48 | transformed.x * radiusNoiseFrequency + time, 49 | transformed.y * radiusNoiseFrequency + time, 50 | transformed.z * radiusNoiseFrequency + time 51 | ) * radiusVariationAmplitude * factor; 52 | return normalize(transformed) * (radiusVariation + radius); 53 | } 54 | 55 | vec3 orthogonal(vec3 v) { 56 | return normalize(abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) 57 | : vec3(0.0, -v.z, v.y)); 58 | } 59 | 60 | vec3 distortNormal(vec3 position, vec3 distortedPosition, vec3 normal){ 61 | vec3 tangent1 = orthogonal(normal); 62 | vec3 tangent2 = normalize(cross(normal, tangent1)); 63 | vec3 nearby1 = position + tangent1 * 0.1; 64 | vec3 nearby2 = position + tangent2 * 0.1; 65 | vec3 distorted1 = distortFunct(nearby1, 1.0); 66 | vec3 distorted2 = distortFunct(nearby2, 1.0); 67 | return normalize(cross(distorted1 - distortedPosition, distorted2 - distortedPosition)); 68 | } 69 | `} 70 | {/*glsl*/ ` 71 | float updateTime = time / 10.0; 72 | transformed = distortFunct(transformed, 1.0); 73 | vec3 distortedNormal = distortNormal(position, transformed, normal); 74 | vNormal = normal + distortedNormal; 75 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed,1.); 76 | `} 77 | 78 | ) 79 | } 80 | 81 | function Scene({ color, ...props }) { 82 | return ( 83 | 84 | 85 | 86 | ) 87 | } 88 | 89 | function App() { 90 | return ( 91 | <> 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | ) 107 | } 108 | 109 | export default App 110 | -------------------------------------------------------------------------------- /example/pages/voronoi.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11' 2 | import * as React from 'react' 3 | import { Suspense, useRef } from 'react' 4 | import { Canvas, useFrame } from 'react-three-fiber' 5 | import { Environment, Sphere } from '@react-three/drei' 6 | import { useTweaks } from 'use-tweaks' 7 | import M, { GenericMaterial } from '../../dist' 8 | import voronoi from '../voronoi' 9 | 10 | function Scene(): JSX.Element { 11 | const material = useRef() 12 | const sphere = useRef() 13 | 14 | const { amplitude, frequency, jitter, metalness, roughness } = useTweaks({ 15 | metalness: { value: 1, min: 0, max: 1 }, 16 | roughness: { value: 1, min: 0, max: 1 }, 17 | amplitude: { value: 1, min: -5, max: 5 }, 18 | frequency: { value: 0.85, min: 0, max: 10 }, 19 | jitter: { value: 0.9, min: 0, max: 2 }, 20 | }) 21 | 22 | useFrame(({ clock }) => { 23 | if (material.current) { 24 | material.current.time = clock.getElapsedTime() 25 | } 26 | }) 27 | 28 | const RADIUS = 4 29 | 30 | return ( 31 | 32 | 45 | 46 | {/*glsl*/ ` 47 | ${voronoi} 48 | 49 | vec3 distortFunct(vec3 transformed, float factor) { 50 | vec3 deformed = transformed.xyz * vec3(1., 7., 3.); 51 | vec2 f = worley(deformed * frequency * 0.1 + time * 0.4, jitter, false) * amplitude * factor; 52 | return normalize(transformed) * (f.x + radius); 53 | } 54 | 55 | vec3 orthogonal(vec3 v) { 56 | return normalize(abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) 57 | : vec3(0.0, -v.z, v.y)); 58 | } 59 | 60 | vec3 distortNormal(vec3 position, vec3 distortedPosition, vec3 normal){ 61 | vec3 tangent1 = orthogonal(normal); 62 | vec3 tangent2 = normalize(cross(normal, tangent1)); 63 | vec3 nearby1 = position + tangent1 * 0.1; 64 | vec3 nearby2 = position + tangent2 * 0.1; 65 | vec3 distorted1 = distortFunct(nearby1, 1.0); 66 | vec3 distorted2 = distortFunct(nearby2, 1.0); 67 | return normalize(cross(distorted1 - distortedPosition, distorted2 - distortedPosition)); 68 | } 69 | `} 70 | 71 | 72 | {/*glsl*/ ` 73 | float updateTime = time / 10.0; 74 | 75 | transformed = distortFunct(transformed, 1.0); 76 | 77 | vec3 distortedNormal = distortNormal(position, transformed, normal); 78 | 79 | vTransformed = transformed; 80 | 81 | vNormal = normal + distortedNormal; 82 | gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed,1.); 83 | `} 84 | 85 | 86 | 87 | ) 88 | } 89 | 90 | function App() { 91 | return ( 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | ) 104 | } 105 | 106 | export default App 107 | -------------------------------------------------------------------------------- /example/simplex3d.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | vec3 mod289(vec3 x) { 3 | return x - floor(x * (1.0 / 289.0)) * 289.0; 4 | } 5 | 6 | vec4 mod289(vec4 x) { 7 | return x - floor(x * (1.0 / 289.0)) * 289.0; 8 | } 9 | 10 | vec4 permute(vec4 x) { 11 | return mod289(((x*34.0)+1.0)*x); 12 | } 13 | 14 | vec4 taylorInvSqrt(vec4 r) 15 | { 16 | return 1.79284291400159 - 0.85373472095314 * r; 17 | } 18 | 19 | float snoise(vec3 v) 20 | { 21 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 22 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 23 | 24 | // First corner 25 | vec3 i = floor(v + dot(v, C.yyy) ); 26 | vec3 x0 = v - i + dot(i, C.xxx) ; 27 | 28 | // Other corners 29 | vec3 g = step(x0.yzx, x0.xyz); 30 | vec3 l = 1.0 - g; 31 | vec3 i1 = min( g.xyz, l.zxy ); 32 | vec3 i2 = max( g.xyz, l.zxy ); 33 | 34 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 35 | // x1 = x0 - i1 + 1.0 * C.xxx; 36 | // x2 = x0 - i2 + 2.0 * C.xxx; 37 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 38 | vec3 x1 = x0 - i1 + C.xxx; 39 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 40 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 41 | 42 | // Permutations 43 | i = mod289(i); 44 | vec4 p = permute( permute( permute( 45 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 46 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 47 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 48 | 49 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 50 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 51 | float n_ = 0.142857142857; // 1.0/7.0 52 | vec3 ns = n_ * D.wyz - D.xzx; 53 | 54 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 55 | 56 | vec4 x_ = floor(j * ns.z); 57 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 58 | 59 | vec4 x = x_ *ns.x + ns.yyyy; 60 | vec4 y = y_ *ns.x + ns.yyyy; 61 | vec4 h = 1.0 - abs(x) - abs(y); 62 | 63 | vec4 b0 = vec4( x.xy, y.xy ); 64 | vec4 b1 = vec4( x.zw, y.zw ); 65 | 66 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 67 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 68 | vec4 s0 = floor(b0)*2.0 + 1.0; 69 | vec4 s1 = floor(b1)*2.0 + 1.0; 70 | vec4 sh = -step(h, vec4(0.0)); 71 | 72 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 73 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 74 | 75 | vec3 p0 = vec3(a0.xy,h.x); 76 | vec3 p1 = vec3(a0.zw,h.y); 77 | vec3 p2 = vec3(a1.xy,h.z); 78 | vec3 p3 = vec3(a1.zw,h.w); 79 | 80 | //Normalise gradients 81 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 82 | p0 *= norm.x; 83 | p1 *= norm.y; 84 | p2 *= norm.z; 85 | p3 *= norm.w; 86 | 87 | // Mix final noise value 88 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 89 | m = m * m; 90 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 91 | dot(p2,x2), dot(p3,x3) ) ); 92 | } 93 | ` 94 | -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100vh; 4 | overflow: hidden; 5 | border: none; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | #root { 11 | height: 100vh; 12 | } 13 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": false, 4 | "target": "es5", 5 | "module": "commonjs", 6 | "jsx": "react", 7 | "moduleResolution": "node", 8 | "noImplicitAny": false, 9 | "noUnusedLocals": false, 10 | "noUnusedParameters": false, 11 | "removeComments": true, 12 | "strictNullChecks": true, 13 | "preserveConstEnums": true, 14 | "sourceMap": true, 15 | "lib": ["es2015", "es2016", "dom"], 16 | "types": ["node"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/voronoi.js: -------------------------------------------------------------------------------- 1 | export default /*glsl*/ ` 2 | // Permutation polynomial: (34x^2 + x) mod 289 3 | vec3 permute(vec3 x) { 4 | return mod((34.0 * x + 1.0) * x, 289.0); 5 | } 6 | 7 | vec3 dist(vec3 x, vec3 y, vec3 z, bool manhattanDistance) { 8 | return manhattanDistance ? abs(x) + abs(y) + abs(z) : (x * x + y * y + z * z); 9 | } 10 | 11 | vec2 worley(vec3 P, float jitter, bool manhattanDistance) { 12 | float K = 0.142857142857; // 1/7 13 | float Ko = 0.428571428571; // 1/2-K/2 14 | float K2 = 0.020408163265306; // 1/(7*7) 15 | float Kz = 0.166666666667; // 1/6 16 | float Kzo = 0.416666666667; // 1/2-1/6*2 17 | 18 | vec3 Pi = mod(floor(P), 289.0); 19 | vec3 Pf = fract(P) - 0.5; 20 | 21 | vec3 Pfx = Pf.x + vec3(1.0, 0.0, -1.0); 22 | vec3 Pfy = Pf.y + vec3(1.0, 0.0, -1.0); 23 | vec3 Pfz = Pf.z + vec3(1.0, 0.0, -1.0); 24 | 25 | vec3 p = permute(Pi.x + vec3(-1.0, 0.0, 1.0)); 26 | vec3 p1 = permute(p + Pi.y - 1.0); 27 | vec3 p2 = permute(p + Pi.y); 28 | vec3 p3 = permute(p + Pi.y + 1.0); 29 | 30 | vec3 p11 = permute(p1 + Pi.z - 1.0); 31 | vec3 p12 = permute(p1 + Pi.z); 32 | vec3 p13 = permute(p1 + Pi.z + 1.0); 33 | 34 | vec3 p21 = permute(p2 + Pi.z - 1.0); 35 | vec3 p22 = permute(p2 + Pi.z); 36 | vec3 p23 = permute(p2 + Pi.z + 1.0); 37 | 38 | vec3 p31 = permute(p3 + Pi.z - 1.0); 39 | vec3 p32 = permute(p3 + Pi.z); 40 | vec3 p33 = permute(p3 + Pi.z + 1.0); 41 | 42 | vec3 ox11 = fract(p11*K) - Ko; 43 | vec3 oy11 = mod(floor(p11*K), 7.0)*K - Ko; 44 | vec3 oz11 = floor(p11*K2)*Kz - Kzo; // p11 < 289 guaranteed 45 | 46 | vec3 ox12 = fract(p12*K) - Ko; 47 | vec3 oy12 = mod(floor(p12*K), 7.0)*K - Ko; 48 | vec3 oz12 = floor(p12*K2)*Kz - Kzo; 49 | 50 | vec3 ox13 = fract(p13*K) - Ko; 51 | vec3 oy13 = mod(floor(p13*K), 7.0)*K - Ko; 52 | vec3 oz13 = floor(p13*K2)*Kz - Kzo; 53 | 54 | vec3 ox21 = fract(p21*K) - Ko; 55 | vec3 oy21 = mod(floor(p21*K), 7.0)*K - Ko; 56 | vec3 oz21 = floor(p21*K2)*Kz - Kzo; 57 | 58 | vec3 ox22 = fract(p22*K) - Ko; 59 | vec3 oy22 = mod(floor(p22*K), 7.0)*K - Ko; 60 | vec3 oz22 = floor(p22*K2)*Kz - Kzo; 61 | 62 | vec3 ox23 = fract(p23*K) - Ko; 63 | vec3 oy23 = mod(floor(p23*K), 7.0)*K - Ko; 64 | vec3 oz23 = floor(p23*K2)*Kz - Kzo; 65 | 66 | vec3 ox31 = fract(p31*K) - Ko; 67 | vec3 oy31 = mod(floor(p31*K), 7.0)*K - Ko; 68 | vec3 oz31 = floor(p31*K2)*Kz - Kzo; 69 | 70 | vec3 ox32 = fract(p32*K) - Ko; 71 | vec3 oy32 = mod(floor(p32*K), 7.0)*K - Ko; 72 | vec3 oz32 = floor(p32*K2)*Kz - Kzo; 73 | 74 | vec3 ox33 = fract(p33*K) - Ko; 75 | vec3 oy33 = mod(floor(p33*K), 7.0)*K - Ko; 76 | vec3 oz33 = floor(p33*K2)*Kz - Kzo; 77 | 78 | vec3 dx11 = Pfx + jitter*ox11; 79 | vec3 dy11 = Pfy.x + jitter*oy11; 80 | vec3 dz11 = Pfz.x + jitter*oz11; 81 | 82 | vec3 dx12 = Pfx + jitter*ox12; 83 | vec3 dy12 = Pfy.x + jitter*oy12; 84 | vec3 dz12 = Pfz.y + jitter*oz12; 85 | 86 | vec3 dx13 = Pfx + jitter*ox13; 87 | vec3 dy13 = Pfy.x + jitter*oy13; 88 | vec3 dz13 = Pfz.z + jitter*oz13; 89 | 90 | vec3 dx21 = Pfx + jitter*ox21; 91 | vec3 dy21 = Pfy.y + jitter*oy21; 92 | vec3 dz21 = Pfz.x + jitter*oz21; 93 | 94 | vec3 dx22 = Pfx + jitter*ox22; 95 | vec3 dy22 = Pfy.y + jitter*oy22; 96 | vec3 dz22 = Pfz.y + jitter*oz22; 97 | 98 | vec3 dx23 = Pfx + jitter*ox23; 99 | vec3 dy23 = Pfy.y + jitter*oy23; 100 | vec3 dz23 = Pfz.z + jitter*oz23; 101 | 102 | vec3 dx31 = Pfx + jitter*ox31; 103 | vec3 dy31 = Pfy.z + jitter*oy31; 104 | vec3 dz31 = Pfz.x + jitter*oz31; 105 | 106 | vec3 dx32 = Pfx + jitter*ox32; 107 | vec3 dy32 = Pfy.z + jitter*oy32; 108 | vec3 dz32 = Pfz.y + jitter*oz32; 109 | 110 | vec3 dx33 = Pfx + jitter*ox33; 111 | vec3 dy33 = Pfy.z + jitter*oy33; 112 | vec3 dz33 = Pfz.z + jitter*oz33; 113 | 114 | vec3 d11 = dist(dx11, dy11, dz11, manhattanDistance); 115 | vec3 d12 =dist(dx12, dy12, dz12, manhattanDistance); 116 | vec3 d13 = dist(dx13, dy13, dz13, manhattanDistance); 117 | vec3 d21 = dist(dx21, dy21, dz21, manhattanDistance); 118 | vec3 d22 = dist(dx22, dy22, dz22, manhattanDistance); 119 | vec3 d23 = dist(dx23, dy23, dz23, manhattanDistance); 120 | vec3 d31 = dist(dx31, dy31, dz31, manhattanDistance); 121 | vec3 d32 = dist(dx32, dy32, dz32, manhattanDistance); 122 | vec3 d33 = dist(dx33, dy33, dz33, manhattanDistance); 123 | 124 | vec3 d1a = min(d11, d12); 125 | d12 = max(d11, d12); 126 | d11 = min(d1a, d13); // Smallest now not in d12 or d13 127 | d13 = max(d1a, d13); 128 | d12 = min(d12, d13); // 2nd smallest now not in d13 129 | vec3 d2a = min(d21, d22); 130 | d22 = max(d21, d22); 131 | d21 = min(d2a, d23); // Smallest now not in d22 or d23 132 | d23 = max(d2a, d23); 133 | d22 = min(d22, d23); // 2nd smallest now not in d23 134 | vec3 d3a = min(d31, d32); 135 | d32 = max(d31, d32); 136 | d31 = min(d3a, d33); // Smallest now not in d32 or d33 137 | d33 = max(d3a, d33); 138 | d32 = min(d32, d33); // 2nd smallest now not in d33 139 | vec3 da = min(d11, d21); 140 | d21 = max(d11, d21); 141 | d11 = min(da, d31); // Smallest now in d11 142 | d31 = max(da, d31); // 2nd smallest now not in d31 143 | d11.xy = (d11.x < d11.y) ? d11.xy : d11.yx; 144 | d11.xz = (d11.x < d11.z) ? d11.xz : d11.zx; // d11.x now smallest 145 | d12 = min(d12, d21); // 2nd smallest now not in d21 146 | d12 = min(d12, d22); // nor in d22 147 | d12 = min(d12, d31); // nor in d31 148 | d12 = min(d12, d32); // nor in d32 149 | d11.yz = min(d11.yz,d12.xy); // nor in d12.yz 150 | d11.y = min(d11.y,d12.z); // Only two more to go 151 | d11.y = min(d11.y,d11.z); // Done! (Phew!) 152 | return sqrt(d11.xy); // F1, F2 153 | 154 | }` 155 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/component-material/1e9efeb05620162781bcd78ad91fa95259b6b133/logo.jpg -------------------------------------------------------------------------------- /make-types.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | 3 | const fragTypes = Object.keys(THREE.ShaderChunk) 4 | .map(x => `"${x}"`) 5 | .filter(x => x.indexOf('frag') > -1) 6 | .join(' | ') 7 | const vertTypes = Object.keys(THREE.ShaderChunk) 8 | .map(x => `"${x}"`) 9 | .filter(x => x.indexOf('vert') > -1) 10 | .join(' | ') 11 | const common = Object.keys(THREE.ShaderChunk) 12 | .map(x => `"${x}"`) 13 | .filter(x => x.indexOf('vert') === -1 && x.indexOf('frag') === -1) 14 | .join(' | ') 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-material", 3 | "version": "1.0.7", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/emmelleppi/component-material.git" 8 | }, 9 | "keywords": [], 10 | "main": "dist/index.js", 11 | "typings": "dist/index.d.ts", 12 | "files": [ 13 | "dist", 14 | "src" 15 | ], 16 | "engines": { 17 | "node": ">=10" 18 | }, 19 | "scripts": { 20 | "start": "tsdx watch", 21 | "build": "tsdx build", 22 | "test": "tsdx test --passWithNoTests", 23 | "lint": "tsdx lint", 24 | "prepare": "tsdx build", 25 | "size": "size-limit", 26 | "analyze": "size-limit --why" 27 | }, 28 | "peerDependencies": { 29 | "react": ">=16" 30 | }, 31 | "husky": { 32 | "hooks": { 33 | "pre-commit": "tsdx lint --fix" 34 | } 35 | }, 36 | "prettier": { 37 | "semi": false, 38 | "trailingComma": "es5", 39 | "singleQuote": true, 40 | "jsxBracketSameLine": true, 41 | "tabWidth": 2, 42 | "printWidth": 120 43 | }, 44 | "author": "Marco Ludovico Perego", 45 | "module": "dist/component-material.esm.js", 46 | "size-limit": [ 47 | { 48 | "path": "dist/component-material.cjs.production.min.js", 49 | "limit": "10 KB" 50 | }, 51 | { 52 | "path": "dist/component-material.esm.js", 53 | "limit": "10 KB" 54 | } 55 | ], 56 | "devDependencies": { 57 | "@babel/core": "7.13.13", 58 | "@babel/preset-env": "^7.14.2", 59 | "@babel/preset-react": "7.13.13", 60 | "@babel/preset-typescript": "^7.13.0", 61 | "@size-limit/preset-small-lib": "^4.9.0", 62 | "@types/react": "^17.0.0", 63 | "@types/react-dom": "^17.0.0", 64 | "@types/three": "^0.126.2", 65 | "babel-loader": "^8.2.2", 66 | "husky": "^4.3.0", 67 | "react": "^17.0.2", 68 | "react-dom": "^17.0.2", 69 | "react-is": "^17.0.2", 70 | "react-three-fiber": "^6.0.13", 71 | "size-limit": "^4.9.0", 72 | "three": "^0.128.0", 73 | "tsdx": "^0.14.1", 74 | "tslib": "^2.0.3", 75 | "typescript": "^4.1.2" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /readme/autocomplete.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/component-material/1e9efeb05620162781bcd78ad91fa95259b6b133/readme/autocomplete.jpg -------------------------------------------------------------------------------- /readme/distortion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/component-material/1e9efeb05620162781bcd78ad91fa95259b6b133/readme/distortion.jpg -------------------------------------------------------------------------------- /readme/glslify.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/component-material/1e9efeb05620162781bcd78ad91fa95259b6b133/readme/glslify.jpg -------------------------------------------------------------------------------- /readme/multiple-instances.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/component-material/1e9efeb05620162781bcd78ad91fa95259b6b133/readme/multiple-instances.jpg -------------------------------------------------------------------------------- /readme/voronoi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/component-material/1e9efeb05620162781bcd78ad91fa95259b6b133/readme/voronoi.jpg -------------------------------------------------------------------------------- /src/component-material.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useRef } from 'react' 2 | import { FRAG, VERT } from './constants' 3 | import createMaterial from './create-material' 4 | import { ChildProps, ExtensionShaderObject, ExtensionShadersObject, Uniforms } from './types/internal' 5 | import { ComponentMaterialProps, GenericMaterial } from './types/index' 6 | 7 | function editShader(shader: string, extensions: ExtensionShaderObject) { 8 | Object.entries(extensions).forEach(([key, { value, replaceChunk }]) => { 9 | if (value && shader.includes(key)) { 10 | shader = shader.replace( 11 | `#include <${key}>`, 12 | ` 13 | ${replaceChunk ? '' : `#include <${key}>`} 14 | ${value} 15 | ` 16 | ) 17 | } 18 | }) 19 | return shader 20 | } 21 | 22 | function editShaderHead(shader: string, head: string) { 23 | if (head && typeof head === 'string') { 24 | shader = ` 25 | ${head} 26 | ${shader} 27 | ` 28 | } 29 | return shader 30 | } 31 | 32 | function addUniforms(shader: string, uniforms: Uniforms) { 33 | return `${Object.entries(uniforms) 34 | .map(([key, { type }]) => `uniform ${type} ${key};`) 35 | .join(' ')} 36 | ${shader} 37 | ` 38 | } 39 | 40 | function addVarying(shader: string, varying: Uniforms) { 41 | return `${Object.entries(varying) 42 | .map(([key, { type }]) => `varying ${type} ${key};`) 43 | .join(' ')} 44 | ${shader} 45 | ` 46 | } 47 | 48 | export const ComponentMaterial = React.forwardRef( 49 | function ComponentMaterial({ children, varyings = {}, uniforms = {}, from, ...props }, ref) { 50 | const uniformsRef = useRef(uniforms) 51 | const varyingsRef = useRef(varyings) 52 | 53 | const _uniforms = useMemo( 54 | () => 55 | Object.entries(uniforms).reduce((acc: any, [key, { value }]) => { 56 | acc[key] = value 57 | return acc 58 | }, {}), 59 | [uniforms] 60 | ) 61 | 62 | const shaders = useMemo( 63 | () => 64 | React.Children.toArray(children).reduce( 65 | (acc: any, child: any) => { 66 | const shader = child?.props?.children 67 | 68 | if (typeof shader === 'string') { 69 | const replaceChunk = child?.props?.replaceChunk || false 70 | const { chunkName, shaderType }: ChildProps = child.type 71 | 72 | if ([VERT, FRAG].includes(shaderType)) { 73 | if (chunkName === 'Head') { 74 | acc[shaderType].head = acc[shaderType].head.concat(` 75 | ${shader} 76 | `) 77 | } else { 78 | if (!acc[shaderType][chunkName]) { 79 | acc[shaderType][chunkName] = { 80 | value: '', 81 | replaceChunk: false, 82 | } 83 | } 84 | 85 | acc[shaderType][chunkName].replaceChunk = replaceChunk 86 | acc[shaderType][chunkName].value = acc[shaderType][chunkName].value.concat(` 87 | ${shader} 88 | `) 89 | } 90 | } else { 91 | acc.common = acc.common.concat(` 92 | ${shader} 93 | `) 94 | } 95 | } 96 | 97 | return acc 98 | }, 99 | { 100 | vert: { 101 | head: '', 102 | }, 103 | frag: { 104 | head: '', 105 | }, 106 | common: '', 107 | } 108 | ), 109 | [children] 110 | ) 111 | 112 | const material = useMemo(() => { 113 | const { vert, frag, common } = shaders 114 | const { head: vertHead, ...vertBody } = vert 115 | const { head: fragHead, ...fragBody } = frag 116 | 117 | const _material = createMaterial(from, uniformsRef.current, shader => { 118 | shader.fragmentShader = editShaderHead(shader.fragmentShader, fragHead) 119 | shader.vertexShader = editShaderHead(shader.vertexShader, vertHead) 120 | shader.fragmentShader = editShaderHead(shader.fragmentShader, common) 121 | shader.vertexShader = editShaderHead(shader.vertexShader, common) 122 | shader.fragmentShader = addUniforms(shader.fragmentShader, uniformsRef.current) 123 | shader.vertexShader = addUniforms(shader.vertexShader, uniformsRef.current) 124 | shader.fragmentShader = addVarying(shader.fragmentShader, varyingsRef.current) 125 | shader.vertexShader = addVarying(shader.vertexShader, varyingsRef.current) 126 | shader.fragmentShader = editShader(shader.fragmentShader, fragBody) 127 | shader.vertexShader = editShader(shader.vertexShader, vertBody) 128 | }) 129 | return new _material() 130 | }, [shaders, from]) 131 | 132 | return 133 | } 134 | ) 135 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const VERT = 'vert' 2 | export const FRAG = 'frag' 3 | export const COMMON = 'common' 4 | export const DEFAULT_VERT_CHUNK = 'project_vertex' 5 | export const DEFAULT_FRAG_CHUNK = 'dithering_fragment' 6 | -------------------------------------------------------------------------------- /src/create-material.ts: -------------------------------------------------------------------------------- 1 | import { MeshPhysicalMaterial, Shader } from 'three' 2 | 3 | import { getKeyValue, setKeyValue } from './helpers/objects' 4 | import { MaterialConstructor } from './types/index' 5 | import { Uniforms } from './types/internal' 6 | 7 | function createMaterial( 8 | baseMaterial: MaterialConstructor = MeshPhysicalMaterial, 9 | uniforms: Uniforms = {}, 10 | onBeforeCompile?: (shader: Shader) => void 11 | ) { 12 | return class ComponentMaterial extends baseMaterial { 13 | constructor(parameters = {}) { 14 | const entries = Object.keys(uniforms) 15 | super(parameters) 16 | this.setValues(parameters) 17 | 18 | entries.forEach(key => { 19 | setKeyValue(this, `_${key}`, { value: uniforms[key] }) 20 | Object.defineProperty(this, key, { 21 | get: () => this[`_${key}`].value, 22 | set: v => (this[`_${key}`].value = v), 23 | }) 24 | }) 25 | } 26 | 27 | onBeforeCompile(shader: Shader) { 28 | const handler = { 29 | get: function(target: Shader, key: keyof Shader) { 30 | return getKeyValue(target, key) 31 | }, 32 | set: function(target: Shader, key: keyof Shader, value: any) { 33 | setKeyValue(target, key, value) 34 | // Accoring to ProxyHandler, the set function should return a boolean. 35 | return true 36 | }, 37 | } 38 | 39 | const entries = Object.keys(uniforms) 40 | entries.forEach(key => { 41 | shader.uniforms[key] = this[`_${key}`] 42 | }) 43 | 44 | const proxiedShader = new Proxy(shader, handler) 45 | 46 | if (onBeforeCompile) { 47 | onBeforeCompile(proxiedShader) 48 | } 49 | } 50 | } 51 | } 52 | 53 | export default createMaterial 54 | -------------------------------------------------------------------------------- /src/generated.ts: -------------------------------------------------------------------------------- 1 | export type fragmentChunks = 2 | | 'alphamap_fragment' 3 | | 'alphamap_pars_fragment' 4 | | 'alphatest_fragment' 5 | | 'aomap_fragment' 6 | | 'aomap_pars_fragment' 7 | | 'bumpmap_pars_fragment' 8 | | 'clipping_planes_fragment' 9 | | 'clipping_planes_pars_fragment' 10 | | 'color_fragment' 11 | | 'color_pars_fragment' 12 | | 'cube_uv_reflection_fragment' 13 | | 'emissivemap_fragment' 14 | | 'emissivemap_pars_fragment' 15 | | 'encodings_fragment' 16 | | 'encodings_pars_fragment' 17 | | 'envmap_fragment' 18 | | 'envmap_common_pars_fragment' 19 | | 'envmap_pars_fragment' 20 | | 'envmap_physical_pars_fragment' 21 | | 'fog_fragment' 22 | | 'fog_pars_fragment' 23 | | 'gradientmap_pars_fragment' 24 | | 'lightmap_fragment' 25 | | 'lightmap_pars_fragment' 26 | | 'lights_toon_fragment' 27 | | 'lights_toon_pars_fragment' 28 | | 'lights_phong_fragment' 29 | | 'lights_phong_pars_fragment' 30 | | 'lights_physical_fragment' 31 | | 'lights_physical_pars_fragment' 32 | | 'lights_fragment_begin' 33 | | 'lights_fragment_maps' 34 | | 'lights_fragment_end' 35 | | 'logdepthbuf_fragment' 36 | | 'logdepthbuf_pars_fragment' 37 | | 'map_fragment' 38 | | 'map_pars_fragment' 39 | | 'map_particle_fragment' 40 | | 'map_particle_pars_fragment' 41 | | 'metalnessmap_fragment' 42 | | 'metalnessmap_pars_fragment' 43 | | 'normal_fragment_begin' 44 | | 'normal_fragment_maps' 45 | | 'normalmap_pars_fragment' 46 | | 'clearcoat_normal_fragment_begin' 47 | | 'clearcoat_normal_fragment_maps' 48 | | 'clearcoat_pars_fragment' 49 | | 'premultiplied_alpha_fragment' 50 | | 'dithering_fragment' 51 | | 'dithering_pars_fragment' 52 | | 'roughnessmap_fragment' 53 | | 'roughnessmap_pars_fragment' 54 | | 'shadowmap_pars_fragment' 55 | | 'shadowmask_pars_fragment' 56 | | 'specularmap_fragment' 57 | | 'specularmap_pars_fragment' 58 | | 'tonemapping_fragment' 59 | | 'tonemapping_pars_fragment' 60 | | 'transmissionmap_fragment' 61 | | 'transmissionmap_pars_fragment' 62 | | 'uv_pars_fragment' 63 | | 'uv2_pars_fragment' 64 | | 'background_frag' 65 | | 'cube_frag' 66 | | 'depth_frag' 67 | | 'distanceRGBA_frag' 68 | | 'equirect_frag' 69 | | 'linedashed_frag' 70 | | 'meshbasic_frag' 71 | | 'meshlambert_frag' 72 | | 'meshmatcap_frag' 73 | | 'meshtoon_frag' 74 | | 'meshphong_frag' 75 | | 'meshphysical_frag' 76 | | 'normal_frag' 77 | | 'points_frag' 78 | | 'shadow_frag' 79 | | 'sprite_frag' 80 | export type vertexChunks = 81 | | 'begin_vertex' 82 | | 'beginnormal_vertex' 83 | | 'clipping_planes_pars_vertex' 84 | | 'clipping_planes_vertex' 85 | | 'color_pars_vertex' 86 | | 'color_vertex' 87 | | 'defaultnormal_vertex' 88 | | 'displacementmap_pars_vertex' 89 | | 'displacementmap_vertex' 90 | | 'envmap_pars_vertex' 91 | | 'envmap_vertex' 92 | | 'fog_vertex' 93 | | 'fog_pars_vertex' 94 | | 'lights_lambert_vertex' 95 | | 'logdepthbuf_pars_vertex' 96 | | 'logdepthbuf_vertex' 97 | | 'morphnormal_vertex' 98 | | 'morphtarget_pars_vertex' 99 | | 'morphtarget_vertex' 100 | | 'project_vertex' 101 | | 'shadowmap_pars_vertex' 102 | | 'shadowmap_vertex' 103 | | 'skinbase_vertex' 104 | | 'skinning_pars_vertex' 105 | | 'skinning_vertex' 106 | | 'skinnormal_vertex' 107 | | 'uv_pars_vertex' 108 | | 'uv_vertex' 109 | | 'uv2_pars_vertex' 110 | | 'uv2_vertex' 111 | | 'worldpos_vertex' 112 | | 'background_vert' 113 | | 'cube_vert' 114 | | 'depth_vert' 115 | | 'distanceRGBA_vert' 116 | | 'equirect_vert' 117 | | 'linedashed_vert' 118 | | 'meshbasic_vert' 119 | | 'meshlambert_vert' 120 | | 'meshmatcap_vert' 121 | | 'meshtoon_vert' 122 | | 'meshphong_vert' 123 | | 'meshphysical_vert' 124 | | 'normal_vert' 125 | | 'points_vert' 126 | | 'shadow_vert' 127 | | 'sprite_vert' 128 | export type commonChunks = 'head' | 'body' | 'bsdfs' | 'common' | 'lights_pars_begin' | 'packing' 129 | -------------------------------------------------------------------------------- /src/helpers/objects.ts: -------------------------------------------------------------------------------- 1 | export const getKeyValue = (obj: T, key: K): T[K] => obj[key] 2 | export const setKeyValue = (obj: T, key: K, value: any): T[K] => (obj[key] = value) 3 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMaterial } from './component-material' 2 | import { frag, vert, common } from './proxies' 3 | 4 | export * from './types/index' 5 | 6 | export type MT = typeof ComponentMaterial & { 7 | Vert: typeof vert 8 | Frag: typeof frag 9 | Common: typeof common 10 | } 11 | 12 | const M = ComponentMaterial 13 | 14 | Object.defineProperties(ComponentMaterial, { 15 | Vert: { 16 | get: (): typeof vert => vert, 17 | }, 18 | Frag: { 19 | get: (): typeof frag => frag, 20 | }, 21 | Common: { 22 | get: (): typeof common => common, 23 | }, 24 | }) 25 | 26 | export default M as MT 27 | -------------------------------------------------------------------------------- /src/proxies.tsx: -------------------------------------------------------------------------------- 1 | import { DEFAULT_FRAG_CHUNK, DEFAULT_VERT_CHUNK, FRAG, VERT } from './constants' 2 | import { ProxyProps, ProxyComponent } from './types/internal' 3 | 4 | import { fragmentChunks, vertexChunks, commonChunks } from './generated' 5 | 6 | function NullFunction() { 7 | return null 8 | } 9 | 10 | type ShaderProxyHelper = { 11 | [key in T]: any 12 | } & { 13 | Body: ProxyComponent 14 | Head: ProxyComponent 15 | } 16 | 17 | // -- VERTEX PROXY -- 18 | const vertHandler = { 19 | get: function(_: any, name: string) { 20 | const Component = function({ children }: ProxyProps) { 21 | return children 22 | } 23 | Object.defineProperty(Component, 'chunkName', { writable: true }) 24 | Object.defineProperty(Component, 'shaderType', { writable: true }) 25 | Component.chunkName = name === 'Body' ? DEFAULT_VERT_CHUNK : name 26 | Component.shaderType = VERT 27 | return Component 28 | }, 29 | } 30 | export const vert: ShaderProxyHelper = new Proxy(NullFunction, vertHandler) 31 | 32 | // -- FRAGMENT PROXY -- 33 | const fragHandler = { 34 | get: function(_: any, name: string) { 35 | const Component = function({ children }: ProxyProps) { 36 | return children 37 | } 38 | Object.defineProperty(Component, 'chunkName', { writable: true }) 39 | Object.defineProperty(Component, 'shaderType', { writable: true }) 40 | Component.chunkName = name === 'Body' ? DEFAULT_FRAG_CHUNK : name 41 | Component.shaderType = FRAG 42 | return Component 43 | }, 44 | } 45 | 46 | export const frag: ShaderProxyHelper = new Proxy(NullFunction, fragHandler) 47 | 48 | export function common({ children }: ProxyProps) { 49 | return (children as unknown) as JSX.Element 50 | } 51 | 52 | // TODO 53 | // // -- NOISE PROXY -- 54 | // const noise = { 55 | // snoise2: "glsl-noise/simplex/2d", 56 | // snoise3: "glsl-noise/simplex/3d", 57 | // snoise4: "glsl-noise/simplex/4d", 58 | // cnoise2: "glsl-noise/classic/2d", 59 | // cnoise3: "glsl-noise/classic/3d", 60 | // cnoise4: "glsl-noise/classic/4d", 61 | // pnoise2: "glsl-noise/periodic/2d", 62 | // pnoise3: "glsl-noise/periodic/3d", 63 | // pnoise4: "glsl-noise/periodic/4d", 64 | // }; 65 | // const noiseHandler = { 66 | // get: function (_, name) { 67 | // const path = noise[name]; 68 | // if (path) { 69 | // const pragma = `#pragma glslify: ${name} = require(${path})`; 70 | // const Component = () => null; 71 | // Object.defineProperty(Component, "shaderType", { writable: true }); 72 | // Object.defineProperty(Component, "toolShader", { writable: true }); 73 | // Component.shaderType = TOOL; 74 | // Component.toolShader = pragma; 75 | // return Component; 76 | // } 77 | // return null; 78 | // }, 79 | // }; 80 | // export const Noise = new Proxy(() => null, noiseHandler); 81 | 82 | // // -- EASING PROXY -- 83 | // const easing = { 84 | // backInOut: "glsl-easings/back-in-out", 85 | // backIn: "glsl-easings/back-in", 86 | // backOut: "glsl-easings/back-out", 87 | // bounceInOut: "glsl-easings/bounce-in-out", 88 | // bounceIn: "glsl-easings/bounce-in", 89 | // bounceOut: "glsl-easings/bounce-out", 90 | // circularInOut: "glsl-easings/circular-in-out", 91 | // circularIn: "glsl-easings/circular-in", 92 | // circularOut: "glsl-easings/circular-out", 93 | // cubicInOut: "glsl-easings/cubic-in-out", 94 | // cubicIn: "glsl-easings/cubic-in", 95 | // cubicOut: "glsl-easings/cubic-out", 96 | // elasticInOut: "glsl-easings/elastic-in-out", 97 | // elasticIn: "glsl-easings/elastic-in", 98 | // elasticOut: "glsl-easings/elastic-out", 99 | // exponentialInOut: "glsl-easings/exponential-in-out", 100 | // exponentialIn: "glsl-easings/exponential-in", 101 | // exponentialOut: "glsl-easings/exponential-out", 102 | // linear: "glsl-easings/linear", 103 | // quadraticInOut: "glsl-easings/quadratic-in-out", 104 | // quadraticIn: "glsl-easings/quadratic-in", 105 | // quadraticOut: "glsl-easings/quadratic-out", 106 | // quarticInOut: "glsl-easings/quartic-in-out", 107 | // quarticIn: "glsl-easings/quartic-in", 108 | // quarticOut: "glsl-easings/quartic-out", 109 | // quinticInOut: "glsl-easings/quintic-in-out", 110 | // quinticIn: "glsl-easings/quintic-in", 111 | // quinticOut: "glsl-easings/quintic-out", 112 | // sineInOut: "glsl-easings/sine-in-out", 113 | // sineIn: "glsl-easings/sine-in", 114 | // sineOut: "glsl-easings/sine-out", 115 | // }; 116 | // const easingHandler = { 117 | // get: function (_, name) { 118 | // const path = easing[name]; 119 | // if (path) { 120 | // const pragma = `#pragma glslify: ${name} = require(${path})`; 121 | // const Component = () => null; 122 | // Object.defineProperty(Component, "shaderType", { writable: true }); 123 | // Object.defineProperty(Component, "toolShader", { writable: true }); 124 | // Component.shaderType = TOOL; 125 | // Component.toolShader = pragma; 126 | // return Component; 127 | // } 128 | // return null; 129 | // }, 130 | // }; 131 | // export const Ease = new Proxy(() => null, easingHandler); 132 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Material } from 'three' 2 | 3 | import { Uniforms, Varyings, AllMaterialProps } from './internal' 4 | 5 | export interface MaterialConstructor { 6 | new (...args: any[]): GenericMaterial 7 | } 8 | export type ComponentMaterialProps = AllMaterialProps & { 9 | varyings?: Varyings 10 | uniforms?: Uniforms 11 | from?: MaterialConstructor 12 | } 13 | export interface GenericMaterial extends Material { 14 | [key: string]: any 15 | } 16 | export type ComponentMaterial = (props: ComponentMaterialProps) => GenericMaterial 17 | -------------------------------------------------------------------------------- /src/types/internal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MaterialProps, 3 | ShadowMaterialProps, 4 | SpriteMaterialProps, 5 | RawShaderMaterialProps, 6 | ShaderMaterialProps, 7 | PointsMaterialProps, 8 | MeshPhysicalMaterialProps, 9 | MeshStandardMaterialProps, 10 | MeshPhongMaterialProps, 11 | MeshToonMaterialProps, 12 | MeshNormalMaterialProps, 13 | MeshLambertMaterialProps, 14 | MeshDepthMaterialProps, 15 | MeshDistanceMaterialProps, 16 | MeshBasicMaterialProps, 17 | MeshMatcapMaterialProps, 18 | LineDashedMaterialProps, 19 | LineBasicMaterialProps, 20 | } from 'react-three-fiber' 21 | 22 | export type ProxyProps = { 23 | children: Child 24 | } 25 | export type ProxyComponent = (props: ProxyProps) => JSX.Element 26 | export type ExtensionsType = { 27 | value?: string 28 | replaceChunk: boolean 29 | } 30 | export type GLProp = { 31 | value?: 32 | | number 33 | | string 34 | | boolean 35 | | THREE.Texture 36 | | THREE.Vector2 37 | | THREE.Vector3 38 | | THREE.Vector4 39 | | Array 40 | | Float32Array 41 | | THREE.Color 42 | | THREE.Quaternion 43 | | THREE.Matrix3 44 | | THREE.Matrix4 45 | | Int32Array 46 | | THREE.CubeTexture 47 | type: string 48 | } 49 | export type Uniforms = { [key: string]: GLProp } 50 | export type Varyings = { 51 | [key: string]: Omit 52 | } 53 | export type ChildProps = { 54 | chunkName: string 55 | shaderType: string 56 | } 57 | export type ExtensionShaderObject = { 58 | [key: string]: ExtensionsType 59 | } 60 | export type ExtensionShadersObject = { 61 | vert: ExtensionShaderObject & { head: string } 62 | frag: ExtensionShaderObject & { head: string } 63 | common: string 64 | } 65 | export type AllMaterialProps = MaterialProps & 66 | ShadowMaterialProps & 67 | SpriteMaterialProps & 68 | RawShaderMaterialProps & 69 | ShaderMaterialProps & 70 | PointsMaterialProps & 71 | MeshPhysicalMaterialProps & 72 | MeshStandardMaterialProps & 73 | MeshPhongMaterialProps & 74 | MeshToonMaterialProps & 75 | MeshNormalMaterialProps & 76 | MeshLambertMaterialProps & 77 | MeshDepthMaterialProps & 78 | MeshDistanceMaterialProps & 79 | MeshBasicMaterialProps & 80 | MeshMatcapMaterialProps & 81 | LineDashedMaterialProps & 82 | LineBasicMaterialProps 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "es2015", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true 34 | } 35 | } 36 | --------------------------------------------------------------------------------