├── examples
├── src
│ ├── Examples
│ │ ├── default
│ │ │ ├── vs.glsl
│ │ │ ├── fs.glsl
│ │ │ ├── Scene.tsx
│ │ │ ├── psrd.glsl
│ │ │ └── Stage.tsx
│ │ ├── Instances
│ │ │ ├── fs.glsl
│ │ │ ├── vs.glsl
│ │ │ └── Scene.tsx
│ │ ├── Bump
│ │ │ ├── vs.glsl
│ │ │ ├── fs.glsl
│ │ │ ├── Scene.tsx
│ │ │ └── Stage.tsx
│ │ ├── Shadows
│ │ │ ├── vs.glsl
│ │ │ ├── fs.glsl
│ │ │ └── Scene.tsx
│ │ ├── Vanilla
│ │ │ ├── vs.glsl
│ │ │ ├── fs.glsl
│ │ │ ├── Scene.tsx
│ │ │ └── Stage.tsx
│ │ ├── MetalBunny
│ │ │ ├── vs.glsl
│ │ │ ├── DiscardMaterial.ts
│ │ │ ├── state
│ │ │ │ └── appState.ts
│ │ │ ├── Ui.tsx
│ │ │ ├── Ui3D.tsx
│ │ │ ├── fs.glsl
│ │ │ ├── index.css
│ │ │ ├── Scene.tsx
│ │ │ └── MeshTransmissionMaterial.tsx
│ │ ├── Points
│ │ │ ├── fs.glsl
│ │ │ ├── vs.glsl
│ │ │ └── Scene.tsx
│ │ ├── Caustics
│ │ │ ├── vs.glsl
│ │ │ ├── components
│ │ │ │ ├── Lights.tsx
│ │ │ │ └── Floor.tsx
│ │ │ ├── Scene.tsx
│ │ │ ├── fs.glsl
│ │ │ └── Caustics.tsx
│ │ ├── Waves
│ │ │ ├── fs.glsl
│ │ │ ├── Lights.tsx
│ │ │ ├── useWaterControls.ts
│ │ │ ├── vs.glsl
│ │ │ └── Scene.tsx
│ │ └── index.ts
│ ├── vite-env.d.ts
│ ├── pages
│ │ ├── NotFound
│ │ │ └── index.tsx
│ │ └── Root
│ │ │ ├── UI
│ │ │ ├── styles.css
│ │ │ ├── Presets.tsx
│ │ │ └── index.tsx
│ │ │ └── index.tsx
│ ├── styles
│ │ └── index.css
│ ├── index.tsx
│ └── App.tsx
├── public
│ ├── Bump
│ │ ├── bumpMap.jpg
│ │ └── LeePerrySmith.glb
│ ├── Shadows
│ │ └── react.png
│ ├── Caustics
│ │ └── pooltiles
│ │ │ ├── tlfmffydy_4K_AO.jpg
│ │ │ ├── tlfmffydy_4K_Albedo.jpg
│ │ │ ├── tlfmffydy_4K_Normal.jpg
│ │ │ ├── tlfmffydy_4K_Roughness.jpg
│ │ │ └── tlfmffydy_4K_Displacement.jpg
│ └── vite.svg
├── tsconfig.node.json
├── .gitignore
├── .eslintrc.cjs
├── vite.config.ts
├── tsconfig.json
├── index.html
├── README.md
└── package.json
├── assets
├── waves-demo.png
├── caustics-demo.png
└── points-demo.png
├── package
├── src
│ ├── vite-env.d.ts
│ ├── maps
│ │ ├── index.ts
│ │ ├── requiredPropsMap.ts
│ │ ├── keywordMap.ts
│ │ ├── availabilityMap.ts
│ │ └── patchMap.ts
│ ├── React
│ │ ├── types.ts
│ │ └── index.tsx
│ ├── sdbm.js
│ ├── utils.ts
│ ├── types.ts
│ ├── defaults.ts
│ └── index.ts
├── tsconfig.json
├── .eslintrc.cjs
├── package.json
└── vite.config.ts
├── .gitignore
├── package.json
├── LICENSE.md
├── .github
└── workflows
│ └── deploy.yml
├── CHANGELOG.md
└── README.md
/examples/src/Examples/default/vs.glsl:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/src/Examples/Instances/fs.glsl:
--------------------------------------------------------------------------------
1 | void main() {
2 | csm_DiffuseColor = vec4(1.);
3 | }
--------------------------------------------------------------------------------
/examples/src/Examples/Bump/vs.glsl:
--------------------------------------------------------------------------------
1 |
2 | varying vec2 vUv;
3 |
4 | void main() {
5 | vUv = uv;
6 | }
--------------------------------------------------------------------------------
/examples/src/Examples/Shadows/vs.glsl:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 |
3 | void main() {
4 | vUv = uv;
5 | }
--------------------------------------------------------------------------------
/examples/src/Examples/Vanilla/vs.glsl:
--------------------------------------------------------------------------------
1 |
2 | varying vec2 vUv;
3 |
4 | void main() {
5 | vUv = uv;
6 | }
--------------------------------------------------------------------------------
/assets/waves-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/assets/waves-demo.png
--------------------------------------------------------------------------------
/package/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/assets/caustics-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/assets/caustics-demo.png
--------------------------------------------------------------------------------
/assets/points-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/assets/points-demo.png
--------------------------------------------------------------------------------
/package/src/maps/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./availabilityMap";
2 | export * from "./keywordMap";
3 | export * from "./patchMap";
4 |
--------------------------------------------------------------------------------
/examples/src/Examples/MetalBunny/vs.glsl:
--------------------------------------------------------------------------------
1 | varying vec3 csm_vPosition;
2 |
3 | void main() {
4 | csm_vPosition = position;
5 | }
--------------------------------------------------------------------------------
/examples/public/Bump/bumpMap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Bump/bumpMap.jpg
--------------------------------------------------------------------------------
/examples/public/Shadows/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Shadows/react.png
--------------------------------------------------------------------------------
/examples/public/Bump/LeePerrySmith.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Bump/LeePerrySmith.glb
--------------------------------------------------------------------------------
/examples/public/Caustics/pooltiles/tlfmffydy_4K_AO.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Caustics/pooltiles/tlfmffydy_4K_AO.jpg
--------------------------------------------------------------------------------
/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/public/Caustics/pooltiles/tlfmffydy_4K_Albedo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Caustics/pooltiles/tlfmffydy_4K_Albedo.jpg
--------------------------------------------------------------------------------
/examples/public/Caustics/pooltiles/tlfmffydy_4K_Normal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Caustics/pooltiles/tlfmffydy_4K_Normal.jpg
--------------------------------------------------------------------------------
/examples/public/Caustics/pooltiles/tlfmffydy_4K_Roughness.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Caustics/pooltiles/tlfmffydy_4K_Roughness.jpg
--------------------------------------------------------------------------------
/examples/public/Caustics/pooltiles/tlfmffydy_4K_Displacement.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FarazzShaikh/THREE-CustomShaderMaterial/HEAD/examples/public/Caustics/pooltiles/tlfmffydy_4K_Displacement.jpg
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/package/src/React/types.ts:
--------------------------------------------------------------------------------
1 | import { CustomShaderMaterialParameters, MaterialConstructor } from "../types";
2 |
3 | export type CustomShaderMaterialProps =
4 | CustomShaderMaterialParameters & {};
5 |
--------------------------------------------------------------------------------
/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/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/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/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/.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/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "allowSyntheticDefaultImports": true,
5 | "jsx": "react-jsx",
6 | "strict": true,
7 | "module": "ESNext",
8 | "moduleResolution": "bundler",
9 | "skipLibCheck": true,
10 | "allowJs": true
11 | },
12 | "include": ["src/**/*"],
13 | "exclude": ["node_modules", "dist"]
14 | }
15 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/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/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/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/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/.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/.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/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/Waves/Lights.tsx:
--------------------------------------------------------------------------------
1 | export default function Lights() {
2 | return (
3 |
4 |
11 |
18 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/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/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/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.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 |
--------------------------------------------------------------------------------
/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/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/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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/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 |
31 | >
32 | )
33 | })
34 |
--------------------------------------------------------------------------------
/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/Bump/fs.glsl:
--------------------------------------------------------------------------------
1 |
2 | varying vec2 vUv;
3 |
4 | uniform sampler2D uBumpMap;
5 | uniform float uBumpScale;
6 |
7 | vec2 dHdxy_fwd() {
8 | vec2 dSTdx = dFdx( vUv );
9 | vec2 dSTdy = dFdy( vUv );
10 | float Hll = uBumpScale * texture2D( uBumpMap, vUv ).x;
11 | float dBx = uBumpScale * texture2D( uBumpMap, vUv + dSTdx ).x - Hll;
12 | float dBy = uBumpScale * texture2D( uBumpMap, vUv + dSTdy ).x - Hll;
13 | return vec2( dBx, dBy );
14 | }
15 |
16 | vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {
17 | vec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );
18 | vec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );
19 | vec3 vN = surf_norm;
20 | vec3 R1 = cross( vSigmaY, vN );
21 | vec3 R2 = cross( vN, vSigmaX );
22 | float fDet = dot( vSigmaX, R1 ) * faceDirection;
23 | vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
24 | return normalize( abs( fDet ) * surf_norm - vGrad );
25 | }
26 |
27 | void main() {
28 | csm_FragNormal = perturbNormalArb(-vViewPosition, csm_FragNormal, dHdxy_fwd(), gl_FrontFacing ? 1.0 : -1.0);
29 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | fragNormal: "csm_FragNormal", // Fragment Normal
9 | clearcoat: "csm_Clearcoat", // Clearcoat factor
10 | clearcoatRoughness: "csm_ClearcoatRoughness", // Clearcoat roughness
11 | clearcoatNormal: "csm_ClearcoatNormal", // Clearcoat normals
12 | transmission: "csm_Transmission", // Transmission
13 | thickness: "csm_Thickness", // Thickness
14 | iridescence: "csm_Iridescence", // Iridescence
15 |
16 | // Extras
17 | pointSize: "csm_PointSize", // gl_PointSize (Frag)
18 | fragColor: "csm_FragColor", // gl_FragColor (Frag)
19 | depthAlpha: "csm_DepthAlpha", // Depth (MeshDepthMaterial)
20 | unlitFac: "csm_UnlitFac", // Unlit factor (mix between csm_FragColor and csm_DiffuseColor)
21 |
22 | // Vert
23 | position: "csm_Position", // gl_Position
24 | positionRaw: "csm_PositionRaw", // gl_Position (without projection)
25 | normal: "csm_Normal", // Vertex Normal
26 | };
27 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
42 |
43 | >
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/examples/src/Examples/Bump/Scene.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Bounds,
3 | Environment,
4 | OrbitControls,
5 | useGLTF,
6 | useTexture,
7 | } from "@react-three/drei";
8 | import { useControls } from "leva";
9 |
10 | import { useMemo } from "react";
11 | import { MeshStandardMaterial } from "three";
12 | import CSM from "three-custom-shader-material";
13 | import { useShader } from "../../pages/Root";
14 |
15 | export function Scene() {
16 | const { nodes } = useGLTF("Bump/LeePerrySmith.glb") as any;
17 | const map = useTexture(import.meta.env.BASE_URL + "Bump/bumpMap.jpg");
18 | const { vs, fs } = useShader();
19 |
20 | const uniforms = useMemo(
21 | () => ({
22 | uBumpMap: { value: map },
23 | uBumpScale: { value: 30 },
24 | }),
25 | []
26 | );
27 |
28 | useControls({
29 | bumpScale: {
30 | value: 30,
31 | min: 0,
32 | max: 100,
33 | step: 0.01,
34 | onChange: (v) => (uniforms.uBumpScale.value = v),
35 | },
36 | });
37 |
38 | return (
39 | <>
40 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 | >
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/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.clearcoat}`]: ["MeshPhysicalMaterial"],
32 | [`${keywordMap.clearcoatRoughness}`]: ["MeshPhysicalMaterial"],
33 | [`${keywordMap.clearcoatNormal}`]: ["MeshPhysicalMaterial"],
34 | [`${keywordMap.transmission}`]: ["MeshPhysicalMaterial"],
35 | [`${keywordMap.thickness}`]: ["MeshPhysicalMaterial"],
36 | };
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
37 | {shader.label}
38 |
39 | ))}
40 |
41 | ))}
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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.4.0",
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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 | import { Scene as BumpScene } from "./Bump/Scene";
30 | import fs_bump from "./Bump/fs.glsl?raw";
31 | import vs_bump from "./Bump/vs.glsl?raw";
32 |
33 | export interface ExampleSceneProps {
34 | fs: string;
35 | vs: string;
36 | }
37 |
38 | export const SHADERS: {
39 | [key: string]: {
40 | fs: string;
41 | vs: string;
42 | slug: string;
43 | label: string;
44 | category: string;
45 | Component: React.ComponentType;
46 | };
47 | } = {
48 | DEFAULT: {
49 | fs: fs_default,
50 | vs: vs_default,
51 | Component: DefaultScene,
52 | slug: "default",
53 | label: "Default",
54 | category: "Examples",
55 | },
56 | VANILLA: {
57 | fs: fs_vanilla,
58 | vs: vs_vanilla,
59 | Component: VanillaScene,
60 | slug: "vanilla",
61 | label: "Vanilla",
62 | category: "Examples",
63 | },
64 | INSTANCES: {
65 | fs: fs_instances,
66 | vs: vs_instances,
67 | slug: "instances",
68 | Component: InstancesScene,
69 | label: "Instances",
70 | category: "Examples",
71 | },
72 | WAVES: {
73 | fs: fs_waves,
74 | vs: vs_waves,
75 | slug: "waves",
76 | Component: WavesScene,
77 | label: "Waves",
78 | category: "Tech Demos",
79 | },
80 | CAUSTICS: {
81 | fs: fs_caustics,
82 | vs: vs_caustics,
83 | slug: "caustics",
84 | Component: CausticsScene,
85 | label: "Caustics",
86 | category: "Tech Demos",
87 | },
88 | POINTS: {
89 | fs: fs_points,
90 | vs: vs_points,
91 | slug: "points",
92 | Component: PointsScene,
93 | label: "Points",
94 | category: "Tech Demos",
95 | },
96 | SHADOWS: {
97 | fs: fs_shadows,
98 | vs: vs_shadows,
99 | slug: "shadows",
100 | Component: ShadowsScene,
101 | label: "Shadows",
102 | category: "Tech Demos",
103 | },
104 | BUMP: {
105 | fs: fs_bump,
106 | vs: vs_bump,
107 | slug: "bump",
108 | Component: BumpScene,
109 | label: "Bump",
110 | category: "Tech Demos",
111 | },
112 | // METAL_BUNNY: {
113 | // fs: fs_metalBunny,
114 | // vs: vs_metalBunny,
115 | // slug: "metal-bunny",
116 | // Component: MetalBunnyScene,
117 | // label: "Metal Bunny",
118 | // category: "Tech Demos",
119 | // },
120 | };
121 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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.fragNormal}`]: {
76 | "#include ": `
77 | #include
78 | normal = ${keywordMap.fragNormal};
79 | `,
80 | },
81 | [`${keywordMap.depthAlpha}`]: {
82 | "gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );": `
83 | gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity * 1.0 - ${keywordMap.depthAlpha} );
84 | `,
85 | "gl_FragColor = packDepthToRGBA( fragCoordZ );": `
86 | if(${keywordMap.depthAlpha} < 1.0) discard;
87 | gl_FragColor = packDepthToRGBA( dist );
88 | `,
89 | "gl_FragColor = packDepthToRGBA( dist );": `
90 | if(${keywordMap.depthAlpha} < 1.0) discard;
91 | gl_FragColor = packDepthToRGBA( dist );
92 | `,
93 | },
94 | [`${keywordMap.clearcoat}`]: {
95 | "material.clearcoat = clearcoat;": `material.clearcoat = ${keywordMap.clearcoat};`,
96 | },
97 | [`${keywordMap.clearcoatRoughness}`]: {
98 | "material.clearcoatRoughness = clearcoatRoughness;": `material.clearcoatRoughness = ${keywordMap.clearcoatRoughness};`,
99 | },
100 | [`${keywordMap.clearcoatNormal}`]: {
101 | "#include ": `
102 | vec3 csm_coat_internal_orthogonal = csm_ClearcoatNormal - (dot(csm_ClearcoatNormal, nonPerturbedNormal) * nonPerturbedNormal);
103 | vec3 csm_coat_internal_projectedbump = mat3(csm_internal_vModelViewMatrix) * csm_coat_internal_orthogonal;
104 | vec3 clearcoatNormal = normalize(nonPerturbedNormal - csm_coat_internal_projectedbump);
105 | `,
106 | },
107 | [`${keywordMap.transmission}`]: {
108 | "material.transmission = transmission;": `
109 | material.transmission = ${keywordMap.transmission};
110 | `,
111 | },
112 | [`${keywordMap.thickness}`]: {
113 | "material.thickness = thickness;": `
114 | material.thickness = ${keywordMap.thickness};
115 | `,
116 | },
117 | [`${keywordMap.iridescence}`]: {
118 | "material.iridescence = iridescence;": `
119 | material.iridescence = ${keywordMap.iridescence};
120 | `,
121 | },
122 | };
123 |
--------------------------------------------------------------------------------
/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 | }
125 | alignItems="center"
126 | colorScheme="teal"
127 | onClick={onCompile}
128 | >
129 | Compile
130 |
131 |
132 |
133 |
134 |
135 |
142 | }
146 | alignItems="center"
147 | colorScheme="teal"
148 | onClick={onCompile}
149 | >
150 | Compile
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | );
159 | }
160 |
--------------------------------------------------------------------------------
/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_FragNormal
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_FragNormal;
41 | #endif
42 |
43 | float csm_DepthAlpha;
44 | #endif
45 | `;
46 |
47 | export const defaultCsmMainDefinitions = /* glsl */ `
48 |
49 | #ifdef IS_VERTEX
50 | // csm_Position & csm_PositionRaw
51 | #ifdef IS_UNKNOWN
52 | csm_Position = vec3(0.0);
53 | csm_PositionRaw = vec4(0.0);
54 | csm_Normal = vec3(0.0);
55 | #else
56 | csm_Position = position;
57 | csm_PositionRaw = projectionMatrix * modelViewMatrix * vec4(position, 1.);
58 | csm_Normal = normal;
59 | #endif
60 |
61 | // csm_PointSize
62 | #ifdef IS_POINTSMATERIAL
63 | csm_PointSize = size;
64 | #endif
65 | #else
66 | csm_UnlitFac = 0.0;
67 |
68 | // csm_DiffuseColor & csm_FragColor
69 | #if defined IS_UNKNOWN || defined IS_SHADERMATERIAL || defined IS_MESHDEPTHMATERIAL || defined IS_MESHDISTANCEMATERIAL || defined IS_MESHNORMALMATERIAL || defined IS_SHADOWMATERIAL
70 | csm_DiffuseColor = vec4(1.0, 0.0, 1.0, 1.0);
71 | csm_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
72 | #else
73 | #ifdef USE_MAP
74 | vec4 _csm_sampledDiffuseColor = texture2D(map, vMapUv);
75 |
76 | #ifdef DECODE_VIDEO_TEXTURE
77 | // inline sRGB decode (TODO: Remove this code when https://crbug.com/1256340 is solved)
78 | _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);
79 | #endif
80 |
81 | csm_DiffuseColor = vec4(diffuse, opacity) * _csm_sampledDiffuseColor;
82 | csm_FragColor = vec4(diffuse, opacity) * _csm_sampledDiffuseColor;
83 | #else
84 | csm_DiffuseColor = vec4(diffuse, opacity);
85 | csm_FragColor = vec4(diffuse, opacity);
86 | #endif
87 | #endif
88 |
89 | // csm_Emissive, csm_Roughness, csm_Metalness
90 | #if defined IS_MESHSTANDARDMATERIAL || defined IS_MESHPHYSICALMATERIAL
91 | csm_Emissive = emissive;
92 | csm_Roughness = roughness;
93 | csm_Metalness = metalness;
94 |
95 | #ifdef USE_IRIDESCENCE
96 | csm_Iridescence = iridescence;
97 | #else
98 | csm_Iridescence = 0.0;
99 | #endif
100 |
101 | #if defined IS_MESHPHYSICALMATERIAL
102 | #ifdef USE_CLEARCOAT
103 | csm_Clearcoat = clearcoat;
104 | csm_ClearcoatRoughness = clearcoatRoughness;
105 | #else
106 | csm_Clearcoat = 0.0;
107 | csm_ClearcoatRoughness = 0.0;
108 | #endif
109 |
110 | #ifdef USE_TRANSMISSION
111 | csm_Transmission = transmission;
112 | csm_Thickness = thickness;
113 | #else
114 | csm_Transmission = 0.0;
115 | csm_Thickness = 0.0;
116 | #endif
117 | #endif
118 | #endif
119 |
120 | // csm_AO
121 | #if defined IS_MESHSTANDARDMATERIAL || defined IS_MESHPHYSICALMATERIAL || defined IS_MESHBASICMATERIAL || defined IS_MESHLAMBERTMATERIAL || defined IS_MESHPHONGMATERIAL || defined IS_MESHTOONMATERIAL
122 | csm_AO = 0.0;
123 | #endif
124 |
125 | #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
126 | #ifdef FLAT_SHADED
127 | vec3 fdx = dFdx( vViewPosition );
128 | vec3 fdy = dFdy( vViewPosition );
129 | csm_FragNormal = normalize( cross( fdx, fdy ) );
130 | #else
131 | csm_FragNormal = normalize(vNormal);
132 | #ifdef DOUBLE_SIDED
133 | csm_FragNormal *= gl_FrontFacing ? 1.0 : - 1.0;
134 | #endif
135 | #endif
136 | #endif
137 |
138 | csm_DepthAlpha = 1.0;
139 | #endif
140 | `;
141 |
142 | export const defaultVertDefinitions = /* glsl */ `
143 | varying mat4 csm_internal_vModelViewMatrix;
144 | `;
145 |
146 | export const defaultVertMain = /* glsl */ `
147 | csm_internal_vModelViewMatrix = modelViewMatrix;
148 | `;
149 |
150 | export const defaultFragDefinitions = /* glsl */ `
151 | varying mat4 csm_internal_vModelViewMatrix;
152 | `;
153 |
154 | export const defaultFragMain = /* glsl */ `
155 |
156 | `;
157 |
--------------------------------------------------------------------------------
/examples/src/Examples/Bump/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Custom Shader Material
2 | Extend Three.js standard materials with your own shaders!
3 |
4 |
5 |
6 |
7 |
8 |
9 |
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 |
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_Clearcoat | `float` | Custom clearcoat factor. | Fragment Shader | Only available in materials with a `clearcoat`. |
137 | | csm_ClearcoatRoughness | `float` | Custom clearcoat roughenss factor. | Fragment Shader | Only available in materials with a `clearcoat`. |
138 | | csm_ClearcoatNormal | `vec3` | Custom clearcoat normal. | Fragment Shader | Only available in materials with a `clearcoat`. |
139 | | csm_Transmission | `float` | Custom transmission factor. | Fragment Shader | Only available in materials with a `transmission`. |
140 | | csm_Thickness | `float` | Custom transmission thickness. | Fragment Shader | Only available in materials with a `transmission`. |
141 | | csm_Iridescence | `float` | Custom iridescence factor. | Fragment Shader | Only available in materials with a `iridescence`. |
142 | | csm_Emissive | `vec3` | Custom emissive color. | Fragment Shader | Only available in materials with a `emissive`. |
143 | | csm_FragNormal | `float` | Custom fragment normal. | Only available in materials with a `normalMap`. |
144 | | Fragmet Shader (Special) | - | - | - | - |
145 | | csm_DepthAlpha | `float` | Custom alpha for `MeshDepthMaterial`. | Fragment Shader | Useful for controlling `customDepthMaterial` with same shader as the shader material. |
146 | | 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. |
147 |
148 | ## Bump mapping
149 |
150 | Bump mapping was removed in v6.4.0. This is becasue it cannot be implimented with a singular clean output variable. You can still impliment bump mapping by using `csm_FragNormal` output variable. See the [Bump example](https://farazzshaikh.github.io/THREE-CustomShaderMaterial/#/bump) for a full working example.
151 |
152 | ## Typing
153 |
154 | 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`.
155 |
156 | ```ts
157 | // Vanilla
158 | const material = new CustomShaderMaterial({
159 | baseMaterial: THREE.MeshPhysicalMaterial,
160 | //...Any props
161 | });
162 |
163 | // React
164 |
165 | baseMaterial={THREE.MeshPhysicalMaterial}
166 | //...Any props
167 | ```
168 |
169 | ### Typing React Refs
170 |
171 | You can type the ref of `CustomShaderMaterial` as follows:
172 |
173 | ```tsx
174 | // NOTE: import from /vanilla module
175 | import CSM from "three-custom-shader-material/vanilla";
176 | import { MeshPhysicalMaterial } from "three";
177 |
178 | const ref = useRef>(null!);
179 | ```
180 |
181 | ## Custom overrides
182 |
183 | You can define any custom overrides you'd like using the `patchMap` prop. The prop is used as shown below.
184 |
185 | ```js
186 | const material = new CustomShaderMaterial({
187 | baseMaterial: THREE.MeshPhysicalMaterial,
188 | vertexShader: ` ... `,
189 | fragmentShader: ... `,
190 | uniforms: {...},
191 | patchMap={{
192 | "": { // The keyword you will assign to in your custom shader
193 | "TO_REPLACE": // The chunk you'd like to replace.
194 | "REPLACED_WITH" // The chunk you'd like put in place of `TO_REPLACE`
195 | }
196 | }}
197 | })
198 | ```
199 |
200 | > 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).
201 | >
202 | > ```js
203 | > patchMap={{
204 | > "*": { "TO_REPLACE": "REPLACED_WITH" }
205 | > }}
206 | > ```
207 |
208 | ## Extending already extended materials
209 |
210 | CSM allows you to extend other CSM instances. Values set in the first shader will affect the next.
211 |
212 | > Note: Extending of other materials that use `onBeforeCompile` may or may not work depending on if the default `#includes` are mangled.
213 |
214 |
215 | Show Vanilla example
216 |
217 | ```js
218 | import CustomShaderMaterial from "three-custom-shader-material/vanilla";
219 |
220 | function Box() {
221 | const material1 = new CustomShaderMaterial({
222 | baseMaterial: THREE.MeshPhysicalMaterial,
223 | //...Any props
224 | });
225 | const material2 = new CustomShaderMaterial({
226 | baseMaterial: material1,
227 | //...Any props
228 | });
229 | }
230 | ```
231 |
232 |
233 |
234 |
235 | Show React example
236 |
237 | ```jsx
238 | import CustomShaderMaterial from "three-custom-shader-material";
239 | import CustomShaderMaterialImpl from "three-custom-shader-material/vanilla";
240 |
241 | function Cube() {
242 | const [materialRef, setMaterialRef] = useState();
243 |
244 | return (
245 | <>
246 |
251 |
252 | {materialRef && (
253 |
257 | )}
258 | >
259 | );
260 | }
261 | ```
262 |
263 |
264 |
265 | ### Gotchas
266 |
267 | - `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`.
268 | - Instancing must be handled manually when using `csm_PositionRaw` by multiplying in `instanceMatrix` into your projection math.
269 | - 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.
270 | - Extending of other materials that use `onBeforeCompile` may or may not work depending on if the default `#includes` are mangled.
271 | - 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:
272 |
273 | ```glsl
274 | void main() {
275 | // shader A
276 | // shader B
277 | // shader C
278 | // shader D
279 |
280 | // original shader
281 | }
282 | ```
283 |
284 | Where A was the first in the chain.
285 |
286 | - Cache key calculation takes into account base material's cache key. Useful for propagating changes across multiple chained CSM instances.
287 | - If you find yourself lost in a patchMap, it's often simpler to just make a `ShaderMaterial` with the necessary `#includes`.
288 |
289 | ## Performance
290 |
291 | With v6, CSM's initialization cost is now negligible 🥳 Still, a couple important notes about performance:
292 |
293 | - Changing these props will rebuild the material
294 | - `baseMaterial`
295 | - `fragmentShader`
296 | - `vertexShader`
297 | - `uniforms`
298 | - `cacheKey`
299 | - CSM uses ThreeJS's default shader program caching system. Materials with the same cache key, will use the the same shader program.
300 | - ` ` and `` are the same, and will use the same cached shader program. The default cache key is such:
301 |
302 | ```js
303 | (cacheKey?.() || hash((vertexShader || "") + (fragmentShader || ""))) +
304 | baseMaterialCacheKey?.();
305 | ```
306 |
307 | You can provide your own cache key function via the `cacheKey` prop.
308 |
309 | > 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.
310 |
311 | > 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.
312 |
313 | ## License
314 |
315 | ```
316 | MIT License
317 |
318 | Copyright (c) 2024 Faraz Shaikh
319 |
320 | Permission is hereby granted, free of charge, to any person obtaining a copy
321 | of this software and associated documentation files (the "Software"), to deal
322 | in the Software without restriction, including without limitation the rights
323 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
324 | copies of the Software, and to permit persons to whom the Software is
325 | furnished to do so, subject to the following conditions:
326 |
327 | The above copyright notice and this permission notice shall be included in all
328 | copies or substantial portions of the Software.
329 |
330 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
331 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
332 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
333 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
334 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
335 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
336 | SOFTWARE.
337 | ```
338 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------