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 |