├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── assets ├── caustics-demo.png ├── points-demo.png └── waves-demo.png ├── examples ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package.json ├── public │ ├── Caustics │ │ └── pooltiles │ │ │ ├── tlfmffydy_4K_AO.jpg │ │ │ ├── tlfmffydy_4K_Albedo.jpg │ │ │ ├── tlfmffydy_4K_Displacement.jpg │ │ │ ├── tlfmffydy_4K_Normal.jpg │ │ │ └── tlfmffydy_4K_Roughness.jpg │ ├── Shadows │ │ └── react.png │ └── vite.svg ├── src │ ├── App.tsx │ ├── Examples │ │ ├── Caustics │ │ │ ├── Caustics.tsx │ │ │ ├── Scene.tsx │ │ │ ├── components │ │ │ │ ├── Floor.tsx │ │ │ │ └── Lights.tsx │ │ │ ├── fs.glsl │ │ │ └── vs.glsl │ │ ├── Instances │ │ │ ├── Scene.tsx │ │ │ ├── fs.glsl │ │ │ └── vs.glsl │ │ ├── MetalBunny │ │ │ ├── DiscardMaterial.ts │ │ │ ├── MeshTransmissionMaterial.tsx │ │ │ ├── Scene.tsx │ │ │ ├── Ui.tsx │ │ │ ├── Ui3D.tsx │ │ │ ├── fs.glsl │ │ │ ├── index.css │ │ │ ├── state │ │ │ │ └── appState.ts │ │ │ └── vs.glsl │ │ ├── Points │ │ │ ├── Scene.tsx │ │ │ ├── fs.glsl │ │ │ └── vs.glsl │ │ ├── Shadows │ │ │ ├── Scene.tsx │ │ │ ├── fs.glsl │ │ │ └── vs.glsl │ │ ├── Vanilla │ │ │ ├── Scene.tsx │ │ │ ├── Stage.tsx │ │ │ ├── fs.glsl │ │ │ └── vs.glsl │ │ ├── Waves │ │ │ ├── Lights.tsx │ │ │ ├── Scene.tsx │ │ │ ├── fs.glsl │ │ │ ├── useWaterControls.ts │ │ │ └── vs.glsl │ │ ├── default │ │ │ ├── Scene.tsx │ │ │ ├── Stage.tsx │ │ │ ├── fs.glsl │ │ │ ├── psrd.glsl │ │ │ └── vs.glsl │ │ └── index.ts │ ├── index.tsx │ ├── pages │ │ ├── NotFound │ │ │ └── index.tsx │ │ └── Root │ │ │ ├── UI │ │ │ ├── Presets.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ │ └── index.tsx │ ├── styles │ │ └── index.css │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── package.json ├── package ├── .eslintrc.cjs ├── package.json ├── src │ ├── React │ │ ├── index.tsx │ │ └── types.ts │ ├── defaults.ts │ ├── index.ts │ ├── maps │ │ ├── availabilityMap.ts │ │ ├── index.ts │ │ ├── keywordMap.ts │ │ ├── patchMap.ts │ │ └── requiredPropsMap.ts │ ├── sdbm.js │ ├── types.ts │ ├── utils.ts │ └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts └── yarn.lock /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node 18 | uses: actions/setup-node@v3 19 | 20 | - name: Build 21 | run: | 22 | yarn 23 | yarn build 24 | 25 | - name: Upload production-ready build files 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: production-files 29 | path: ./examples/dist 30 | 31 | deploy: 32 | name: Deploy 33 | needs: build 34 | runs-on: ubuntu-latest 35 | if: github.ref == 'refs/heads/main' 36 | 37 | steps: 38 | - name: Download artifact 39 | uses: actions/download-artifact@v4 40 | with: 41 | name: production-files 42 | path: ./examples/dist 43 | 44 | - name: Deploy to GitHub Pages 45 | uses: peaceiris/actions-gh-pages@v3 46 | with: 47 | github_token: ${{ secrets.GITHUB_TOKEN }} 48 | publish_dir: ./examples/dist 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | package/LICENSE.md 26 | package/README.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 6.2.0 4 | 5 | - Add `CustomShaderMaterial.clone()` method 6 | - Update regex used to extract main function's body from user's shader code. 7 | 8 | ## 6.0.0 9 | 10 | Rewritten from scratch, now with no dependencies, optimized performance and better type inference. 11 | 12 | - Add `csm_Iridescence` 13 | - Add `csm_Transmission` 14 | - Add `csm_Thickness` 15 | - Add `csm_UnlitFac` 16 | - Mix factor between lit (`csm_DiffuseColor`) and unlit (`csm_FragColor`) shading. 17 | - **[BREAKING]**: Removed official support for extending third-party materials and materials that already use `onBeforeCompile`. 18 | - This is due to the complexity of the implementation and the lack of a good way to handle it. 19 | - You can still extend materials, they may or may not work as expected. 20 | 21 | ## 5.5.0 22 | 23 | - **[BREAKING]**: Your shader code is now scoped! 24 | - Now you will not have token redefinition errors within your shader when using tokens already used by Three. For example `mvPosition`. 25 | - Please note that attributes, uniforms and varying cannot be scoped and will still cause errors if redefined. For example `vUv` 26 | - Shader code should not be broken. However, `patchMaps` might be, depending on your implementation. Please open an issue and I may help you fix it. 27 | - **[BREAKING]**: Made generic type T required for vanilla `CustomShaderMaterial` class. 28 | 29 | - This facilitates better type inference for the base material props in vanilla contexts. React remains unchanged. 30 | 31 | - Add `csm_Clearcoat` 32 | - Clearcoat factor 33 | - Add `csm_ClearcoatRoughness` 34 | - Clearcoat roughness factor 35 | - Add `csm_ClearcoatNormal` 36 | 37 | - Perturbation to the fragment normal used for clearcoat shading 38 | 39 | - Refactor out string manipulations using `glsl-tokenizer`, `glsl-token-functions` and `glsl-token-string` making recompilation more performant and less error-prone. 40 | 41 | ## 5.3.4 42 | 43 | - Fix for Three r0.150.2 44 | 45 | ## 5.3.0 46 | 47 | - Add `csm_bump` 48 | 49 | ## 5.2.0 50 | 51 | - Now extend already extended materials 52 | - Extend/chain CSM instances 53 | - Extend any other material that uses `onBeforeCompile` internally 54 | 55 | ## 5.1.0 56 | 57 | ### Features 58 | 59 | - Now prevent exact match function in `patchMaps` by passing `"*"` as the search keyword. 60 | 61 | ### Fixes 62 | 63 | - Fixed uniforms now injected with a ShaderMaterial as `baseMaterial` 64 | 65 | ## 5.0.0 66 | 67 | ### Features 68 | 69 | #### Improved types 70 | 71 | CSM will now infer options/prop types based on the base material 72 | 73 | ```tsx 74 | 78 | 79 | 83 | ``` 84 | 85 | #### Added `csm_AO` 86 | 87 | Now override AO within your shader. Available in Physical and Standard material 88 | 89 | ```glsl 90 | csm_AO = 0.0; // PBR Ambient Occlusion 91 | ``` 92 | 93 | #### Misc 94 | 95 | - FIxed transmission with `MeshPhysicalMaterial` as base 96 | - Restructure repo and examples 97 | 98 | ## 5.0.0-next.3 99 | 100 | - Fix transmission for MeshPhysicalMaterial base 101 | 102 | ## 5.0.0-next.2 103 | 104 | ### Major Changes 105 | 106 | - 01ec3c5: Add csm_AO 107 | 108 | ## 5.0.0-next.1 109 | 110 | ### Minor Changes 111 | 112 | - Improve types 113 | 114 | ## 5.0.0-next.0 115 | 116 | ### Major Changes 117 | 118 | - Restructure repo 119 | 120 | ## Version 3.5.0 121 | 122 | ### Added `csm_Roughness` and `csm_Metalness` 123 | 124 | ```glsl 125 | csm_Roughness = 0.0; // PBR Roughness 126 | csm_Metalness = 1.0; // PBR Metalness 127 | ``` 128 | 129 | ### Misc 130 | 131 | - Updated deps 132 | - Fixed `.clone()` (#20) 133 | 134 | ## Version 3.4.0 135 | 136 | ### Added support to extend already initialized materials 137 | 138 | Vanilla: 139 | 140 | ```js 141 | const material = new Material(); 142 | new CustomShaderMaterial({ baseMaterial: material }); 143 | ``` 144 | 145 | React: 146 | 147 | ```jsx 148 | const material = useMemo(() => new Material(), []) 149 | 150 | ``` 151 | 152 | ### Add support for custom patch maps 153 | 154 | Vanilla: 155 | 156 | ```js 157 | new CustomShaderMaterial({ patchMap: {...} }) 158 | ``` 159 | 160 | React: 161 | 162 | ```jsx 163 | 164 | ``` 165 | 166 | ### Upgraded shader parsing 167 | 168 | - Shaders are indentation-sensitive no more. 169 | - Supports full set of standard GLSL syntax. 170 | 171 | ## Version 3.3.3 **[Breaking]** 172 | 173 | ### Changes 174 | 175 | - Swapped plain params for object-params 176 | 177 | ```js 178 | // Old 179 | const material = new CustomShaderMaterial( 180 | baseMaterial, 181 | fragmentShader, 182 | vertexShader, 183 | uniforms 184 | ); 185 | material.update(fragmentShader, vertexShader, uniforms); 186 | 187 | // New 188 | const material = new CustomShaderMaterial({ 189 | baseMaterial, 190 | fragmentShader, 191 | vertexShader, 192 | uniforms, 193 | cacheKey, 194 | }); 195 | material.update({ fragmentShader, vertexShader, uniforms, cacheKey }); 196 | ``` 197 | 198 | - Added smarter cache key 199 | - Custom cache key is now a hash: `hash([fragmentShader, vertexShader, uniforms])` 200 | - Custom cache key function can be supplied with constructor or update function. `cacheKey : () => string` 201 | 202 | ## Version 3.2.10 203 | 204 | ### Changes 205 | 206 | - Now supports `csm_Emissive` 207 | - Override emissive color from fragment shader 208 | - Updated types to include `PointsMaterial` (#15) 209 | 210 | ## Version 3.1.0 **[Breaking]** 211 | 212 | ### Changes 213 | 214 | - Move vanilla lib to `three-custom-shader-material/vanilla` 215 | - `import CustomShaderMaterial from "three-custom-shader-material/vanilla" 216 | 217 | ## Version 3.0.0 **[Breaking]** 218 | 219 | ### Changes 220 | 221 | - Rewritten from scratch 222 | - Ported to `react-three-fiber` 223 | 224 | ## Version 2.4.3 225 | 226 | ### Changes 227 | 228 | - Update deps. 229 | 230 | ## Version 2.4.2 231 | 232 | ### Changes 233 | 234 | - Added `gl_PointSize` override. 235 | 236 | ## Version 2.4.1 237 | 238 | ### Changes 239 | 240 | - Updated Readme 241 | - Moved some `dependencies` to `devDependencies` 242 | 243 | ## Version 2.4.0 244 | 245 | ### Changes 246 | 247 | - Output variables `newPos`, `newNormal` and `newColor` depricated in favor of `csm_Position`, `csm_Normal` and `csm_DiffuseColor` 248 | - This is a non-breaking change as the old ones still work for compatibility. 249 | - Output variables from custom shader are now optional 250 | - `csm_Position`, `csm_Normal` and `csm_DiffuseColor` now default to their orignal values (`position`, `objectNormal` and `vColor`) if not set in custom shader. 251 | 252 | ### Enhancements 253 | 254 | - Rewritten in TypeScript 255 | - Minified to reduce filesize 256 | 257 | ## Version 2.3.1 258 | 259 | ### New 260 | 261 | - Added support for `MeshDepthMaterial` and `PointsMaterial`. 262 | - These materials aren't well tested. 263 | 264 | ## Version 2.3.0 265 | 266 | ### New 267 | 268 | - Now supports **all** material types. 269 | - Now you can set the **diffuse color** of the object as well. 270 | - Added CDN-friendly version of the library - `build/three-csm.m.cdn.js`. 271 | 272 | ### Changed 273 | 274 | - Renamed `build/three-csm.module.js` to `build/three-csm.m.js` 275 | - Updated docs and readme. 276 | 277 | ## Version 2.2.1 278 | 279 | ### Changed 280 | 281 | - Added warnings for unsupported features. 282 | 283 | ## Version 2.2.0 284 | 285 | ### Changed 286 | 287 | - Fixes #3. Big thanks to Steve Trettel (@stevejtrettel) 288 | 289 | ## Version 2.1.1 290 | 291 | ### Changed 292 | 293 | - Fix for [CVE-2021-23358](https://github.com/advisories/GHSA-cf4h-3jhx-xvhq) 294 | 295 | ## Version 2.1.0 296 | 297 | ### Added 298 | 299 | - Ability to include custom Fragment Shaders 300 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Faraz Shaikh 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 |

Custom Shader Material

2 |

Extend Three.js standard materials with your own shaders!

3 | 4 |
5 | 6 |

7 | Waves 8 | Points 9 | Caustics 10 |

11 |

12 |

13 | These demos are real, you can click them! They contains full code, too. 14 | More demos here! 📦 15 | 16 |

17 |
18 |

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | Chat on Twitter 32 | 33 |

34 | 35 | Custom Shader Material (CSM) lets you extend Three.js' material library with your own Vertex and Fragment shaders. **_It Supports both Vanilla and React!_** 36 | 37 |
38 | Show Vanilla example 39 | 40 | ```js 41 | import CustomShaderMaterial from "three-custom-shader-material/vanilla"; 42 | 43 | function Box() { 44 | const geometry = new THREE.BoxGeometry(); 45 | const material = new CustomShaderMaterial({ 46 | baseMaterial: THREE.MeshPhysicalMaterial, 47 | vertexShader: /* glsl */ ` ... `, // Your vertex Shader 48 | fragmentShader: /* glsl */ ` ... `, // Your fragment Shader 49 | // Your Uniforms 50 | uniforms: { 51 | uTime: { value: 0 }, 52 | ... 53 | }, 54 | // Base material properties 55 | flatShading: true, 56 | color: 0xff00ff, 57 | ... 58 | }); 59 | 60 | return new THREE.Mesh(geometry, material); 61 | } 62 | ``` 63 | 64 |
65 | 66 |
67 | Show React example 68 | 69 | ```jsx 70 | import CustomShaderMaterial from 'three-custom-shader-material' 71 | 72 | function Cube() { 73 | const materialRef = useRef() 74 | 75 | useFrame((state) => { 76 | if (materialRef.current) { 77 | materialRef.current.uniforms.uTime.value = state.clock.elapsedTime 78 | } 79 | }) 80 | 81 | return ( 82 | 83 | 84 | 99 | 100 | ) 101 | } 102 | ``` 103 | 104 |
105 |
106 | Show Vue (Tresjs) example 107 | 108 | > Moved to [Cientos' Docs](https://cientos.tresjs.org/guide/materials/custom-shader-material.html#trescustomshadermaterial) 109 | 110 |
111 | 112 | ## Installation 113 | 114 | ```bash 115 | npm install three-custom-shader-material 116 | yarn add three-custom-shader-material 117 | ``` 118 | 119 | ## Output Variables 120 | 121 | CSM provides the following output variables, all of them are optional but you MUST use these variables like you would use standard GLSL output variables to see results. 122 | 123 | | Variable | Type | Description | Available In | Notes | 124 | | --------------------------------- | ------- | ---------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | 125 | |

Vertex Shader

| - | - | - | - | 126 | | csm_Position | `vec3` | Custom vertex position. | Vertex Shader | csm_Position will be projected furthur down the line. Thus, no projection is needed here. | 127 | | csm_PositionRaw | `vec4` | Direct equivalent of `gl_Position`. | Vertex Shader | | 128 | | csm_Normal | `vec3` | Custom vertex normals. | Vertex Shader | 129 | | csm_PointSize | `float` | Direct equivalent of `gl_PointSize`. | Vertex Shader | Only available in `PointsMaterial` | 130 | |

Fragmet Shader

| - | - | - | - | 131 | | csm_DiffuseColor | `vec4` | Custom diffuse color. | Fragment Shader | Base material's shading will be applied to this color. | 132 | | csm_FragColor | `vec4` | Direct equivalent of `gl_FragColor`. | Fragment Shader | csm_FragColor will override any shading applied by a base material. To preserve shading and other effects like roughness and metalness, use `csm_DiffuseColor` | 133 | | csm_Roughness | `float` | Custom roughness. | Fragment Shader | Only available in materials with an `roughnessMap`. | 134 | | csm_Metalness | `float` | Custom metalness. | Fragment Shader | Only available in materials with an `metalnessMap`. | 135 | | csm_AO | `float` | Custom AO. | Fragment Shader | Only available in materials with an `aoMap`. | 136 | | csm_Bump | `vec3` | Custom bump as perturbation to fragment normals. | Fragment Shader | Only available in materials with a `bumpMap`. | 137 | | csm_Clearcoat | `float` | Custom clearcoat factor. | Fragment Shader | Only available in materials with a `clearcoat`. | 138 | | csm_ClearcoatRoughness | `float` | Custom clearcoat roughenss factor. | Fragment Shader | Only available in materials with a `clearcoat`. | 139 | | csm_ClearcoatNormal | `vec3` | Custom clearcoat normal. | Fragment Shader | Only available in materials with a `clearcoat`. | 140 | | csm_Transmission | `float` | Custom transmission factor. | Fragment Shader | Only available in materials with a `transmission`. | 141 | | csm_Thickness | `float` | Custom transmission thickness. | Fragment Shader | Only available in materials with a `transmission`. | 142 | | csm_Iridescence | `float` | Custom iridescence factor. | Fragment Shader | Only available in materials with a `iridescence`. | 143 | | csm_Emissive | `vec3` | Custom emissive color. | Fragment Shader | Only available in materials with a `emissive`. | 144 | | csm_FragNormal | `float` | Custom fragment normal. | Only available in materials with a `normalMap`. | 145 | |

Fragmet Shader (Special)

| - | - | - | - | 146 | | csm_DepthAlpha | `float` | Custom alpha for `MeshDepthMaterial`. | Fragment Shader | Useful for controlling `customDepthMaterial` with same shader as the shader material. | 147 | | csm_UnlitFac | `float` | Custom mix between `csm_DiffuseColor` and `csm_FragColor`. | Fragment Shader | Can be used to mix lit and unlit materials. Set to `1.0` by default if `csm_FragColor` is found in shader string. | 148 | 149 | ## Typing 150 | 151 | CSM infers prop types based on the `baseMaterial` prop. However, if this does not work for what ever reason, you can pass your base material type as a generic to `CustomShaderMaterial`. 152 | 153 | ```ts 154 | // Vanilla 155 | const material = new CustomShaderMaterial({ 156 | baseMaterial: THREE.MeshPhysicalMaterial, 157 | //...Any props 158 | }); 159 | 160 | // React 161 | 162 | baseMaterial={THREE.MeshPhysicalMaterial} 163 | //...Any props 164 | ``` 165 | 166 | ## Custom overrides 167 | 168 | You can define any custom overrides you'd like using the `patchMap` prop. The prop is used as shown below. 169 | 170 | ```js 171 | const material = new CustomShaderMaterial({ 172 | baseMaterial: THREE.MeshPhysicalMaterial, 173 | vertexShader: ` ... `, 174 | fragmentShader: ... `, 175 | uniforms: {...}, 176 | patchMap={{ 177 | "": { // The keyword you will assign to in your custom shader 178 | "TO_REPLACE": // The chunk you'd like to replace. 179 | "REPLACED_WITH" // The chunk you'd like put in place of `TO_REPLACE` 180 | } 181 | }} 182 | }) 183 | ``` 184 | 185 | > Note: If `` is not found in shader string, the patch map will not be applied. To ALWAYS apply a patch map, use the special keyword - `*` (star). 186 | > 187 | > ```js 188 | > patchMap={{ 189 | > "*": { "TO_REPLACE": "REPLACED_WITH" } 190 | > }} 191 | > ``` 192 | 193 | ## Extending already extended materials 194 | 195 | CSM allows you to extend other CSM instances. Values set in the first shader will affect the next. 196 | 197 | > Note: Extending of other materials that use `onBeforeCompile` may or may not work depending on if the default `#includes` are mangled. 198 | 199 |
200 | Show Vanilla example 201 | 202 | ```js 203 | import CustomShaderMaterial from "three-custom-shader-material/vanilla"; 204 | 205 | function Box() { 206 | const material1 = new CustomShaderMaterial({ 207 | baseMaterial: THREE.MeshPhysicalMaterial, 208 | //...Any props 209 | }); 210 | const material2 = new CustomShaderMaterial({ 211 | baseMaterial: material1, 212 | //...Any props 213 | }); 214 | } 215 | ``` 216 | 217 |
218 | 219 |
220 | Show React example 221 | 222 | ```jsx 223 | import CustomShaderMaterial from "three-custom-shader-material"; 224 | import CustomShaderMaterialImpl from "three-custom-shader-material/vanilla"; 225 | 226 | function Cube() { 227 | const [materialRef, setMaterialRef] = useState(); 228 | 229 | return ( 230 | <> 231 | 236 | 237 | {materialRef && ( 238 | 242 | )} 243 | 244 | ); 245 | } 246 | ``` 247 | 248 |
249 | 250 | ### Gotchas 251 | 252 | - `csm_Position` **MUST** be a non-projected vector. i.e., no need to multiply `projectionMatrix` or `modelViewPosition` with it. If you require projection, use `csm_PositionRaw`. 253 | - Instancing must be handled manually when using `csm_PositionRaw` by multiplying in `instanceMatrix` into your projection math. 254 | - When extending already extended material, variables, uniforms, attributes, varyings and functions are **NOT** scoped to the material they are defined in. Thus, you **WILL** get redefinition errors if you do not manually scope these identifiers. 255 | - Extending of other materials that use `onBeforeCompile` may or may not work depending on if the default `#includes` are mangled. 256 | - When using an instance of CSM as the baseMaterial, or chining multiple CSM instances, or when extending any material that uses `onBeforeCompile` the injection order is as follows: 257 | 258 | ```glsl 259 | void main() { 260 | // shader A 261 | // shader B 262 | // shader C 263 | // shader D 264 | 265 | // original shader 266 | } 267 | ``` 268 | 269 | Where A was the first in the chain. 270 | 271 | - Cache key calculation takes into account base material's cache key. Useful for propagating changes across multiple chained CSM instances. 272 | - If you find yourself lost in a patchMap, it's often simpler to just make a `ShaderMaterial` with the necessary `#includes`. 273 | 274 | ## Performance 275 | 276 | With v6, CSM's initialization cost is now negligible 🥳 Still, a couple important notes about performance: 277 | 278 | - Changing these props will rebuild the material 279 | - `baseMaterial` 280 | - `fragmentShader` 281 | - `vertexShader` 282 | - `uniforms` 283 | - `cacheKey` 284 | - CSM uses ThreeJS's default shader program caching system. Materials with the same cache key, will use the the same shader program. 285 | - `` and `` are the same, and will use the same cached shader program. The default cache key is such: 286 | 287 | ```js 288 | (cacheKey?.() || hash((vertexShader || "") + (fragmentShader || ""))) + 289 | baseMaterialCacheKey?.(); 290 | ``` 291 | 292 | You can provide your own cache key function via the `cacheKey` prop. 293 | 294 | > Note: CSM will only rebuild if the **reference** to the above props change, for example, in React, doing `uniforms={{...}}` means that the uniforms object is unstable, i.e. it is re-created, with a **new** reference every render. Instead, condsider memoizing the uniforms prop `const uniforms = useMemo(() -> ({...}));`. The uniforms object will then have the same refrence on every render. 295 | 296 | > If the uniforms are memoized, changing their value by doing `uniforms.foo.value = ...` will not cause CSM to rebuild, as the refrence of `uniforms` does not change. 297 | 298 | ## License 299 | 300 | ``` 301 | MIT License 302 | 303 | Copyright (c) 2024 Faraz Shaikh 304 | 305 | Permission is hereby granted, free of charge, to any person obtaining a copy 306 | of this software and associated documentation files (the "Software"), to deal 307 | in the Software without restriction, including without limitation the rights 308 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 309 | copies of the Software, and to permit persons to whom the Software is 310 | furnished to do so, subject to the following conditions: 311 | 312 | The above copyright notice and this permission notice shall be included in all 313 | copies or substantial portions of the Software. 314 | 315 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 316 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 317 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 318 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 319 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 320 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 321 | SOFTWARE. 322 | ``` 323 | -------------------------------------------------------------------------------- /assets/caustics-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/assets/caustics-demo.png -------------------------------------------------------------------------------- /assets/points-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/assets/points-demo.png -------------------------------------------------------------------------------- /assets/waves-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/assets/waves-demo.png -------------------------------------------------------------------------------- /examples/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 | 11 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-custom-shader-material-examples", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@chakra-ui/react": "^2.8.2", 13 | "@emotion/react": "^11.11.4", 14 | "@emotion/styled": "^11.11.5", 15 | "@gsimone/suzanne": "^0.0.12", 16 | "@react-three/drei": "^9.103.0", 17 | "@react-three/fiber": "^8.16.1", 18 | "@uiw/react-textarea-code-editor": "^3.0.2", 19 | "framer-motion": "^11.1.3", 20 | "gl-noise": "^1.6.1", 21 | "leva": "^0.9.35", 22 | "r3f-perf": "^7.2.3", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "react-icons": "^5.0.1", 26 | "react-router-dom": "^6.22.3", 27 | "three": "^0.173.0", 28 | "three-stdlib": "^2.29.6" 29 | }, 30 | "devDependencies": { 31 | "@types/react": "^18.2.66", 32 | "@types/react-dom": "^18.2.22", 33 | "@types/three": "^0.172.0", 34 | "@typescript-eslint/eslint-plugin": "^7.2.0", 35 | "@typescript-eslint/parser": "^7.2.0", 36 | "@vitejs/plugin-react": "^4.2.1", 37 | "eslint": "^8.57.0", 38 | "eslint-plugin-react-hooks": "^4.6.0", 39 | "eslint-plugin-react-refresh": "^0.4.6", 40 | "gh-pages": "^6.1.1", 41 | "typescript": "^5.2.2", 42 | "vite": "^5.2.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/public/Caustics/pooltiles/tlfmffydy_4K_AO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/examples/public/Caustics/pooltiles/tlfmffydy_4K_AO.jpg -------------------------------------------------------------------------------- /examples/public/Caustics/pooltiles/tlfmffydy_4K_Albedo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/examples/public/Caustics/pooltiles/tlfmffydy_4K_Albedo.jpg -------------------------------------------------------------------------------- /examples/public/Caustics/pooltiles/tlfmffydy_4K_Displacement.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/examples/public/Caustics/pooltiles/tlfmffydy_4K_Displacement.jpg -------------------------------------------------------------------------------- /examples/public/Caustics/pooltiles/tlfmffydy_4K_Normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/examples/public/Caustics/pooltiles/tlfmffydy_4K_Normal.jpg -------------------------------------------------------------------------------- /examples/public/Caustics/pooltiles/tlfmffydy_4K_Roughness.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/examples/public/Caustics/pooltiles/tlfmffydy_4K_Roughness.jpg -------------------------------------------------------------------------------- /examples/public/Shadows/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/examples/public/Shadows/react.png -------------------------------------------------------------------------------- /examples/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/index.css"; 2 | 3 | import { RouterProvider, createHashRouter } from "react-router-dom"; 4 | import { SHADERS } from "./Examples"; 5 | import { NotFound } from "./pages/NotFound"; 6 | import { Root } from "./pages/Root"; 7 | 8 | const router = createHashRouter([ 9 | { 10 | path: "/", 11 | element: , 12 | children: [ 13 | ...Object.values(SHADERS).map((preset) => ({ 14 | path: preset.slug, 15 | element: , 16 | })), 17 | { 18 | path: "/", 19 | element: , 20 | }, 21 | ], 22 | }, 23 | { 24 | path: "*", 25 | element: , 26 | }, 27 | ]); 28 | 29 | export default function App() { 30 | return ; 31 | } 32 | -------------------------------------------------------------------------------- /examples/src/Examples/Caustics/Caustics.tsx: -------------------------------------------------------------------------------- 1 | import { useHelper } from "@react-three/drei"; 2 | import { useFrame } from "@react-three/fiber"; 3 | import { patchShaders } from "gl-noise/build/glNoise.m"; 4 | import { useEffect, useMemo, useRef } from "react"; 5 | import { Box3Helper, Material, MathUtils, Vector3 } from "three"; 6 | import CustomShaderMaterial from "../../../../package/src"; 7 | import { useShader } from "../../pages/Root"; 8 | import Lights from "./components/Lights"; 9 | 10 | const center = new Vector3(0, 0, 0); 11 | export default function Caustics({ children }) { 12 | const lightRef = useRef(null!); 13 | const { vs, fs } = useShader(); 14 | 15 | const ref = useRef(null!); 16 | const objref = useRef(null!); 17 | 18 | useHelper(objref.current, Box3Helper); 19 | 20 | const uniforms = useMemo( 21 | () => ({ 22 | uPosition: { 23 | value: new Vector3(-2, 1, 1), 24 | }, 25 | uRotaiton: { 26 | value: new Vector3(1, 1, 1), 27 | }, 28 | uAngle: { 29 | value: MathUtils.degToRad(45), 30 | }, 31 | uScale: { 32 | value: new Vector3(), 33 | }, 34 | uTime: { 35 | value: 0, 36 | }, 37 | }), 38 | [] 39 | ); 40 | 41 | useFrame(({ clock }) => { 42 | uniforms.uTime.value = clock.elapsedTime; 43 | 44 | if (lightRef.current) { 45 | uniforms.uPosition.value.copy(lightRef.current.object.position); 46 | uniforms.uScale.value.copy(lightRef.current.object.scale); 47 | 48 | const vector = new Vector3(0, 0, 0); 49 | lightRef.current.object.getWorldDirection(vector); 50 | uniforms.uRotaiton.value.copy(vector); 51 | uniforms.uAngle.value = vector.angleTo(center); 52 | } 53 | }); 54 | 55 | const prevMaterials = useRef<{ 56 | [id: string]: Material; 57 | }>({}); 58 | const csmInstances = useRef([]); 59 | 60 | useEffect(() => { 61 | ref.current.traverse((obj) => { 62 | if (obj.isMesh && obj.material) { 63 | if (!prevMaterials.current[obj.material.uuid]) { 64 | prevMaterials.current[obj.material.uuid] = obj.material.clone(); 65 | obj.material.dispose(); 66 | 67 | obj.material = new CustomShaderMaterial({ 68 | baseMaterial: obj.material, 69 | vertexShader: vs, 70 | fragmentShader: patchShaders(fs), 71 | uniforms: uniforms, 72 | patchMap: { 73 | "*": { 74 | "#include ": ` 75 | #include 76 | gl_FragColor = getCausticsColor(gl_FragColor); 77 | `, 78 | }, 79 | }, 80 | }); 81 | 82 | csmInstances.current.push(obj.material); 83 | } 84 | } 85 | }); 86 | 87 | return () => { 88 | if (ref.current) { 89 | ref.current.traverse((obj) => { 90 | if (obj.isMesh) { 91 | obj.material.dispose(); 92 | obj.material = prevMaterials.current[obj.material.uuid]; 93 | } 94 | }); 95 | } else { 96 | Object.values(prevMaterials.current).forEach((material) => 97 | material.dispose() 98 | ); 99 | for (const csm of csmInstances.current) { 100 | csm.dispose(); 101 | } 102 | prevMaterials.current = {}; 103 | csmInstances.current = []; 104 | } 105 | }; 106 | }, []); 107 | 108 | return ( 109 | <> 110 | 111 | 112 | {children} 113 | 114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /examples/src/Examples/Caustics/Scene.tsx: -------------------------------------------------------------------------------- 1 | import suzanne from "@gsimone/suzanne"; 2 | import { 3 | OrbitControls, 4 | PerspectiveCamera, 5 | TransformControls, 6 | useGLTF, 7 | } from "@react-three/drei"; 8 | import { Suspense } from "react"; 9 | import { MathUtils } from "three"; 10 | import Caustics from "./Caustics"; 11 | import { Floor } from "./components/Floor"; 12 | 13 | function Thing() { 14 | const size = 5; 15 | const { nodes } = useGLTF(suzanne); 16 | 17 | return ( 18 | <> 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | export function Scene() { 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /examples/src/Examples/Caustics/components/Floor.tsx: -------------------------------------------------------------------------------- 1 | import { useTexture } from "@react-three/drei"; 2 | 3 | export function Floor({ size = 30, ...props }) { 4 | const textureRepeat = size / 2 / 2; 5 | 6 | const [Albedo, AO, Displacement, Normal, Roughness] = useTexture([ 7 | import.meta.env.BASE_URL + "/Caustics/pooltiles/tlfmffydy_4K_Albedo.jpg", 8 | import.meta.env.BASE_URL + "/Caustics/pooltiles/tlfmffydy_4K_AO.jpg", 9 | import.meta.env.BASE_URL + 10 | "/Caustics/pooltiles/tlfmffydy_4K_Displacement.jpg", 11 | import.meta.env.BASE_URL + "/Caustics/pooltiles/tlfmffydy_4K_Normal.jpg", 12 | import.meta.env.BASE_URL + "/Caustics/pooltiles/tlfmffydy_4K_Roughness.jpg", 13 | ]); 14 | 15 | return ( 16 | 17 | 18 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /examples/src/Examples/Caustics/components/Lights.tsx: -------------------------------------------------------------------------------- 1 | import { TransformControls } from "@react-three/drei"; 2 | import { forwardRef, memo } from "react"; 3 | import { TransformControls as TransformControlsImpl } from "three-stdlib"; 4 | 5 | const Lights = forwardRef((_, ref) => { 6 | return ( 7 | <> 8 | 13 | 14 | 20 | 28 | 29 | 30 | ); 31 | }); 32 | export default memo(Lights); 33 | -------------------------------------------------------------------------------- /examples/src/Examples/Caustics/fs.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 csm_vWorldPosition; 2 | varying vec3 csm_vPosition; 3 | varying vec3 csm_vNormal; 4 | varying vec2 csm_vUv; 5 | 6 | uniform vec3 uPosition; 7 | uniform vec3 uRotaiton; 8 | uniform vec3 uScale; 9 | uniform float uTime; 10 | uniform float uAngle; 11 | 12 | #ifdef IS_MESHBASICMATERIAL 13 | #include 14 | #include 15 | const bool receiveShadow = true; 16 | #endif 17 | #include 18 | 19 | mat4 rotationMatrix(vec3 axis, float angle) { 20 | axis = normalize(axis); 21 | float s = sin(angle); 22 | float c = cos(angle); 23 | float oc = 1.0 - c; 24 | 25 | return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, 26 | oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, 27 | oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 28 | 0.0, 0.0, 0.0, 1.0); 29 | } 30 | 31 | vec3 rotate(vec3 v, vec3 axis, float angle) { 32 | mat4 m = rotationMatrix(axis, angle); 33 | return (m * vec4(v, 1.0)).xyz; 34 | } 35 | 36 | float sdBox(vec3 p, vec3 b) { 37 | vec3 q = abs(p) - b; 38 | return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0); 39 | } 40 | 41 | vec4 getCausticsColor(vec4 color) { 42 | vec3 scale = uScale; 43 | vec3 p = csm_vWorldPosition; 44 | p.x -= uPosition.x; 45 | p.y -= uPosition.y; 46 | p.z -= uPosition.z; 47 | vec3 pos = rotate(p, uRotaiton, uAngle); 48 | 49 | float box = 1. - clamp(sdBox(p, scale), 0., 1.); 50 | box = box >= 0.5 ? 1. : 0.; 51 | 52 | gln_tWorleyOpts opts = gln_tWorleyOpts(1., -2., 1., false); 53 | 54 | 55 | float noiseScale = 1.7; 56 | float t = (uTime * 0.1); 57 | float offset = 0.05; 58 | 59 | vec3 n1 = vec3( 60 | gln_worley(((pos.xz + t) + vec2(offset, offset)) * noiseScale, opts), 61 | gln_worley(((pos.xz + t) + vec2(offset, -offset)) * noiseScale, opts), 62 | gln_worley(((pos.xz + t) + vec2(-offset, -offset)) * noiseScale, opts) 63 | ); 64 | 65 | float noiseScale2 = 1.2; 66 | float t2 = (uTime * 0.2); 67 | float offset2 = 0.02; 68 | vec3 n2 = vec3( 69 | gln_worley(((pos.xz + t2) + vec2(offset2, offset2)) * noiseScale2, opts), 70 | gln_worley(((pos.xz + t2) + vec2(offset2, -offset2)) * noiseScale2, opts), 71 | gln_worley(((pos.xz + t2) + vec2(-offset2, -offset2)) * noiseScale2, opts) 72 | ); 73 | 74 | vec3 n = min(n1, n2); 75 | n = pow(n, vec3(3.)) * 1.2; 76 | 77 | vec3 projectorDirection = normalize(pos); 78 | float dotProduct = 1. - dot(csm_vNormal, projectorDirection); 79 | dotProduct = pow(dotProduct, 3.); 80 | dotProduct = clamp(dotProduct, 0., 1.); 81 | 82 | float shadow = getShadowMask(); 83 | 84 | float fac = dotProduct * box * shadow; 85 | vec3 c = color.rgb + n; 86 | return mix(color, vec4(c, 1.), fac); 87 | // return vec4(vec3(n), 1.); 88 | 89 | } 90 | -------------------------------------------------------------------------------- /examples/src/Examples/Caustics/vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 csm_vWorldPosition; 2 | varying vec3 csm_vPosition; 3 | varying vec3 csm_vNormal; 4 | varying vec2 csm_vUv; 5 | 6 | #ifdef IS_MESHBASICMATERIAL 7 | #include 8 | #include 9 | #endif 10 | 11 | void main() { 12 | csm_vNormal = normal; 13 | csm_vUv = uv; 14 | csm_vPosition = position; 15 | csm_vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz; 16 | } -------------------------------------------------------------------------------- /examples/src/Examples/Instances/Scene.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Environment, 3 | OrbitControls, 4 | PerspectiveCamera, 5 | } from "@react-three/drei"; 6 | import { useFrame } from "@react-three/fiber"; 7 | import { Suspense, useEffect, useMemo, useRef } from "react"; 8 | import { 9 | BufferGeometry, 10 | InstancedMesh, 11 | MeshPhysicalMaterial, 12 | Object3D, 13 | } from "three"; 14 | 15 | import { patchShaders } from "gl-noise/build/glNoise.m"; 16 | import CustomShaderMaterialType from "../../../../package/src"; 17 | import CustomShaderMaterial from "../../../../package/src/React"; 18 | import { useShader } from "../../pages/Root"; 19 | 20 | const amount = 150; 21 | const dummy = new Object3D(); 22 | function Thing() { 23 | const { vs, fs } = useShader(); 24 | const ref = useRef< 25 | InstancedMesh< 26 | BufferGeometry, 27 | CustomShaderMaterialType 28 | > 29 | >(null!); 30 | 31 | useEffect(() => { 32 | const mesh = ref.current; 33 | 34 | let i = 0; 35 | for (let x = 0; x < amount; x++) { 36 | dummy.position.set(Math.random(), Math.random(), Math.random()); 37 | dummy.rotation.set(Math.random(), Math.random(), Math.random()); 38 | dummy.position.multiplyScalar(10); 39 | 40 | dummy.position.x -= 5; 41 | dummy.position.y -= 5; 42 | dummy.position.z -= 5; 43 | 44 | dummy.updateMatrix(); 45 | 46 | mesh.setMatrixAt(i++, dummy.matrix); 47 | } 48 | mesh.instanceMatrix.needsUpdate = true; 49 | }, []); 50 | 51 | const uniforms = useMemo( 52 | () => ({ 53 | uTime: { 54 | value: 0, 55 | }, 56 | }), 57 | [] 58 | ); 59 | 60 | useFrame(({ clock }) => { 61 | ref.current.material.uniforms.uTime.value = clock.elapsedTime; 62 | }); 63 | 64 | return ( 65 | 66 | 67 | 68 | 75 | 76 | 77 | ); 78 | } 79 | 80 | export function Scene() { 81 | return ( 82 | <> 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /examples/src/Examples/Instances/fs.glsl: -------------------------------------------------------------------------------- 1 | void main() { 2 | csm_DiffuseColor = vec4(1.); 3 | } -------------------------------------------------------------------------------- /examples/src/Examples/Instances/vs.glsl: -------------------------------------------------------------------------------- 1 | uniform float uTime; 2 | 3 | vec3 displace(vec3 point) { 4 | vec3 instancePosition = (instanceMatrix * vec4(point, 1.)).xyz; 5 | return instancePosition + (normal * gln_perlin((instancePosition * 2.) + uTime) * 0.5); 6 | } 7 | 8 | vec3 orthogonal(vec3 v) { 9 | return normalize(abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) 10 | : vec3(0.0, -v.z, v.y)); 11 | } 12 | 13 | vec3 recalcNormals(vec3 newPos) { 14 | float offset = 0.001; 15 | vec3 tangent = orthogonal(normal); 16 | vec3 bitangent = normalize(cross(normal, tangent)); 17 | vec3 neighbour1 = position + tangent * offset; 18 | vec3 neighbour2 = position + bitangent * offset; 19 | 20 | vec3 displacedNeighbour1 = displace(neighbour1); 21 | vec3 displacedNeighbour2 = displace(neighbour2); 22 | 23 | vec3 displacedTangent = displacedNeighbour1 - newPos; 24 | vec3 displacedBitangent = displacedNeighbour2 - newPos; 25 | 26 | return normalize(cross(displacedTangent, displacedBitangent)); 27 | } 28 | 29 | void main() { 30 | vec3 p = displace(position); 31 | csm_PositionRaw = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(p, 1.); 32 | csm_Normal = recalcNormals(p); 33 | } -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/DiscardMaterial.ts: -------------------------------------------------------------------------------- 1 | import { shaderMaterial } from "@react-three/drei"; 2 | 3 | export const DiscardMaterial = /* @__PURE__ */ shaderMaterial( 4 | {}, 5 | "void main() { }", 6 | "void main() { gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); discard; }" 7 | ); 8 | -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/MeshTransmissionMaterial.tsx: -------------------------------------------------------------------------------- 1 | /** Author: @N8Programs https://github.com/N8python 2 | * https://gist.github.com/N8python/eb42d25c7cd00d12e965ac9cba544317 3 | * Inspired by: @ore_ukonpower and http://next.junni.co.jp 4 | * https://github.com/junni-inc/next.junni.co.jp/blob/master/src/ts/MainScene/World/Sections/Section2/Transparents/Transparent/shaders/transparent.fs 5 | */ 6 | 7 | import { useFBO } from "@react-three/drei"; 8 | import { extend, useFrame } from "@react-three/fiber"; 9 | import { ForwardRefComponent } from "framer-motion"; 10 | import * as React from "react"; 11 | import * as THREE from "three"; 12 | import { DiscardMaterial } from "./DiscardMaterial"; 13 | 14 | type MeshTransmissionMaterialType = Omit< 15 | JSX.IntrinsicElements["meshPhysicalMaterial"], 16 | "args" | "roughness" | "thickness" | "transmission" 17 | > & { 18 | /* Transmission, default: 1 */ 19 | transmission?: number; 20 | /* Thickness (refraction), default: 0 */ 21 | thickness?: number; 22 | /* Roughness (blur), default: 0 */ 23 | roughness?: number; 24 | /* Chromatic aberration, default: 0.03 */ 25 | chromaticAberration?: number; 26 | /* Anisotropy, default: 0.1 */ 27 | anisotropy?: number; 28 | /* AnisotropicBlur, default: 0.1 */ 29 | anisotropicBlur?: number; 30 | /* Distortion, default: 0 */ 31 | distortion?: number; 32 | /* Distortion scale, default: 0.5 */ 33 | distortionScale?: number; 34 | /* Temporal distortion (speed of movement), default: 0.0 */ 35 | temporalDistortion?: number; 36 | /** The scene rendered into a texture (use it to share a texture between materials), default: null */ 37 | buffer?: THREE.Texture; 38 | /** Internals */ 39 | time?: number; 40 | /** Internals */ 41 | args?: [samples: number, transmissionSampler: boolean]; 42 | }; 43 | 44 | type MeshTransmissionMaterialProps = Omit< 45 | MeshTransmissionMaterialType, 46 | "args" 47 | > & { 48 | /** transmissionSampler, you can use the threejs transmission sampler texture that is 49 | * generated once for all transmissive materials. The upside is that it can be faster if you 50 | * use multiple MeshPhysical and Transmission materials, the downside is that transmissive materials 51 | * using this can't see other transparent or transmissive objects, default: false */ 52 | transmissionSampler?: boolean; 53 | /** Render the backside of the material (more cost, better results), default: false */ 54 | backside?: boolean; 55 | /** Backside thickness (when backside is true), default: 0 */ 56 | backsideThickness?: number; 57 | backsideEnvMapIntensity?: number; 58 | /** Resolution of the local buffer, default: undefined (fullscreen) */ 59 | resolution?: number; 60 | /** Resolution of the local buffer for backfaces, default: undefined (fullscreen) */ 61 | backsideResolution?: number; 62 | /** Refraction samples, default: 6 */ 63 | samples?: number; 64 | /** Buffer scene background (can be a texture, a cubetexture or a color), default: null */ 65 | background?: THREE.Texture | THREE.Color; 66 | }; 67 | 68 | interface Uniform { 69 | value: T; 70 | } 71 | 72 | interface Shader { 73 | uniforms: { [uniform: string]: Uniform }; 74 | vertexShader: string; 75 | fragmentShader: string; 76 | } 77 | 78 | declare global { 79 | namespace JSX { 80 | interface IntrinsicElements { 81 | meshTransmissionMaterial: MeshTransmissionMaterialType; 82 | } 83 | } 84 | } 85 | 86 | export class MeshTransmissionMaterialImpl extends THREE.MeshPhysicalMaterial { 87 | uniforms: { 88 | chromaticAberration: Uniform; 89 | transmission: Uniform; 90 | transmissionMap: Uniform; 91 | _transmission: Uniform; 92 | thickness: Uniform; 93 | roughness: Uniform; 94 | thicknessMap: Uniform; 95 | attenuationDistance: Uniform; 96 | attenuationColor: Uniform; 97 | anisotropicBlur: Uniform; 98 | time: Uniform; 99 | distortion: Uniform; 100 | distortionScale: Uniform; 101 | temporalDistortion: Uniform; 102 | buffer: Uniform; 103 | }; 104 | 105 | constructor(samples = 6, transmissionSampler = false) { 106 | super(); 107 | 108 | this.uniforms = { 109 | chromaticAberration: { value: 0.05 }, 110 | // Transmission must always be 0, unless transmissionSampler is being used 111 | transmission: { value: 0 }, 112 | // Instead a workaround is used, see below for reasons why 113 | _transmission: { value: 1 }, 114 | transmissionMap: { value: null }, 115 | // Roughness is 1 in THREE.MeshPhysicalMaterial but it makes little sense in a transmission material 116 | roughness: { value: 0 }, 117 | thickness: { value: 0 }, 118 | thicknessMap: { value: null }, 119 | attenuationDistance: { value: Infinity }, 120 | attenuationColor: { value: new THREE.Color("white") }, 121 | anisotropicBlur: { value: 0.1 }, 122 | time: { value: 0 }, 123 | distortion: { value: 0.0 }, 124 | distortionScale: { value: 0.5 }, 125 | temporalDistortion: { value: 0.0 }, 126 | buffer: { value: null }, 127 | }; 128 | 129 | this.onBeforeCompile = (shader) => { 130 | shader.uniforms = { 131 | ...shader.uniforms, 132 | ...this.uniforms, 133 | }; 134 | 135 | // Fix for r153-r156 anisotropy chunks 136 | // https://github.com/mrdoob/three.js/pull/26716 137 | if ((this as any).anisotropy > 0) shader.defines.USE_ANISOTROPY = ""; 138 | 139 | // If the transmission sampler is active inject a flag 140 | if (transmissionSampler) shader.defines.USE_SAMPLER = ""; 141 | // Otherwise we do use use .transmission and must therefore force USE_TRANSMISSION 142 | // because threejs won't inject it for us 143 | else shader.defines.USE_TRANSMISSION = ""; 144 | 145 | // Head 146 | shader.fragmentShader = 147 | /*glsl*/ ` 148 | uniform float chromaticAberration; 149 | uniform float anisotropicBlur; 150 | uniform float time; 151 | uniform float distortion; 152 | uniform float distortionScale; 153 | uniform float temporalDistortion; 154 | uniform sampler2D buffer; 155 | 156 | vec3 random3(vec3 c) { 157 | float j = 4096.0*sin(dot(c,vec3(17.0, 59.4, 15.0))); 158 | vec3 r; 159 | r.z = fract(512.0*j); 160 | j *= .125; 161 | r.x = fract(512.0*j); 162 | j *= .125; 163 | r.y = fract(512.0*j); 164 | return r-0.5; 165 | } 166 | 167 | uint hash( uint x ) { 168 | x += ( x << 10u ); 169 | x ^= ( x >> 6u ); 170 | x += ( x << 3u ); 171 | x ^= ( x >> 11u ); 172 | x += ( x << 15u ); 173 | return x; 174 | } 175 | 176 | // Compound versions of the hashing algorithm I whipped together. 177 | uint hash( uvec2 v ) { return hash( v.x ^ hash(v.y) ); } 178 | uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ); } 179 | uint hash( uvec4 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); } 180 | 181 | // Construct a float with half-open range [0:1] using low 23 bits. 182 | // All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0. 183 | float floatConstruct( uint m ) { 184 | const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask 185 | const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32 186 | m &= ieeeMantissa; // Keep only mantissa bits (fractional part) 187 | m |= ieeeOne; // Add fractional part to 1.0 188 | float f = uintBitsToFloat( m ); // Range [1:2] 189 | return f - 1.0; // Range [0:1] 190 | } 191 | 192 | // Pseudo-random value in half-open range [0:1]. 193 | float randomBase( float x ) { return floatConstruct(hash(floatBitsToUint(x))); } 194 | float randomBase( vec2 v ) { return floatConstruct(hash(floatBitsToUint(v))); } 195 | float randomBase( vec3 v ) { return floatConstruct(hash(floatBitsToUint(v))); } 196 | float randomBase( vec4 v ) { return floatConstruct(hash(floatBitsToUint(v))); } 197 | float rand(float seed) { 198 | float result = randomBase(vec3(gl_FragCoord.xy, seed)); 199 | return result; 200 | } 201 | 202 | const float F3 = 0.3333333; 203 | const float G3 = 0.1666667; 204 | 205 | float snoise(vec3 p) { 206 | vec3 s = floor(p + dot(p, vec3(F3))); 207 | vec3 x = p - s + dot(s, vec3(G3)); 208 | vec3 e = step(vec3(0.0), x - x.yzx); 209 | vec3 i1 = e*(1.0 - e.zxy); 210 | vec3 i2 = 1.0 - e.zxy*(1.0 - e); 211 | vec3 x1 = x - i1 + G3; 212 | vec3 x2 = x - i2 + 2.0*G3; 213 | vec3 x3 = x - 1.0 + 3.0*G3; 214 | vec4 w, d; 215 | w.x = dot(x, x); 216 | w.y = dot(x1, x1); 217 | w.z = dot(x2, x2); 218 | w.w = dot(x3, x3); 219 | w = max(0.6 - w, 0.0); 220 | d.x = dot(random3(s), x); 221 | d.y = dot(random3(s + i1), x1); 222 | d.z = dot(random3(s + i2), x2); 223 | d.w = dot(random3(s + 1.0), x3); 224 | w *= w; 225 | w *= w; 226 | d *= w; 227 | return dot(d, vec4(52.0)); 228 | } 229 | 230 | float snoiseFractal(vec3 m) { 231 | return 0.5333333* snoise(m) 232 | +0.2666667* snoise(2.0*m) 233 | +0.1333333* snoise(4.0*m) 234 | +0.0666667* snoise(8.0*m); 235 | }\n` + shader.fragmentShader; 236 | 237 | // Remove transmission 238 | shader.fragmentShader = shader.fragmentShader.replace( 239 | "#include ", 240 | /*glsl*/ ` 241 | #ifdef USE_TRANSMISSION 242 | // Transmission code is based on glTF-Sampler-Viewer 243 | // https://github.com/KhronosGroup/glTF-Sample-Viewer 244 | uniform float _transmission; 245 | float transmission = 0.0; 246 | uniform float thickness; 247 | uniform float attenuationDistance; 248 | uniform vec3 attenuationColor; 249 | #ifdef USE_TRANSMISSIONMAP 250 | uniform sampler2D transmissionMap; 251 | #endif 252 | #ifdef USE_THICKNESSMAP 253 | uniform sampler2D thicknessMap; 254 | #endif 255 | uniform vec2 transmissionSamplerSize; 256 | uniform sampler2D transmissionSamplerMap; 257 | uniform mat4 modelMatrix; 258 | uniform mat4 projectionMatrix; 259 | varying vec3 vWorldPosition; 260 | vec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) { 261 | // Direction of refracted light. 262 | vec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior ); 263 | // Compute rotation-independant scaling of the model matrix. 264 | vec3 modelScale; 265 | modelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) ); 266 | modelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) ); 267 | modelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) ); 268 | // The thickness is specified in local space. 269 | return normalize( refractionVector ) * thickness * modelScale; 270 | } 271 | float applyIorToRoughness( const in float roughness, const in float ior ) { 272 | // Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and 273 | // an IOR of 1.5 results in the default amount of microfacet refraction. 274 | return roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 ); 275 | } 276 | vec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) { 277 | float framebufferLod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior ); 278 | #ifdef USE_SAMPLER 279 | #ifdef texture2DLodEXT 280 | return texture2DLodEXT(transmissionSamplerMap, fragCoord.xy, framebufferLod); 281 | #else 282 | return texture2D(transmissionSamplerMap, fragCoord.xy, framebufferLod); 283 | #endif 284 | #else 285 | return texture2D(buffer, fragCoord.xy); 286 | #endif 287 | } 288 | vec3 applyVolumeAttenuation( const in vec3 radiance, const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) { 289 | if ( isinf( attenuationDistance ) ) { 290 | // Attenuation distance is +∞, i.e. the transmitted color is not attenuated at all. 291 | return radiance; 292 | } else { 293 | // Compute light attenuation using Beer's law. 294 | vec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance; 295 | vec3 transmittance = exp( - attenuationCoefficient * transmissionDistance ); // Beer's law 296 | return transmittance * radiance; 297 | } 298 | } 299 | vec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor, 300 | const in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix, 301 | const in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness, 302 | const in vec3 attenuationColor, const in float attenuationDistance ) { 303 | vec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); 304 | vec3 refractedRayExit = position + transmissionRay; 305 | // Project refracted vector on the framebuffer, while mapping to normalized device coordinates. 306 | vec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 ); 307 | vec2 refractionCoords = ndcPos.xy / ndcPos.w; 308 | refractionCoords += 1.0; 309 | refractionCoords /= 2.0; 310 | // Sample framebuffer to get pixel the refracted ray hits. 311 | vec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior ); 312 | vec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance ); 313 | // Get the specular component. 314 | vec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness ); 315 | return vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a ); 316 | } 317 | #endif\n` 318 | ); 319 | 320 | // Add refraction 321 | shader.fragmentShader = shader.fragmentShader.replace( 322 | "#include ", 323 | /*glsl*/ ` 324 | // Improve the refraction to use the world pos 325 | material.transmission = _transmission; 326 | material.transmissionAlpha = 1.0; 327 | material.thickness = thickness; 328 | material.attenuationDistance = attenuationDistance; 329 | material.attenuationColor = attenuationColor; 330 | #ifdef USE_TRANSMISSIONMAP 331 | material.transmission *= texture2D( transmissionMap, vUv ).r; 332 | #endif 333 | #ifdef USE_THICKNESSMAP 334 | material.thickness *= texture2D( thicknessMap, vUv ).g; 335 | #endif 336 | 337 | vec3 pos = vWorldPosition; 338 | float runningSeed = 0.0; 339 | vec3 v = normalize( cameraPosition - pos ); 340 | vec3 n = inverseTransformDirection( normal, viewMatrix ); 341 | vec3 transmission = vec3(0.0); 342 | float transmissionR, transmissionB, transmissionG; 343 | float randomCoords = rand(runningSeed++); 344 | float thickness_smear = thickness * max(pow(roughnessFactor, 0.33), anisotropicBlur); 345 | vec3 distortionNormal = vec3(0.0); 346 | vec3 temporalOffset = vec3(time, -time, -time) * temporalDistortion; 347 | if (distortion > 0.0) { 348 | distortionNormal = distortion * vec3(snoiseFractal(vec3((pos * distortionScale + temporalOffset))), snoiseFractal(vec3(pos.zxy * distortionScale - temporalOffset)), snoiseFractal(vec3(pos.yxz * distortionScale + temporalOffset))); 349 | } 350 | for (float i = 0.0; i < ${samples}.0; i ++) { 351 | vec3 sampleNorm = normalize(n + roughnessFactor * roughnessFactor * 2.0 * normalize(vec3(rand(runningSeed++) - 0.5, rand(runningSeed++) - 0.5, rand(runningSeed++) - 0.5)) * pow(rand(runningSeed++), 0.33) + distortionNormal); 352 | transmissionR = getIBLVolumeRefraction( 353 | sampleNorm, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90, 354 | pos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness + thickness_smear * (i + randomCoords) / float(${samples}), 355 | material.attenuationColor, material.attenuationDistance 356 | ).r; 357 | transmissionG = getIBLVolumeRefraction( 358 | sampleNorm, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90, 359 | pos, modelMatrix, viewMatrix, projectionMatrix, material.ior * (1.0 + chromaticAberration * (i + randomCoords) / float(${samples})) , material.thickness + thickness_smear * (i + randomCoords) / float(${samples}), 360 | material.attenuationColor, material.attenuationDistance 361 | ).g; 362 | transmissionB = getIBLVolumeRefraction( 363 | sampleNorm, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90, 364 | pos, modelMatrix, viewMatrix, projectionMatrix, material.ior * (1.0 + 2.0 * chromaticAberration * (i + randomCoords) / float(${samples})), material.thickness + thickness_smear * (i + randomCoords) / float(${samples}), 365 | material.attenuationColor, material.attenuationDistance 366 | ).b; 367 | transmission.r += transmissionR; 368 | transmission.g += transmissionG; 369 | transmission.b += transmissionB; 370 | } 371 | transmission /= ${samples}.0; 372 | totalDiffuse = mix( totalDiffuse, transmission.rgb, material.transmission );\n` 373 | ); 374 | }; 375 | 376 | Object.keys(this.uniforms).forEach((name) => 377 | Object.defineProperty(this, name, { 378 | get: () => { 379 | const uniform = this.uniforms[name]; 380 | if (!uniform) return undefined; 381 | return uniform.value; 382 | }, 383 | set: (v) => { 384 | const uniform = this.uniforms[name]; 385 | if (!uniform) return; 386 | uniform.value = v; 387 | }, 388 | }) 389 | ); 390 | } 391 | } 392 | 393 | // @ts-ignore 394 | export const MeshTransmissionMaterial: ForwardRefComponent< 395 | MeshTransmissionMaterialProps, 396 | JSX.IntrinsicElements["meshTransmissionMaterial"] 397 | > = /* @__PURE__ */ React.forwardRef( 398 | ( 399 | { 400 | buffer, 401 | transmissionSampler = false, 402 | backside = false, 403 | side = THREE.FrontSide, 404 | transmission = 1, 405 | thickness = 2, 406 | backsideThickness = 0, 407 | backsideEnvMapIntensity = 1, 408 | samples = 10, 409 | resolution, 410 | backsideResolution, 411 | background, 412 | anisotropy, 413 | anisotropicBlur, 414 | ...props 415 | }: MeshTransmissionMaterialProps, 416 | fref 417 | ) => { 418 | extend({ MeshTransmissionMaterial: MeshTransmissionMaterialImpl }); 419 | 420 | const ref = React.useRef( 421 | null! 422 | ); 423 | const [discardMaterial] = React.useState(() => new DiscardMaterial()); 424 | const fboBack = useFBO(backsideResolution || resolution); 425 | const fboMain = useFBO(resolution); 426 | 427 | let oldBg; 428 | let oldEnvMapIntensity; 429 | let oldTone; 430 | let parent; 431 | useFrame((state) => { 432 | ref.current.time = state.clock.getElapsedTime(); 433 | 434 | // Render only if the buffer matches the built-in and no transmission sampler is set 435 | if (ref.current.buffer === fboMain.texture && !transmissionSampler) { 436 | parent = (ref.current as any).__r3f.parent as THREE.Object3D; 437 | 438 | if (parent) { 439 | // Save defaults 440 | oldTone = state.gl.toneMapping; 441 | oldBg = state.scene.background; 442 | oldEnvMapIntensity = ref.current.envMapIntensity; 443 | 444 | // Switch off tonemapping lest it double tone maps 445 | // Save the current background and set the HDR as the new BG 446 | // Use discardmaterial, the parent will be invisible, but it's shadows will still be cast 447 | state.gl.toneMapping = THREE.NoToneMapping; 448 | if (background) state.scene.background = background; 449 | parent.material = discardMaterial; 450 | 451 | if (backside) { 452 | // Render into the backside buffer 453 | state.gl.setRenderTarget(fboBack); 454 | state.gl.render(state.scene, state.camera); 455 | // And now prepare the material for the main render using the backside buffer 456 | parent.material = ref.current; 457 | parent.material.buffer = fboBack.texture; 458 | parent.material.thickness = backsideThickness; 459 | parent.material.side = THREE.BackSide; 460 | parent.material.envMapIntensity = backsideEnvMapIntensity; 461 | } 462 | 463 | // Render into the main buffer 464 | state.gl.setRenderTarget(fboMain); 465 | state.gl.render(state.scene, state.camera); 466 | 467 | parent.material = ref.current; 468 | parent.material.thickness = thickness; 469 | parent.material.side = side; 470 | parent.material.buffer = fboMain.texture; 471 | parent.material.envMapIntensity = oldEnvMapIntensity; 472 | 473 | // Set old state back 474 | state.scene.background = oldBg; 475 | state.gl.setRenderTarget(null); 476 | state.gl.toneMapping = oldTone; 477 | } 478 | } 479 | }); 480 | 481 | // Forward ref 482 | React.useImperativeHandle(fref, () => ref.current, []); 483 | 484 | return ( 485 | 0 and execute extra renders. 495 | // The exception is when transmissionSampler is set, in which case we are using three's built in sampler. 496 | anisotropicBlur={anisotropicBlur ?? anisotropy} 497 | transmission={transmissionSampler ? transmission : 0} 498 | thickness={thickness} 499 | side={side} 500 | /> 501 | ); 502 | } 503 | ); 504 | -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/Scene.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AccumulativeShadows, 3 | Caustics, 4 | Environment, 5 | Grid, 6 | OrbitControls, 7 | PerspectiveCamera, 8 | RandomizedLight, 9 | useGLTF, 10 | } from "@react-three/drei"; 11 | import { useFrame } from "@react-three/fiber"; 12 | import { Suspense, useMemo, useRef, useState } from "react"; 13 | import { Color } from "three"; 14 | 15 | import { patchShaders } from "gl-noise/build/glNoise.m"; 16 | import CSM from "../../../../package/src/React"; 17 | import { useShader } from "../../pages/Root"; 18 | import { MeshTransmissionMaterial } from "./MeshTransmissionMaterial"; 19 | 20 | function Thing() { 21 | const { vs, fs } = useShader(); 22 | 23 | const { nodes } = useGLTF( 24 | "https://vazxmixjsiawhamofees.supabase.co/storage/v1/object/public/models/bunny/model.gltf" 25 | ) as any; 26 | 27 | const uniforms = useMemo( 28 | () => ({ 29 | colorMap: { 30 | value: [ 31 | new Color("#b7dfa5"), 32 | new Color("#decf8d"), 33 | new Color("#bdb281"), 34 | new Color("#547561"), 35 | new Color("#0e1d18"), 36 | ].map((col) => { 37 | const hsl = { 38 | h: 0, 39 | s: 0, 40 | l: 0, 41 | }; 42 | col.getHSL(hsl); 43 | col.setHSL( 44 | hsl.h, // 45 | hsl.s * 2, 46 | hsl.l * 0.75 47 | ); 48 | 49 | return col; 50 | }), 51 | }, 52 | uTime: { 53 | value: 0, 54 | }, 55 | }), 56 | [] 57 | ); 58 | 59 | const gridRef = useRef(); 60 | 61 | useFrame((state, dt) => { 62 | // uniforms.uTime.value += dt; 63 | }); 64 | 65 | const [mtmRef, setMtmRef] = useState(); 66 | 67 | return ( 68 | <> 69 | 79 | 80 | void (r && setMtmRef(r as any))} 82 | /> 83 | 84 | {mtmRef && ( 85 | 98 | )} 99 | 100 | 101 | 102 | 116 | 117 | ); 118 | } 119 | 120 | export function Scene() { 121 | return ( 122 | <> 123 | <> 124 | 125 | 126 | 127 | 128 | 129 | 133 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | ); 150 | } 151 | -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/Ui.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react' 2 | import { FaGithub, FaCode } from 'react-icons/fa' 3 | 4 | export const Ui = forwardRef((props, ref) => { 5 | return ( 6 | <> 7 |
8 | fps: 9 |
10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Made with 🧡 by{' '} 20 | 21 | Faraz Shaikh 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | ) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/Ui3D.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Text } from '@react-three/drei' 2 | import appState from './state/appState' 3 | 4 | const textConfig = { 5 | anchorX: 'center' as const, 6 | font: '/CabinSketch-Regular.ttf', 7 | fontSize: 1, 8 | color: '#000000', 9 | rotation: [-Math.PI / 2, 0, 0] as [number, number, number], 10 | } 11 | 12 | export function Ui3D() { 13 | const { isMetallic, isBump, setMetallic, setBump } = appState() 14 | 15 | return ( 16 | <> 17 | 18 | three- 19 | 20 | 21 | Custom 22 | 23 | 24 | Shader 25 | 26 | 27 | Material 28 | 29 | 30 | 31 |
32 |

33 | 34 | MeshTransmissionMaterial 35 | 36 |
37 | with color and metalness 38 |
39 | driven by noise. 40 |

41 |
42 | 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/fs.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 colorMap[5]; 2 | uniform float uTime; 3 | 4 | varying vec3 csm_vPosition; 5 | 6 | gln_tFBMOpts opts = gln_tFBMOpts(0.0, 0.3, 2.0, 0.5, 1.0, 5, false, false); 7 | 8 | float domainWarp(vec3 uv) { 9 | float scale = 2.; 10 | float falloff = 1.; 11 | vec3 outUv = vec3(0.); 12 | 13 | for (float i = 0.; i < 10.; i += 1.) { 14 | vec3 offset = vec3( 15 | (i + uTime) * 0.1 , 16 | (i + uTime) * 0.2 , 17 | (i + uTime) * 0.3 18 | ); 19 | 20 | vec3 dUv = vec3( 21 | gln_sfbm((scale * uv) + outUv, opts), 22 | gln_sfbm((scale * uv) + outUv, opts), 23 | gln_sfbm((scale * uv) + outUv, opts) 24 | ); 25 | 26 | outUv = falloff * dUv + offset; 27 | } 28 | 29 | return gln_sfbm(uv + scale * outUv, opts); 30 | } 31 | 32 | vec3 saturateColor(vec3 v, float s) { 33 | return vec3( 34 | clamp(v.x, 0., s), 35 | clamp(v.y, 0., s), 36 | clamp(v.z, 0., s) 37 | ); 38 | } 39 | 40 | // Get color from colorMap based on t. Smoothly interpolate between colors. 41 | vec3 colorMapLookup(float t) { 42 | float tScaled = t * 4.; 43 | float tFloor = floor(tScaled); 44 | float tFrac = tScaled - tFloor; 45 | 46 | int index = int(tFloor); 47 | int nextIndex = int(tFloor) + 1; 48 | 49 | if(index < 0) index = 4; 50 | if(index > 4) index = 0; 51 | if(nextIndex < 0) nextIndex = 4; 52 | if(nextIndex > 4) nextIndex = 0; 53 | 54 | vec3 colorA = colorMap[index]; 55 | vec3 colorB = colorMap[nextIndex]; 56 | 57 | return mix(colorA, colorB, tFrac); 58 | } 59 | 60 | void main() { 61 | float warpedNoise = domainWarp(csm_vPosition); 62 | vec3 col = colorMapLookup(warpedNoise); 63 | csm_DiffuseColor = vec4(col, 1.); 64 | 65 | float noise = gln_simplex(csm_vPosition); 66 | csm_Metalness = smoothstep(0.49, 0.5, noise); 67 | } -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/index.css: -------------------------------------------------------------------------------- 1 | /*styles.css*/ 2 | @import url('https://fonts.googleapis.com/css2?family=Cabin+Sketch&family=Rampart+One&display=swap'); 3 | 4 | * { 5 | box-sizing: border-box; 6 | } 7 | 8 | html, 9 | body, 10 | #root { 11 | width: 100%; 12 | height: 100%; 13 | margin: 0; 14 | padding: 0; 15 | background: #181818; 16 | } 17 | 18 | body { 19 | font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, 20 | segoe ui, arial, sans-serif; 21 | } 22 | 23 | .copy { 24 | position: absolute; 25 | bottom: 0; 26 | right: 0; 27 | width: 100%; 28 | 29 | display: flex; 30 | justify-content: space-between; 31 | align-items: center; 32 | color: white; 33 | pointer-events: none; 34 | } 35 | 36 | .copy svg { 37 | width: 32px; 38 | fill: white; 39 | } 40 | 41 | .copy > * { 42 | padding: 32px; 43 | } 44 | 45 | a { 46 | font-style: italic; 47 | color: #5387ff; 48 | font-weight: bold; 49 | text-decoration: underline; 50 | pointer-events: all; 51 | } 52 | 53 | canvas { 54 | cursor: grab; 55 | } 56 | 57 | canvas:active { 58 | cursor: grabbing; 59 | } 60 | 61 | .infoCard { 62 | font-size: 8px; 63 | text-align: center; 64 | pointer-events: none; 65 | user-select: none; 66 | } 67 | 68 | .infoCard p { 69 | color: #547561; 70 | font-family: 'Cabin Sketch', sans-serif; 71 | } 72 | 73 | .infoCard a { 74 | color: #0e1d18; 75 | cursor: pointer; 76 | font-family: 'Rampart One', sans-serif; 77 | text-decoration: none; 78 | pointer-events: all; 79 | } 80 | 81 | .infoCard a:hover { 82 | text-decoration: underline; 83 | } 84 | 85 | .fps { 86 | position: absolute; 87 | top: 0; 88 | left: 0; 89 | width: 100%; 90 | height: 100%; 91 | padding: 16px; 92 | font-family: 'Rampart One', sans-serif; 93 | font-weight: bold; 94 | pointer-events: none; 95 | } 96 | 97 | .my-tag { 98 | position: absolute; 99 | bottom: 0; 100 | right: 0; 101 | width: 100%; 102 | 103 | display: flex; 104 | justify-content: space-between; 105 | align-items: center; 106 | color: black; 107 | pointer-events: none; 108 | 109 | font-family: 'Cabin Sketch', sans-serif; 110 | font-size: 18px; 111 | pointer-events: none; 112 | } 113 | 114 | .my-tag svg { 115 | width: 32px; 116 | fill: black; 117 | } 118 | 119 | .my-tag > * { 120 | padding: 32px; 121 | padding-bottom: 64px; 122 | } 123 | 124 | .my-tag a { 125 | color: #0e1d18; 126 | cursor: pointer; 127 | font-family: 'Rampart One', sans-serif; 128 | text-underline-offset: 2px; 129 | text-decoration: none; 130 | pointer-events: all; 131 | } 132 | 133 | .my-tag a:hover { 134 | text-decoration: underline; 135 | } 136 | -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/state/appState.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | 3 | interface AppState { 4 | isMetallic: boolean; 5 | isBump: boolean; 6 | setMetallic: (isMetallic: boolean) => void; 7 | setBump: (isBump: boolean) => void; 8 | } 9 | 10 | export default create((set) => ({ 11 | isMetallic: true, 12 | isBump: false, 13 | 14 | setMetallic: (isMetallic: boolean) => set({ isMetallic }), 15 | setBump: (isBump: boolean) => set({ isBump }), 16 | })); 17 | -------------------------------------------------------------------------------- /examples/src/Examples/MetalBunny/vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 csm_vPosition; 2 | 3 | void main() { 4 | csm_vPosition = position; 5 | } -------------------------------------------------------------------------------- /examples/src/Examples/Points/Scene.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Environment, 3 | OrbitControls, 4 | PerspectiveCamera, 5 | } from "@react-three/drei"; 6 | import { useFrame } from "@react-three/fiber"; 7 | import { Suspense, useEffect, useMemo, useRef } from "react"; 8 | import { IcosahedronGeometry, Points, PointsMaterial } from "three"; 9 | 10 | import { patchShaders } from "gl-noise/build/glNoise.m"; 11 | import CustomShaderMaterialType from "../../../../package/src"; 12 | import CustomShaderMaterial from "../../../../package/src/React"; 13 | import { useShader } from "../../pages/Root"; 14 | 15 | function Thing() { 16 | const { vs, fs } = useShader(); 17 | 18 | const pointsRef = useRef(null!); 19 | const matRef = useRef>(null!); 20 | 21 | useEffect(() => { 22 | pointsRef.current.geometry = new IcosahedronGeometry(1, 32); 23 | }, []); 24 | 25 | useFrame(({ clock }) => { 26 | matRef.current.uniforms.uTime.value = clock.elapsedTime; 27 | }); 28 | 29 | const uniforms = useMemo( 30 | () => ({ 31 | uTime: { 32 | value: 0, 33 | }, 34 | }), 35 | [] 36 | ); 37 | 38 | return ( 39 | 40 | 41 | 50 | 51 | 52 | ); 53 | } 54 | 55 | export function Scene() { 56 | return ( 57 | <> 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /examples/src/Examples/Points/fs.glsl: -------------------------------------------------------------------------------- 1 | varying float vVisibility; 2 | varying vec3 vViewNormal; 3 | 4 | void main() { 5 | vec2 uv = vec2(gl_PointCoord.x, 1. - gl_PointCoord.y); 6 | vec2 cUV = 2. * uv - 1.; 7 | float a = .15 / length(cUV); 8 | float alpha = 1.; 9 | if(a < 0.15) alpha = 0.; 10 | 11 | csm_DiffuseColor = vec4(vViewNormal, (vVisibility + 0.01) * alpha); 12 | } 13 | -------------------------------------------------------------------------------- /examples/src/Examples/Points/vs.glsl: -------------------------------------------------------------------------------- 1 | uniform float uTime; 2 | varying float vVisibility; 3 | varying vec3 vViewNormal; 4 | 5 | void main() { 6 | vec3 n = gln_curl(position + uTime * 0.05); 7 | 8 | vec3 _viewNormal = normalMatrix * normal; 9 | vViewNormal = _viewNormal; 10 | vec4 _mvPosition = modelViewMatrix * vec4(position, 1.); 11 | 12 | float visibility = step(-0.1, dot(-normalize(_mvPosition.xyz), normalize(_viewNormal))); 13 | vVisibility = visibility; 14 | 15 | csm_Position = position + (normal * n * 0.5); 16 | csm_PointSize += ((1. - visibility) * 0.05); 17 | } -------------------------------------------------------------------------------- /examples/src/Examples/Shadows/Scene.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | OrbitControls, 3 | PerspectiveCamera, 4 | Plane, 5 | useTexture, 6 | } from "@react-three/drei"; 7 | import { useControls } from "leva"; 8 | import { useMemo } from "react"; 9 | import { 10 | DoubleSide, 11 | MeshDepthMaterial, 12 | MeshDistanceMaterial, 13 | MeshStandardMaterial, 14 | PlaneGeometry, 15 | } from "three"; 16 | import CSM from "../../../../package/src/React"; 17 | import { useShader } from "../../pages/Root"; 18 | 19 | export function Scene() { 20 | const map = useTexture(import.meta.env.BASE_URL + "Shadows/react.png"); 21 | 22 | const { vs, fs } = useShader(); 23 | 24 | const uniforms = useMemo( 25 | () => ({ 26 | uMap: { value: map }, 27 | }), 28 | [] 29 | ); 30 | 31 | const { lightType } = useControls({ 32 | lightType: { 33 | options: ["point", "directional"], 34 | value: "point", 35 | label: "Light Type", 36 | }, 37 | }); 38 | 39 | return ( 40 | <> 41 | 42 | 43 | 44 | {(() => { 45 | switch (lightType) { 46 | case "point": 47 | return ( 48 | 55 | ); 56 | case "directional": 57 | return ( 58 | 65 | ); 66 | 67 | default: 68 | return null; 69 | } 70 | })()} 71 | 72 | 73 | 74 | 75 | 76 | 81 | 89 | 96 | 103 | 104 | 105 | 106 | 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /examples/src/Examples/Shadows/fs.glsl: -------------------------------------------------------------------------------- 1 | uniform sampler2D uMap; 2 | 3 | varying vec2 vUv; 4 | 5 | void main() { 6 | vec4 color = texture2D(uMap, vUv); 7 | csm_DiffuseColor = color; 8 | csm_DepthAlpha = color.a; 9 | } -------------------------------------------------------------------------------- /examples/src/Examples/Shadows/vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | 3 | void main() { 4 | vUv = uv; 5 | } -------------------------------------------------------------------------------- /examples/src/Examples/Vanilla/Scene.tsx: -------------------------------------------------------------------------------- 1 | import { OrbitControls, Sphere } from "@react-three/drei"; 2 | import * as THREE from "three"; 3 | 4 | import { useControls } from "leva"; 5 | import { useEffect, useMemo } from "react"; 6 | import CSM, { CSMProxy } from "three-custom-shader-material/vanilla"; 7 | import { useShader } from "../../pages/Root"; 8 | import { Stage } from "./Stage"; 9 | 10 | export function Scene() { 11 | const { vs, fs } = useShader(); 12 | 13 | const material = useMemo(() => { 14 | const mat = new CSM({ 15 | baseMaterial: THREE.MeshPhysicalMaterial, 16 | vertexShader: vs, 17 | fragmentShader: fs, 18 | transmission: 1, 19 | roughness: 0.2, 20 | thickness: 2, 21 | }).clone() as CSMProxy; 22 | 23 | return mat; 24 | }, [vs, fs]); 25 | 26 | useControls( 27 | { 28 | color: { 29 | value: "#ff0000", 30 | label: "Color", 31 | onChange: (color: string) => { 32 | material.color.set(color); 33 | }, 34 | }, 35 | flatShading: { 36 | value: false, 37 | label: "Flat Shading", 38 | onChange: (flatShading: boolean) => { 39 | material.flatShading = flatShading; 40 | material.needsUpdate = true; 41 | }, 42 | }, 43 | }, 44 | [material] 45 | ); 46 | 47 | useEffect(() => () => material.dispose(), [material]); 48 | 49 | return ( 50 | <> 51 | 52 | 53 | 70 | 71 | 72 | 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /examples/src/Examples/Vanilla/Stage.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AccumulativeShadows, 3 | AccumulativeShadowsProps, 4 | Bounds, 5 | Center, 6 | CenterProps, 7 | ContactShadows, 8 | ContactShadowsProps, 9 | Environment, 10 | EnvironmentProps, 11 | RandomizedLight, 12 | RandomizedLightProps, 13 | useBounds, 14 | } from "@react-three/drei"; 15 | import * as React from "react"; 16 | 17 | const presets = { 18 | rembrandt: { 19 | main: [1, 2, 1], 20 | fill: [-2, -0.5, -2], 21 | }, 22 | portrait: { 23 | main: [-1, 2, 0.5], 24 | fill: [-1, 0.5, -1.5], 25 | }, 26 | upfront: { 27 | main: [0, 2, 1], 28 | fill: [-1, 0.5, -1.5], 29 | }, 30 | soft: { 31 | main: [-2, 4, 4], 32 | fill: [-1, 0.5, -1.5], 33 | }, 34 | }; 35 | 36 | type StageShadows = Partial & 37 | Partial & 38 | Partial & { 39 | type: "contact" | "accumulative"; 40 | /** Shadow plane offset, default: 0 */ 41 | offset?: number; 42 | /** Shadow bias, default: -0.0001 */ 43 | bias?: number; 44 | /** Shadow normal bias, default: 0 */ 45 | normalBias?: number; 46 | /** Shadow map size, default: 1024 */ 47 | size?: number; 48 | }; 49 | 50 | type StageProps = { 51 | /** Lighting setup, default: "rembrandt" */ 52 | preset?: 53 | | "rembrandt" 54 | | "portrait" 55 | | "upfront" 56 | | "soft" 57 | | { 58 | main: [x: number, y: number, z: number]; 59 | fill: [x: number, y: number, z: number]; 60 | }; 61 | /** Controls the ground shadows, default: "contact" */ 62 | shadows?: boolean | "contact" | "accumulative" | StageShadows; 63 | /** Optionally wraps and thereby centers the models using , can also be a margin, default: true */ 64 | adjustCamera?: boolean | number; 65 | /** The default environment, default: "city" */ 66 | environment?: any | Partial | null; 67 | /** The lighting intensity, default: 0.5 */ 68 | intensity?: number; 69 | /** To adjust centering, default: undefined */ 70 | center?: Partial; 71 | }; 72 | 73 | function Refit({ radius, adjustCamera }) { 74 | const api = useBounds(); 75 | React.useEffect(() => { 76 | if (adjustCamera) api.refresh().clip().fit(); 77 | }, [radius, adjustCamera]); 78 | return null; 79 | } 80 | 81 | export function Stage({ 82 | children, 83 | center, 84 | adjustCamera = true, 85 | intensity = 0.5, 86 | shadows = "contact", 87 | environment = "city", 88 | preset = "rembrandt", 89 | ...props 90 | }: JSX.IntrinsicElements["group"] & StageProps) { 91 | const config = typeof preset === "string" ? presets[preset] : preset; 92 | const [{ radius, height }, set] = React.useState({ 93 | radius: 0, 94 | width: 0, 95 | height: 0, 96 | depth: 0, 97 | }); 98 | const shadowBias = (shadows as StageShadows)?.bias ?? -0.0001; 99 | const normalBias = (shadows as StageShadows)?.normalBias ?? 0; 100 | const shadowSize = (shadows as StageShadows)?.size ?? 1024; 101 | const shadowOffset = (shadows as StageShadows)?.offset ?? 0; 102 | const contactShadow = 103 | shadows === "contact" || (shadows as StageShadows)?.type === "contact"; 104 | const accumulativeShadow = 105 | shadows === "accumulative" || 106 | (shadows as StageShadows)?.type === "accumulative"; 107 | const shadowSpread = { ...(typeof shadows === "object" ? shadows : {}) }; 108 | const environmentProps = !environment 109 | ? null 110 | : typeof environment === "string" 111 | ? { preset: environment } 112 | : environment; 113 | const onCentered = React.useCallback((props) => { 114 | const { width, height, depth, boundingSphere } = props; 115 | set({ radius: boundingSphere.radius, width, height, depth }); 116 | if (center?.onCentered) center.onCentered(props); 117 | }, []); 118 | return ( 119 | <> 120 | 121 | 134 | 142 | 149 | 150 |
155 | {children} 156 |
157 |
158 | 159 | 160 | {contactShadow && ( 161 | 167 | )} 168 | 169 | 170 | 171 | {accumulativeShadow && ( 172 | 180 | 196 | 197 | )} 198 | 199 | 200 | 201 | 202 | {environment && } 203 | 204 | 205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /examples/src/Examples/Vanilla/fs.glsl: -------------------------------------------------------------------------------- 1 | 2 | varying vec2 vUv; 3 | 4 | void main() { 5 | csm_Iridescence = 1.0; 6 | // csm_DiffuseColor = vec4(1.0, 1.0, 0.0, 1.0); 7 | } -------------------------------------------------------------------------------- /examples/src/Examples/Vanilla/vs.glsl: -------------------------------------------------------------------------------- 1 | 2 | varying vec2 vUv; 3 | 4 | void main() { 5 | vUv = uv; 6 | } -------------------------------------------------------------------------------- /examples/src/Examples/Waves/Lights.tsx: -------------------------------------------------------------------------------- 1 | export default function Lights() { 2 | return ( 3 | 4 | 11 | 18 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /examples/src/Examples/Waves/Scene.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { 3 | ContactShadows, 4 | Environment, 5 | OrbitControls, 6 | PerspectiveCamera, 7 | } from "@react-three/drei"; 8 | import { useFrame } from "@react-three/fiber"; 9 | import { patchShaders } from "gl-noise/build/glNoise.m"; 10 | import { useControls } from "leva"; 11 | import { Suspense, useMemo, useRef } from "react"; 12 | import * as THREE from "three"; 13 | import CSMType from "../../../../package/src"; 14 | import CSM from "../../../../package/src/React"; 15 | import { useShader } from "../../pages/Root"; 16 | import Lights from "./Lights"; 17 | 18 | const BASE_MATERIALS = { 19 | MeshStandardMaterial: THREE.MeshStandardMaterial, 20 | MeshPhysicalMaterial: THREE.MeshPhysicalMaterial, 21 | MeshBasicMaterial: THREE.MeshBasicMaterial, 22 | MeshLambertMaterial: THREE.MeshLambertMaterial, 23 | MeshPhongMaterial: THREE.MeshPhongMaterial, 24 | }; 25 | 26 | function FrameUpdate({ materialRef }) { 27 | useFrame(({ clock }) => { 28 | if (materialRef.current) { 29 | materialRef.current.uniforms.uTime.value = -clock.elapsedTime / 5; 30 | } 31 | }); 32 | 33 | return null!; 34 | } 35 | 36 | export function Scene() { 37 | const { vs, fs } = useShader(); 38 | const materialRef = useRef(null!); 39 | 40 | // useWaterControls(materialRef); 41 | 42 | const { 43 | Base: baseKey, 44 | visible, 45 | flatShading, 46 | } = useControls( 47 | "Material", 48 | { 49 | Base: { 50 | options: Object.keys(BASE_MATERIALS), 51 | value: "MeshPhysicalMaterial", 52 | label: "Base Material", 53 | }, 54 | visible: { 55 | value: true, 56 | }, 57 | flatShading: { 58 | value: false, 59 | label: "Flat Shading", 60 | }, 61 | }, 62 | 63 | [] 64 | ); 65 | 66 | const baseMaterial = BASE_MATERIALS[baseKey]; 67 | 68 | const uniforms = useMemo( 69 | () => ({ 70 | uTime: { value: 0 }, 71 | waterColor: { 72 | value: new THREE.Color("#52a7f7").convertLinearToSRGB(), 73 | }, 74 | waterHighlight: { 75 | value: new THREE.Color("#b3ffff").convertLinearToSRGB(), 76 | }, 77 | offset: { 78 | value: 0.4, 79 | }, 80 | contrast: { 81 | value: 3.1, 82 | }, 83 | brightness: { 84 | value: 1, 85 | }, 86 | uHeight: { 87 | value: 0.2, 88 | }, 89 | }), 90 | [] 91 | ); 92 | 93 | return ( 94 | <> 95 | 96 | 97 | {["MeshPhysicalMaterial", "MeshStanderedMaterial"].includes(baseKey) ? ( 98 | 99 | ) : ( 100 | 101 | )} 102 | 103 | 104 | 110 | 111 | 123 | 124 | 125 | 126 | 134 | 135 | 136 | 137 | 138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /examples/src/Examples/Waves/fs.glsl: -------------------------------------------------------------------------------- 1 | varying float vHeight; 2 | 3 | uniform vec3 waterColor; 4 | uniform vec3 waterHighlight; 5 | 6 | uniform float offset; 7 | uniform float contrast; 8 | uniform float brightness; 9 | 10 | vec3 calcColor() { 11 | float mask = (pow(vHeight, 2.) - offset) * contrast; 12 | vec3 diffuseColor = mix(waterColor, waterHighlight, mask); 13 | diffuseColor *= brightness; 14 | return diffuseColor; 15 | } 16 | 17 | void main() { 18 | csm_DiffuseColor = vec4(calcColor(), 1.0); 19 | } -------------------------------------------------------------------------------- /examples/src/Examples/Waves/useWaterControls.ts: -------------------------------------------------------------------------------- 1 | import { useControls } from "leva"; 2 | import React from "react"; 3 | import { Color } from "three"; 4 | import CustomShaderMaterialType from "../../../../package/src"; 5 | 6 | export default function useWaterControls( 7 | material: React.RefObject> 8 | ) { 9 | useControls( 10 | "Water", 11 | () => { 12 | if (!material.current) return {}; 13 | 14 | return { 15 | Color: { 16 | value: "#52a7f7", 17 | onChange: (v) => { 18 | material.current!.uniforms.waterColor.value = new Color( 19 | v 20 | ).convertLinearToSRGB(); 21 | }, 22 | }, 23 | HighlightColor: { 24 | value: "#b3ffff", 25 | onChange: (v) => { 26 | material.current!.uniforms.waterHighlight.value = new Color( 27 | v 28 | ).convertLinearToSRGB(); 29 | }, 30 | }, 31 | 32 | Brightness: { 33 | value: 0.5, 34 | min: 0, 35 | max: 1, 36 | onChange: (v) => { 37 | material.current!.uniforms.brightness.value = v * 2; 38 | }, 39 | }, 40 | }; 41 | }, 42 | [material] 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /examples/src/Examples/Waves/vs.glsl: -------------------------------------------------------------------------------- 1 | 2 | uniform float uTime; 3 | uniform float uHeight; 4 | varying float vHeight; 5 | 6 | vec3 displace(vec3 point) { 7 | 8 | vec3 p = point; 9 | 10 | p.y += uTime * 2.0; 11 | 12 | gln_tFBMOpts fbmOpts = gln_tFBMOpts(1.0, 0.4, 2.3, 0.4, 1.0, 5, false, false); 13 | 14 | gln_tGerstnerWaveOpts A = gln_tGerstnerWaveOpts(vec2(0.0, -1.0), 0.5, 2.0); 15 | gln_tGerstnerWaveOpts B = gln_tGerstnerWaveOpts(vec2(0.0, 1.0), 0.25, 4.0); 16 | gln_tGerstnerWaveOpts C = gln_tGerstnerWaveOpts(vec2(1.0, 1.0), 0.15, 6.0); 17 | gln_tGerstnerWaveOpts D = gln_tGerstnerWaveOpts(vec2(1.0, 1.0), 0.4, 2.0); 18 | 19 | vec3 n = vec3(0.0); 20 | 21 | if(p.z >= uHeight / 2.0) { 22 | n.z += gln_normalize(gln_pfbm(p.xy + (uTime * 0.5), fbmOpts)); 23 | n += gln_GerstnerWave(p, A, uTime).xzy; 24 | n += gln_GerstnerWave(p, B, uTime).xzy * 0.5; 25 | n += gln_GerstnerWave(p, C, uTime).xzy * 0.25; 26 | n += gln_GerstnerWave(p, D, uTime).xzy * 0.2; 27 | } 28 | 29 | vHeight = n.z; 30 | 31 | return point + n; 32 | } 33 | 34 | vec3 orthogonal(vec3 v) { 35 | return normalize(abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) 36 | : vec3(0.0, -v.z, v.y)); 37 | } 38 | 39 | vec3 recalcNormals(vec3 newPos) { 40 | float offset = 0.001; 41 | vec3 tangent = orthogonal(normal); 42 | vec3 bitangent = normalize(cross(normal, tangent)); 43 | vec3 neighbour1 = position + tangent * offset; 44 | vec3 neighbour2 = position + bitangent * offset; 45 | 46 | vec3 displacedNeighbour1 = displace(neighbour1); 47 | vec3 displacedNeighbour2 = displace(neighbour2); 48 | 49 | vec3 displacedTangent = displacedNeighbour1 - newPos; 50 | vec3 displacedBitangent = displacedNeighbour2 - newPos; 51 | 52 | return normalize(cross(displacedTangent, displacedBitangent)); 53 | } 54 | 55 | 56 | void main() { 57 | csm_Position = displace(position); 58 | csm_Normal = recalcNormals(csm_Position); 59 | } -------------------------------------------------------------------------------- /examples/src/Examples/default/Scene.tsx: -------------------------------------------------------------------------------- 1 | import { Box, OrbitControls, Sphere } from "@react-three/drei"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { MeshBasicMaterial } from "three"; 5 | import CSM from "three-custom-shader-material"; 6 | 7 | import { Perf } from "r3f-perf"; 8 | 9 | export const SceneTestX = ({ ...props }) => { 10 | const [fragmentShader, setFragmentShader] = useState(/*glsl*/ ` 11 | void main() { 12 | csm_DiffuseColor = vec4(1.0, 0.0, 0.0, 1.0); 13 | } 14 | `); 15 | 16 | useEffect(() => { 17 | setFragmentShader(/*glsl*/ ` 18 | void main() { 19 | csm_DiffuseColor = vec4(0.0, 1.0, 0.0, 1.0); 20 | } 21 | `); 22 | }, []); 23 | 24 | return ( 25 | <> 26 | 27 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export function Scene() { 34 | const [visible, setVisible] = useState(true); 35 | const [fragmentShader, setFragmentShader] = useState(/*glsl*/ ` 36 | void main() { 37 | csm_DiffuseColor = vec4(1.0, 0.0, 0.0, 1.0); 38 | } 39 | `); 40 | 41 | useEffect(() => { 42 | setInterval(() => { 43 | setFragmentShader(/*glsl*/ ` 44 | void main() { 45 | csm_DiffuseColor = vec4(${Math.random()}, ${Math.random()}, ${Math.random()}, 1.0); 46 | } 47 | `); 48 | }, 1000); 49 | }, []); 50 | 51 | return ( 52 | <> 53 | 54 | 55 | 56 | 57 | {visible && ( 58 | 59 | 63 | 64 | )} 65 | 66 | setVisible(!visible)}> 67 | 68 | 69 | 70 | 71 | 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /examples/src/Examples/default/Stage.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AccumulativeShadows, 3 | AccumulativeShadowsProps, 4 | Bounds, 5 | Center, 6 | CenterProps, 7 | ContactShadows, 8 | ContactShadowsProps, 9 | Environment, 10 | EnvironmentProps, 11 | RandomizedLight, 12 | RandomizedLightProps, 13 | useBounds, 14 | } from "@react-three/drei"; 15 | import * as React from "react"; 16 | 17 | const presets = { 18 | rembrandt: { 19 | main: [1, 2, 1], 20 | fill: [-2, -0.5, -2], 21 | }, 22 | portrait: { 23 | main: [-1, 2, 0.5], 24 | fill: [-1, 0.5, -1.5], 25 | }, 26 | upfront: { 27 | main: [0, 2, 1], 28 | fill: [-1, 0.5, -1.5], 29 | }, 30 | soft: { 31 | main: [-2, 4, 4], 32 | fill: [-1, 0.5, -1.5], 33 | }, 34 | }; 35 | 36 | type StageShadows = Partial & 37 | Partial & 38 | Partial & { 39 | type: "contact" | "accumulative"; 40 | /** Shadow plane offset, default: 0 */ 41 | offset?: number; 42 | /** Shadow bias, default: -0.0001 */ 43 | bias?: number; 44 | /** Shadow normal bias, default: 0 */ 45 | normalBias?: number; 46 | /** Shadow map size, default: 1024 */ 47 | size?: number; 48 | }; 49 | 50 | type StageProps = { 51 | /** Lighting setup, default: "rembrandt" */ 52 | preset?: 53 | | "rembrandt" 54 | | "portrait" 55 | | "upfront" 56 | | "soft" 57 | | { 58 | main: [x: number, y: number, z: number]; 59 | fill: [x: number, y: number, z: number]; 60 | }; 61 | /** Controls the ground shadows, default: "contact" */ 62 | shadows?: boolean | "contact" | "accumulative" | StageShadows; 63 | /** Optionally wraps and thereby centers the models using , can also be a margin, default: true */ 64 | adjustCamera?: boolean | number; 65 | /** The default environment, default: "city" */ 66 | environment?: any | Partial | null; 67 | /** The lighting intensity, default: 0.5 */ 68 | intensity?: number; 69 | /** To adjust centering, default: undefined */ 70 | center?: Partial; 71 | }; 72 | 73 | function Refit({ radius, adjustCamera }) { 74 | const api = useBounds(); 75 | React.useEffect(() => { 76 | if (adjustCamera) api.refresh().clip().fit(); 77 | }, [radius, adjustCamera]); 78 | return null; 79 | } 80 | 81 | export function Stage({ 82 | children, 83 | center, 84 | adjustCamera = true, 85 | intensity = 0.5, 86 | shadows = "contact", 87 | environment = "city", 88 | preset = "rembrandt", 89 | ...props 90 | }: JSX.IntrinsicElements["group"] & StageProps) { 91 | const config = typeof preset === "string" ? presets[preset] : preset; 92 | const [{ radius, height }, set] = React.useState({ 93 | radius: 0, 94 | width: 0, 95 | height: 0, 96 | depth: 0, 97 | }); 98 | const shadowBias = (shadows as StageShadows)?.bias ?? -0.0001; 99 | const normalBias = (shadows as StageShadows)?.normalBias ?? 0; 100 | const shadowSize = (shadows as StageShadows)?.size ?? 1024; 101 | const shadowOffset = (shadows as StageShadows)?.offset ?? 0; 102 | const contactShadow = 103 | shadows === "contact" || (shadows as StageShadows)?.type === "contact"; 104 | const accumulativeShadow = 105 | shadows === "accumulative" || 106 | (shadows as StageShadows)?.type === "accumulative"; 107 | const shadowSpread = { ...(typeof shadows === "object" ? shadows : {}) }; 108 | const environmentProps = !environment 109 | ? null 110 | : typeof environment === "string" 111 | ? { preset: environment } 112 | : environment; 113 | const onCentered = React.useCallback((props) => { 114 | const { width, height, depth, boundingSphere } = props; 115 | set({ radius: boundingSphere.radius, width, height, depth }); 116 | if (center?.onCentered) center.onCentered(props); 117 | }, []); 118 | return ( 119 | <> 120 | 121 | 134 | 142 | 149 | 150 |
155 | {children} 156 |
157 |
158 | 159 | 160 | {contactShadow && ( 161 | 167 | )} 168 | 169 | 170 | 171 | {accumulativeShadow && ( 172 | 180 | 196 | 197 | )} 198 | 199 | 200 | 201 | 202 | {environment && } 203 | 204 | 205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /examples/src/Examples/default/fs.glsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | void func1() { 4 | csm_DiffuseColor = vec4(1.0, 0.0, 1.0, 1.0); 5 | csm_Roughness == 0.5; 6 | csm_Iridescence = 1.0; 7 | csm_Emissive = vec3(1.0, 1.0, 1.0); 8 | csm_Transmission = 0.5; 9 | } 10 | 11 | void main() { 12 | // csm_FragColor = vec4(0.0, 1.0, 1.0, 1.0); 13 | func1(); 14 | } -------------------------------------------------------------------------------- /examples/src/Examples/default/psrd.glsl: -------------------------------------------------------------------------------- 1 | // psrdnoise (c) Stefan Gustavson and Ian McEwan, 2 | // ver. 2021-12-02, published under the MIT license: 3 | // https://github.com/stegu/psrdnoise/ 4 | 5 | vec4 permute(vec4 i) { 6 | vec4 im = mod(i, 289.0); 7 | return mod(((im*34.0)+10.0)*im, 289.0); 8 | } 9 | 10 | float psrdnoise(vec3 x, vec3 period, float alpha, out vec3 gradient) { 11 | const mat3 M = mat3(0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0); 12 | const mat3 Mi = mat3(-0.5, 0.5, 0.5, 0.5,-0.5, 0.5, 0.5, 0.5,-0.5); 13 | vec3 uvw = M * x; 14 | vec3 i0 = floor(uvw), f0 = fract(uvw); 15 | vec3 g_ = step(f0.xyx, f0.yzz), l_ = 1.0 - g_; 16 | vec3 g = vec3(l_.z, g_.xy), l = vec3(l_.xy, g_.z); 17 | vec3 o1 = min( g, l ), o2 = max( g, l ); 18 | vec3 i1 = i0 + o1, i2 = i0 + o2, i3 = i0 + vec3(1.0); 19 | vec3 v0 = Mi * i0, v1 = Mi * i1, v2 = Mi * i2, v3 = Mi * i3; 20 | vec3 x0 = x - v0, x1 = x - v1, x2 = x - v2, x3 = x - v3; 21 | if(any(greaterThan(period, vec3(0.0)))) { 22 | vec4 vx = vec4(v0.x, v1.x, v2.x, v3.x); 23 | vec4 vy = vec4(v0.y, v1.y, v2.y, v3.y); 24 | vec4 vz = vec4(v0.z, v1.z, v2.z, v3.z); 25 | if(period.x > 0.0) vx = mod(vx, period.x); 26 | if(period.y > 0.0) vy = mod(vy, period.y); 27 | if(period.z > 0.0) vz = mod(vz, period.z); 28 | i0 = floor(M * vec3(vx.x, vy.x, vz.x) + 0.5); 29 | i1 = floor(M * vec3(vx.y, vy.y, vz.y) + 0.5); 30 | i2 = floor(M * vec3(vx.z, vy.z, vz.z) + 0.5); 31 | i3 = floor(M * vec3(vx.w, vy.w, vz.w) + 0.5); 32 | } 33 | vec4 hash = permute( permute( permute( 34 | vec4(i0.z, i1.z, i2.z, i3.z )) 35 | + vec4(i0.y, i1.y, i2.y, i3.y )) 36 | + vec4(i0.x, i1.x, i2.x, i3.x )); 37 | vec4 theta = hash * 3.883222077; 38 | vec4 sz = hash * -0.006920415 + 0.996539792; 39 | vec4 psi = hash * 0.108705628; 40 | vec4 Ct = cos(theta), St = sin(theta); 41 | vec4 sz_prime = sqrt( 1.0 - sz*sz ); 42 | vec4 gx, gy, gz; 43 | if(alpha != 0.0) { 44 | vec4 px = Ct * sz_prime, py = St * sz_prime, pz = sz; 45 | vec4 Sp = sin(psi), Cp = cos(psi), Ctp = St*Sp - Ct*Cp; 46 | vec4 qx = mix( Ctp*St, Sp, sz), qy = mix(-Ctp*Ct, Cp, sz); 47 | vec4 qz = -(py*Cp + px*Sp); 48 | vec4 Sa = vec4(sin(alpha)), Ca = vec4(cos(alpha)); 49 | gx = Ca*px + Sa*qx; gy = Ca*py + Sa*qy; gz = Ca*pz + Sa*qz; 50 | } 51 | else { 52 | gx = Ct * sz_prime; gy = St * sz_prime; gz = sz; 53 | } 54 | vec3 g0 = vec3(gx.x, gy.x, gz.x), g1 = vec3(gx.y, gy.y, gz.y); 55 | vec3 g2 = vec3(gx.z, gy.z, gz.z), g3 = vec3(gx.w, gy.w, gz.w); 56 | vec4 w = 0.5-vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)); 57 | w = max(w, 0.0); vec4 w2 = w * w, w3 = w2 * w; 58 | vec4 gdotx = vec4(dot(g0,x0), dot(g1,x1), dot(g2,x2), dot(g3,x3)); 59 | float n = dot(w3, gdotx); 60 | vec4 dw = -6.0 * w2 * gdotx; 61 | vec3 dn0 = w3.x * g0 + dw.x * x0; 62 | vec3 dn1 = w3.y * g1 + dw.y * x1; 63 | vec3 dn2 = w3.z * g2 + dw.z * x2; 64 | vec3 dn3 = w3.w * g3 + dw.w * x3; 65 | gradient = 39.5 * (dn0 + dn1 + dn2 + dn3); 66 | return 39.5 * n; 67 | } -------------------------------------------------------------------------------- /examples/src/Examples/default/vs.glsl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/7c27f7d3f3c829826cdd257d1617434061631317/examples/src/Examples/default/vs.glsl -------------------------------------------------------------------------------- /examples/src/Examples/index.ts: -------------------------------------------------------------------------------- 1 | import { Scene as DefaultScene } from "./default/Scene"; 2 | import fs_default from "./default/fs.glsl?raw"; 3 | import vs_default from "./default/vs.glsl?raw"; 4 | 5 | import { Scene as VanillaScene } from "./Vanilla/Scene"; 6 | import fs_vanilla from "./Vanilla/fs.glsl?raw"; 7 | import vs_vanilla from "./Vanilla/vs.glsl?raw"; 8 | 9 | import { Scene as InstancesScene } from "./Instances/Scene"; 10 | import fs_instances from "./Instances/fs.glsl?raw"; 11 | import vs_instances from "./Instances/vs.glsl?raw"; 12 | 13 | import { Scene as WavesScene } from "./Waves/Scene"; 14 | import fs_waves from "./Waves/fs.glsl?raw"; 15 | import vs_waves from "./Waves/vs.glsl?raw"; 16 | 17 | import { Scene as CausticsScene } from "./Caustics/Scene"; 18 | import fs_caustics from "./Caustics/fs.glsl?raw"; 19 | import vs_caustics from "./Caustics/vs.glsl?raw"; 20 | 21 | import { Scene as PointsScene } from "./Points/Scene"; 22 | import fs_points from "./Points/fs.glsl?raw"; 23 | import vs_points from "./Points/vs.glsl?raw"; 24 | 25 | import { Scene as ShadowsScene } from "./Shadows/Scene"; 26 | import fs_shadows from "./Shadows/fs.glsl?raw"; 27 | import vs_shadows from "./Shadows/vs.glsl?raw"; 28 | 29 | export interface ExampleSceneProps { 30 | fs: string; 31 | vs: string; 32 | } 33 | 34 | export const SHADERS: { 35 | [key: string]: { 36 | fs: string; 37 | vs: string; 38 | slug: string; 39 | label: string; 40 | category: string; 41 | Component: React.ComponentType; 42 | }; 43 | } = { 44 | DEFAULT: { 45 | fs: fs_default, 46 | vs: vs_default, 47 | Component: DefaultScene, 48 | slug: "default", 49 | label: "Default", 50 | category: "Examples", 51 | }, 52 | VANILLA: { 53 | fs: fs_vanilla, 54 | vs: vs_vanilla, 55 | Component: VanillaScene, 56 | slug: "vanilla", 57 | label: "Vanilla", 58 | category: "Examples", 59 | }, 60 | INSTANCES: { 61 | fs: fs_instances, 62 | vs: vs_instances, 63 | slug: "instances", 64 | Component: InstancesScene, 65 | label: "Instances", 66 | category: "Examples", 67 | }, 68 | WAVES: { 69 | fs: fs_waves, 70 | vs: vs_waves, 71 | slug: "waves", 72 | Component: WavesScene, 73 | label: "Waves", 74 | category: "Tech Demos", 75 | }, 76 | CAUSTICS: { 77 | fs: fs_caustics, 78 | vs: vs_caustics, 79 | slug: "caustics", 80 | Component: CausticsScene, 81 | label: "Caustics", 82 | category: "Tech Demos", 83 | }, 84 | POINTS: { 85 | fs: fs_points, 86 | vs: vs_points, 87 | slug: "points", 88 | Component: PointsScene, 89 | label: "Points", 90 | category: "Tech Demos", 91 | }, 92 | SHADOWS: { 93 | fs: fs_shadows, 94 | vs: vs_shadows, 95 | slug: "shadows", 96 | Component: ShadowsScene, 97 | label: "Shadows", 98 | category: "Tech Demos", 99 | }, 100 | // METAL_BUNNY: { 101 | // fs: fs_metalBunny, 102 | // vs: vs_metalBunny, 103 | // slug: "metal-bunny", 104 | // Component: MetalBunnyScene, 105 | // label: "Metal Bunny", 106 | // category: "Tech Demos", 107 | // }, 108 | }; 109 | -------------------------------------------------------------------------------- /examples/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { ChakraProvider } from "@chakra-ui/react"; 2 | import { StrictMode } from "react"; 3 | import { createRoot } from "react-dom/client"; 4 | import App from "./App"; 5 | 6 | const container = document.getElementById("root"); 7 | const root = createRoot(container!); 8 | root.render( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /examples/src/pages/NotFound/index.tsx: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | export function NotFound() { 5 | const navigate = useNavigate(); 6 | 7 | useLayoutEffect(() => { 8 | navigate("/", { replace: true }); 9 | }, []); 10 | 11 | return null!; 12 | } 13 | -------------------------------------------------------------------------------- /examples/src/pages/Root/UI/Presets.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Stack, Text } from "@chakra-ui/react"; 2 | import { Link, useLocation } from "react-router-dom"; 3 | import { SHADERS } from "../../../Examples"; 4 | 5 | export function Presets() { 6 | const location = useLocation(); 7 | const slug = location.pathname.replace("/", ""); 8 | const currentShader = 9 | Object.values(SHADERS).find((shader) => shader.slug === slug) || 10 | SHADERS.WAVES; 11 | 12 | const groupedShaders = Object.values(SHADERS).reduce((acc, shader) => { 13 | if (!acc[shader.category]) { 14 | acc[shader.category] = []; 15 | } 16 | acc[shader.category].push(shader as typeof SHADERS.WAVES); 17 | return acc; 18 | }, {} as Record); 19 | 20 | return ( 21 | 22 | {Object.entries(groupedShaders).map(([category, shaders]) => ( 23 | 24 | {category} 25 | {shaders.map((shader) => ( 26 | 39 | ))} 40 | 41 | ))} 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /examples/src/pages/Root/UI/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | Flex, 5 | IconButton, 6 | Tab, 7 | TabList, 8 | TabPanel, 9 | TabPanels, 10 | Tabs, 11 | VStack, 12 | useBreakpointValue, 13 | useToast, 14 | } from "@chakra-ui/react"; 15 | import CodeEditor from "@uiw/react-textarea-code-editor"; 16 | import { useLayoutEffect, useRef, useState } from "react"; 17 | import { BsGear } from "react-icons/bs"; 18 | import { IoIosClose, IoIosCode } from "react-icons/io"; 19 | import { Presets } from "./Presets"; 20 | import "./styles.css"; 21 | 22 | interface UIProps { 23 | vs: string; 24 | fs: string; 25 | setShader: (shader: [string, string]) => void; 26 | } 27 | 28 | export function UI({ vs, fs, setShader }: UIProps) { 29 | const [open, setOpen] = useState(false); 30 | const [hasError, setHasError] = useState(false); 31 | 32 | const breakpoint = useBreakpointValue({ base: "sm", md: "md", lg: "lg" }); 33 | const toast = useToast(); 34 | 35 | useLayoutEffect(() => { 36 | // Override console.error to set error state 37 | const originalError = console.error; 38 | console.error = (...args: any[]) => { 39 | originalError(...args); 40 | 41 | const errorText = args.join(" "); 42 | if (errorText.includes("THREE.WebGLProgram: Shader Error")) { 43 | const shaderType = errorText.includes("FRAGMENT") 44 | ? "Fragment" 45 | : "Vertex"; 46 | 47 | toast({ 48 | title: `${shaderType} Shader compile error: Please check your code.`, 49 | }); 50 | } 51 | }; 52 | 53 | return () => { 54 | console.error = originalError; 55 | }; 56 | }, []); 57 | 58 | const fsEditorRef = useRef(null!); 59 | const vsEditorRef = useRef(null!); 60 | const onCompile = () => { 61 | setShader([vsEditorRef.current.value, fsEditorRef.current.value]); 62 | }; 63 | 64 | return ( 65 | 77 | 78 | } 81 | onClick={() => setOpen((s) => !s)} 82 | position="absolute" 83 | top="0" 84 | right="0" 85 | transform={`translate(100%, 0) scale(${open ? 0 : 1})`} 86 | zIndex={1000} 87 | borderTopRightRadius={0} 88 | borderTopLeftRadius={0} 89 | borderBottomLeftRadius={0} 90 | /> 91 | 92 | 93 | 94 | Preset 95 | Vertex 96 | Fragment 97 | 98 | } 102 | onClick={() => setOpen(false)} 103 | /> 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 121 | 131 | 132 | 133 | 134 | 135 | 142 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | ); 159 | } 160 | -------------------------------------------------------------------------------- /examples/src/pages/Root/UI/styles.css: -------------------------------------------------------------------------------- 1 | .code-editor { 2 | height: 90%; 3 | width: 100%; 4 | overflow: scroll !important; 5 | 6 | font-size: 1rem; 7 | 8 | background-color: rgba(0, 0, 0, 0.8); 9 | font-family: ui-monospace, SFMono-Regular, SF Mono, Consolas, Liberation Mono, 10 | Menlo, monospace; 11 | 12 | /* Dark gradient on right and bottom edge to indicate scroll */ 13 | } 14 | 15 | .code-editor .token.punctuation { 16 | color: white !important; 17 | } 18 | 19 | /* make thin scroll bar */ 20 | .code-editor::-webkit-scrollbar { 21 | width: 5px; 22 | } 23 | .code-editor::-webkit-scrollbar-thumb { 24 | background: #888; 25 | } 26 | .code-editor::-webkit-scrollbar-thumb:hover { 27 | background: #555; 28 | } 29 | .code-editor::-webkit-scrollbar-track { 30 | background: #555; 31 | } 32 | -------------------------------------------------------------------------------- /examples/src/pages/Root/index.tsx: -------------------------------------------------------------------------------- 1 | import { Canvas } from "@react-three/fiber"; 2 | import { useLayoutEffect, useState } from "react"; 3 | import { Outlet, useLocation, useOutletContext } from "react-router-dom"; 4 | import { SHADERS } from "../../Examples"; 5 | import { UI } from "./UI"; 6 | 7 | type ContextType = { 8 | vs: string; 9 | fs: string; 10 | setShader: (shader: [string, string]) => void; 11 | }; 12 | 13 | export function Root() { 14 | const location = useLocation(); 15 | const slug = location.pathname.replace("/", ""); 16 | const shader = 17 | Object.values(SHADERS).find((shader) => shader.slug === slug) || 18 | SHADERS.WAVES; 19 | 20 | const [[vs, fs], setShader] = useState([shader.vs, shader.fs]); 21 | 22 | useLayoutEffect(() => { 23 | setShader([shader.vs, shader.fs]); 24 | }, [slug]); 25 | 26 | return ( 27 |
28 | 29 | 30 | 31 | 32 | {/* */} 33 | 34 |
35 | ); 36 | } 37 | 38 | export function useShader() { 39 | return useOutletContext(); 40 | } 41 | -------------------------------------------------------------------------------- /examples/src/styles/index.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | 6 | #root, 7 | body, 8 | canvas { 9 | width: 100vw; 10 | height: 100vh; 11 | overflow: hidden; 12 | margin: 0; 13 | } 14 | 15 | main { 16 | width: 100%; 17 | height: 100%; 18 | 19 | display: flex; 20 | } 21 | 22 | .r3f-perf { 23 | z-index: 1000 !important; 24 | } 25 | -------------------------------------------------------------------------------- /examples/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | "allowJs": true, 17 | "paths": { 18 | "three-custom-shader-material": ["../package/src/React/index.tsx"], 19 | "three-custom-shader-material/vanilla": ["../package/src/index.ts"] 20 | } 21 | }, 22 | "include": ["src"], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /examples/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /examples/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import path from "path"; 3 | import { defineConfig } from "vite"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | base: "/THREE-CustomShaderMaterial/", 8 | plugins: [react()], 9 | resolve: { 10 | alias: { 11 | "three-custom-shader-material/vanilla": path.resolve( 12 | __dirname, 13 | "../package/src/index.ts" 14 | ), 15 | "three-custom-shader-material": path.resolve( 16 | __dirname, 17 | "../package/src/React/index.tsx" 18 | ), 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-custom-shader-material-monorepo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "homepage": "https://github.com/FarazzShaikh/THREE-CustomShaderMaterial", 7 | "repository": "git@github.com:FarazzShaikh/THREE-CustomShaderMaterial.git", 8 | "author": "Faraz Shaikh ", 9 | "license": "MIT", 10 | "workspaces": [ 11 | "package", 12 | "examples" 13 | ], 14 | "scripts": { 15 | "prebuild": "yarn --cwd package build", 16 | "build": "yarn --cwd examples build", 17 | "preview": "yarn --cwd examples preview", 18 | "dev": "yarn --cwd examples dev", 19 | "release": "yarn --cwd package release" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-custom-shader-material", 3 | "description": "Extend Three.js standard materials with your own shaders!", 4 | "private": false, 5 | "version": "6.3.7", 6 | "type": "module", 7 | "license": "MIT", 8 | "files": [ 9 | "./**/*" 10 | ], 11 | "homepage": "https://github.com/FarazzShaikh/THREE-CustomShaderMaterial", 12 | "main": "three-custom-shader-material.cjs.js", 13 | "module": "three-custom-shader-material.es.js", 14 | "types": "react.d.ts", 15 | "exports": { 16 | ".": { 17 | "import": "./three-custom-shader-material.es.js", 18 | "require": "./three-custom-shader-material.cjs.js", 19 | "types": "./react.d.ts" 20 | }, 21 | "./vanilla": { 22 | "import": "./vanilla/three-custom-shader-material.es.js", 23 | "require": "./vanilla/three-custom-shader-material.cjs.js", 24 | "types": "./vanilla/vanilla.d.ts" 25 | } 26 | }, 27 | "scripts": { 28 | "build": "tsc && vite build && cp ./package.json ./dist/package.json" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20.12.2", 32 | "@types/react": "^18.2.66", 33 | "@types/react-dom": "^18.2.22", 34 | "@types/three": "^0.172.0", 35 | "@typescript-eslint/eslint-plugin": "^7.2.0", 36 | "@typescript-eslint/parser": "^7.2.0", 37 | "@vitejs/plugin-react": "^4.2.1", 38 | "concurrently": "^8.2.2", 39 | "eslint": "^8.57.0", 40 | "eslint-plugin-react-hooks": "^4.6.0", 41 | "eslint-plugin-react-refresh": "^0.4.6", 42 | "typescript": "^5.2.2", 43 | "vite": "^5.2.0", 44 | "vite-plugin-dts": "^3.8.1" 45 | }, 46 | "peerDependencies": { 47 | "@react-three/fiber": ">=8.0", 48 | "react": ">=18.0", 49 | "three": ">=0.159" 50 | }, 51 | "peerDependenciesMeta": { 52 | "@react-three/fiber": { 53 | "optional": true 54 | }, 55 | "react": { 56 | "optional": true 57 | } 58 | }, 59 | "dependencies": {} 60 | } 61 | -------------------------------------------------------------------------------- /package/src/React/index.tsx: -------------------------------------------------------------------------------- 1 | import { AttachType } from "@react-three/fiber/dist/declarations/src/core/renderer"; 2 | import * as React from "react"; 3 | import { Material } from "three"; 4 | import CustomShaderMaterialImpl, { MaterialConstructor } from "../index"; 5 | import { CustomShaderMaterialProps } from "./types"; 6 | 7 | function useDidUpdateEffect( 8 | fn: (...opts: any[]) => any, 9 | inputs: React.DependencyList 10 | ) { 11 | const didMountRef = React.useRef(false); 12 | 13 | React.useEffect(() => { 14 | if (didMountRef.current) { 15 | return fn(); 16 | } 17 | didMountRef.current = true; 18 | }, inputs); 19 | } 20 | 21 | function CustomShaderMaterial( 22 | { 23 | baseMaterial, 24 | vertexShader, 25 | fragmentShader, 26 | uniforms, 27 | cacheKey, 28 | patchMap, 29 | attach, 30 | ...opts 31 | }: CustomShaderMaterialProps, 32 | ref: React.Ref> 33 | ) { 34 | const material = React.useMemo(() => { 35 | return new CustomShaderMaterialImpl({ 36 | baseMaterial, 37 | vertexShader, 38 | fragmentShader, 39 | uniforms, 40 | cacheKey, 41 | patchMap, 42 | ...opts, 43 | }); 44 | }, [baseMaterial]); 45 | 46 | useDidUpdateEffect(() => { 47 | material.dispose(); 48 | material.update({ 49 | vertexShader, 50 | fragmentShader, 51 | uniforms, 52 | patchMap, 53 | cacheKey, 54 | }); 55 | }, [vertexShader, fragmentShader, uniforms, patchMap, cacheKey]); 56 | 57 | React.useEffect(() => () => material.dispose(), [material]); 58 | 59 | return ( 60 | 66 | ); 67 | } 68 | 69 | // export default React.forwardRef(CustomShaderMaterial) as < 70 | // T extends MaterialConstructor 71 | // >( 72 | // props: CustomShaderMaterialProps & { ref?: React.Ref> } 73 | // ) => React.ReactElement; 74 | 75 | export default React.forwardRef(CustomShaderMaterial) as < 76 | T extends MaterialConstructor = typeof Material 77 | >( 78 | props: CustomShaderMaterialProps & { 79 | ref?: React.Ref>; 80 | attach?: AttachType; 81 | } 82 | ) => React.ReactElement; 83 | 84 | export { type CustomShaderMaterialProps } from "./types"; 85 | -------------------------------------------------------------------------------- /package/src/React/types.ts: -------------------------------------------------------------------------------- 1 | import { CustomShaderMaterialParameters, MaterialConstructor } from "../types"; 2 | 3 | export type CustomShaderMaterialProps = 4 | CustomShaderMaterialParameters & {}; 5 | -------------------------------------------------------------------------------- /package/src/defaults.ts: -------------------------------------------------------------------------------- 1 | export const defaultCsmDefinitions = /* glsl */ ` 2 | 3 | #ifdef IS_VERTEX 4 | vec3 csm_Position; 5 | vec4 csm_PositionRaw; 6 | vec3 csm_Normal; 7 | 8 | // csm_PointSize 9 | #ifdef IS_POINTSMATERIAL 10 | float csm_PointSize; 11 | #endif 12 | #else 13 | vec4 csm_DiffuseColor; 14 | vec4 csm_FragColor; 15 | float csm_UnlitFac; 16 | 17 | // csm_Emissive, csm_Roughness, csm_Metalness 18 | #if defined IS_MESHSTANDARDMATERIAL || defined IS_MESHPHYSICALMATERIAL 19 | vec3 csm_Emissive; 20 | float csm_Roughness; 21 | float csm_Metalness; 22 | float csm_Iridescence; 23 | 24 | #if defined IS_MESHPHYSICALMATERIAL 25 | float csm_Clearcoat; 26 | float csm_ClearcoatRoughness; 27 | vec3 csm_ClearcoatNormal; 28 | float csm_Transmission; 29 | float csm_Thickness; 30 | #endif 31 | #endif 32 | 33 | // csm_AO 34 | #if defined IS_MESHSTANDARDMATERIAL || defined IS_MESHPHYSICALMATERIAL || defined IS_MESHBASICMATERIAL || defined IS_MESHLAMBERTMATERIAL || defined IS_MESHPHONGMATERIAL || defined IS_MESHTOONMATERIAL 35 | float csm_AO; 36 | #endif 37 | 38 | // csm_Bump 39 | #if defined IS_MESHLAMBERTMATERIAL || defined IS_MESHMATCAPMATERIAL || defined IS_MESHNORMALMATERIAL || defined IS_MESHPHONGMATERIAL || defined IS_MESHPHYSICALMATERIAL || defined IS_MESHSTANDARDMATERIAL || defined IS_MESHTOONMATERIAL || defined IS_SHADOWMATERIAL 40 | vec3 csm_Bump; 41 | vec3 csm_FragNormal; 42 | #endif 43 | 44 | float csm_DepthAlpha; 45 | #endif 46 | `; 47 | 48 | export const defaultCsmMainDefinitions = /* glsl */ ` 49 | 50 | #ifdef IS_VERTEX 51 | // csm_Position & csm_PositionRaw 52 | #ifdef IS_UNKNOWN 53 | csm_Position = vec3(0.0); 54 | csm_PositionRaw = vec4(0.0); 55 | csm_Normal = vec3(0.0); 56 | #else 57 | csm_Position = position; 58 | csm_PositionRaw = projectionMatrix * modelViewMatrix * vec4(position, 1.); 59 | csm_Normal = normal; 60 | #endif 61 | 62 | // csm_PointSize 63 | #ifdef IS_POINTSMATERIAL 64 | csm_PointSize = size; 65 | #endif 66 | #else 67 | csm_UnlitFac = 0.0; 68 | 69 | // csm_DiffuseColor & csm_FragColor 70 | #if defined IS_UNKNOWN || defined IS_SHADERMATERIAL || defined IS_MESHDEPTHMATERIAL || defined IS_MESHDISTANCEMATERIAL || defined IS_MESHNORMALMATERIAL || defined IS_SHADOWMATERIAL 71 | csm_DiffuseColor = vec4(1.0, 0.0, 1.0, 1.0); 72 | csm_FragColor = vec4(1.0, 0.0, 1.0, 1.0); 73 | #else 74 | #ifdef USE_MAP 75 | vec4 _csm_sampledDiffuseColor = texture2D(map, vMapUv); 76 | 77 | #ifdef DECODE_VIDEO_TEXTURE 78 | // inline sRGB decode (TODO: Remove this code when https://crbug.com/1256340 is solved) 79 | _csm_sampledDiffuseColor = vec4(mix(pow(_csm_sampledDiffuseColor.rgb * 0.9478672986 + vec3(0.0521327014), vec3(2.4)), _csm_sampledDiffuseColor.rgb * 0.0773993808, vec3(lessThanEqual(_csm_sampledDiffuseColor.rgb, vec3(0.04045)))), _csm_sampledDiffuseColor.w); 80 | #endif 81 | 82 | csm_DiffuseColor = vec4(diffuse, opacity) * _csm_sampledDiffuseColor; 83 | csm_FragColor = vec4(diffuse, opacity) * _csm_sampledDiffuseColor; 84 | #else 85 | csm_DiffuseColor = vec4(diffuse, opacity); 86 | csm_FragColor = vec4(diffuse, opacity); 87 | #endif 88 | #endif 89 | 90 | // csm_Emissive, csm_Roughness, csm_Metalness 91 | #if defined IS_MESHSTANDARDMATERIAL || defined IS_MESHPHYSICALMATERIAL 92 | csm_Emissive = emissive; 93 | csm_Roughness = roughness; 94 | csm_Metalness = metalness; 95 | 96 | #ifdef USE_IRIDESCENCE 97 | csm_Iridescence = iridescence; 98 | #else 99 | csm_Iridescence = 0.0; 100 | #endif 101 | 102 | #if defined IS_MESHPHYSICALMATERIAL 103 | #ifdef USE_CLEARCOAT 104 | csm_Clearcoat = clearcoat; 105 | csm_ClearcoatRoughness = clearcoatRoughness; 106 | #else 107 | csm_Clearcoat = 0.0; 108 | csm_ClearcoatRoughness = 0.0; 109 | #endif 110 | 111 | #ifdef USE_TRANSMISSION 112 | csm_Transmission = transmission; 113 | csm_Thickness = thickness; 114 | #else 115 | csm_Transmission = 0.0; 116 | csm_Thickness = 0.0; 117 | #endif 118 | #endif 119 | #endif 120 | 121 | // csm_AO 122 | #if defined IS_MESHSTANDARDMATERIAL || defined IS_MESHPHYSICALMATERIAL || defined IS_MESHBASICMATERIAL || defined IS_MESHLAMBERTMATERIAL || defined IS_MESHPHONGMATERIAL || defined IS_MESHTOONMATERIAL 123 | csm_AO = 0.0; 124 | #endif 125 | 126 | // csm_Bump 127 | #if defined IS_MESHLAMBERTMATERIAL || defined IS_MESHMATCAPMATERIAL || defined IS_MESHNORMALMATERIAL || defined IS_MESHPHONGMATERIAL || defined IS_MESHPHYSICALMATERIAL || defined IS_MESHSTANDARDMATERIAL || defined IS_MESHTOONMATERIAL || defined IS_SHADOWMATERIAL 128 | csm_Bump = vec3(0.0); 129 | #ifdef FLAT_SHADED 130 | vec3 fdx = dFdx( vViewPosition ); 131 | vec3 fdy = dFdy( vViewPosition ); 132 | csm_FragNormal = normalize( cross( fdx, fdy ) ); 133 | #else 134 | csm_FragNormal = normalize(vNormal); 135 | #ifdef DOUBLE_SIDED 136 | csm_FragNormal *= gl_FrontFacing ? 1.0 : - 1.0; 137 | #endif 138 | #endif 139 | #endif 140 | 141 | csm_DepthAlpha = 1.0; 142 | #endif 143 | `; 144 | 145 | export const defaultVertDefinitions = /* glsl */ ` 146 | varying mat4 csm_internal_vModelViewMatrix; 147 | `; 148 | 149 | export const defaultVertMain = /* glsl */ ` 150 | csm_internal_vModelViewMatrix = modelViewMatrix; 151 | `; 152 | 153 | export const defaultFragDefinitions = /* glsl */ ` 154 | varying mat4 csm_internal_vModelViewMatrix; 155 | `; 156 | 157 | export const defaultFragMain = /* glsl */ ` 158 | 159 | `; 160 | -------------------------------------------------------------------------------- /package/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { 3 | defaultCsmDefinitions, 4 | defaultCsmMainDefinitions, 5 | defaultFragDefinitions, 6 | defaultFragMain, 7 | defaultVertDefinitions, 8 | defaultVertMain, 9 | } from "./defaults"; 10 | import { availabilityMap, defaultPatchMap, keywordMap } from "./maps"; 11 | import { requiredPropsMap } from "./maps/requiredPropsMap"; 12 | import hash from "./sdbm"; 13 | import * as TYPES from "./types"; 14 | import { isConstructor, stripComments } from "./utils"; 15 | 16 | export default class CustomShaderMaterial< 17 | T extends TYPES.MaterialConstructor = typeof THREE.Material 18 | > extends THREE.Material { 19 | uniforms: TYPES.Uniform = {}; 20 | vertexShader: string = ""; 21 | fragmentShader: string = ""; 22 | 23 | constructor({ 24 | baseMaterial, 25 | vertexShader, 26 | fragmentShader, 27 | uniforms, 28 | patchMap, 29 | cacheKey, 30 | ...opts 31 | }: TYPES.CustomShaderMaterialParameters) { 32 | if (!baseMaterial) { 33 | throw new Error("CustomShaderMaterial: baseMaterial is required."); 34 | } 35 | 36 | let base: THREE.Material; 37 | if (isConstructor(baseMaterial)) { 38 | // If base material is a constructor, instantiate it 39 | // if opts is empty, replace it with undefined 40 | const isEmptyOpts = Object.keys(opts).length === 0; 41 | base = new baseMaterial(isEmptyOpts ? undefined : opts); 42 | } else { 43 | // Else, use the already created instance as the base material 44 | // and copy options onto it 45 | base = baseMaterial; 46 | Object.assign(base, opts); 47 | } 48 | 49 | // Blacklist some materials that are not supported 50 | const blackList = ["ShaderMaterial", "RawShaderMaterial"]; 51 | if (blackList.includes(base.type)) { 52 | throw new Error( 53 | `CustomShaderMaterial does not support ${base.type} as a base material.` 54 | ); 55 | } 56 | 57 | super(); 58 | 59 | // Return a proxy to the base material with CSM types and methods 60 | const extendedBase = base as typeof base & TYPES.CSMProxy; 61 | extendedBase.name = `CustomShaderMaterial<${base.name || base.type}>`; 62 | extendedBase.update = this.update; 63 | extendedBase.__csm = { 64 | prevOnBeforeCompile: base.onBeforeCompile, 65 | baseMaterial: base, 66 | vertexShader, 67 | fragmentShader, 68 | uniforms, 69 | patchMap, 70 | cacheKey, 71 | }; 72 | 73 | const prevUniforms = extendedBase.uniforms || {}; 74 | const newUniforms = uniforms || {}; 75 | const mergedUniforms = { ...prevUniforms, ...newUniforms }; 76 | 77 | extendedBase.uniforms = this.uniforms = mergedUniforms; 78 | extendedBase.vertexShader = this.vertexShader = vertexShader || ""; 79 | extendedBase.fragmentShader = this.fragmentShader = fragmentShader || ""; 80 | 81 | // Initialize custom shaders 82 | extendedBase.update({ 83 | fragmentShader: extendedBase.fragmentShader, 84 | vertexShader: extendedBase.vertexShader, 85 | uniforms: extendedBase.uniforms, 86 | patchMap, 87 | cacheKey, 88 | }); 89 | 90 | // Merge "this" with the extended base 91 | Object.assign(this, extendedBase); 92 | 93 | // Copy getters and setters from the base material 94 | const gettersAndSetters = Object.getOwnPropertyDescriptors( 95 | Object.getPrototypeOf(extendedBase) 96 | ); 97 | 98 | for (const key in gettersAndSetters) { 99 | const descriptor = gettersAndSetters[key]; 100 | if (descriptor.get || descriptor.set) { 101 | Object.defineProperty(this, key, descriptor); 102 | } 103 | } 104 | 105 | // Override type setter because of this BS: https://github.com/mrdoob/three.js/blob/841ca14e89f3ec925e071a321958e49a883343c0/src/materials/Material.js#L22 106 | Object.defineProperty(this, "type", { 107 | get() { 108 | return base.type; 109 | }, 110 | set(value) { 111 | base.type = value; 112 | }, 113 | }); 114 | 115 | return this; 116 | } 117 | 118 | update({ 119 | fragmentShader: _fs, 120 | vertexShader: _vs, 121 | uniforms, 122 | cacheKey, 123 | patchMap, 124 | }: Omit, "baseMaterial">) { 125 | // Strip comments from shaders, makes it so that commented keywords are not detected 126 | const vertexShader = stripComments(_vs || ""); 127 | const fragmentShader = stripComments(_fs || ""); 128 | 129 | // Get typed `this` for the proxy 130 | const self = this as typeof this & TYPES.CSMProxy; 131 | 132 | // Replace the shaders if they are provided 133 | if (uniforms) self.uniforms = uniforms; 134 | if (_vs) self.vertexShader = _vs; 135 | if (_fs) self.fragmentShader = _fs; 136 | 137 | // Some keywords require certain properties to be set for their chunks to be included via #ifdef 138 | // so we must check if the shaders contain these keywords and set the properties accordingly 139 | Object.entries(requiredPropsMap).forEach(([prop, matchKeywords]) => { 140 | for (const keyword in matchKeywords) { 141 | const matchKeyword = matchKeywords[keyword]; 142 | if ( 143 | (fragmentShader && fragmentShader.includes(matchKeyword)) || 144 | (vertexShader && vertexShader.includes(matchKeyword)) 145 | ) { 146 | // @ts-ignore 147 | if (!self[prop]) { 148 | // @ts-ignore 149 | self[prop] = 1; 150 | } 151 | } 152 | } 153 | }); 154 | 155 | // Check it the previous onBeforeCompile exists 156 | const prevOnBeforeCompile = self.__csm.prevOnBeforeCompile; 157 | 158 | // Helper function to extend the shader 159 | const extendShader = ( 160 | prevShader: string, 161 | newShader?: string, 162 | isFrag?: boolean 163 | ) => { 164 | let mainBody: string | undefined; 165 | let beforeMain: string = ""; 166 | 167 | // Prepare the main body and beforeMain 168 | if (newShader) { 169 | // Simpler approach to extract main function body 170 | const mainStartIndex = newShader.search(/void\s+main\s*\(\s*\)\s*{/); 171 | if (mainStartIndex !== -1) { 172 | // Get everything before main function 173 | beforeMain = newShader.slice(0, mainStartIndex); 174 | 175 | // Find the matching closing brace using brace counting 176 | let braceCount = 0; 177 | let mainEndIndex = -1; 178 | 179 | for (let i = mainStartIndex; i < newShader.length; i++) { 180 | if (newShader[i] === "{") braceCount++; 181 | if (newShader[i] === "}") { 182 | braceCount--; 183 | if (braceCount === 0) { 184 | mainEndIndex = i; 185 | break; 186 | } 187 | } 188 | } 189 | 190 | if (mainEndIndex !== -1) { 191 | // Extract main body without the outer braces 192 | const fullMain = newShader.slice(mainStartIndex, mainEndIndex + 1); 193 | mainBody = fullMain.slice(fullMain.indexOf("{") + 1, -1); 194 | } 195 | } else { 196 | beforeMain = newShader; 197 | } 198 | } 199 | 200 | // Set csm_UnlitFac if csm_FragColor is used to preserve 201 | // legacy behavior. 202 | if (isFrag) { 203 | const hasFragColor = newShader 204 | ? newShader.includes(keywordMap.fragColor) 205 | : false; 206 | if (hasFragColor && mainBody) { 207 | mainBody = "csm_UnlitFac = 1.0;\n" + mainBody; 208 | } 209 | } 210 | 211 | const defaultsAlreadyIncluded = prevShader.includes("//~CSM_DEFAULTS"); 212 | 213 | // Inject 214 | if (defaultsAlreadyIncluded) { 215 | prevShader = prevShader.replace( 216 | "void main() {", 217 | ` 218 | // THREE-CustomShaderMaterial by Faraz Shaikh: https://github.com/FarazzShaikh/THREE-CustomShaderMaterial 219 | 220 | ${beforeMain} 221 | 222 | void main() { 223 | ` 224 | ); 225 | 226 | const lastMainEndIndex = prevShader.lastIndexOf("//~CSM_MAIN_END"); 227 | 228 | if (lastMainEndIndex !== -1) { 229 | const toAppend = ` 230 | ${mainBody ? `${mainBody}` : ""} 231 | //~CSM_MAIN_END 232 | `; 233 | prevShader = 234 | prevShader.slice(0, lastMainEndIndex) + 235 | toAppend + 236 | prevShader.slice(lastMainEndIndex); 237 | } 238 | } else { 239 | const regex = /void\s*main\s*\(\s*\)\s*{/gm; 240 | 241 | prevShader = prevShader.replace( 242 | regex, 243 | ` 244 | // THREE-CustomShaderMaterial by Faraz Shaikh: https://github.com/FarazzShaikh/THREE-CustomShaderMaterial 245 | 246 | //~CSM_DEFAULTS 247 | ${isFrag ? defaultFragDefinitions : defaultVertDefinitions} 248 | ${defaultCsmDefinitions} 249 | 250 | ${beforeMain} 251 | 252 | void main() { 253 | { 254 | ${defaultCsmMainDefinitions} 255 | } 256 | ${isFrag ? defaultFragMain : defaultVertMain} 257 | 258 | ${mainBody ? `${mainBody}` : ""} 259 | //~CSM_MAIN_END 260 | ` 261 | ); 262 | } 263 | 264 | return prevShader; 265 | }; 266 | 267 | // Override onBeforeCompile 268 | self.onBeforeCompile = ( 269 | shader: THREE.WebGLProgramParametersWithUniforms, 270 | renderer: THREE.WebGLRenderer 271 | ) => { 272 | // Apply previous onBeforeCompile 273 | prevOnBeforeCompile?.(shader, renderer); 274 | 275 | const userPatchMap = patchMap || {}; 276 | // const mergedPatchMap = { ...defaultPatchMap, ...userPatchMap }; 277 | 278 | // Append some defines 279 | const type = self.type; 280 | const typeDefine = type 281 | ? `#define IS_${type.toUpperCase()};\n` 282 | : `#define IS_UNKNOWN;\n`; 283 | shader.vertexShader = 284 | typeDefine + "#define IS_VERTEX\n" + shader.vertexShader; 285 | shader.fragmentShader = 286 | typeDefine + "#define IS_FRAGMENT\n" + shader.fragmentShader; 287 | 288 | // Check if the keyword is available in the current material type 289 | const runPatchMap = (_patchMap: TYPES.CSMPatchMap) => { 290 | for (const keyword in _patchMap) { 291 | const doesIncludeInVert = 292 | keyword === "*" || (vertexShader && vertexShader.includes(keyword)); 293 | const doesIncludeInFrag = 294 | keyword === "*" || 295 | (fragmentShader && fragmentShader.includes(keyword)); 296 | 297 | if (doesIncludeInFrag || doesIncludeInVert) { 298 | const availableIn = availabilityMap[keyword]; 299 | 300 | if ( 301 | availableIn && 302 | availableIn !== "*" && 303 | (Array.isArray(availableIn) 304 | ? !availableIn.includes(type) 305 | : availableIn !== type) 306 | ) { 307 | console.error( 308 | `CustomShaderMaterial: ${keyword} is not available in ${type}. Shader cannot compile.` 309 | ); 310 | return; 311 | } 312 | 313 | const patchMap = _patchMap[keyword]; 314 | 315 | for (const toReplace in patchMap) { 316 | const replaceWith = patchMap[toReplace]; 317 | 318 | if (typeof replaceWith === "object") { 319 | const type = replaceWith.type; 320 | const value = replaceWith.value; 321 | 322 | if (type === "fs") { 323 | shader.fragmentShader = shader.fragmentShader.replace( 324 | toReplace, 325 | value 326 | ); 327 | } else if (type === "vs") { 328 | shader.vertexShader = shader.vertexShader.replace( 329 | toReplace, 330 | value 331 | ); 332 | } 333 | } else if (replaceWith) { 334 | shader.vertexShader = shader.vertexShader.replace( 335 | toReplace, 336 | replaceWith 337 | ); 338 | shader.fragmentShader = shader.fragmentShader.replace( 339 | toReplace, 340 | replaceWith 341 | ); 342 | } 343 | } 344 | } 345 | } 346 | }; 347 | 348 | runPatchMap(defaultPatchMap); 349 | runPatchMap(userPatchMap); 350 | 351 | // Extend the shaders 352 | shader.vertexShader = extendShader( 353 | shader.vertexShader, 354 | vertexShader, 355 | false 356 | ); 357 | 358 | shader.fragmentShader = extendShader( 359 | shader.fragmentShader, 360 | fragmentShader, 361 | true 362 | ); 363 | 364 | if (uniforms) { 365 | shader.uniforms = { ...shader.uniforms, ...self.uniforms }; 366 | } 367 | 368 | self.uniforms = shader.uniforms; 369 | }; 370 | 371 | const prevCacheKey = self.customProgramCacheKey; 372 | 373 | self.customProgramCacheKey = () => { 374 | return ( 375 | (cacheKey?.() || hash((vertexShader || "") + (fragmentShader || ""))) + 376 | prevCacheKey?.call(self) 377 | ); 378 | }; 379 | 380 | self.needsUpdate = true; 381 | } 382 | 383 | clone() { 384 | // Get typed `this` for the proxy 385 | const self = this as typeof this & TYPES.CSMProxy; 386 | 387 | // @ts-ignore 388 | const newObj = new self.constructor({ 389 | baseMaterial: self.__csm.baseMaterial.clone(), 390 | vertexShader: self.__csm.vertexShader, 391 | fragmentShader: self.__csm.fragmentShader, 392 | uniforms: self.__csm.uniforms, 393 | patchMap: self.__csm.patchMap, 394 | cacheKey: self.__csm.cacheKey, 395 | }); 396 | 397 | return newObj; 398 | } 399 | } 400 | 401 | export { 402 | type CSMPatchMap, 403 | type CSMProxy, 404 | type CustomShaderMaterialParameters, 405 | type MaterialConstructor, 406 | } from "./types"; 407 | -------------------------------------------------------------------------------- /package/src/maps/availabilityMap.ts: -------------------------------------------------------------------------------- 1 | import { keywordMap } from "./keywordMap"; 2 | 3 | // Map of CSM keywords to the materials they are available in 4 | // Some keywords are only available in certain materials 5 | export const availabilityMap = { 6 | [`${keywordMap.position}`]: "*", 7 | [`${keywordMap.positionRaw}`]: "*", 8 | [`${keywordMap.normal}`]: "*", 9 | [`${keywordMap.depthAlpha}`]: "*", 10 | [`${keywordMap.pointSize}`]: ["PointsMaterial"], 11 | 12 | [`${keywordMap.diffuse}`]: "*", 13 | [`${keywordMap.fragColor}`]: "*", 14 | [`${keywordMap.fragNormal}`]: "*", 15 | [`${keywordMap.unlitFac}`]: "*", 16 | [`${keywordMap.emissive}`]: ["MeshStandardMaterial", "MeshPhysicalMaterial"], 17 | [`${keywordMap.roughness}`]: ["MeshStandardMaterial", "MeshPhysicalMaterial"], 18 | [`${keywordMap.metalness}`]: ["MeshStandardMaterial", "MeshPhysicalMaterial"], 19 | [`${keywordMap.iridescence}`]: [ 20 | "MeshStandardMaterial", 21 | "MeshPhysicalMaterial", 22 | ], 23 | [`${keywordMap.ao}`]: [ 24 | "MeshStandardMaterial", 25 | "MeshPhysicalMaterial", 26 | "MeshBasicMaterial", 27 | "MeshLambertMaterial", 28 | "MeshPhongMaterial", 29 | "MeshToonMaterial", 30 | ], 31 | [`${keywordMap.bump}`]: [ 32 | "MeshLambertMaterial", 33 | "MeshMatcapMaterial", 34 | "MeshNormalMaterial", 35 | "MeshPhongMaterial", 36 | "MeshPhysicalMaterial", 37 | "MeshStandardMaterial", 38 | "MeshToonMaterial", 39 | "ShadowMaterial", 40 | ], 41 | [`${keywordMap.clearcoat}`]: ["MeshPhysicalMaterial"], 42 | [`${keywordMap.clearcoatRoughness}`]: ["MeshPhysicalMaterial"], 43 | [`${keywordMap.clearcoatNormal}`]: ["MeshPhysicalMaterial"], 44 | [`${keywordMap.transmission}`]: ["MeshPhysicalMaterial"], 45 | [`${keywordMap.thickness}`]: ["MeshPhysicalMaterial"], 46 | }; 47 | -------------------------------------------------------------------------------- /package/src/maps/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./availabilityMap"; 2 | export * from "./keywordMap"; 3 | export * from "./patchMap"; 4 | -------------------------------------------------------------------------------- /package/src/maps/keywordMap.ts: -------------------------------------------------------------------------------- 1 | export const keywordMap = { 2 | // PBR (frag) 3 | diffuse: "csm_DiffuseColor", // Color + alpha 4 | roughness: "csm_Roughness", // Roughness 5 | metalness: "csm_Metalness", // Metalness 6 | emissive: "csm_Emissive", // Emissive 7 | ao: "csm_AO", // AO 8 | bump: "csm_Bump", // Bump 9 | fragNormal: "csm_FragNormal", // Fragment Normal 10 | clearcoat: "csm_Clearcoat", // Clearcoat factor 11 | clearcoatRoughness: "csm_ClearcoatRoughness", // Clearcoat roughness 12 | clearcoatNormal: "csm_ClearcoatNormal", // Clearcoat normals 13 | transmission: "csm_Transmission", // Transmission 14 | thickness: "csm_Thickness", // Thickness 15 | iridescence: "csm_Iridescence", // Iridescence 16 | 17 | // Extras 18 | pointSize: "csm_PointSize", // gl_PointSize (Frag) 19 | fragColor: "csm_FragColor", // gl_FragColor (Frag) 20 | depthAlpha: "csm_DepthAlpha", // Depth (MeshDepthMaterial) 21 | unlitFac: "csm_UnlitFac", // Unlit factor (mix between csm_FragColor and csm_DiffuseColor) 22 | 23 | // Vert 24 | position: "csm_Position", // gl_Position 25 | positionRaw: "csm_PositionRaw", // gl_Position (without projection) 26 | normal: "csm_Normal", // Vertex Normal 27 | }; 28 | -------------------------------------------------------------------------------- /package/src/maps/patchMap.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | import { CSMPatchMap } from "../types"; 3 | import { keywordMap } from "./keywordMap"; 4 | 5 | // Map of CSM keywords to their substitutions 6 | export const defaultPatchMap: CSMPatchMap = { 7 | // VERT 8 | "*": { 9 | "#include ": 10 | THREE.ShaderChunk.lights_physical_fragment, 11 | "#include ": THREE.ShaderChunk.transmission_fragment, 12 | }, 13 | [`${keywordMap.normal}`]: { 14 | "#include ": ` 15 | vec3 objectNormal = ${keywordMap.normal}; 16 | #ifdef USE_TANGENT 17 | vec3 objectTangent = vec3( tangent.xyz ); 18 | #endif 19 | `, 20 | }, 21 | [`${keywordMap.position}`]: { 22 | "#include ": ` 23 | vec3 transformed = ${keywordMap.position}; 24 | `, 25 | }, 26 | [`${keywordMap.positionRaw}`]: { 27 | "#include ": ` 28 | #include 29 | gl_Position = ${keywordMap.positionRaw}; 30 | `, 31 | }, 32 | [`${keywordMap.pointSize}`]: { 33 | "gl_PointSize = size;": ` 34 | gl_PointSize = ${keywordMap.pointSize}; 35 | `, 36 | }, 37 | 38 | // FRAG 39 | 40 | [`${keywordMap.diffuse}`]: { 41 | "#include ": ` 42 | #include 43 | diffuseColor = ${keywordMap.diffuse}; 44 | `, 45 | }, 46 | [`${keywordMap.fragColor}`]: { 47 | "#include ": ` 48 | #include 49 | gl_FragColor = mix(gl_FragColor, ${keywordMap.fragColor}, ${keywordMap.unlitFac}); 50 | `, 51 | }, 52 | [`${keywordMap.emissive}`]: { 53 | "vec3 totalEmissiveRadiance = emissive;": ` 54 | vec3 totalEmissiveRadiance = ${keywordMap.emissive}; 55 | `, 56 | }, 57 | [`${keywordMap.roughness}`]: { 58 | "#include ": ` 59 | #include 60 | roughnessFactor = ${keywordMap.roughness}; 61 | `, 62 | }, 63 | [`${keywordMap.metalness}`]: { 64 | "#include ": ` 65 | #include 66 | metalnessFactor = ${keywordMap.metalness}; 67 | `, 68 | }, 69 | [`${keywordMap.ao}`]: { 70 | "#include ": ` 71 | #include 72 | reflectedLight.indirectDiffuse *= 1. - ${keywordMap.ao}; 73 | `, 74 | }, 75 | [`${keywordMap.bump}`]: { 76 | "#include ": ` 77 | #include 78 | 79 | vec3 csm_internal_orthogonal = ${keywordMap.bump} - (dot(${keywordMap.bump}, normal) * normal); 80 | vec3 csm_internal_projectedbump = mat3(csm_internal_vModelViewMatrix) * csm_internal_orthogonal; 81 | normal = normalize(normal - csm_internal_projectedbump); 82 | `, 83 | }, 84 | [`${keywordMap.fragNormal}`]: { 85 | "#include ": ` 86 | #include 87 | normal = ${keywordMap.fragNormal}; 88 | `, 89 | }, 90 | [`${keywordMap.depthAlpha}`]: { 91 | "gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );": ` 92 | gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity * 1.0 - ${keywordMap.depthAlpha} ); 93 | `, 94 | "gl_FragColor = packDepthToRGBA( fragCoordZ );": ` 95 | if(${keywordMap.depthAlpha} < 1.0) discard; 96 | gl_FragColor = packDepthToRGBA( dist ); 97 | `, 98 | "gl_FragColor = packDepthToRGBA( dist );": ` 99 | if(${keywordMap.depthAlpha} < 1.0) discard; 100 | gl_FragColor = packDepthToRGBA( dist ); 101 | `, 102 | }, 103 | [`${keywordMap.clearcoat}`]: { 104 | "material.clearcoat = clearcoat;": `material.clearcoat = ${keywordMap.clearcoat};`, 105 | }, 106 | [`${keywordMap.clearcoatRoughness}`]: { 107 | "material.clearcoatRoughness = clearcoatRoughness;": `material.clearcoatRoughness = ${keywordMap.clearcoatRoughness};`, 108 | }, 109 | [`${keywordMap.clearcoatNormal}`]: { 110 | "#include ": ` 111 | vec3 csm_coat_internal_orthogonal = csm_ClearcoatNormal - (dot(csm_ClearcoatNormal, nonPerturbedNormal) * nonPerturbedNormal); 112 | vec3 csm_coat_internal_projectedbump = mat3(csm_internal_vModelViewMatrix) * csm_coat_internal_orthogonal; 113 | vec3 clearcoatNormal = normalize(nonPerturbedNormal - csm_coat_internal_projectedbump); 114 | `, 115 | }, 116 | [`${keywordMap.transmission}`]: { 117 | "material.transmission = transmission;": ` 118 | material.transmission = ${keywordMap.transmission}; 119 | `, 120 | }, 121 | [`${keywordMap.thickness}`]: { 122 | "material.thickness = thickness;": ` 123 | material.thickness = ${keywordMap.thickness}; 124 | `, 125 | }, 126 | [`${keywordMap.iridescence}`]: { 127 | "material.iridescence = iridescence;": ` 128 | material.iridescence = ${keywordMap.iridescence}; 129 | `, 130 | }, 131 | }; 132 | -------------------------------------------------------------------------------- /package/src/maps/requiredPropsMap.ts: -------------------------------------------------------------------------------- 1 | import { keywordMap } from "./keywordMap"; 2 | 3 | /** 4 | * Map of props to their keywords 5 | * this is because Three only injects some defines if certain properties are set in the material options. 6 | * 7 | * For example, "clearcoat" must be set for 3js to include the #USE_CLEARCOAT define in the shader. 8 | * and thus for our custom clearcoar variant to work 9 | */ 10 | export const requiredPropsMap = { 11 | clearcoat: [ 12 | keywordMap.clearcoat, 13 | keywordMap.clearcoatNormal, 14 | keywordMap.clearcoatRoughness, 15 | ], 16 | transmission: [keywordMap.transmission], 17 | iridescence: [keywordMap.iridescence], 18 | }; 19 | -------------------------------------------------------------------------------- /package/src/sdbm.js: -------------------------------------------------------------------------------- 1 | //https://github.com/sindresorhus/sdbm 2 | 3 | export default function sdbm(string) { 4 | let hash = 0; 5 | 6 | for (let i = 0; i < string.length; i++) { 7 | hash = string.charCodeAt(i) + (hash << 6) + (hash << 16) - hash; 8 | } 9 | 10 | // Convert it to an unsigned 32-bit integer. 11 | const h = hash >>> 0; 12 | const str = String(h); 13 | return str; 14 | } 15 | -------------------------------------------------------------------------------- /package/src/types.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | export interface IUniform { 4 | value: TValue; 5 | } 6 | 7 | export type Uniform = { 8 | [key: string]: IUniform; 9 | }; 10 | 11 | export type MaterialConstructor = new (...opts: any[]) => THREE.Material; 12 | 13 | type MaterialParams = 14 | ConstructorParameters[0]; 15 | 16 | export type CSMPatchMap = { 17 | [keyword: string]: { 18 | [toReplace: string]: string | { value: string; type: "fs" | "vs" }; 19 | }; 20 | }; 21 | 22 | export type CustomShaderMaterialBaseParameters = 23 | { 24 | baseMaterial: T | InstanceType; 25 | vertexShader?: string; 26 | fragmentShader?: string; 27 | uniforms?: Uniform; 28 | patchMap?: CSMPatchMap; 29 | cacheKey?: () => string; 30 | }; 31 | 32 | export type CustomShaderMaterialParameters = 33 | CustomShaderMaterialBaseParameters & 34 | (MaterialParams extends undefined ? any : MaterialParams); 35 | 36 | export type CSMProxy = InstanceType & { 37 | vertexShader: string; 38 | fragmentShader: string; 39 | uniforms: Uniform; 40 | update: ( 41 | opts: Omit, "baseMaterial"> 42 | ) => void; 43 | 44 | __csm: Omit, "baseMaterial"> & { 45 | prevOnBeforeCompile: THREE.Material["onBeforeCompile"]; 46 | baseMaterial: THREE.Material; 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /package/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { MaterialConstructor } from "./types"; 2 | 3 | // Hacky, yikes! 4 | export function isConstructor( 5 | f: T | InstanceType 6 | ): f is T { 7 | try { 8 | // @ts-ignore 9 | new f(); 10 | } catch (err) { 11 | if ((err as any).message.indexOf("is not a constructor") >= 0) { 12 | return false; 13 | } 14 | } 15 | return true; 16 | } 17 | 18 | // Remove all comments in a string 19 | // both block and inline comments 20 | export function stripComments(str: string) { 21 | return str.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, ""); 22 | } 23 | -------------------------------------------------------------------------------- /package/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "allowSyntheticDefaultImports": true, 5 | "jsx": "react-jsx", 6 | "strict": true, 7 | "moduleResolution": "Node", 8 | "skipLibCheck": true, 9 | "allowJs": true 10 | }, 11 | "include": ["src/**/*"], 12 | "exclude": ["node_modules", "dist"] 13 | } 14 | -------------------------------------------------------------------------------- /package/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import fs from "fs/promises"; 3 | import path from "path"; 4 | import { defineConfig } from "vite"; 5 | import dts from "vite-plugin-dts"; 6 | 7 | function copyFiles() { 8 | return { 9 | name: "copy-license", 10 | closeBundle: async () => { 11 | await fs.copyFile("../LICENSE.md", "./dist/LICENSE.md"); 12 | await fs.copyFile("../README.md", "./dist/README.md"); 13 | await fs.copyFile("./dist/vanilla.d.ts", "./dist/vanilla/vanilla.d.ts"); 14 | await fs.rm("./dist/vanilla.d.ts"); 15 | 16 | // Write vanilla package.json 17 | const vanillaJson = { 18 | main: "three-custom-shader-material.cjs.js", 19 | module: "three-custom-shader-material.es.js", 20 | type: "module", 21 | types: "vanilla.d.ts", 22 | }; 23 | await fs.writeFile( 24 | "./dist/vanilla/package.json", 25 | JSON.stringify(vanillaJson, null, 2) 26 | ); 27 | }, 28 | }; 29 | } 30 | 31 | export default defineConfig({ 32 | build: { 33 | lib: { 34 | entry: { 35 | vanilla: path.resolve(__dirname, "src/index.ts"), 36 | react: path.resolve(__dirname, "src/React/index.tsx"), 37 | }, 38 | name: "three-custom-shader-material", 39 | formats: ["es", "cjs"], 40 | fileName: (format, entry) => { 41 | switch (entry) { 42 | case "vanilla": 43 | return `vanilla/three-custom-shader-material.${format}.js`; 44 | case "react": 45 | return `three-custom-shader-material.${format}.js`; 46 | default: 47 | return `${entry}.${format}.js`; 48 | } 49 | }, 50 | }, 51 | rollupOptions: { 52 | external: [ 53 | "react", 54 | "react/jsx-runtime", 55 | "react-dom", 56 | "react-dom/client", 57 | "three", 58 | "@react-three/fiber", 59 | ], 60 | }, 61 | sourcemap: true, 62 | emptyOutDir: true, 63 | }, 64 | plugins: [ 65 | react(), 66 | dts({ 67 | rollupTypes: true, 68 | }), 69 | copyFiles(), 70 | ], 71 | }); 72 | --------------------------------------------------------------------------------