├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── assets ├── configurator.png └── lamina.png ├── examples ├── README.md ├── complex-materials │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.svg │ │ ├── fonts │ │ │ └── Roboto_Bold.json │ │ ├── index.html │ │ └── lighting_bunny_copy.gltf │ ├── src │ │ ├── App.tsx │ │ ├── Blob.tsx │ │ ├── Bunny.tsx │ │ ├── Floor.tsx │ │ ├── Lighting.tsx │ │ ├── Marble.tsx │ │ ├── Tag.tsx │ │ ├── Text.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ ├── thumbnail.png │ ├── tsconfig.json │ └── yarn.lock ├── example-configurator │ ├── package.json │ ├── public │ │ ├── favicon.svg │ │ ├── index.html │ │ ├── initialMaterial.json │ │ └── monkey.glb │ ├── src │ │ ├── App.tsx │ │ ├── Monkey.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── types.ts │ ├── tsconfig.json │ ├── webpack.config.js │ └── yarn.lock ├── example-vanilla │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.svg │ │ └── index.html │ ├── src │ │ ├── main.ts │ │ └── style.css │ ├── tsconfig.json │ ├── webpack.config.js │ └── yarn.lock └── layer-materials │ ├── package.json │ ├── public │ ├── favicon.svg │ └── index.html │ ├── src │ ├── App.js │ ├── index.js │ └── styles.css │ ├── thumbnail.png │ ├── webpack.config.js │ └── yarn.lock ├── package.json ├── rollup.config.js ├── scripts ├── link.sh ├── publish.sh └── upgrade.sh ├── src ├── chunks │ ├── BlendModes.ts │ ├── Helpers.ts │ └── Noise.ts ├── core │ ├── Abstract.ts │ ├── Color.ts │ ├── Depth.ts │ ├── Displace.ts │ ├── Fresnel.ts │ ├── Gradient.ts │ ├── Matcap.ts │ ├── Noise.ts │ ├── Normal.ts │ └── Texture.ts ├── debug.tsx ├── index.tsx ├── types.ts ├── utils │ ├── ExportUtils.ts │ └── Functions.ts └── vanilla.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Poimandres 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

lamina

4 |

🍰 An extensible, layer based shader material for ThreeJS

5 | 6 |
7 | 8 |

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Chat on Twitter 17 | 18 | 19 | Chat on Twitter 20 | 21 |

22 | 23 |
24 | 25 |

26 | 27 | 28 |

29 |

30 | These demos are real, you can click them! They contain the full code, too. 📦 More examples here 31 |

32 |
33 | 34 | ## Notice 35 | _From @farazzshaikh_: **Lamina has been archived as of April 5 2023.** 36 | 37 | This project needs maintainers and a good rewrite from scratch. Lamina does a lot of hacky processing to achieve its API goals. As time has gone by I have started to doubt if it’s worth it. These hacks make it unreliable, unpredictable and slow. Not to mentaion, quite convoluted to maintain and debug. There might be better APIs or implimentations for this kind of library but I currently do not have the bandwidth to dedicate to finding them. Perhaps in the future. 38 | 39 | Lamina is built on top of [three-custom-shader-material (CSM)](https://github.com/FarazzShaikh/THREE-CustomShaderMaterial) and any effects that are achieved by lamina can be done with CSM in a predictable and performant manner albeit at a lower level. 40 | 41 | Feel free to use Lamina, however, support will be lacking. If you'd like to resurrect this library, then feel free to reach out on [our Discord](https://discord.gg/poimandres) and tag me (Faraz#9759). 42 | 43 | --- 44 | 45 | `lamina` lets you create materials with a declarative, system of layers. Layers make it incredibly easy to stack and blend effects. This approach was first made popular by the [Spline team](https://spline.design/). 46 | 47 | ```jsx 48 | import { LayerMaterial, Depth } from 'lamina' 49 | 50 | function GradientSphere() { 51 | return ( 52 | 53 | 58 | 67 | 68 | 69 | ) 70 | } 71 | ``` 72 | 73 |
74 | Show Vanilla example 75 | 76 | Lamina can be used with vanilla Three.js. Each layer is just a class. 77 | 78 | ```js 79 | import { LayerMaterial, Depth } from 'lamina/vanilla' 80 | 81 | const geometry = new THREE.SphereGeometry(1, 128, 64) 82 | const material = new LayerMaterial({ 83 | color: '#d9d9d9', 84 | lighting: 'physical', 85 | transmission: 1, 86 | layers: [ 87 | new Depth({ 88 | colorA: '#002f4b', 89 | colorB: '#f2fdff', 90 | alpha: 0.5, 91 | mode: 'multiply', 92 | near: 0, 93 | far: 2, 94 | origin: new THREE.Vector3(1, 1, 1), 95 | }), 96 | ], 97 | }) 98 | 99 | const mesh = new THREE.Mesh(geometry, material) 100 | ``` 101 | 102 | Note: To match the colors of the react example, you must convert all colors to Linear encoding like so: 103 | 104 | ```js 105 | new Depth({ 106 | colorA: new THREE.Color('#002f4b').convertSRGBToLinear(), 107 | colorB: new THREE.Color('#f2fdff').convertSRGBToLinear(), 108 | alpha: 0.5, 109 | mode: 'multiply', 110 | near: 0, 111 | far: 2, 112 | origin: new THREE.Vector3(1, 1, 1), 113 | }), 114 | ``` 115 | 116 |
117 | 118 | ## Layers 119 | 120 | ### `LayerMaterial` 121 | 122 | `LayerMaterial` can take in the following parameters: 123 | 124 | | Prop | Type | Default | 125 | | ---------- | ----------------------------------------------------------------------- | ----------------- | 126 | | `name` | `string` | `"LayerMaterial"` | 127 | | `color` | `THREE.ColorRepresentation \| THREE.Color` | `"white"` | 128 | | `alpha` | `number` | `1` | 129 | | `lighting` | `'phong' \| 'physical' \| 'toon' \| 'basic' \| 'lambert' \| 'standard'` | `'basic'` | 130 | | `layers`\* | `Abstract[]` | `[]` | 131 | 132 | The `lighting` prop controls the shading that is applied on the material. The material then accepts all the material properties supported by ThreeJS of the material type specified by the `lighting` prop. 133 | 134 | \* Note: the `layers` prop is only available on the `LayerMaterial` class, not the component. Pass in layers as children in React. 135 | 136 | ### Built-in layers 137 | 138 | Here are the layers that lamina currently provides 139 | 140 | | Name | Function | 141 | | ----------------------- | -------------------------------------- | 142 | | Fragment Layers | | 143 | | [`Color`](#color) | Flat color. | 144 | | [`Depth`](#depth) | Depth based gradient. | 145 | | [`Fresnel`](#fresnel) | Fresnel shading (strip or rim-lights). | 146 | | [`Gradient`](#gradient) | Linear gradient. | 147 | | [`Matcap`](#matcap) | Load in a Matcap. | 148 | | [`Noise`](#noise) | White, perlin or simplex noise . | 149 | | [`Normal`](#normal) | Visualize vertex normals. | 150 | | [`Texture`](#texture) | Image texture. | 151 | | Vertex Layers | | 152 | | [`Displace`](#displace) | Displace vertices using. noise | 153 | 154 | See the section for each layer for the options on it. 155 | 156 | ### Debugger 157 | 158 | Lamina comes with a handy debugger that lets you tweek parameters till you're satisfied with the result! Then, just copy the JSX and paste! 159 | 160 | Replace `LayerMaterial` with `DebugLayerMaterial` to enable it. 161 | 162 | ```jsx 163 | 164 | 173 | 174 | ``` 175 | 176 | Any custom layers are automatically compatible with the debugger. However, for advanced inputs, see the [Advanced Usage](#advanced-usage) section. 177 | 178 | ### Writing your own layers 179 | 180 | You can write your own layers by extending the `Abstract` class. The concept is simple: 181 | 182 | > Each layer can be treated as an isolated shader program that produces a `vec4` color. 183 | 184 | The color of each layer will be blended together using the specified blend mode. A list of all available blend modes can be found [here](#blendmode) 185 | 186 | ```ts 187 | import { Abstract } from 'lamina/vanilla' 188 | 189 | // Extend the Abstract layer 190 | class CustomLayer extends Abstract { 191 | // Define stuff as static properties! 192 | 193 | // Uniforms: Must begin with prefix "u_". 194 | // Assign them their default value. 195 | // Any unifroms here will automatically be set as properties on the class as setters and getters. 196 | // There setters and getters will update the underlying unifrom. 197 | static u_color = 'red' // Can be accessed as CustomLayer.color 198 | static u_alpha = 1 // Can be accessed as CustomLayer.alpha 199 | 200 | // Define your fragment shader just like you already do! 201 | // Only difference is, you must return the final color of this layer 202 | static fragmentShader = ` 203 | uniform vec3 u_color; 204 | uniform float u_alpha; 205 | 206 | // Varyings must be prefixed by "v_" 207 | varying vec3 v_Position; 208 | 209 | vec4 main() { 210 | // Local variables must be prefixed by "f_" 211 | vec4 f_color = vec4(u_color, u_alpha); 212 | return f_color; 213 | } 214 | ` 215 | 216 | // Optionally Define a vertex shader! 217 | // Same rules as fragment shaders, except no blend modes. 218 | // Return a non-projected vec3 position. 219 | static vertexShader = ` 220 | // Varyings must be prefixed by "v_" 221 | varying vec3 v_Position; 222 | 223 | void main() { 224 | v_Position = position; 225 | return position * 2.; 226 | } 227 | ` 228 | 229 | constructor(props) { 230 | // You MUST call `super` with the current constructor as the first argument. 231 | // Second argument is optional and provides non-uniform parameters like blend mode, name and visibility. 232 | super(CustomLayer, { 233 | name: 'CustomLayer', 234 | ...props, 235 | }) 236 | } 237 | } 238 | ``` 239 | 240 | 👉 Note: The vertex shader must return a vec3. You do not need to set `gl_Position` or transform the model view. lamina will handle this automatically down the chain. 241 | 242 | 👉 Note: You can use lamina's noise functions inside of your own layer without any additional imports: `lamina_noise_perlin()`, `lamina_noise_simplex()`, `lamina_noise_worley()`, `lamina_noise_white()`, `lamina_noise_swirl()`. 243 | 244 | If you need a specialized or advance use-case, see the [Advanced Usage](#advanced-usage) section 245 | 246 | ### Using your own layers 247 | 248 | Custom layers are Vanilla compatible by default. 249 | 250 | To use them with React-three-fiber, you must use the `extend` function to add the layer to your component library! 251 | 252 | ```jsx 253 | import { extend } from "@react-three/fiber" 254 | 255 | extend({ CustomLayer }) 256 | 257 | // ... 258 | const ref = useRef(); 259 | 260 | // Animate uniforms using a ref. 261 | useFrame(({ clock }) => { 262 | ref.current.color.setRGB( 263 | Math.sin(clock.elapsedTime), 264 | Math.cos(clock.elapsedTime), 265 | Math.sin(clock.elapsedTime), 266 | ) 267 | }) 268 | 269 | 270 | 275 | 276 | ``` 277 | 278 | ## Advanced Usage 279 | 280 | For more advanced custom layers, lamina provides the `onParse` event. 281 | 282 | > This event runs after the layer's shader and uniforms are parsed. 283 | 284 | This means you can use it to inject functionality that isn't by the basic layer extension syntax. 285 | 286 | Here is a common use case - Adding non-uniform options to layers that directly sub out shader code. 287 | 288 | ```ts 289 | class CustomLayer extends Abstract { 290 | static u_color = 'red' 291 | static u_alpha = 1 292 | 293 | static vertexShader = `...` 294 | static fragmentShader = ` 295 | // ... 296 | float f_dist = lamina_mapping_template; // Temp value, will be used to inject code later on. 297 | // ... 298 | ` 299 | 300 | // Get some shader code based off mapping parameter 301 | static getMapping(mapping) { 302 | switch (mapping) { 303 | default: 304 | case 'uv': 305 | return `some_shader_code` 306 | 307 | case 'world': 308 | return `some_other_shader_code` 309 | } 310 | } 311 | 312 | // Set non-uniform defaults. 313 | mapping: 'uv' | 'world' = 'uv' 314 | 315 | // Non unifrom params must be passed to the constructor 316 | constructor(props) { 317 | super( 318 | CustomLayer, 319 | { 320 | name: 'CustomLayer', 321 | ...props, 322 | }, 323 | // This is onParse callback 324 | (self: CustomLayer) => { 325 | // Add to Leva (debugger) schema. 326 | // This will create a dropdown select component on the debugger. 327 | self.schema.push({ 328 | value: self.mapping, 329 | label: 'mapping', 330 | options: ['uv', 'world'], 331 | }) 332 | 333 | // Get shader chunk based off selected mapping value 334 | const mapping = CustomLayer.getMapping(self.mapping) 335 | 336 | // Inject shader chunk in current layer's shader code 337 | self.fragmentShader = self.fragmentShader.replace('lamina_mapping_template', mapping) 338 | } 339 | ) 340 | } 341 | } 342 | ``` 343 | 344 | In react... 345 | 346 | ```jsx 347 | // ... 348 | 349 | 355 | 356 | ``` 357 | 358 | ## Layers 359 | 360 | Every layer has these props in common. 361 | 362 | | Prop | Type | Default | 363 | | --------- | ------------------------- | ------------------------- | 364 | | `mode` | [`BlendMode`](#blendmode) | `"normal"` | 365 | | `name` | `string` | `` | 366 | | `visible` | `boolean` | `true` | 367 | 368 | All props are optional. 369 | 370 | ### `Color` 371 | 372 | Flat color. 373 | 374 | | Prop | Type | Default | 375 | | ------- | ------------------------------------------ | ------- | 376 | | `color` | `THREE.ColorRepresentation \| THREE.Color` | `"red"` | 377 | | `alpha` | `number` | `1` | 378 | 379 | ### `Normal` 380 | 381 | Visualize vertex normals 382 | 383 | | Prop | Type | Default | 384 | | ----------- | ----------------------------------------- | ----------- | 385 | | `direction` | `THREE.Vector3 \| [number,number,number]` | `[0, 0, 0]` | 386 | | `alpha` | `number` | `1` | 387 | 388 | ### `Depth` 389 | 390 | Depth based gradient. Colors are lerp-ed based on `mapping` props which may have the following values: 391 | 392 | - `vector`: distance from `origin` to fragment's world position. 393 | - `camera`: distance from camera to fragment's world position. 394 | - `world`: distance from fragment to center (0, 0, 0). 395 | 396 | | Prop | Type | Default | 397 | | --------- | ------------------------------------------ | ----------- | 398 | | `colorA` | `THREE.ColorRepresentation \| THREE.Color` | `"white"` | 399 | | `colorB` | `THREE.ColorRepresentation \| THREE.Color` | `"black"` | 400 | | `alpha` | `number` | `1` | 401 | | `near` | `number` | `2` | 402 | | `far` | `number` | `10` | 403 | | `origin` | `THREE.Vector3 \| [number,number,number]` | `[0, 0, 0]` | 404 | | `mapping` | `"vector" \| "camera" \| "world"` | `"vector"` | 405 | 406 | ### `Fresnel` 407 | 408 | Fresnel shading. 409 | 410 | | Prop | Type | Default | 411 | | ----------- | ------------------------------------------ | --------- | 412 | | `color` | `THREE.ColorRepresentation \| THREE.Color` | `"white"` | 413 | | `alpha` | `number` | `1` | 414 | | `power` | `number` | `0` | 415 | | `intensity` | `number` | `1` | 416 | | `bias` | `number` | `2` | 417 | 418 | ### `Gradient` 419 | 420 | Linear gradient based off distance from `start` to `end` in a specified `axes`. `start` and `end` are points on the `axes` selected. The distance between `start` and `end` is used to lerp the colors. 421 | 422 | | Prop | Type | Default | 423 | | ---------- | ------------------------------------------ | --------- | 424 | | `colorA` | `THREE.ColorRepresentation \| THREE.Color` | `"white"` | 425 | | `colorB` | `THREE.ColorRepresentation \| THREE.Color` | `"black"` | 426 | | `alpha` | `number` | `1` | 427 | | `contrast` | `number` | `1` | 428 | | `start` | `number` | `1` | 429 | | `end` | `number` | `-1` | 430 | | `axes` | `"x" \| "y" \| "z"` | `"x"` | 431 | | `mapping` | `"local" \| "world" \| "uv"` | `"local"` | 432 | 433 | ### `Noise` 434 | 435 | Various noise functions. 436 | 437 | | Prop | Type | Default | 438 | | --------- | ------------------------------------------- | ----------- | 439 | | `colorA` | `THREE.ColorRepresentation \| THREE.Color` | `"white"` | 440 | | `colorB` | `THREE.ColorRepresentation \| THREE.Color` | `"black"` | 441 | | `colorC` | `THREE.ColorRepresentation \| THREE.Color` | `"white"` | 442 | | `colorD` | `THREE.ColorRepresentation \| THREE.Color` | `"black"` | 443 | | `alpha` | `number` | `1` | 444 | | `scale` | `number` | `1` | 445 | | `offset` | `THREE.Vector3 \| [number, number, number]` | `[0, 0, 0]` | 446 | | `mapping` | `"local" \| "world" \| "uv"` | `"local"` | 447 | | `type` | `"perlin' \| "simplex" \| "cell" \| "curl"` | `"perlin"` | 448 | 449 | ### `Matcap` 450 | 451 | Set a Matcap texture. 452 | 453 | | Prop | Type | Default | 454 | | ------- | --------------- | ----------- | 455 | | `map` | `THREE.Texture` | `undefined` | 456 | | `alpha` | `number` | `1` | 457 | 458 | ### `Texture` 459 | 460 | Set a texture. 461 | 462 | | Prop | Type | Default | 463 | | ------- | --------------- | ----------- | 464 | | `map` | `THREE.Texture` | `undefined` | 465 | | `alpha` | `number` | `1` | 466 | 467 | ### `BlendMode` 468 | 469 | Blend modes currently available in lamina 470 | 471 | | `normal` | `divide` | 472 | | ---------- | ----------- | 473 | | `add` | `overlay` | 474 | | `subtract` | `screen` | 475 | | `multiply` | `softlight` | 476 | | `lighten` | `reflect` | 477 | | `darken` | `negation` | 478 | 479 | ## Vertex layers 480 | 481 | Layers that affect the vertex shader 482 | 483 | ### `Displace` 484 | 485 | Displace vertices with various noise. 486 | 487 | | Prop | Type | Default | 488 | | ---------- | ------------------------------------------- | ----------- | 489 | | `strength` | `number` | `1` | 490 | | `scale` | `number` | `1` | 491 | | `mapping` | `"local" \| "world" \| "uv"` | `"local"` | 492 | | `type` | `"perlin' \| "simplex" \| "cell" \| "curl"` | `"perlin"` | 493 | | `offset` | `THREE.Vector3 \| [number,number,number]` | `[0, 0, 0]` | 494 | -------------------------------------------------------------------------------- /assets/configurator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/lamina/5d614a975635928f4dcf321dd6c9b7894c01ebd2/assets/configurator.png -------------------------------------------------------------------------------- /assets/lamina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/lamina/5d614a975635928f4dcf321dd6c9b7894c01ebd2/assets/lamina.png -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

lamina

4 |

🍰 An extensible, layer based shader material for ThreeJS

5 | 6 |
7 | 8 |

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Chat on Twitter 17 | 18 | 19 | Chat on Twitter 20 | 21 |

22 | 23 |
24 | 25 | ## Examples 26 | 27 | | Name | Link | Description | 28 | | -------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | 29 | | Layer Material | https://codesandbox.io/embed/github/pmndrs/lamina/tree/main/examples/layer-materials | Basic usage of Lamina in React. | 30 | | Vanilla | https://codesandbox.io/embed/github/pmndrs/lamina/tree/main/examples/example-vanilla | Basic usage of Lamina in Vanilla ThreeJS. | 31 | | Configurator | https://codesandbox.io/embed/github/pmndrs/lamina/tree/main/examples/layer-materials | A Lamina material configurator allowing you make materials and test out new layers. | 32 | | Complex | https://codesandbox.io/embed/github/pmndrs/lamina/tree/main/examples/layer-materials | Shows complex usage of lamina with lighting, instancing, physics and base material properties. | 33 | -------------------------------------------------------------------------------- /examples/complex-materials/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/complex-materials/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /examples/complex-materials/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "complex-materials", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@pmndrs/branding": "^0.0.8", 7 | "@react-spring/three": "^9.5.2", 8 | "@react-three/cannon": "^6.3.0", 9 | "@react-three/drei": "^9.19.7", 10 | "@react-three/fiber": "^8.3.1", 11 | "@types/react": "^18.0.17", 12 | "@types/react-dom": "^18.0.6", 13 | "@types/three": "^0.143.0", 14 | "lamina": "^1.1.23", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-scripts": "5.0.1", 18 | "three": "^0.143.0", 19 | "typescript": "^4.7.4" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/complex-materials/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/complex-materials/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lamina | Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/complex-materials/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useState } from 'react' 2 | import { Canvas } from '@react-three/fiber' 3 | import { Bounds, Loader, OrbitControls } from '@react-three/drei' 4 | import Bunny from './Bunny' 5 | import Blob from './Blob' 6 | import Marble from './Marble' 7 | import { Debug, Physics } from '@react-three/cannon' 8 | import Floor from './Floor' 9 | import TextComponent from './Text' 10 | import Tag from './Tag' 11 | import Lighting from './Lighting' 12 | 13 | function App() { 14 | const [loaded, setLoaded] = useState(false) 15 | return ( 16 | <> 17 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {/* */} 55 | 56 | 57 | {loaded && } 58 | 59 | 60 | 61 | ) 62 | } 63 | 64 | export default App 65 | -------------------------------------------------------------------------------- /examples/complex-materials/src/Blob.tsx: -------------------------------------------------------------------------------- 1 | import { Sphere } from '@react-three/drei' 2 | import { GroupProps, useFrame } from '@react-three/fiber' 3 | import { Depth, Displace, Fresnel, LayerMaterial } from 'lamina' 4 | import { useMemo, useRef } from 'react' 5 | import { MathUtils, Mesh, Vector3 } from 'three' 6 | import { Displace as DisplaceType } from 'lamina/vanilla' 7 | import { DisplaceProps } from 'lamina/types' 8 | 9 | export default function Blob({ 10 | displaceProps, 11 | ...props 12 | }: GroupProps & { 13 | displaceProps?: DisplaceProps 14 | }) { 15 | const ref = useRef(null!) 16 | const rand = useMemo(() => Math.random(), []) 17 | const strength = useRef(0) 18 | const displaceRef = useRef(null!) 19 | 20 | useFrame(({ clock }, dt) => { 21 | ref.current.position.y = Math.sin(clock.elapsedTime + rand * 100) * 0.1 - 0.2 22 | 23 | if (displaceRef.current.strength !== strength.current) { 24 | displaceRef.current.strength = MathUtils.lerp( 25 | displaceRef.current.strength, // 26 | strength.current, 27 | 0.1 28 | ) 29 | } 30 | 31 | if (strength.current > 0) { 32 | displaceRef.current.offset.x += 0.3 * dt 33 | } 34 | }) 35 | 36 | return ( 37 | 38 | (strength.current = 0.2)} 41 | onPointerLeave={() => (strength.current = 0)} 42 | ref={ref} 43 | args={[0.4, 128, 128]} 44 | > 45 | 52 | 59 | 60 | 68 | 69 | 70 | 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /examples/complex-materials/src/Bunny.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Auto-generated by: https://github.com/pmndrs/gltfjsx 3 | */ 4 | 5 | import React, { useRef, useLayoutEffect } from 'react' 6 | import { useGLTF } from '@react-three/drei' 7 | import { Depth, Fresnel, LayerMaterial, Noise } from 'lamina' 8 | import { useSphere } from '@react-three/cannon' 9 | import { Group } from 'three' 10 | 11 | export default function Bunny(props: any) { 12 | const group = useRef(null!) 13 | const { nodes, materials } = useGLTF('/lighting_bunny_copy.gltf') as any 14 | 15 | const [ref] = useSphere(() => ({})) 16 | 17 | return ( 18 | 19 | {/* @ts-ignore */} 20 | 21 | {/* @ts-ignore */} 22 | 23 | 24 | 25 | 34 | 35 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | useGLTF.preload('/lighting_bunny_copy.gltf') 53 | -------------------------------------------------------------------------------- /examples/complex-materials/src/Floor.tsx: -------------------------------------------------------------------------------- 1 | import { PlaneProps, usePlane } from '@react-three/cannon' 2 | 3 | export default function Floor(props: PlaneProps) { 4 | const [ref] = usePlane(() => ({ rotation: [-Math.PI / 2, 0, 0], position: [0, -1.3, 0] })) 5 | return ( 6 | // @ts-ignore 7 | 8 | 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /examples/complex-materials/src/Lighting.tsx: -------------------------------------------------------------------------------- 1 | import { Environment } from '@react-three/drei' 2 | import { Suspense } from 'react' 3 | 4 | export default function Lighting() { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /examples/complex-materials/src/Marble.tsx: -------------------------------------------------------------------------------- 1 | import { Depth, Fresnel, LayerMaterial } from 'lamina' 2 | import { useLayoutEffect, useState } from 'react' 3 | 4 | import { useBox, useSphere, usePlane } from '@react-three/cannon' 5 | import { Box, Plane } from '@react-three/drei' 6 | import { BackSide } from 'three' 7 | 8 | export default function Marble({ setLoaded }: { setLoaded: any }) { 9 | const [number] = useState(200) 10 | 11 | const [ref] = useSphere(() => ({ 12 | args: [0.1], 13 | mass: 1, 14 | position: [Math.random() - 0.5, Math.random() * 2 + 4, Math.random() - 0.5], 15 | })) 16 | 17 | useLayoutEffect(() => { 18 | setLoaded(true) 19 | }, []) 20 | 21 | return ( 22 | 23 | {/* @ts-ignore */} 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /examples/complex-materials/src/Tag.tsx: -------------------------------------------------------------------------------- 1 | import { Logo } from '@pmndrs/branding' 2 | 3 | export default function Tag() { 4 | return ( 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Made with 🧡 by{' '} 14 | 15 | Faraz Shaikh 16 | 17 | 18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /examples/complex-materials/src/Text.tsx: -------------------------------------------------------------------------------- 1 | import { useBox } from '@react-three/cannon' 2 | import { extend, useFrame } from '@react-three/fiber' 3 | import { Text3D } from '@react-three/drei' 4 | import { Depth, Fresnel, Gradient, LayerMaterial } from 'lamina' 5 | import { useRef } from 'react' 6 | import { MathUtils } from 'three' 7 | 8 | export default function TextComponent() { 9 | const depthRef = useRef(null!) 10 | const depthRef2 = useRef(null!) 11 | 12 | useFrame(({ mouse, viewport }) => { 13 | const x = MathUtils.lerp( 14 | depthRef.current.origin.x, // 15 | (mouse.x * viewport.width) / 2 + 1.27, 16 | 0.05 17 | ) 18 | const y = MathUtils.lerp( 19 | depthRef.current.origin.y, // 20 | (mouse.y * viewport.width) / 2 + 1.27, 21 | 0.05 22 | ) 23 | 24 | depthRef.current.origin.set(x, y, 0) 25 | depthRef2.current.origin.set(x, y, 0) 26 | }) 27 | 28 | return ( 29 | 30 | 31 | 32 | 33 | 40 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 63 | 73 | 74 | 75 | 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /examples/complex-materials/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 4 | 'Droid Sans', 'Helvetica Neue', sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | code { 10 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 11 | } 12 | 13 | body, 14 | canvas { 15 | width: 100vw; 16 | height: 100vh; 17 | overflow: hidden; 18 | background-color: #2114db; 19 | } 20 | 21 | .copy { 22 | position: absolute; 23 | bottom: 0; 24 | right: 0; 25 | width: 100%; 26 | 27 | display: flex; 28 | justify-content: space-between; 29 | align-items: center; 30 | color: #ffffff; 31 | pointer-events: none; 32 | } 33 | 34 | .copy svg { 35 | width: 32px; 36 | fill: white; 37 | } 38 | 39 | .copy > * { 40 | padding: 32px; 41 | } 42 | 43 | a { 44 | font-style: italic; 45 | color: #ee7d3b; 46 | font-weight: bold; 47 | text-decoration: none; 48 | pointer-events: all; 49 | } 50 | 51 | canvas { 52 | cursor: grab; 53 | } 54 | 55 | canvas:active { 56 | cursor: grabbing; 57 | } 58 | -------------------------------------------------------------------------------- /examples/complex-materials/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import App from './App' 3 | import './index.css' 4 | 5 | const container = document.getElementById('root') 6 | const root = createRoot(container!) 7 | root.render() 8 | -------------------------------------------------------------------------------- /examples/complex-materials/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/complex-materials/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/lamina/5d614a975635928f4dcf321dd6c9b7894c01ebd2/examples/complex-materials/thumbnail.png -------------------------------------------------------------------------------- /examples/complex-materials/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/example-configurator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lamina-example-configurator", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@types/node": "^17.0.23", 9 | "@types/react": "^18.0.0", 10 | "@types/react-dom": "^18.0.0", 11 | "@types/webpack": "^5.28.0", 12 | "@types/webpack-dev-server": "^4.7.2", 13 | "css-loader": "^6.7.1", 14 | "fork-ts-checker-webpack-plugin": "^7.2.1", 15 | "html-webpack-plugin": "^5.5.0", 16 | "style-loader": "^3.3.1", 17 | "ts-loader": "^9.2.8", 18 | "ts-node": "^10.7.0", 19 | "tsconfig-paths-webpack-plugin": "^3.5.2", 20 | "typescript": "^4.6.3", 21 | "webpack": "^5.70.0", 22 | "webpack-cli": "^4.9.2", 23 | "webpack-dev-server": "^4.7.4" 24 | }, 25 | "dependencies": { 26 | "@react-three/drei": "^9.0.1", 27 | "@react-three/fiber": "^8.0.3", 28 | "@types/styled-components": "^5.1.24", 29 | "@types/three": "^0.139.0", 30 | "framer-motion": "^6.2.8", 31 | "lamina": "^1.1.23", 32 | "leva": "^0.9.23", 33 | "react": "^18.0.0", 34 | "react-dom": "^18.0.0", 35 | "styled-components": "^5.3.5", 36 | "three": "^0.139.1" 37 | }, 38 | "scripts": { 39 | "start": "webpack-cli serve --mode=development --env development --open --hot", 40 | "build": "webpack --mode=production --env production --progress" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/example-configurator/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/example-configurator/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lamina | Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/example-configurator/public/initialMaterial.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "properties": { 4 | "name": "Monkey w/ freckles", 5 | "lighting": "basic", 6 | "color": "white", 7 | "alpha": 1 8 | }, 9 | 10 | "layers": [ 11 | { 12 | "constructor": "Depth", 13 | "properties": { 14 | "near": -0.06800000000000028, 15 | "far": 5, 16 | "origin": [0, 0, 3], 17 | "colorA": "#ff4eb8", 18 | "colorB": "#24dbf8", 19 | "alpha": 1, 20 | "name": "Depth", 21 | "mode": "normal", 22 | "visible": true, 23 | "mapping": "vector" 24 | } 25 | }, 26 | { 27 | "constructor": "Depth", 28 | "properties": { 29 | "near": 1, 30 | "far": 3, 31 | "origin": [0, 0, -1.3670000000000089], 32 | "colorA": "#ff7800", 33 | "colorB": "black", 34 | "alpha": 1, 35 | "name": "Depth", 36 | "mode": "screen", 37 | "visible": true, 38 | "mapping": "vector" 39 | } 40 | }, 41 | { 42 | "constructor": "Fresnel", 43 | "properties": { 44 | "color": "white", 45 | "alpha": 1, 46 | "bias": 0, 47 | "intensity": 1, 48 | "power": 1.9099999999999757, 49 | "name": "Fresnel", 50 | "mode": "softlight", 51 | "visible": true 52 | } 53 | }, 54 | { 55 | "constructor": "Noise", 56 | "properties": { 57 | "colorA": "#3bffd0", 58 | "colorB": "#4e4e4e", 59 | "colorC": "#000000", 60 | "colorD": "#000000", 61 | "alpha": 1, 62 | "scale": 50, 63 | "name": "noise", 64 | "mode": "lighten", 65 | "visible": true, 66 | "type": "perlin", 67 | "mapping": "local" 68 | } 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /examples/example-configurator/public/monkey.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/lamina/5d614a975635928f4dcf321dd6c9b7894c01ebd2/examples/example-configurator/public/monkey.glb -------------------------------------------------------------------------------- /examples/example-configurator/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { Canvas } from '@react-three/fiber' 3 | import { Leva } from 'leva' 4 | import Monkey from './Monkey' 5 | import styled from 'styled-components' 6 | import { Environment, Stats } from '@react-three/drei' 7 | import { Color } from 'three' 8 | 9 | export default function App() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | const LevaContainer = styled.div` 31 | & > div[class*='leva-c'] { 32 | left: 10px; 33 | right: unset; 34 | } 35 | ` 36 | -------------------------------------------------------------------------------- /examples/example-configurator/src/Monkey.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Auto-generated by: https://github.com/pmndrs/gltfjsx 3 | */ 4 | 5 | import React, { useEffect, useMemo, useState } from 'react' 6 | import { useGLTF, TransformControls, OrbitControls } from '@react-three/drei' 7 | import { Color, DebugLayerMaterial, Depth, LayerMaterial } from 'lamina' 8 | import * as LAYERS from 'lamina' 9 | import { button, useControls } from 'leva' 10 | import { SerializedLayer } from 'lamina/types' 11 | 12 | export default function Monkey() { 13 | const { nodes, scene } = useGLTF('/monkey.glb') as any 14 | const orbitControls = React.useRef(null!) 15 | const transformControls = React.useRef(null!) 16 | 17 | React.useEffect(() => { 18 | if (transformControls.current) { 19 | const { current: controls } = transformControls 20 | const callback = (event: any) => (orbitControls.current.enabled = !event.value) 21 | controls.addEventListener('dragging-changed', callback) 22 | return () => controls.removeEventListener('dragging-changed', callback) 23 | } 24 | }) 25 | 26 | const [layers, setLayers] = useState([]) 27 | const [materialProps, setMaterialProps] = useState<{ [key: string]: any }>({}) 28 | 29 | useEffect(() => { 30 | ;(async () => { 31 | const json = await (await fetch('/initialMaterial.json')).json() 32 | const l = json.layers.map((layer: SerializedLayer) => { 33 | // @ts-ignore 34 | const Component = LAYERS[layer.constructor] 35 | return 36 | }) 37 | 38 | setMaterialProps(json.properties) 39 | setLayers(l) 40 | })() 41 | }, []) 42 | 43 | useControls('Layers', { 44 | Type: { 45 | options: [ 46 | 'Abstract', // 47 | 'Color', 48 | 'Depth', 49 | 'Displace', 50 | 'Fresnel', 51 | 'Gradient', 52 | 'Matcap', 53 | 'Noise', 54 | 'Texture', 55 | ], 56 | value: 'Color', 57 | }, 58 | Add: button((get) => { 59 | const k = get('Layers.Type') 60 | // @ts-ignore 61 | const Component = LAYERS[k] 62 | 63 | setLayers((s) => [...s, ]) 64 | }), 65 | }) 66 | 67 | return ( 68 | <> 69 | 70 | 71 | 72 | 73 | {...layers} 74 | 75 | 76 | 77 | 78 | 79 | 80 | ) 81 | } 82 | 83 | useGLTF.preload('/monkey.glb') 84 | -------------------------------------------------------------------------------- /examples/example-configurator/src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body, 7 | #root { 8 | width: 100%; 9 | height: 100%; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | body { 15 | background: white; 16 | padding: 16px; 17 | box-sizing: border-box; 18 | } 19 | 20 | canvas { 21 | cursor: grab; 22 | } 23 | 24 | canvas:active { 25 | cursor: grabbing; 26 | } 27 | -------------------------------------------------------------------------------- /examples/example-configurator/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import App from './App' 3 | import './index.css' 4 | 5 | const container = document.getElementById('root') 6 | const root = createRoot(container!) 7 | root.render() 8 | -------------------------------------------------------------------------------- /examples/example-configurator/src/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export const BlendModes = { 4 | normal: 1, 5 | add: 2, 6 | subtract: 3, 7 | multiply: 4, 8 | addsub: 5, 9 | lighten: 6, 10 | darken: 7, 11 | switch: 8, 12 | divide: 9, 13 | overlay: 10, 14 | screen: 11, 15 | softlight: 12, 16 | } 17 | -------------------------------------------------------------------------------- /examples/example-configurator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "allowJs": false, 6 | "skipLibCheck": false, 7 | "esModuleInterop": false, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "ESNext", 12 | "moduleResolution": "Node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | "baseUrl": "." 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/example-configurator/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const { TsconfigPathsPlugin } = require('tsconfig-paths-webpack-plugin') 5 | 6 | const webpackConfig = (env) => ({ 7 | entry: './src/index.tsx', 8 | ...(env.production || !env.development ? {} : { devtool: 'eval-source-map' }), 9 | resolve: { 10 | extensions: ['.ts', '.tsx', '.js'], 11 | //TODO waiting on https://github.com/dividab/tsconfig-paths-webpack-plugin/issues/61 12 | //@ts-ignore 13 | plugins: [new TsconfigPathsPlugin()], 14 | }, 15 | output: { 16 | path: path.join(__dirname, '/dist'), 17 | filename: 'build.js', 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.tsx?$/, 23 | loader: 'ts-loader', 24 | options: { 25 | transpileOnly: true, 26 | }, 27 | exclude: /dist/, 28 | }, 29 | { 30 | test: /\.css$/i, 31 | use: ['style-loader', 'css-loader'], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new HtmlWebpackPlugin({ 37 | template: './public/index.html', 38 | }), 39 | new webpack.DefinePlugin({ 40 | 'process.env.PRODUCTION': env.production || !env.development, 41 | 'process.env.NAME': JSON.stringify(require('./package.json').name), 42 | 'process.env.VERSION': JSON.stringify(require('./package.json').version), 43 | }), 44 | ], 45 | }) 46 | 47 | module.exports = webpackConfig 48 | -------------------------------------------------------------------------------- /examples/example-vanilla/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /examples/example-vanilla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lamina-example", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "webpack-dev-server --open" 7 | }, 8 | "devDependencies": { 9 | "copy-webpack-plugin": "^10.2.4", 10 | "css-loader": "^6.7.1", 11 | "html-webpack-plugin": "^5.5.0", 12 | "style-loader": "^3.3.1", 13 | "ts-loader": "^9.2.8", 14 | "typescript": "^4.6.3", 15 | "webpack-dev-server": "^4.7.4" 16 | }, 17 | "dependencies": { 18 | "@types/react": "^18.0.0", 19 | "@types/react-dom": "^18.0.0", 20 | "@types/three": "^0.139.0", 21 | "lamina": "^1.1.23", 22 | "three": "^0.139.1", 23 | "webpack": "5.70.0", 24 | "webpack-cli": "^4.9.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/example-vanilla/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/example-vanilla/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lamina | Example 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/example-vanilla/src/main.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 3 | import { LayerMaterial, Color, Depth, Fresnel, Noise } from 'lamina/vanilla' 4 | import './style.css' 5 | 6 | const scene = new THREE.Scene() 7 | const camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.001, 1000) 8 | camera.position.set(2, 0, 0) 9 | 10 | const renderer = new THREE.WebGLRenderer({ antialias: true }) 11 | renderer.outputEncoding = THREE.sRGBEncoding 12 | renderer.toneMapping = THREE.ACESFilmicToneMapping 13 | renderer.setSize(window.innerWidth, window.innerHeight) 14 | renderer.domElement.style.width = '100%' 15 | renderer.domElement.style.height = '100%' 16 | document.body.appendChild(renderer.domElement) 17 | 18 | const flowerGeometry = new THREE.TorusKnotGeometry(0.4, 0.05, 400, 32, 3, 7) 19 | const flowerMaterial = new LayerMaterial({ 20 | color: new THREE.Color('#ff4eb8').convertSRGBToLinear(), 21 | layers: [ 22 | new Depth({ 23 | far: 3, 24 | origin: [1, 1, 1], 25 | colorA: new THREE.Color('#ff00e3').convertSRGBToLinear(), 26 | colorB: new THREE.Color('#00ffff').convertSRGBToLinear(), 27 | alpha: 0.5, 28 | mode: 'multiply', 29 | mapping: 'vector', 30 | }), 31 | new Depth({ 32 | name: 'MouseDepth', 33 | near: 0.25, 34 | far: 2, 35 | origin: [-0.9760456268614979, 0.48266696923176067, 0], 36 | colorA: new THREE.Color('#ffe100').convertSRGBToLinear(), 37 | alpha: 0.5, 38 | mode: 'lighten', 39 | mapping: 'vector', 40 | }), 41 | new Fresnel({ 42 | mode: 'softlight', 43 | }), 44 | ], 45 | }) 46 | 47 | const flowerMesh = new THREE.Mesh(flowerGeometry, flowerMaterial) 48 | flowerMesh.rotateY(Math.PI / 2) 49 | flowerMesh.scale.setScalar(2) 50 | scene.add(flowerMesh) 51 | 52 | const geometry = new THREE.SphereGeometry(1, 64, 64) 53 | const material = new LayerMaterial({ 54 | side: THREE.BackSide, 55 | layers: [ 56 | new Color({ 57 | color: new THREE.Color('#f0aed2').convertSRGBToLinear(), 58 | }), 59 | new Depth({ 60 | near: 0, 61 | far: 300, 62 | origin: [10, 10, 10], 63 | colorA: new THREE.Color('blue').convertSRGBToLinear(), 64 | colorB: new THREE.Color('#00aaff').convertSRGBToLinear(), 65 | alpha: 0.5, 66 | mode: 'multiply', 67 | }), 68 | new Depth({ 69 | near: 0, 70 | far: 300, 71 | origin: [100, 100, 100], 72 | colorA: new THREE.Color('#ff0000').convertSRGBToLinear(), 73 | colorB: new THREE.Color('#00aaff').convertSRGBToLinear(), 74 | alpha: 0.5, 75 | mode: 'multiply', 76 | }), 77 | ], 78 | }) 79 | 80 | const mesh = new THREE.Mesh(geometry, material) 81 | mesh.scale.setScalar(100) 82 | scene.add(mesh) 83 | 84 | { 85 | const geometry = new THREE.SphereGeometry(0.2, 64, 64) 86 | const material = new THREE.MeshPhysicalMaterial({ 87 | transmission: 1, 88 | // @ts-ignore 89 | thickness: 10, 90 | roughness: 0.2, 91 | }) 92 | 93 | const mesh = new THREE.Mesh(geometry, material) 94 | scene.add(mesh) 95 | } 96 | 97 | const controls = new OrbitControls(camera, renderer.domElement) 98 | 99 | const pLight = new THREE.DirectionalLight() 100 | pLight.intensity = 2 101 | pLight.shadow.mapSize.set(1024, 1024) 102 | scene.add(pLight) 103 | 104 | const clock = new THREE.Clock() 105 | 106 | const depthLayer = flowerMaterial.layers.find((e) => e.name === 'MouseDepth') 107 | const vec = new THREE.Vector3() 108 | window.addEventListener('mousemove', (e) => { 109 | const m = new THREE.Vector2( 110 | THREE.MathUtils.mapLinear(e.x / window.innerWidth, 0, 1, 1, -1), 111 | THREE.MathUtils.mapLinear(e.y / window.innerHeight, 0, 1, 1, -1) 112 | ) 113 | 114 | // @ts-ignore 115 | depthLayer.origin = vec.set(-m.y, m.x, 0) 116 | }) 117 | 118 | function animate() { 119 | requestAnimationFrame(animate) 120 | renderer.render(scene, camera) 121 | controls.update() 122 | 123 | const delta = clock.getDelta() 124 | mesh.rotation.x = mesh.rotation.y = mesh.rotation.z += delta 125 | flowerMesh.rotation.z += delta / 2 126 | } 127 | 128 | animate() 129 | -------------------------------------------------------------------------------- /examples/example-vanilla/src/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body, 7 | #root { 8 | width: 100%; 9 | height: 100%; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | body { 15 | background: #f0f0f0; 16 | } 17 | -------------------------------------------------------------------------------- /examples/example-vanilla/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "baseUrl": "." 13 | }, 14 | "include": ["./src"] 15 | } 16 | -------------------------------------------------------------------------------- /examples/example-vanilla/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: path.resolve(__dirname, './src/main.ts'), 5 | output: { 6 | filename: 'index.js', 7 | path: path.resolve(__dirname, 'dist'), 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.ts$/, 13 | exclude: [/node_modules/], 14 | loader: 'ts-loader', 15 | }, 16 | { 17 | test: /\.css$/i, 18 | use: ['style-loader', 'css-loader'], 19 | }, 20 | ], 21 | }, 22 | resolve: { 23 | extensions: ['.ts', '.js'], 24 | }, 25 | 26 | devServer: { 27 | static: { 28 | directory: path.join(__dirname, 'public'), 29 | }, 30 | port: 3001, 31 | }, 32 | mode: 'development', 33 | } 34 | -------------------------------------------------------------------------------- /examples/layer-materials/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layer-materials", 3 | "version": "1.0.0", 4 | "description": "Shows how to form self-contained components with their own state and user interaction.", 5 | "keywords": [ 6 | "layermaterial", 7 | "depth", 8 | "gradient", 9 | "lamina" 10 | ], 11 | "main": "src/index.js", 12 | "dependencies": { 13 | "@babel/core": "^7.17.0", 14 | "@babel/preset-env": "^7.16.11", 15 | "@babel/preset-react": "^7.16.7", 16 | "@react-three/drei": "9.0.1", 17 | "@react-three/fiber": "8.0.3", 18 | "@types/react": "^18.0.0", 19 | "@types/react-dom": "^18.0.0", 20 | "@types/three": "^0.139.0", 21 | "babel-loader": "^8.2.4", 22 | "css-loader": "^6.6.0", 23 | "html-webpack-plugin": "^5.5.0", 24 | "lamina": "^1.1.23", 25 | "leva": "0.9.23", 26 | "react": "18.0.0", 27 | "react-dom": "18.0.0", 28 | "style-loader": "^3.3.1", 29 | "three": "^0.139.1", 30 | "webpack": "^5.68.0", 31 | "webpack-cli": "^4.9.2", 32 | "webpack-dev-server": "^4.7.4" 33 | }, 34 | "devDependencies": {}, 35 | "scripts": { 36 | "start": "webpack-cli serve --mode=development --env development --open --hot", 37 | "build": "webpack --mode=production --env production --progress" 38 | }, 39 | "browserslist": [ 40 | ">0.2%", 41 | "not dead", 42 | "not ie <= 11", 43 | "not op_mini all" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /examples/layer-materials/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/layer-materials/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | lamina | Example 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/layer-materials/src/App.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import React, { useRef, Suspense } from 'react' 3 | import { Canvas, useFrame } from '@react-three/fiber' 4 | import { OrbitControls } from '@react-three/drei' 5 | import { DebugLayerMaterial, LayerMaterial, Depth, Color, Fresnel, Noise, Normal } from 'lamina' 6 | import { Vector3 } from 'three' 7 | 8 | export default function App() { 9 | const props = { base: '#ff4eb8', colorA: '#00ffff', colorB: '#ff00e3' } 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | } 26 | 27 | function Bg() { 28 | const mesh = useRef() 29 | useFrame((state, delta) => { 30 | mesh.current.rotation.x = mesh.current.rotation.y = mesh.current.rotation.z += delta 31 | }) 32 | return ( 33 | 34 | 35 | 36 | 37 | 46 | 47 | 48 | ) 49 | } 50 | 51 | const vec = new Vector3(0, 0, 0) 52 | function Flower({ base, colorA, colorB }) { 53 | const mesh = useRef() 54 | const depth = useRef() 55 | useFrame((state, delta) => { 56 | mesh.current.rotation.z += delta / 2 57 | depth.current.origin.set(-state.mouse.y, state.mouse.x, 0) 58 | }) 59 | return ( 60 | 61 | 62 | 63 | 64 | 73 | 74 | 75 | 76 | 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /examples/layer-materials/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App' 4 | import './styles.css' 5 | 6 | const container = document.getElementById('root') 7 | const root = createRoot(container) 8 | root.render() 9 | -------------------------------------------------------------------------------- /examples/layer-materials/src/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body, 7 | #root { 8 | width: 100%; 9 | height: 100%; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | body { 15 | background: #f0f0f0; 16 | } 17 | -------------------------------------------------------------------------------- /examples/layer-materials/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/lamina/5d614a975635928f4dcf321dd6c9b7894c01ebd2/examples/layer-materials/thumbnail.png -------------------------------------------------------------------------------- /examples/layer-materials/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | 5 | const webpackConfig = (env) => ({ 6 | devtool: 'source-map', 7 | entry: './src/index.js', 8 | ...(env.production || !env.development ? {} : { devtool: 'eval-source-map' }), 9 | resolve: { 10 | extensions: ['.jsx', '.js'] 11 | }, 12 | output: { 13 | path: path.join(__dirname, '/dist'), 14 | filename: 'build.js' 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.(jsx|js)$/, 20 | include: path.resolve(__dirname, 'src'), 21 | exclude: /node_modules/, 22 | use: [ 23 | { 24 | loader: 'babel-loader', 25 | options: { 26 | presets: [ 27 | [ 28 | '@babel/preset-env', 29 | { 30 | targets: 'defaults' 31 | } 32 | ], 33 | '@babel/preset-react' 34 | ] 35 | } 36 | } 37 | ] 38 | }, 39 | { 40 | test: /\.css$/i, 41 | use: ['style-loader', 'css-loader'] 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new HtmlWebpackPlugin({ 47 | template: './public/index.html' 48 | }), 49 | new webpack.DefinePlugin({ 50 | 'process.env.PRODUCTION': env.production || !env.development, 51 | 'process.env.NAME': JSON.stringify(require('./package.json').name), 52 | 'process.env.VERSION': JSON.stringify(require('./package.json').version) 53 | }) 54 | ] 55 | }) 56 | 57 | module.exports = webpackConfig 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lamina", 3 | "private": true, 4 | "version": "1.1.23", 5 | "description": "🍰 An extensable, layer based shader material for ThreeJS.", 6 | "main": "./index.cjs", 7 | "module": "./index.js", 8 | "types": "./index.d.ts", 9 | "files": [ 10 | "**" 11 | ], 12 | "exports": { 13 | "./package.json": "./package.json", 14 | ".": { 15 | "types": "./index.d.ts", 16 | "require": "./index.cjs.js", 17 | "import": "./index.js" 18 | }, 19 | "./vanilla": { 20 | "types": "./vanilla.d.ts", 21 | "require": "./vanilla.cjs.js", 22 | "import": "./vanilla.js" 23 | } 24 | }, 25 | "keywords": [ 26 | "react", 27 | "shaders", 28 | "layers", 29 | "materials", 30 | "threejs", 31 | "webgl", 32 | "3d" 33 | ], 34 | "scripts": { 35 | "start": "tsc --emitDeclarationOnly && rollup -c -w", 36 | "build": "rollup -c", 37 | "postbuild": "tsc --emitDeclarationOnly && cp package.json dist/package.json", 38 | "release": "chmod +x ./scripts/publish.sh && ./scripts/publish.sh", 39 | "patchJSON": "json -I -f dist/package.json -e \"this.private=false;\"" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/pmndrs/lamina.git" 44 | }, 45 | "author": "Faraz Shaikh ", 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/pmndrs/lamina/issues" 49 | }, 50 | "devDependencies": { 51 | "@babel/core": "7.18.9", 52 | "@babel/plugin-proposal-class-properties": "^7.16.0", 53 | "@babel/plugin-transform-modules-commonjs": "7.18.6", 54 | "@babel/plugin-transform-parameters": "7.18.8", 55 | "@babel/plugin-transform-runtime": "7.18.9", 56 | "@babel/plugin-transform-template-literals": "7.18.9", 57 | "@babel/preset-env": "7.18.9", 58 | "@babel/preset-react": "^7.16.7", 59 | "@babel/preset-typescript": "^7.16.0", 60 | "@react-three/fiber": "8.2.2", 61 | "@rollup/plugin-babel": "^5.3.0", 62 | "@rollup/plugin-node-resolve": "^13.0.6", 63 | "@types/node": "^18.6.3", 64 | "@types/react": "^18.0.0", 65 | "@types/react-dom": "^18.0.0", 66 | "@types/three": "^0.143.0", 67 | "json": "^11.0.0", 68 | "lint-staged": "^13.0.3", 69 | "prettier": "^2.6.1", 70 | "react": "^18.0.0", 71 | "react-dom": "^18.0.0", 72 | "rollup": "^2.70.1", 73 | "rollup-plugin-size-snapshot": "^0.12.0", 74 | "rollup-plugin-terser": "^7.0.2", 75 | "three": "^0.143.0", 76 | "typescript": "^4.6.3" 77 | }, 78 | "dependencies": { 79 | "glsl-token-descope": "^1.0.2", 80 | "glsl-token-functions": "^1.0.1", 81 | "glsl-token-string": "^1.0.1", 82 | "glsl-tokenizer": "^2.1.5", 83 | "lamina": "^1.1.23", 84 | "leva": "^0.9.20", 85 | "three-custom-shader-material": "^4.0.0" 86 | }, 87 | "peerDependencies": { 88 | "@react-three/fiber": ">=8.0", 89 | "react": ">=18.0", 90 | "react-dom": ">=18.0", 91 | "three": ">=0.138" 92 | }, 93 | "peerDependenciesMeta": { 94 | "@react-three/fiber": { 95 | "optional": true 96 | }, 97 | "react": { 98 | "optional": true 99 | }, 100 | "react-dom": { 101 | "optional": true 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import babel from '@rollup/plugin-babel' 3 | import resolve from '@rollup/plugin-node-resolve' 4 | 5 | const root = process.platform === 'win32' ? path.resolve('/') : '/' 6 | const external = (id) => !id.startsWith('.') && !id.startsWith(root) 7 | const extensions = ['.js', '.jsx', '.ts', '.tsx', '.json'] 8 | 9 | const getBabelOptions = ({ useESModules }) => ({ 10 | babelrc: false, 11 | extensions, 12 | exclude: '**/node_modules/**', 13 | babelHelpers: 'runtime', 14 | presets: [ 15 | [ 16 | '@babel/preset-env', 17 | { 18 | include: [ 19 | '@babel/plugin-proposal-optional-chaining', 20 | '@babel/plugin-proposal-nullish-coalescing-operator', 21 | '@babel/plugin-proposal-numeric-separator', 22 | '@babel/plugin-proposal-logical-assignment-operators', 23 | ], 24 | bugfixes: true, 25 | loose: true, 26 | modules: false, 27 | targets: '> 1%, not dead, not ie 11, not op_mini all', 28 | }, 29 | ], 30 | '@babel/preset-react', 31 | '@babel/preset-typescript', 32 | ], 33 | plugins: [['@babel/transform-runtime', { regenerator: false, useESModules }]], 34 | }) 35 | 36 | export default [ 37 | { 38 | input: `./src/index.tsx`, 39 | output: { file: `dist/index.js`, format: 'esm' }, 40 | external, 41 | plugins: [babel(getBabelOptions({ useESModules: true })), resolve({ extensions })], 42 | }, 43 | { 44 | input: `./src/index.tsx`, 45 | output: { file: `dist/index.cjs.js`, format: 'cjs' }, 46 | external, 47 | plugins: [babel(getBabelOptions({ useESModules: false })), resolve({ extensions })], 48 | }, 49 | { 50 | input: `./src/vanilla.ts`, 51 | output: { file: `dist/vanilla.js`, format: 'esm' }, 52 | external, 53 | plugins: [babel(getBabelOptions({ useESModules: true })), resolve({ extensions })], 54 | }, 55 | { 56 | input: `./src/vanilla.ts`, 57 | output: { file: `dist/vanilla.cjs.js`, format: 'cjs' }, 58 | external, 59 | plugins: [babel(getBabelOptions({ useESModules: false })), resolve({ extensions })], 60 | }, 61 | ] 62 | -------------------------------------------------------------------------------- /scripts/link.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./node_modeules ./yarn.lock 2 | yarn 3 | 4 | yarn build 5 | 6 | cd dist 7 | yarn link 8 | cd ../ 9 | 10 | cd ./node_modules/react 11 | yarn link 12 | cd ../../ 13 | cd ./node_modules/three 14 | yarn link 15 | cd ../../ 16 | cd ./node_modules/@react-three/fiber 17 | yarn link 18 | cd ../../../ 19 | 20 | # for d in ./examples/* ; do 21 | # cd $d 22 | # rm -rf ./node_modeules ./yarn.lock 23 | # yarn 24 | 25 | # yarn link three-custom-shader-material 26 | # yarn link react 27 | # yarn link three 28 | # yarn link @react-three/fiber 29 | # cd ../../ 30 | # done 31 | 32 | cd ./examples/example-configurator 33 | rm -rf ./node_modeules ./yarn.lock 34 | yarn 35 | 36 | yarn link lamina 37 | yarn link react 38 | yarn link react-dom 39 | yarn link three 40 | yarn link @react-three/fiber 41 | cd ../../ -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | yarn build &&\ 2 | cp package.json dist/package.json &&\ 3 | cp README.md dist &&\ 4 | cp LICENSE dist &&\ 5 | yarn patchJSON &&\ 6 | cd dist &&\ 7 | npm publish -------------------------------------------------------------------------------- /scripts/upgrade.sh: -------------------------------------------------------------------------------- 1 | for d in ./examples/* ; do 2 | cd $d 3 | yarn add lamina@latest 4 | cd ../../ 5 | done -------------------------------------------------------------------------------- /src/chunks/BlendModes.ts: -------------------------------------------------------------------------------- 1 | export default /* glsl */ ` 2 | vec4 lamina_blend_add(const in vec4 x, const in vec4 y, const in float opacity) { 3 | 4 | return vec4(min(x.xyz + y.xyz, 1.0) * opacity + x.xyz * (1.0 - opacity), x.a); 5 | 6 | } 7 | vec3 lamina_blend_alpha(const in vec3 x, const in vec3 y, const in float opacity) { 8 | 9 | return y * opacity + x * (1.0 - opacity); 10 | 11 | } 12 | 13 | vec4 lamina_blend_alpha(const in vec4 x, const in vec4 y, const in float opacity) { 14 | 15 | float a = min(y.a, opacity); 16 | 17 | return vec4(lamina_blend_alpha(x.rgb, y.rgb, a), x.a); 18 | 19 | } 20 | vec4 lamina_blend_average(const in vec4 x, const in vec4 y, const in float opacity) { 21 | 22 | return vec4((x.xyz + y.xyz) * 0.5 * opacity + x.xyz * (1.0 - opacity), x.a); 23 | 24 | } 25 | float lamina_blend_color_burn(const in float x, const in float y) { 26 | 27 | return (y == 0.0) ? y : max(1.0 - (1.0 - x) / y, 0.0); 28 | 29 | } 30 | 31 | vec4 lamina_blend_color_burn(const in vec4 x, const in vec4 y, const in float opacity) { 32 | 33 | vec4 z = vec4( 34 | lamina_blend_color_burn(x.r, y.r), 35 | lamina_blend_color_burn(x.g, y.g), 36 | lamina_blend_color_burn(x.b, y.b), 37 | lamina_blend_color_burn(x.a, y.a) 38 | ); 39 | 40 | return vec4(z.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 41 | 42 | } 43 | float lamina_blend_color_dodge(const in float x, const in float y) { 44 | 45 | return (y == 1.0) ? y : min(x / (1.0 - y), 1.0); 46 | 47 | } 48 | 49 | vec4 lamina_blend_color_dodge(const in vec4 x, const in vec4 y, const in float opacity) { 50 | 51 | vec4 z = vec4( 52 | lamina_blend_color_dodge(x.r, y.r), 53 | lamina_blend_color_dodge(x.g, y.g), 54 | lamina_blend_color_dodge(x.b, y.b), 55 | lamina_blend_color_dodge(x.a, y.a) 56 | ); 57 | 58 | return vec4(z.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 59 | 60 | } 61 | vec4 lamina_blend_darken(const in vec4 x, const in vec4 y, const in float opacity) { 62 | 63 | return vec4(min(x.xyz, y.xyz) * opacity + x.xyz * (1.0 - opacity), x.a); 64 | 65 | } 66 | vec4 lamina_blend_difference(const in vec4 x, const in vec4 y, const in float opacity) { 67 | 68 | return vec4(abs(x.xyz - y.xyz) * opacity + x.xyz * (1.0 - opacity), x.a); 69 | 70 | } 71 | float lamina_blend_divide(const in float x, const in float y) { 72 | 73 | return (y > 0.0) ? min(x / y, 1.0) : 1.0; 74 | 75 | } 76 | 77 | vec4 lamina_blend_divide(const in vec4 x, const in vec4 y, const in float opacity) { 78 | 79 | vec4 z = vec4( 80 | lamina_blend_divide(x.r, y.r), 81 | lamina_blend_divide(x.g, y.g), 82 | lamina_blend_divide(x.b, y.b), 83 | lamina_blend_divide(x.a, y.a) 84 | ); 85 | 86 | return vec4(z.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 87 | 88 | } 89 | vec4 lamina_blend_exclusion(const in vec4 x, const in vec4 y, const in float opacity) { 90 | 91 | return vec4((x.xyz + y.xyz - 2.0 * x.xyz * y.xyz) * opacity + x.xyz * (1.0 - opacity), x.a); 92 | 93 | } 94 | vec4 lamina_blend_lighten(const in vec4 x, const in vec4 y, const in float opacity) { 95 | 96 | return vec4(max(x.xyz, y.xyz) * opacity + x.xyz * (1.0 - opacity), x.a); 97 | 98 | } 99 | vec4 lamina_blend_multiply(const in vec4 x, const in vec4 y, const in float opacity) { 100 | 101 | return vec4( x.xyz * y.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 102 | 103 | } 104 | vec4 lamina_blend_negation(const in vec4 x, const in vec4 y, const in float opacity) { 105 | 106 | return vec4((1.0 - abs(1.0 - x.xyz - y.xyz)) * opacity + x.xyz * (1.0 - opacity), x.a); 107 | 108 | } 109 | vec4 lamina_blend_normal(const in vec4 x, const in vec4 y, const in float opacity) { 110 | 111 | return vec4(y.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 112 | 113 | } 114 | float lamina_blend_overlay(const in float x, const in float y) { 115 | 116 | return (x < 0.5) ? (2.0 * x * y) : (1.0 - 2.0 * (1.0 - x) * (1.0 - y)); 117 | 118 | } 119 | 120 | vec4 lamina_blend_overlay(const in vec4 x, const in vec4 y, const in float opacity) { 121 | 122 | vec4 z = vec4( 123 | lamina_blend_overlay(x.r, y.r), 124 | lamina_blend_overlay(x.g, y.g), 125 | lamina_blend_overlay(x.b, y.b), 126 | lamina_blend_overlay(x.a, y.a) 127 | ); 128 | 129 | return vec4(z.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 130 | 131 | } 132 | float lamina_blend_reflect(const in float x, const in float y) { 133 | 134 | return (y == 1.0) ? y : min(x * x / (1.0 - y), 1.0); 135 | 136 | } 137 | 138 | vec4 lamina_blend_reflect(const in vec4 x, const in vec4 y, const in float opacity) { 139 | 140 | vec4 z = vec4( 141 | lamina_blend_reflect(x.r, y.r), 142 | lamina_blend_reflect(x.g, y.g), 143 | lamina_blend_reflect(x.b, y.b), 144 | lamina_blend_reflect(x.a, y.a) 145 | ); 146 | 147 | return vec4(z.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 148 | 149 | } 150 | vec4 lamina_blend_screen(const in vec4 x, const in vec4 y, const in float opacity) { 151 | 152 | return vec4((1.0 - (1.0 - x.xyz) * (1.0 - y.xyz)) * opacity + x.xyz * (1.0 - opacity), x.a); 153 | 154 | } 155 | float lamina_blend_softlight(const in float x, const in float y) { 156 | 157 | return (y < 0.5) ? 158 | (2.0 * x * y + x * x * (1.0 - 2.0 * y)) : 159 | (sqrt(x) * (2.0 * y - 1.0) + 2.0 * x * (1.0 - y)); 160 | 161 | } 162 | 163 | vec4 lamina_blend_softlight(const in vec4 x, const in vec4 y, const in float opacity) { 164 | 165 | vec4 z = vec4( 166 | lamina_blend_softlight(x.r, y.r), 167 | lamina_blend_softlight(x.g, y.g), 168 | lamina_blend_softlight(x.b, y.b), 169 | lamina_blend_softlight(x.a, y.a) 170 | ); 171 | 172 | return vec4(z.xyz * opacity + x.xyz * (1.0 - opacity), x.a); 173 | 174 | } 175 | vec4 lamina_blend_subtract(const in vec4 x, const in vec4 y, const in float opacity) { 176 | 177 | return vec4(max(x.xyz + y.xyz - 1.0, 0.0) * opacity + x.xyz * (1.0 - opacity), x.a); 178 | 179 | } 180 | 181 | ` 182 | -------------------------------------------------------------------------------- /src/chunks/Helpers.ts: -------------------------------------------------------------------------------- 1 | export default /* glsl */ ` 2 | 3 | float lamina_map(float value, float min1, float max1, float min2, float max2) { 4 | return min2 + (value - min1) * (max2 - min2) / (max1 - min1); 5 | } 6 | 7 | float lamina_normalize(float v) { return lamina_map(v, -1.0, 1.0, 0.0, 1.0); } 8 | `; 9 | -------------------------------------------------------------------------------- /src/chunks/Noise.ts: -------------------------------------------------------------------------------- 1 | export default /* glsl */ ` 2 | 3 | // From: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 4 | // Huge thanks to the creators of these algorithms 5 | 6 | float lamina_noise_mod289(float x){return x - floor(x * (1.0 / 289.0)) * 289.0;} 7 | vec4 lamina_noise_mod289(vec4 x){return x - floor(x * (1.0 / 289.0)) * 289.0;} 8 | vec4 lamina_noise_perm(vec4 x){return lamina_noise_mod289(((x * 34.0) + 1.0) * x);} 9 | vec4 lamina_noise_permute(vec4 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); } 10 | vec4 lamina_noise_taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } 11 | 12 | 13 | float lamina_noise_white(vec2 p) { 14 | return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * 15 | (0.1 + abs(sin(p.y * 13.0 + p.x)))); 16 | } 17 | 18 | float lamina_noise_white(vec3 p) { 19 | return lamina_noise_white(p.xy); 20 | } 21 | 22 | 23 | vec3 lamina_noise_fade(vec3 t) { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); } 24 | 25 | float lamina_noise_perlin(vec3 P) { 26 | vec3 Pi0 = floor(P); // Integer part for indexing 27 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 28 | Pi0 = mod(Pi0, 289.0); 29 | Pi1 = mod(Pi1, 289.0); 30 | vec3 Pf0 = fract(P); // Fractional part for interpolation 31 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 32 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 33 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 34 | vec4 iz0 = Pi0.zzzz; 35 | vec4 iz1 = Pi1.zzzz; 36 | 37 | vec4 ixy = lamina_noise_permute(lamina_noise_permute(ix) + iy); 38 | vec4 ixy0 = lamina_noise_permute(ixy + iz0); 39 | vec4 ixy1 = lamina_noise_permute(ixy + iz1); 40 | 41 | vec4 gx0 = ixy0 / 7.0; 42 | vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5; 43 | gx0 = fract(gx0); 44 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 45 | vec4 sz0 = step(gz0, vec4(0.0)); 46 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 47 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 48 | 49 | vec4 gx1 = ixy1 / 7.0; 50 | vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5; 51 | gx1 = fract(gx1); 52 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 53 | vec4 sz1 = step(gz1, vec4(0.0)); 54 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 55 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 56 | 57 | vec3 g000 = vec3(gx0.x, gy0.x, gz0.x); 58 | vec3 g100 = vec3(gx0.y, gy0.y, gz0.y); 59 | vec3 g010 = vec3(gx0.z, gy0.z, gz0.z); 60 | vec3 g110 = vec3(gx0.w, gy0.w, gz0.w); 61 | vec3 g001 = vec3(gx1.x, gy1.x, gz1.x); 62 | vec3 g101 = vec3(gx1.y, gy1.y, gz1.y); 63 | vec3 g011 = vec3(gx1.z, gy1.z, gz1.z); 64 | vec3 g111 = vec3(gx1.w, gy1.w, gz1.w); 65 | 66 | vec4 norm0 = lamina_noise_taylorInvSqrt( 67 | vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 68 | g000 *= norm0.x; 69 | g010 *= norm0.y; 70 | g100 *= norm0.z; 71 | g110 *= norm0.w; 72 | vec4 norm1 = lamina_noise_taylorInvSqrt( 73 | vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 74 | g001 *= norm1.x; 75 | g011 *= norm1.y; 76 | g101 *= norm1.z; 77 | g111 *= norm1.w; 78 | 79 | float n000 = dot(g000, Pf0); 80 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 81 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 82 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 83 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 84 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 85 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 86 | float n111 = dot(g111, Pf1); 87 | 88 | vec3 fade_xyz = lamina_noise_fade(Pf0); 89 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), 90 | fade_xyz.z); 91 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 92 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 93 | return lamina_normalize(2.2 * n_xyz); 94 | } 95 | 96 | float lamina_noise_simplex(vec3 v) { 97 | const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); 98 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 99 | 100 | // First corner 101 | vec3 i = floor(v + dot(v, C.yyy)); 102 | vec3 x0 = v - i + dot(i, C.xxx); 103 | 104 | // Other corners 105 | vec3 g = step(x0.yzx, x0.xyz); 106 | vec3 l = 1.0 - g; 107 | vec3 i1 = min(g.xyz, l.zxy); 108 | vec3 i2 = max(g.xyz, l.zxy); 109 | 110 | // x0 = x0 - 0. + 0.0 * C 111 | vec3 x1 = x0 - i1 + 1.0 * C.xxx; 112 | vec3 x2 = x0 - i2 + 2.0 * C.xxx; 113 | vec3 x3 = x0 - 1. + 3.0 * C.xxx; 114 | 115 | // Permutations 116 | i = mod(i, 289.0); 117 | vec4 p = lamina_noise_permute(lamina_noise_permute(lamina_noise_permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + 118 | vec4(0.0, i1.y, i2.y, 1.0)) + 119 | i.x + vec4(0.0, i1.x, i2.x, 1.0)); 120 | 121 | // Gradients 122 | // ( N*N points uniformly over a square, mapped onto an octahedron.) 123 | float n_ = 1.0 / 7.0; // N=7 124 | vec3 ns = n_ * D.wyz - D.xzx; 125 | 126 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,N*N) 127 | 128 | vec4 x_ = floor(j * ns.z); 129 | vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) 130 | 131 | vec4 x = x_ * ns.x + ns.yyyy; 132 | vec4 y = y_ * ns.x + ns.yyyy; 133 | vec4 h = 1.0 - abs(x) - abs(y); 134 | 135 | vec4 b0 = vec4(x.xy, y.xy); 136 | vec4 b1 = vec4(x.zw, y.zw); 137 | 138 | vec4 s0 = floor(b0) * 2.0 + 1.0; 139 | vec4 s1 = floor(b1) * 2.0 + 1.0; 140 | vec4 sh = -step(h, vec4(0.0)); 141 | 142 | vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; 143 | vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; 144 | 145 | vec3 p0 = vec3(a0.xy, h.x); 146 | vec3 p1 = vec3(a0.zw, h.y); 147 | vec3 p2 = vec3(a1.xy, h.z); 148 | vec3 p3 = vec3(a1.zw, h.w); 149 | 150 | // Normalise gradients 151 | vec4 norm = 152 | lamina_noise_taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); 153 | p0 *= norm.x; 154 | p1 *= norm.y; 155 | p2 *= norm.z; 156 | p3 *= norm.w; 157 | 158 | // Mix final noise value 159 | vec4 m = 160 | max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); 161 | m = m * m; 162 | return lamina_normalize(42.0 * 163 | dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)))); 164 | } 165 | 166 | vec3 lamina_noise_simplex3(vec3 x) { 167 | float s = lamina_noise_simplex(vec3(x)); 168 | float s1 = lamina_noise_simplex(vec3(x.y - 19.1, x.z + 33.4, x.x + 47.2)); 169 | float s2 = lamina_noise_simplex(vec3(x.z + 74.2, x.x - 124.5, x.y + 99.4)); 170 | vec3 c = vec3(s, s1, s2); 171 | return c; 172 | } 173 | 174 | vec3 lamina_noise_curl(vec3 p) { 175 | const float e = .1; 176 | vec3 dx = vec3(e, 0.0, 0.0); 177 | vec3 dy = vec3(0.0, e, 0.0); 178 | vec3 dz = vec3(0.0, 0.0, e); 179 | 180 | vec3 p_x0 = lamina_noise_simplex3(p - dx); 181 | vec3 p_x1 = lamina_noise_simplex3(p + dx); 182 | vec3 p_y0 = lamina_noise_simplex3(p - dy); 183 | vec3 p_y1 = lamina_noise_simplex3(p + dy); 184 | vec3 p_z0 = lamina_noise_simplex3(p - dz); 185 | vec3 p_z1 = lamina_noise_simplex3(p + dz); 186 | 187 | float x = p_y1.z - p_y0.z - p_z1.y + p_z0.y; 188 | float y = p_z1.x - p_z0.x - p_x1.z + p_x0.z; 189 | float z = p_x1.y - p_x0.y - p_y1.x + p_y0.x; 190 | 191 | const float divisor = 1.0 / (2.0 * e); 192 | return normalize(vec3(x, y, z) * divisor); 193 | } 194 | 195 | vec3 lamina_permute(vec3 x) { 196 | return mod((34.0 * x + 1.0) * x, 289.0); 197 | } 198 | 199 | vec3 lamina_dist(vec3 x, vec3 y, vec3 z, bool manhattanDistance) { 200 | return manhattanDistance ? abs(x) + abs(y) + abs(z) : (x * x + y * y + z * z); 201 | } 202 | 203 | // From: https://github.com/Erkaman/glsl-worley 204 | float lamina_noise_worley(vec3 P) { 205 | float jitter = 1.; 206 | bool manhattanDistance = false; 207 | 208 | float K = 0.142857142857; // 1/7 209 | float Ko = 0.428571428571; // 1/2-K/2 210 | float K2 = 0.020408163265306; // 1/(7*7) 211 | float Kz = 0.166666666667; // 1/6 212 | float Kzo = 0.416666666667; // 1/2-1/6*2 213 | 214 | vec3 Pi = mod(floor(P), 289.0); 215 | vec3 Pf = fract(P) - 0.5; 216 | 217 | vec3 Pfx = Pf.x + vec3(1.0, 0.0, -1.0); 218 | vec3 Pfy = Pf.y + vec3(1.0, 0.0, -1.0); 219 | vec3 Pfz = Pf.z + vec3(1.0, 0.0, -1.0); 220 | 221 | vec3 p = lamina_permute(Pi.x + vec3(-1.0, 0.0, 1.0)); 222 | vec3 p1 = lamina_permute(p + Pi.y - 1.0); 223 | vec3 p2 = lamina_permute(p + Pi.y); 224 | vec3 p3 = lamina_permute(p + Pi.y + 1.0); 225 | 226 | vec3 p11 = lamina_permute(p1 + Pi.z - 1.0); 227 | vec3 p12 = lamina_permute(p1 + Pi.z); 228 | vec3 p13 = lamina_permute(p1 + Pi.z + 1.0); 229 | 230 | vec3 p21 = lamina_permute(p2 + Pi.z - 1.0); 231 | vec3 p22 = lamina_permute(p2 + Pi.z); 232 | vec3 p23 = lamina_permute(p2 + Pi.z + 1.0); 233 | 234 | vec3 p31 = lamina_permute(p3 + Pi.z - 1.0); 235 | vec3 p32 = lamina_permute(p3 + Pi.z); 236 | vec3 p33 = lamina_permute(p3 + Pi.z + 1.0); 237 | 238 | vec3 ox11 = fract(p11*K) - Ko; 239 | vec3 oy11 = mod(floor(p11*K), 7.0)*K - Ko; 240 | vec3 oz11 = floor(p11*K2)*Kz - Kzo; // p11 < 289 guaranteed 241 | 242 | vec3 ox12 = fract(p12*K) - Ko; 243 | vec3 oy12 = mod(floor(p12*K), 7.0)*K - Ko; 244 | vec3 oz12 = floor(p12*K2)*Kz - Kzo; 245 | 246 | vec3 ox13 = fract(p13*K) - Ko; 247 | vec3 oy13 = mod(floor(p13*K), 7.0)*K - Ko; 248 | vec3 oz13 = floor(p13*K2)*Kz - Kzo; 249 | 250 | vec3 ox21 = fract(p21*K) - Ko; 251 | vec3 oy21 = mod(floor(p21*K), 7.0)*K - Ko; 252 | vec3 oz21 = floor(p21*K2)*Kz - Kzo; 253 | 254 | vec3 ox22 = fract(p22*K) - Ko; 255 | vec3 oy22 = mod(floor(p22*K), 7.0)*K - Ko; 256 | vec3 oz22 = floor(p22*K2)*Kz - Kzo; 257 | 258 | vec3 ox23 = fract(p23*K) - Ko; 259 | vec3 oy23 = mod(floor(p23*K), 7.0)*K - Ko; 260 | vec3 oz23 = floor(p23*K2)*Kz - Kzo; 261 | 262 | vec3 ox31 = fract(p31*K) - Ko; 263 | vec3 oy31 = mod(floor(p31*K), 7.0)*K - Ko; 264 | vec3 oz31 = floor(p31*K2)*Kz - Kzo; 265 | 266 | vec3 ox32 = fract(p32*K) - Ko; 267 | vec3 oy32 = mod(floor(p32*K), 7.0)*K - Ko; 268 | vec3 oz32 = floor(p32*K2)*Kz - Kzo; 269 | 270 | vec3 ox33 = fract(p33*K) - Ko; 271 | vec3 oy33 = mod(floor(p33*K), 7.0)*K - Ko; 272 | vec3 oz33 = floor(p33*K2)*Kz - Kzo; 273 | 274 | vec3 dx11 = Pfx + jitter*ox11; 275 | vec3 dy11 = Pfy.x + jitter*oy11; 276 | vec3 dz11 = Pfz.x + jitter*oz11; 277 | 278 | vec3 dx12 = Pfx + jitter*ox12; 279 | vec3 dy12 = Pfy.x + jitter*oy12; 280 | vec3 dz12 = Pfz.y + jitter*oz12; 281 | 282 | vec3 dx13 = Pfx + jitter*ox13; 283 | vec3 dy13 = Pfy.x + jitter*oy13; 284 | vec3 dz13 = Pfz.z + jitter*oz13; 285 | 286 | vec3 dx21 = Pfx + jitter*ox21; 287 | vec3 dy21 = Pfy.y + jitter*oy21; 288 | vec3 dz21 = Pfz.x + jitter*oz21; 289 | 290 | vec3 dx22 = Pfx + jitter*ox22; 291 | vec3 dy22 = Pfy.y + jitter*oy22; 292 | vec3 dz22 = Pfz.y + jitter*oz22; 293 | 294 | vec3 dx23 = Pfx + jitter*ox23; 295 | vec3 dy23 = Pfy.y + jitter*oy23; 296 | vec3 dz23 = Pfz.z + jitter*oz23; 297 | 298 | vec3 dx31 = Pfx + jitter*ox31; 299 | vec3 dy31 = Pfy.z + jitter*oy31; 300 | vec3 dz31 = Pfz.x + jitter*oz31; 301 | 302 | vec3 dx32 = Pfx + jitter*ox32; 303 | vec3 dy32 = Pfy.z + jitter*oy32; 304 | vec3 dz32 = Pfz.y + jitter*oz32; 305 | 306 | vec3 dx33 = Pfx + jitter*ox33; 307 | vec3 dy33 = Pfy.z + jitter*oy33; 308 | vec3 dz33 = Pfz.z + jitter*oz33; 309 | 310 | vec3 d11 = lamina_dist(dx11, dy11, dz11, manhattanDistance); 311 | vec3 d12 = lamina_dist(dx12, dy12, dz12, manhattanDistance); 312 | vec3 d13 = lamina_dist(dx13, dy13, dz13, manhattanDistance); 313 | vec3 d21 = lamina_dist(dx21, dy21, dz21, manhattanDistance); 314 | vec3 d22 = lamina_dist(dx22, dy22, dz22, manhattanDistance); 315 | vec3 d23 = lamina_dist(dx23, dy23, dz23, manhattanDistance); 316 | vec3 d31 = lamina_dist(dx31, dy31, dz31, manhattanDistance); 317 | vec3 d32 = lamina_dist(dx32, dy32, dz32, manhattanDistance); 318 | vec3 d33 = lamina_dist(dx33, dy33, dz33, manhattanDistance); 319 | 320 | vec3 d1a = min(d11, d12); 321 | d12 = max(d11, d12); 322 | d11 = min(d1a, d13); // Smallest now not in d12 or d13 323 | d13 = max(d1a, d13); 324 | d12 = min(d12, d13); // 2nd smallest now not in d13 325 | vec3 d2a = min(d21, d22); 326 | d22 = max(d21, d22); 327 | d21 = min(d2a, d23); // Smallest now not in d22 or d23 328 | d23 = max(d2a, d23); 329 | d22 = min(d22, d23); // 2nd smallest now not in d23 330 | vec3 d3a = min(d31, d32); 331 | d32 = max(d31, d32); 332 | d31 = min(d3a, d33); // Smallest now not in d32 or d33 333 | d33 = max(d3a, d33); 334 | d32 = min(d32, d33); // 2nd smallest now not in d33 335 | vec3 da = min(d11, d21); 336 | d21 = max(d11, d21); 337 | d11 = min(da, d31); // Smallest now in d11 338 | d31 = max(da, d31); // 2nd smallest now not in d31 339 | d11.xy = (d11.x < d11.y) ? d11.xy : d11.yx; 340 | d11.xz = (d11.x < d11.z) ? d11.xz : d11.zx; // d11.x now smallest 341 | d12 = min(d12, d21); // 2nd smallest now not in d21 342 | d12 = min(d12, d22); // nor in d22 343 | d12 = min(d12, d31); // nor in d31 344 | d12 = min(d12, d32); // nor in d32 345 | d11.yz = min(d11.yz,d12.xy); // nor in d12.yz 346 | d11.y = min(d11.y,d12.z); // Only two more to go 347 | d11.y = min(d11.y,d11.z); // Done! (Phew!) 348 | 349 | vec2 F = sqrt(d11.xy); 350 | return F.x; // F1, F2 351 | 352 | } 353 | 354 | float lamina_noise_swirl(vec3 position) { 355 | float scale = 0.1; 356 | float freq = 4. * scale; 357 | float t = 1.; 358 | 359 | vec3 pos = (position * scale) + lamina_noise_curl(position * 7. * scale); 360 | 361 | float worley1 = 1. - lamina_noise_worley((pos * (freq * 2.)) + (t * 2.)); 362 | float worley2 = 1. - lamina_noise_worley((pos * (freq * 4.)) + (t * 4.)); 363 | float worley3 = 1. - lamina_noise_worley((pos * (freq * 8.)) + (t * 8.)); 364 | float worley4 = 1. - lamina_noise_worley((pos * (freq * 16.)) + (t * 16.)); 365 | 366 | float fbm1 = worley1 * .625 + worley2 * .25 + worley3 * .125; 367 | float fbm2 = worley2 * .625 + worley3 * .25 + worley4 * .125; 368 | float fbm3 = worley3 * .75 + worley4 * .25; 369 | 370 | vec3 curlWorleyFbm = vec3(fbm1, fbm2, fbm3); 371 | float curlWorley = curlWorleyFbm.r * .625 + curlWorleyFbm.g * .25 + 372 | curlWorleyFbm.b * .125; 373 | 374 | return curlWorley; 375 | } 376 | 377 | 378 | `; 379 | -------------------------------------------------------------------------------- /src/core/Abstract.ts: -------------------------------------------------------------------------------- 1 | import { getSpecialParameters, getUniform, isSerializableType, serializeProp } from '../utils/Functions' 2 | import { Color, IUniform, MathUtils, Texture, Vector3 } from 'three' 3 | import { BlendMode, BlendModes, LayerProps, SerializedLayer } from '../types' 4 | 5 | // @ts-ignore 6 | import tokenize from 'glsl-tokenizer' 7 | // @ts-ignore 8 | import descope from 'glsl-token-descope' 9 | // @ts-ignore 10 | import stringify from 'glsl-token-string' 11 | // @ts-ignore 12 | import tokenFunctions from 'glsl-token-functions' 13 | 14 | export default class Abstract { 15 | uuid: string = MathUtils.generateUUID().replace(/-/g, '_') 16 | name: string = 'LayerMaterial' 17 | mode: BlendMode = 'normal' 18 | visible: boolean = true 19 | uniforms: { 20 | [key: string]: IUniform 21 | } 22 | 23 | onParse?: (self: Abstract & any) => void 24 | 25 | fragmentShader: string 26 | vertexShader: string 27 | vertexVariables: string 28 | fragmentVariables: string 29 | 30 | schema: { 31 | value: any 32 | label: any 33 | options?: any[] 34 | }[] 35 | 36 | constructor(c: new () => Abstract, props?: LayerProps | null, onParse?: (self: Abstract & any) => void) { 37 | const defaults = Object.getOwnPropertyNames(c).filter((e) => e.startsWith('u_')) 38 | const uniforms: { [key: string]: any } = defaults.reduce((a, v) => { 39 | let value = Object.getOwnPropertyDescriptor(c, v)?.value 40 | 41 | if (isSerializableType(value) || value instanceof Color) value = value.clone() 42 | 43 | return { 44 | ...a, 45 | [v.slice(1)]: value, 46 | } 47 | }, {}) 48 | 49 | for (const key in uniforms) { 50 | const propName = key.split('_')[1] 51 | if (props?.[propName] !== undefined) uniforms[key] = props[propName] 52 | } 53 | 54 | if (props) { 55 | Object.keys(props).map((key) => { 56 | if (props[key] !== undefined) { 57 | // @ts-ignore 58 | this[key] = props[key] 59 | } 60 | }) 61 | } 62 | 63 | this.uniforms = {} 64 | this.schema = [] 65 | const properties: PropertyDescriptorMap & ThisType = {} 66 | Object.keys(uniforms).map((key) => { 67 | const propName = key.split('_')[1] 68 | 69 | this.uniforms[`u_${this.uuid}_${propName}`] = { 70 | value: getUniform(uniforms[key]), 71 | } 72 | 73 | this.schema.push({ 74 | value: uniforms[key], 75 | label: propName, 76 | }) 77 | 78 | properties[propName] = { 79 | set: (v: any) => { 80 | this.uniforms[`u_${this.uuid}_${propName}`].value = getUniform(v) 81 | }, 82 | get: () => { 83 | return this.uniforms[`u_${this.uuid}_${propName}`].value 84 | }, 85 | } 86 | }) 87 | 88 | if (props?.name) this.name = props.name 89 | if (props?.mode) this.mode = props.mode 90 | if (props?.visible) this.visible = props.visible 91 | 92 | Object.defineProperties(this, properties) 93 | 94 | this.vertexShader = '' 95 | this.fragmentShader = '' 96 | this.vertexVariables = '' 97 | this.fragmentVariables = '' 98 | this.onParse = onParse 99 | 100 | this.buildShaders(c) 101 | 102 | // Remove Name field from Debugger until a way to 103 | // rename Leva folders is found 104 | // this.schema.push({ 105 | // value: this.name, 106 | // label: 'name', 107 | // }) 108 | this.schema.push({ 109 | value: this.mode, 110 | label: 'mode', 111 | options: Object.values(BlendModes), 112 | }) 113 | this.schema.push({ 114 | value: this.visible, 115 | label: 'visible', 116 | }) 117 | } 118 | 119 | buildShaders(constructor: any) { 120 | const shaders = Object.getOwnPropertyNames(constructor) 121 | .filter((e) => e === 'fragmentShader' || e === 'vertexShader') 122 | .reduce( 123 | (a, v) => ({ 124 | ...a, 125 | [v]: Object.getOwnPropertyDescriptor(constructor, v)?.value, 126 | }), 127 | {} 128 | ) as { 129 | fragmentShader: string 130 | vertexShader: string 131 | } 132 | 133 | const tokens = { 134 | vert: tokenize(shaders.vertexShader || ''), 135 | frag: tokenize(shaders.fragmentShader || ''), 136 | } 137 | 138 | const descoped = { 139 | vert: descope(tokens.vert, this.renameTokens.bind(this)), 140 | frag: descope(tokens.frag, this.renameTokens.bind(this)), 141 | } 142 | 143 | const funcs = { 144 | vert: tokenFunctions(descoped.vert), 145 | frag: tokenFunctions(descoped.frag), 146 | } 147 | 148 | const mainIndex = { 149 | vert: funcs.vert 150 | .map((e: any) => { 151 | return e.name 152 | }) 153 | .indexOf('main'), 154 | frag: funcs.frag 155 | .map((e: any) => { 156 | return e.name 157 | }) 158 | .indexOf('main'), 159 | } 160 | 161 | const variables = { 162 | vert: mainIndex.vert >= 0 ? stringify(descoped.vert.slice(0, funcs.vert[mainIndex.vert].outer[0])) : '', 163 | frag: mainIndex.frag >= 0 ? stringify(descoped.frag.slice(0, funcs.frag[mainIndex.frag].outer[0])) : '', 164 | } 165 | 166 | const funcBodies = { 167 | vert: mainIndex.vert >= 0 ? this.getShaderFromIndex(descoped.vert, funcs.vert[mainIndex.vert].body) : '', 168 | frag: mainIndex.frag >= 0 ? this.getShaderFromIndex(descoped.frag, funcs.frag[mainIndex.frag].body) : '', 169 | } 170 | 171 | this.vertexShader = this.processFinal(funcBodies.vert, true) 172 | this.fragmentShader = this.processFinal(funcBodies.frag) 173 | this.vertexVariables = variables.vert 174 | this.fragmentVariables = variables.frag 175 | 176 | this.onParse?.(this) 177 | this.schema = this.schema.filter((value, index) => { 178 | const _value = value.label 179 | return ( 180 | index === 181 | this.schema.findIndex((obj) => { 182 | return obj.label === _value 183 | }) 184 | ) 185 | }) 186 | } 187 | 188 | renameTokens(name: string) { 189 | if (name.startsWith('u_')) { 190 | const slice = name.slice(2) 191 | return `u_${this.uuid}_${slice}` 192 | } else if (name.startsWith('v_')) { 193 | const slice = name.slice(2) 194 | return `v_${this.uuid}_${slice}` 195 | } else if (name.startsWith('f_')) { 196 | const slice = name.slice(2) 197 | return `f_${this.uuid}_${slice}` 198 | } else { 199 | return name 200 | } 201 | } 202 | 203 | processFinal(shader: string, isVertex?: boolean) { 204 | const s: string = shader.replace(/\sf_/gm, ` f_${this.uuid}_`).replace(/\(f_/gm, `(f_${this.uuid}_`) 205 | 206 | const returnValue = s.match(/^.*return.*$/gm) 207 | let sReplaced = s.replace(/^.*return.*$/gm, '') 208 | 209 | if (returnValue?.[0]) { 210 | const returnVariable = returnValue[0].replace('return', '').trim().replace(';', '') 211 | 212 | const blendMode = this.getBlendMode(returnVariable, 'lamina_finalColor') 213 | sReplaced += isVertex ? `lamina_finalPosition = ${returnVariable};` : `lamina_finalColor = ${blendMode};` 214 | } 215 | 216 | return sReplaced 217 | } 218 | 219 | getShaderFromIndex(tokens: any, index: number[]) { 220 | return stringify(tokens.slice(index[0], index[1])) 221 | } 222 | 223 | getBlendMode(b: string, a: string) { 224 | switch (this.mode) { 225 | default: 226 | case 'normal': 227 | return `lamina_blend_alpha(${a}, ${b}, ${b}.a)` 228 | case 'add': 229 | return `lamina_blend_add(${a}, ${b}, ${b}.a)` 230 | case 'subtract': 231 | return `lamina_blend_subtract(${a}, ${b}, ${b}.a)` 232 | case 'multiply': 233 | return `lamina_blend_multiply(${a}, ${b}, ${b}.a)` 234 | case 'lighten': 235 | return `lamina_blend_lighten(${a}, ${b}, ${b}.a)` 236 | case 'darken': 237 | return `lamina_blend_darken(${a}, ${b}, ${b}.a)` 238 | case 'divide': 239 | return `lamina_blend_divide(${a}, ${b}, ${b}.a)` 240 | case 'overlay': 241 | return `lamina_blend_overlay(${a}, ${b}, ${b}.a)` 242 | case 'screen': 243 | return `lamina_blend_screen(${a}, ${b}, ${b}.a)` 244 | case 'softlight': 245 | return `lamina_blend_softlight(${a}, ${b}, ${b}.a)` 246 | case 'reflect': 247 | return `lamina_blend_reflect(${a}, ${b}, ${b}.a)` 248 | case 'negation': 249 | return `lamina_blend_negation(${a}, ${b}, ${b}.a)` 250 | } 251 | } 252 | 253 | getSchema() { 254 | const latestSchema = this.schema.map(({ label, options, ...rest }) => { 255 | return { 256 | label, 257 | options, 258 | ...getSpecialParameters(label), 259 | ...rest, 260 | // @ts-ignore 261 | value: serializeProp(this[label]), 262 | } 263 | }) 264 | 265 | return latestSchema 266 | } 267 | 268 | serialize(): SerializedLayer { 269 | const name = this.constructor.name.split('$')[0] 270 | let nonUniformPropKeys = Object.keys(this) 271 | nonUniformPropKeys = nonUniformPropKeys.filter( 272 | (e) => 273 | ![ 274 | 'uuid', 275 | 'uniforms', 276 | 'schema', 277 | 'fragmentShader', 278 | 'vertexShader', 279 | 'fragmentVariables', 280 | 'vertexVariables', 281 | 'attribs', 282 | 'events', 283 | '__r3f', 284 | 'onParse', 285 | ].includes(e) 286 | ) 287 | const nonUniformProps = {} 288 | nonUniformPropKeys.forEach((k) => { 289 | // @ts-ignore 290 | nonUniformProps[k] = this[k] 291 | }) 292 | 293 | const props: { [key: string]: any } = {} 294 | for (const key in this.uniforms) { 295 | const name = key.replace(`u_${this.uuid}_`, '') 296 | props[name] = serializeProp(this.uniforms[key].value) 297 | } 298 | 299 | return { 300 | constructor: name, 301 | properties: { 302 | ...props, 303 | ...nonUniformProps, 304 | }, 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/core/Color.ts: -------------------------------------------------------------------------------- 1 | import { ColorProps } from '../types' 2 | import Abstract from './Abstract' 3 | 4 | export default class Color extends Abstract { 5 | static u_color = 'red' 6 | static u_alpha = 1 7 | 8 | static fragmentShader = ` 9 | uniform vec3 u_color; 10 | uniform float u_alpha; 11 | 12 | void main() { 13 | return vec4(u_color, u_alpha); 14 | } 15 | ` 16 | 17 | constructor(props?: ColorProps) { 18 | super(Color, { 19 | name: 'Color', 20 | ...props, 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/core/Depth.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three' 2 | import { DepthProps } from '../types' 3 | import Abstract from './Abstract' 4 | 5 | type AbstractExtended = Abstract & { 6 | mapping: DepthProps['mapping'] 7 | } 8 | 9 | export default class Depth extends Abstract { 10 | static u_near = 2 11 | static u_far = 10 12 | static u_origin = new Vector3(0, 0, 0) 13 | static u_colorA = 'white' 14 | static u_colorB = 'black' 15 | static u_alpha = 1 16 | 17 | static vertexShader = ` 18 | varying vec3 v_worldPosition; 19 | varying vec3 v_position; 20 | 21 | void main() { 22 | v_worldPosition = (vec4(position, 1.0) * modelMatrix).xyz; 23 | v_position = position; 24 | } 25 | ` 26 | 27 | static fragmentShader = ` 28 | uniform float u_alpha; 29 | uniform float u_near; 30 | uniform float u_far; 31 | uniform float u_isVector; 32 | uniform vec3 u_origin; 33 | uniform vec3 u_colorA; 34 | uniform vec3 u_colorB; 35 | 36 | varying vec3 v_worldPosition; 37 | varying vec3 v_position; 38 | 39 | void main() { 40 | float f_dist = lamina_mapping_template; 41 | float f_depth = (f_dist - u_near) / (u_far - u_near); 42 | vec3 f_depthColor = mix(u_colorB, u_colorA, 1.0 - clamp(f_depth, 0., 1.)); 43 | 44 | 45 | return vec4(f_depthColor, u_alpha); 46 | } 47 | ` 48 | 49 | mapping: 'vector' | 'world' | 'camera' = 'vector' 50 | 51 | constructor(props?: DepthProps) { 52 | super( 53 | Depth, 54 | { 55 | name: 'Depth', 56 | ...props, 57 | }, 58 | (self: Depth) => { 59 | self.schema.push({ 60 | value: self.mapping, 61 | label: 'mapping', 62 | options: ['vector', 'world', 'camera'], 63 | }) 64 | 65 | const mapping = Depth.getMapping(self.uuid, self.mapping) 66 | 67 | self.fragmentShader = self.fragmentShader.replace('lamina_mapping_template', mapping) 68 | } 69 | ) 70 | } 71 | 72 | private static getMapping(uuid: string, type?: string) { 73 | switch (type) { 74 | default: 75 | case 'vector': 76 | return `length(v_${uuid}_worldPosition - u_${uuid}_origin)` 77 | case 'world': 78 | return `length(v_${uuid}_position - vec3(0.))` 79 | case 'camera': 80 | return `length(v_${uuid}_worldPosition - cameraPosition)` 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/core/Displace.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three' 2 | import { ColorProps, DisplaceProps, MappingType, MappingTypes, NoiseProps, NoiseType, NoiseTypes } from '../types' 3 | import Abstract from './Abstract' 4 | 5 | type AbstractExtended = Abstract & { 6 | type: NoiseType 7 | mapping: MappingType 8 | } 9 | 10 | export default class Displace extends Abstract { 11 | static u_strength = 1 12 | static u_scale = 1 13 | static u_offset = new Vector3(0, 0, 0) 14 | 15 | static vertexShader = ` 16 | 17 | uniform float u_strength; 18 | uniform float u_scale; 19 | uniform vec3 u_offset; 20 | 21 | vec3 displace(vec3 p) { 22 | vec3 f_position = lamina_mapping_template; 23 | float f_n = lamina_noise_template((f_position + u_offset) * u_scale) * u_strength; 24 | vec3 f_newPosition = p + (f_n * normal); 25 | 26 | return f_newPosition; 27 | } 28 | 29 | 30 | vec3 orthogonal(vec3 v) { 31 | return normalize(abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0) 32 | : vec3(0.0, -v.z, v.y)); 33 | } 34 | vec3 recalcNormals(vec3 newPos) { 35 | float offset = 0.001; 36 | vec3 tangent = orthogonal(normal); 37 | vec3 bitangent = normalize(cross(normal, tangent)); 38 | vec3 neighbour1 = position + tangent * offset; 39 | vec3 neighbour2 = position + bitangent * offset; 40 | vec3 displacedNeighbour1 = displace(neighbour1); 41 | vec3 displacedNeighbour2 = displace(neighbour2); 42 | vec3 displacedTangent = displacedNeighbour1 - newPos; 43 | vec3 displacedBitangent = displacedNeighbour2 - newPos; 44 | return normalize(cross(displacedTangent, displacedBitangent)); 45 | } 46 | 47 | 48 | void main() { 49 | 50 | vec3 f_newPosition = displace(position); 51 | lamina_finalNormal = recalcNormals(f_newPosition); 52 | 53 | return f_newPosition; 54 | } 55 | ` 56 | 57 | type: NoiseType = 'perlin' 58 | mapping: MappingType = 'local' 59 | 60 | constructor(props?: DisplaceProps) { 61 | super( 62 | Displace, 63 | { 64 | name: 'Displace', 65 | ...props, 66 | }, 67 | (self: Displace) => { 68 | self.schema.push({ 69 | value: self.type, 70 | label: 'type', 71 | options: Object.values(NoiseTypes), 72 | }) 73 | 74 | self.schema.push({ 75 | value: self.mapping, 76 | label: 'mapping', 77 | options: Object.values(MappingTypes), 78 | }) 79 | 80 | const noiseFunc = Displace.getNoiseFunction(self.type) 81 | const mapping = Displace.getMapping(self.mapping) 82 | 83 | self.vertexVariables = self.vertexVariables.replace('lamina_mapping_template', mapping) 84 | self.vertexVariables = self.vertexVariables.replace('lamina_noise_template', noiseFunc) 85 | } 86 | ) 87 | } 88 | 89 | private static getNoiseFunction(type?: string) { 90 | switch (type) { 91 | default: 92 | case 'perlin': 93 | return `lamina_noise_perlin` 94 | case 'simplex': 95 | return `lamina_noise_simplex` 96 | case 'cell': 97 | return `lamina_noise_worley` 98 | case 'white': 99 | return `lamina_noise_white` 100 | case 'curl': 101 | return `lamina_noise_swirl` 102 | } 103 | } 104 | 105 | private static getMapping(type?: string) { 106 | switch (type) { 107 | default: 108 | case 'local': 109 | return `p` 110 | case 'world': 111 | return `(modelMatrix * vec4(p,1.0)).xyz` 112 | case 'uv': 113 | return `vec3(uv, 0.)` 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/core/Fresnel.ts: -------------------------------------------------------------------------------- 1 | import { FresnelProps } from '../types' 2 | import Abstract from './Abstract' 3 | 4 | export default class Fresnel extends Abstract { 5 | static u_color = 'white' 6 | static u_alpha = 1 7 | static u_bias = 0 8 | static u_intensity = 1 9 | static u_power = 2 10 | static u_factor = 1 11 | 12 | static vertexShader = ` 13 | varying vec3 v_worldPosition; 14 | varying vec3 v_worldNormal; 15 | 16 | void main() { 17 | v_worldPosition = vec3(-viewMatrix[0][2], -viewMatrix[1][2], -viewMatrix[2][2]); 18 | v_worldNormal = normalize( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal ); 19 | 20 | } 21 | ` 22 | 23 | static fragmentShader = ` 24 | uniform vec3 u_color; 25 | uniform float u_alpha; 26 | uniform float u_bias; 27 | uniform float u_intensity; 28 | uniform float u_power; 29 | uniform float u_factor; 30 | 31 | varying vec3 v_worldPosition; 32 | varying vec3 v_worldNormal; 33 | 34 | void main() { 35 | float f_a = (u_factor + dot(v_worldPosition, v_worldNormal)); 36 | float f_fresnel = u_bias + u_intensity * pow(abs(f_a), u_power); 37 | 38 | f_fresnel = clamp(f_fresnel, 0.0, 1.0); 39 | return vec4(f_fresnel * u_color, u_alpha); 40 | } 41 | ` 42 | 43 | constructor(props?: FresnelProps) { 44 | super(Fresnel, { 45 | name: 'Fresnel', 46 | ...props, 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/core/Gradient.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three' 2 | import { GradientProps, MappingType, MappingTypes } from '../types' 3 | import Abstract from './Abstract' 4 | 5 | export default class Gradient extends Abstract { 6 | static u_colorA = 'white' 7 | static u_colorB = 'black' 8 | static u_alpha = 1 9 | 10 | static u_start = 1 11 | static u_end = -1 12 | static u_contrast = 1 13 | 14 | static vertexShader = ` 15 | varying vec3 v_position; 16 | 17 | vod main() { 18 | v_position = lamina_mapping_template; 19 | } 20 | ` 21 | 22 | static fragmentShader = ` 23 | uniform vec3 u_colorA; 24 | uniform vec3 u_colorB; 25 | uniform vec3 u_axis; 26 | uniform float u_alpha; 27 | uniform float u_start; 28 | uniform float u_end; 29 | uniform float u_contrast; 30 | 31 | varying vec3 v_position; 32 | 33 | void main() { 34 | 35 | float f_step = smoothstep(u_start, u_end, v_position.axes_template * u_contrast); 36 | vec3 f_color = mix(u_colorA, u_colorB, f_step); 37 | 38 | return vec4(f_color, u_alpha); 39 | } 40 | ` 41 | 42 | axes: 'x' | 'y' | 'z' = 'x' 43 | mapping: MappingType = 'local' 44 | 45 | constructor(props?: GradientProps) { 46 | super( 47 | Gradient, 48 | { 49 | name: 'Gradient', 50 | ...props, 51 | }, 52 | (self: Gradient) => { 53 | self.schema.push({ 54 | value: self.axes, 55 | label: 'axes', 56 | options: ['x', 'y', 'z'], 57 | }) 58 | 59 | self.schema.push({ 60 | value: self.mapping, 61 | label: 'mapping', 62 | options: Object.values(MappingTypes), 63 | }) 64 | 65 | const mapping = Gradient.getMapping(self.mapping) 66 | 67 | self.vertexShader = self.vertexShader.replace('lamina_mapping_template', mapping || 'local') 68 | self.fragmentShader = self.fragmentShader.replace('axes_template', self.axes || 'x') 69 | } 70 | ) 71 | } 72 | 73 | private static getMapping(type?: string) { 74 | switch (type) { 75 | default: 76 | case 'local': 77 | return `position` 78 | case 'world': 79 | return `(modelMatrix * vec4(position,1.0)).xyz` 80 | case 'uv': 81 | return `vec3(uv, 0.)` 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/core/Matcap.ts: -------------------------------------------------------------------------------- 1 | import { MatcapProps } from "../types"; 2 | import Abstract from "./Abstract"; 3 | 4 | // Credits: https://www.clicktorelease.com/blog/creating-spherical-environment-mapping-shader/ 5 | 6 | export default class Matcap extends Abstract { 7 | static u_alpha = 1; 8 | static u_map = undefined; 9 | 10 | static vertexShader = ` 11 | varying vec3 v_position; 12 | varying vec3 v_normal; 13 | 14 | void main() { 15 | v_position = normalize( vec3( modelViewMatrix * vec4( position, 1.0 ) ) ); 16 | v_normal = normalize( normalMatrix * normal ); 17 | } 18 | `; 19 | 20 | static fragmentShader = ` 21 | uniform sampler2D u_map; 22 | uniform float u_alpha; 23 | varying vec3 v_position; 24 | varying vec3 v_normal; 25 | 26 | 27 | void main() { 28 | vec3 f_r = reflect( v_position, v_normal ); 29 | float f_m = 2. * sqrt( pow( f_r.x, 2. ) + pow( f_r.y, 2. ) + pow( f_r.z + 1., 2. ) ); 30 | vec2 f_vN = f_r.xy / f_m + .5; 31 | 32 | vec3 f_base = texture2D(u_map, f_vN).rgb; 33 | 34 | return vec4(f_base, u_alpha); 35 | } 36 | `; 37 | 38 | constructor(props?: MatcapProps) { 39 | super(Matcap, { 40 | name: "Matcap", 41 | ...props, 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/Noise.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three' 2 | import { ColorProps, MappingType, MappingTypes, NoiseProps, NoiseType, NoiseTypes } from '../types' 3 | import Abstract from './Abstract' 4 | 5 | type AbstractExtended = Abstract & { 6 | type: NoiseType 7 | mapping: MappingType 8 | } 9 | 10 | export default class Noise extends Abstract { 11 | static u_colorA = '#666666' 12 | static u_colorB = '#666666' 13 | static u_colorC = '#FFFFFF' 14 | static u_colorD = '#FFFFFF' 15 | 16 | static u_alpha = 1 17 | static u_scale = 1 18 | static u_offset = new Vector3(0, 0, 0) 19 | 20 | static vertexShader = ` 21 | varying vec3 v_position; 22 | 23 | void main() { 24 | v_position = lamina_mapping_template; 25 | } 26 | ` 27 | 28 | static fragmentShader = ` 29 | uniform vec3 u_colorA; 30 | uniform vec3 u_colorB; 31 | uniform vec3 u_colorC; 32 | uniform vec3 u_colorD; 33 | uniform vec3 u_offset; 34 | 35 | uniform float u_alpha; 36 | uniform float u_scale; 37 | 38 | varying vec3 v_position; 39 | 40 | 41 | void main() { 42 | float f_n = lamina_noise_template((v_position + u_offset) * u_scale); 43 | 44 | float f_step1 = 0.; 45 | float f_step2 = 0.2; 46 | float f_step3 = 0.6; 47 | float f_step4 = 1.; 48 | 49 | vec3 f_color = mix(u_colorA, u_colorB, smoothstep(f_step1, f_step2, f_n)); 50 | f_color = mix(f_color, u_colorC, smoothstep(f_step2, f_step3, f_n)); 51 | f_color = mix(f_color, u_colorD, smoothstep(f_step3, f_step4, f_n)); 52 | 53 | return vec4(f_color, u_alpha); 54 | } 55 | ` 56 | 57 | type: NoiseType = 'perlin' 58 | mapping: MappingType = 'local' 59 | 60 | constructor(props?: NoiseProps) { 61 | super( 62 | Noise, 63 | { 64 | name: 'noise', 65 | ...props, 66 | }, 67 | (self: Noise) => { 68 | self.schema.push({ 69 | value: self.type, 70 | label: 'type', 71 | options: Object.values(NoiseTypes), 72 | }) 73 | 74 | self.schema.push({ 75 | value: self.mapping, 76 | label: 'mapping', 77 | options: Object.values(MappingTypes), 78 | }) 79 | 80 | const noiseFunc = Noise.getNoiseFunction(self.type) 81 | const mapping = Noise.getMapping(self.mapping) 82 | 83 | self.vertexShader = self.vertexShader.replace('lamina_mapping_template', mapping) 84 | self.fragmentShader = self.fragmentShader.replace('lamina_noise_template', noiseFunc) 85 | } 86 | ) 87 | } 88 | 89 | private static getNoiseFunction(type?: string) { 90 | switch (type) { 91 | default: 92 | case 'perlin': 93 | return `lamina_noise_perlin` 94 | case 'simplex': 95 | return `lamina_noise_simplex` 96 | case 'cell': 97 | return `lamina_noise_worley` 98 | case 'white': 99 | return `lamina_noise_white` 100 | case 'curl': 101 | return `lamina_noise_swirl` 102 | } 103 | } 104 | 105 | private static getMapping(type?: string) { 106 | switch (type) { 107 | default: 108 | case 'local': 109 | return `position` 110 | case 'world': 111 | return `(modelMatrix * vec4(position,1.0)).xyz` 112 | case 'uv': 113 | return `vec3(uv, 0.)` 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/core/Normal.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from 'three' 2 | import { NormalProps } from '../types' 3 | import Abstract from './Abstract' 4 | 5 | export default class Normal extends Abstract { 6 | static u_alpha = 1 7 | static u_direction = new Vector3(1, 1, 1) 8 | 9 | static vertexShader = ` 10 | varying vec3 v_normals; 11 | 12 | void main() { 13 | v_normals = normal; 14 | } 15 | ` 16 | 17 | static fragmentShader = ` 18 | uniform float u_alpha; 19 | uniform vec3 u_color; 20 | uniform vec3 u_direction; 21 | 22 | varying vec3 v_normals; 23 | 24 | void main() { 25 | vec3 f_normalColor = vec3(1.); 26 | f_normalColor.x = v_normals.x * u_direction.x; 27 | f_normalColor.y = v_normals.y * u_direction.y; 28 | f_normalColor.z = v_normals.z * u_direction.z; 29 | 30 | return vec4(f_normalColor, u_alpha); 31 | } 32 | ` 33 | 34 | constructor(props?: NormalProps) { 35 | super(Normal, { 36 | name: 'Normal', 37 | ...props, 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/core/Texture.ts: -------------------------------------------------------------------------------- 1 | import { TextureProps } from '../types' 2 | import Abstract from './Abstract' 3 | 4 | export default class Texture extends Abstract { 5 | static u_alpha = 1 6 | static u_map = undefined 7 | 8 | static vertexShader = ` 9 | varying vec2 v_uv; 10 | 11 | void main() { 12 | v_uv = uv; 13 | } 14 | ` 15 | 16 | static fragmentShader = ` 17 | uniform sampler2D u_map; 18 | uniform float u_alpha; 19 | varying vec2 v_uv; 20 | 21 | void main() { 22 | vec4 f_color = texture2D(u_map, v_uv); 23 | return vec4(f_color.rgb, f_color.a * u_alpha); 24 | } 25 | ` 26 | 27 | constructor(props?: TextureProps) { 28 | super(Texture, { 29 | name: 'Texture', 30 | ...props, 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/debug.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | extend, 3 | MeshPhongMaterialProps, 4 | MeshPhysicalMaterialProps, 5 | MeshToonMaterialProps, 6 | MeshBasicMaterialProps, 7 | MeshLambertMaterialProps, 8 | MeshStandardMaterialProps, 9 | } from '@react-three/fiber' 10 | import { createRoot } from 'react-dom/client' 11 | 12 | import { button, LevaPanel, useControls, useCreateStore } from 'leva' 13 | import { DataItem, StoreType } from 'leva/dist/declarations/src/types' 14 | import React, { useEffect, useImperativeHandle, useMemo, useState } from 'react' 15 | import { getLayerMaterialArgs, getUniform } from './utils/Functions' 16 | import { serializedLayersToJSX, serializedLayersToJS } from './utils/ExportUtils' 17 | import * as LAYERS from './vanilla' 18 | import { Color, ColorRepresentation, TextureLoader } from 'three' 19 | import { LayerMaterialProps, ShadingType, ShadingTypes } from './types' 20 | 21 | extend({ 22 | LayerMaterial: LAYERS.LayerMaterial, 23 | }) 24 | 25 | function DynamicLeva({ 26 | name, 27 | layers, 28 | store, 29 | setUpdate, 30 | }: { 31 | setUpdate: any 32 | name: string 33 | layers: any[] 34 | store: StoreType 35 | }) { 36 | useControls( 37 | name, 38 | () => { 39 | const o: any = {} 40 | layers.forEach((layer, i: number) => { 41 | const n = `${layer.label} ~${i}` 42 | o[n] = layer 43 | o[n].onChange = () => setUpdate([`${name}.${n}`, layer.label]) 44 | }) 45 | return o 46 | }, 47 | { store }, 48 | [layers, name] 49 | ) 50 | 51 | return null 52 | } 53 | 54 | type AllMaterialProps = MeshPhongMaterialProps & // 55 | MeshPhysicalMaterialProps & 56 | MeshToonMaterialProps & 57 | MeshBasicMaterialProps & 58 | MeshLambertMaterialProps & 59 | MeshStandardMaterialProps 60 | 61 | const DebugLayerMaterial = React.forwardRef< 62 | LAYERS.LayerMaterial, 63 | React.PropsWithChildren> 64 | >(({ children, ...props }, forwardRef) => { 65 | const ref = React.useRef< 66 | LAYERS.LayerMaterial & { 67 | [key: string]: any 68 | } 69 | >(null!) 70 | useImperativeHandle(forwardRef, () => ref.current) 71 | const store = useCreateStore() 72 | const [layers, setLayers] = React.useState<{ [name: string]: any[] }>({}) 73 | const [path, setPath] = React.useState(['', '']) 74 | const textureLoader = useMemo(() => new TextureLoader(), []) 75 | 76 | useControls( 77 | { 78 | 'Copy JSX': button(() => { 79 | const serialized = ref.current.layers.map((l) => l.serialize()) 80 | const jsx = serializedLayersToJSX(serialized, ref.current.serialize()) 81 | navigator.clipboard.writeText(jsx) 82 | }), 83 | 'Copy JS': button(() => { 84 | const serialized = ref.current.layers.map((l) => l.serialize()) 85 | const js = serializedLayersToJS(serialized, ref.current.serialize()) 86 | navigator.clipboard.writeText(js) 87 | }), 88 | }, 89 | { store } 90 | ) 91 | 92 | const { Lighting } = useControls( 93 | 'Base', 94 | { 95 | Color: { 96 | value: '#' + new Color(ref.current?.color || props?.color || 'white').convertLinearToSRGB().getHexString(), 97 | onChange: (v) => { 98 | ref.current.color = v 99 | }, 100 | }, 101 | Alpha: { 102 | value: ref.current?.alpha || props?.alpha || 1, 103 | min: 0, 104 | max: 1, 105 | onChange: (v) => { 106 | ref.current.alpha = v 107 | }, 108 | }, 109 | Lighting: { 110 | value: ref.current?.lighting || props?.lighting || 'basic', 111 | options: Object.keys(ShadingTypes), 112 | }, 113 | }, 114 | { store } 115 | ) 116 | const [args, otherProps] = useMemo(() => getLayerMaterialArgs({ ...props, lighting: Lighting }), [props, Lighting]) 117 | 118 | React.useEffect(() => { 119 | const layers = ref.current.layers 120 | 121 | const schema: { [name: string]: any[] } = {} 122 | layers.forEach((layer: any, i: number) => { 123 | if (layer.getSchema) schema[`${layer.name} ~${i}`] = layer.getSchema() 124 | }) 125 | 126 | setLayers(schema) 127 | }, [children]) 128 | 129 | React.useEffect(() => { 130 | const data = store.getData() 131 | const updatedData = data[path[0]] as DataItem & { 132 | value: any 133 | } 134 | if (updatedData) { 135 | const split = path[0].split('.') 136 | const index = parseInt(split[0].split(' ~')[1]) 137 | const property = path[1] 138 | const id = ref.current.layers[index].uuid 139 | const uniform = ref.current.uniforms[`u_${id}_${property}`] 140 | const layer = ref.current.layers[index] as LAYERS.Abstract & { 141 | [key: string]: any 142 | } 143 | 144 | if (property !== 'map') { 145 | layer[property] = updatedData.value 146 | if (uniform) { 147 | uniform.value = getUniform(updatedData.value) 148 | } else { 149 | layer.buildShaders(layer.constructor) 150 | ref.current.refresh() 151 | } 152 | } else { 153 | ;(async () => { 154 | try { 155 | if (updatedData.value) { 156 | const t = await textureLoader.loadAsync(updatedData.value) 157 | layer[property] = t 158 | uniform.value = t 159 | } else { 160 | layer[property] = undefined 161 | uniform.value = undefined 162 | } 163 | } catch (error) { 164 | console.error(error) 165 | } 166 | })() 167 | } 168 | } 169 | }, [path]) 170 | 171 | React.useLayoutEffect(() => { 172 | ref.current.layers = (ref.current as any).__r3f.objects 173 | ref.current.refresh() 174 | }, [children, args]) 175 | 176 | React.useLayoutEffect(() => { 177 | const root = document.body.querySelector('#root') 178 | const div = document.createElement('div') 179 | 180 | if (root) { 181 | root.appendChild(div) 182 | const levaRoot = createRoot(div) 183 | levaRoot.render( 184 | 190 | ) 191 | } 192 | 193 | return () => { 194 | div.remove() 195 | } 196 | }, [props.name]) 197 | 198 | return ( 199 | <> 200 | {Object.entries(layers).map(([name, layers], i) => ( 201 | 202 | ))} 203 | 204 | {children} 205 | 206 | 207 | ) 208 | }) 209 | 210 | export default DebugLayerMaterial 211 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | extend, 3 | Node, 4 | MeshPhongMaterialProps, 5 | MeshPhysicalMaterialProps, 6 | MeshToonMaterialProps, 7 | MeshBasicMaterialProps, 8 | MeshLambertMaterialProps, 9 | MeshStandardMaterialProps, 10 | } from '@react-three/fiber' 11 | import React, { useMemo, useImperativeHandle } from 'react' 12 | import { 13 | DepthProps, 14 | ColorProps, 15 | LayerMaterialProps, 16 | NoiseProps, 17 | FresnelProps, 18 | GradientProps, 19 | MatcapProps, 20 | TextureProps, 21 | DisplaceProps, 22 | NormalProps, 23 | } from './types' 24 | import * as LAYERS from './vanilla' 25 | import DebugLayerMaterial from './debug' 26 | import { getLayerMaterialArgs } from './utils/Functions' 27 | import { ColorRepresentation } from 'three' 28 | 29 | declare global { 30 | namespace JSX { 31 | interface IntrinsicElements { 32 | layerMaterial: Node 33 | debuglayerMaterial: Node 34 | depth_: Node 35 | color_: Node 36 | noise_: Node 37 | fresnel_: Node 38 | gradient_: Node 39 | matcap_: Node 40 | texture_: Node 41 | displace_: Node 42 | normal_: Node 43 | } 44 | } 45 | } 46 | 47 | extend({ 48 | LayerMaterial: LAYERS.LayerMaterial, 49 | Depth_: LAYERS.Depth, 50 | Color_: LAYERS.Color, 51 | Noise_: LAYERS.Noise, 52 | Fresnel_: LAYERS.Fresnel, 53 | Gradient_: LAYERS.Gradient, 54 | Matcap_: LAYERS.Matcap, 55 | Texture_: LAYERS.Texture, 56 | Displace_: LAYERS.Displace, 57 | Normal_: LAYERS.Normal, 58 | }) 59 | 60 | type AllMaterialProps = MeshPhongMaterialProps & // 61 | MeshPhysicalMaterialProps & 62 | MeshToonMaterialProps & 63 | MeshBasicMaterialProps & 64 | MeshLambertMaterialProps & 65 | MeshStandardMaterialProps 66 | 67 | const LayerMaterial = React.forwardRef< 68 | LAYERS.LayerMaterial, 69 | React.PropsWithChildren> 70 | >(({ children, ...props }, forwardRef) => { 71 | const ref = React.useRef(null!) 72 | useImperativeHandle(forwardRef, () => ref.current) 73 | 74 | React.useLayoutEffect(() => { 75 | ref.current.layers = (ref.current as any).__r3f.objects 76 | ref.current.refresh() 77 | }, [children]) 78 | 79 | const [args, otherProps] = useMemo(() => getLayerMaterialArgs(props), [props]) 80 | 81 | return ( 82 | 83 | {children} 84 | 85 | ) 86 | }) 87 | 88 | function getNonUniformArgs(props: any) { 89 | return [ 90 | { 91 | mode: props?.mode, 92 | visible: props?.visible, 93 | type: props?.type, 94 | mapping: props?.mapping, 95 | map: props?.map, 96 | axes: props?.axes, 97 | }, 98 | ] as any 99 | } 100 | 101 | const Depth = React.forwardRef((props, forwardRef) => { 102 | //@ts-ignore 103 | return 104 | }) as React.ForwardRefExoticComponent> 105 | 106 | const Color = React.forwardRef((props, ref) => { 107 | //@ts-ignore 108 | return 109 | }) as React.ForwardRefExoticComponent> 110 | 111 | const Noise = React.forwardRef((props, ref) => { 112 | //@ts-ignore 113 | return 114 | }) as React.ForwardRefExoticComponent> 115 | 116 | const Fresnel = React.forwardRef((props, ref) => { 117 | //@ts-ignore 118 | return 119 | }) as React.ForwardRefExoticComponent> 120 | 121 | const Gradient = React.forwardRef((props, ref) => { 122 | //@ts-ignore 123 | return 124 | }) as React.ForwardRefExoticComponent> 125 | 126 | const Matcap = React.forwardRef((props, ref) => { 127 | //@ts-ignore 128 | return 129 | }) as React.ForwardRefExoticComponent> 130 | 131 | const Texture = React.forwardRef((props, ref) => { 132 | //@ts-ignore 133 | return 134 | }) as React.ForwardRefExoticComponent> 135 | 136 | const Displace = React.forwardRef((props, ref) => { 137 | //@ts-ignore 138 | return 139 | }) as React.ForwardRefExoticComponent> 140 | 141 | const Normal = React.forwardRef((props, ref) => { 142 | //@ts-ignore 143 | return 144 | }) as React.ForwardRefExoticComponent> 145 | 146 | export { DebugLayerMaterial, LayerMaterial, Depth, Color, Noise, Fresnel, Gradient, Matcap, Texture, Displace, Normal } 147 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { Abstract } from './vanilla' 3 | 4 | export const BlendModes: { 5 | [key: string]: string 6 | } = { 7 | normal: 'normal', 8 | add: 'add', 9 | subtract: 'subtract', 10 | multiply: 'multiply', 11 | lighten: 'lighten', 12 | darken: 'darken', 13 | divide: 'divide', 14 | overlay: 'overlay', 15 | screen: 'screen', 16 | softlight: 'softlight', 17 | negation: 'negation', 18 | reflect: 'reflect', 19 | } 20 | 21 | export type BlendMode = 22 | | 'normal' 23 | | 'add' 24 | | 'subtract' 25 | | 'multiply' 26 | | 'lighten' 27 | | 'darken' 28 | | 'divide' 29 | | 'overlay' 30 | | 'screen' 31 | | 'softlight' 32 | | 'reflect' 33 | | 'negation' 34 | 35 | export const NoiseTypes: { 36 | [key: string]: string 37 | } = { 38 | perlin: 'perlin', 39 | simplex: 'simplex', 40 | cell: 'cell', 41 | curl: 'curl', 42 | white: 'white', 43 | } 44 | 45 | export type NoiseType = 'perlin' | 'simplex' | 'cell' | 'curl' | 'white' 46 | 47 | export const MappingTypes: { 48 | [key: string]: string 49 | } = { 50 | local: 'local', 51 | world: 'world', 52 | uv: 'uv', 53 | } 54 | 55 | export type MappingType = 'local' | 'world' | 'uv' 56 | 57 | export const ShadingTypes: { 58 | [key: string]: new () => THREE.Material 59 | } = { 60 | phong: THREE.MeshPhongMaterial, 61 | physical: THREE.MeshPhysicalMaterial, 62 | toon: THREE.MeshToonMaterial, 63 | basic: THREE.MeshBasicMaterial, 64 | lambert: THREE.MeshLambertMaterial, 65 | standard: THREE.MeshStandardMaterial, 66 | } 67 | 68 | export type ShadingType = 'phong' | 'physical' | 'toon' | 'basic' | 'lambert' | 'standard' 69 | 70 | export interface BaseProps { 71 | color?: THREE.ColorRepresentation | THREE.Color 72 | alpha?: number 73 | name?: string 74 | } 75 | 76 | export interface LayerMaterialParameters { 77 | layers?: Abstract[] 78 | color?: THREE.ColorRepresentation | THREE.Color 79 | alpha?: number 80 | lighting?: ShadingType 81 | name?: string 82 | } 83 | export type LayerMaterialProps = Omit 84 | 85 | export interface LayerProps { 86 | mode?: BlendMode 87 | name?: string 88 | visible?: boolean 89 | [key: string]: any 90 | } 91 | 92 | export interface ColorProps extends LayerProps { 93 | color?: THREE.ColorRepresentation | THREE.Color 94 | alpha?: number 95 | } 96 | export interface NormalProps extends LayerProps { 97 | direction?: THREE.Vector3 | [number, number, number] 98 | alpha?: number 99 | } 100 | 101 | export interface DepthProps extends LayerProps { 102 | colorA?: THREE.ColorRepresentation | THREE.Color 103 | colorB?: THREE.ColorRepresentation | THREE.Color 104 | alpha?: number 105 | near?: number 106 | far?: number 107 | origin?: THREE.Vector3 | [number, number, number] 108 | mapping?: 'vector' | 'world' | 'camera' 109 | } 110 | 111 | export interface NoiseProps extends LayerProps { 112 | colorA?: THREE.ColorRepresentation | THREE.Color 113 | colorB?: THREE.ColorRepresentation | THREE.Color 114 | colorC?: THREE.ColorRepresentation | THREE.Color 115 | colorD?: THREE.ColorRepresentation | THREE.Color 116 | alpha?: number 117 | mapping?: MappingType 118 | type?: NoiseType 119 | scale?: number 120 | offset?: THREE.Vector3 | [number, number, number] 121 | } 122 | export interface DisplaceProps extends LayerProps { 123 | strength?: number 124 | scale?: number 125 | mapping?: MappingType 126 | type?: NoiseType 127 | offset?: THREE.Vector3 | [number, number, number] 128 | } 129 | 130 | export interface FresnelProps extends LayerProps { 131 | color?: THREE.ColorRepresentation | THREE.Color 132 | alpha?: number 133 | power?: number 134 | intensity?: number 135 | bias?: number 136 | } 137 | export interface GradientProps extends LayerProps { 138 | colorA?: THREE.ColorRepresentation | THREE.Color 139 | colorB?: THREE.ColorRepresentation | THREE.Color 140 | axes?: 'x' | 'y' | 'z' 141 | alpha?: number 142 | contrast?: number 143 | start?: number 144 | end?: number 145 | mapping?: MappingType 146 | } 147 | 148 | export interface MatcapProps extends LayerProps { 149 | map?: THREE.Texture 150 | alpha?: number 151 | } 152 | export interface TextureProps extends LayerProps { 153 | map?: THREE.Texture 154 | alpha?: number 155 | } 156 | 157 | export interface SerializedLayer { 158 | constructor: string 159 | properties: { 160 | [name: string]: any 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/utils/ExportUtils.ts: -------------------------------------------------------------------------------- 1 | import { SerializedLayer } from 'src/types' 2 | import * as LAYERS from '../vanilla' 3 | 4 | function getPropsFromLayer(layer: SerializedLayer) { 5 | // @ts-ignore 6 | const constructor = LAYERS[layer.constructor] 7 | const instance = new constructor() 8 | let props = '' 9 | Object.entries(layer.properties).forEach(([key, val]) => { 10 | const defaultVal = constructor['u_' + key] ?? instance[key] 11 | 12 | switch (key) { 13 | case 'name': 14 | if (val !== layer.constructor) props += ` ${key}={${JSON.stringify(val)}}` 15 | break 16 | 17 | case 'visible': 18 | if (!val) props += ` ${key}={${JSON.stringify(val)}}` 19 | break 20 | 21 | default: 22 | if (val !== defaultVal) props += ` ${key}={${JSON.stringify(val)}}` 23 | break 24 | } 25 | }) 26 | 27 | return props 28 | } 29 | 30 | export function serializedLayersToJSX(layers: SerializedLayer[], material: SerializedLayer) { 31 | const materialProps = getPropsFromLayer(material) 32 | 33 | const jsx = ` 34 | 35 | ${layers 36 | .map((layer) => { 37 | const props = getPropsFromLayer(layer) 38 | return `<${layer.constructor}${props} />` 39 | }) 40 | .join('\n\t')} 41 | 42 | ` 43 | 44 | return jsx 45 | } 46 | 47 | function getJSPropsFromLayer(layer: SerializedLayer) { 48 | // @ts-ignore 49 | const constructor = LAYERS[layer.constructor]; 50 | const instance = new constructor(); 51 | let props = '\t'; 52 | let entries = Object.entries(layer.properties); 53 | entries.forEach(([key, val], idx) => { 54 | var _constructor; 55 | const eol = '\n\t\t'; 56 | if (key.includes('color')) { 57 | const v = typeof val === "string" ? val : '#' + val.getHexString(); 58 | props += `${key}: ${JSON.stringify(v)},${eol}`; 59 | } else { 60 | const defaultVal = (_constructor = constructor['u_' + key]) != null ? _constructor : instance[key]; 61 | switch (key) { 62 | case 'name': 63 | if (val !== layer.constructor) props += `${key}: ${JSON.stringify(val)},${eol}`; 64 | break; 65 | 66 | case 'visible': 67 | if (!val) props += `${key}:${JSON.stringify(val)},${eol}`; 68 | break; 69 | 70 | default: 71 | if (val !== defaultVal) props += `${key}: ${JSON.stringify(val)},${eol}`; 72 | break; 73 | } 74 | } 75 | }); 76 | return props; 77 | } 78 | 79 | export function serializedLayersToJS(layers: SerializedLayer[], material: SerializedLayer){ 80 | const materialProps = getJSPropsFromLayer(material); 81 | const jsLayers = `${layers.map(l => { 82 | return `new ${l.constructor}({ 83 | ${getJSPropsFromLayer(l)} 84 | })` 85 | }).join(',\n\t\t')}` 86 | 87 | const js = ` 88 | new LayerMaterial({ 89 | ${materialProps} 90 | layers: [ 91 | ${jsLayers} 92 | ] 93 | })` 94 | 95 | return js; 96 | } 97 | -------------------------------------------------------------------------------- /src/utils/Functions.ts: -------------------------------------------------------------------------------- 1 | import { Color, Matrix3, Matrix4, Texture, Vector2, Vector3, Vector4 } from 'three' 2 | import { LayerMaterialProps } from '../types' 3 | 4 | export function getUniform(value: any) { 5 | if (typeof value === 'string') { 6 | return new Color(value).convertLinearToSRGB() 7 | } 8 | 9 | return value 10 | } 11 | 12 | export function getSpecialParameters(label: string) { 13 | switch (label) { 14 | case 'alpha': 15 | return { 16 | min: 0, 17 | max: 1, 18 | } 19 | case 'scale': 20 | return { 21 | min: 0, 22 | } 23 | 24 | case 'map': 25 | return { 26 | image: undefined, 27 | } 28 | 29 | default: 30 | return {} 31 | } 32 | } 33 | 34 | export function getLayerMaterialArgs({ color, alpha, lighting, name, ...rest }: LayerMaterialProps & any = {}) { 35 | return [ 36 | { 37 | color, 38 | alpha, 39 | lighting, 40 | name, 41 | }, 42 | rest, 43 | ] as any 44 | } 45 | 46 | export function isSerializableType(prop: any) { 47 | return ( 48 | prop instanceof Vector3 || 49 | prop instanceof Vector2 || 50 | prop instanceof Vector4 || 51 | prop instanceof Matrix3 || 52 | prop instanceof Matrix4 53 | ) 54 | } 55 | 56 | export function serializeProp(prop: any) { 57 | if (isSerializableType(prop)) { 58 | return prop.toArray() 59 | } else if (prop instanceof Color) { 60 | return '#' + prop.clone().convertLinearToSRGB().getHexString() 61 | } else if (prop instanceof Texture) { 62 | return prop.image.src 63 | } 64 | 65 | return prop 66 | } 67 | -------------------------------------------------------------------------------- /src/vanilla.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | 3 | import Abstract from './core/Abstract' 4 | import Depth from './core/Depth' 5 | import Color from './core/Color' 6 | import Noise from './core/Noise' 7 | import Fresnel from './core/Fresnel' 8 | import Gradient from './core/Gradient' 9 | import Matcap from './core/Matcap' 10 | import Texture from './core/Texture' 11 | import Displace from './core/Displace' 12 | import Normal from './core/Normal' 13 | 14 | import BlendModesChunk from './chunks/BlendModes' 15 | import NoiseChunk from './chunks/Noise' 16 | import HelpersChunk from './chunks/Helpers' 17 | import { LayerMaterialParameters, SerializedLayer, ShadingType, ShadingTypes } from './types' 18 | import { 19 | ColorRepresentation, 20 | MeshBasicMaterialParameters, 21 | MeshLambertMaterialParameters, 22 | MeshPhongMaterialParameters, 23 | MeshPhysicalMaterialParameters, 24 | MeshStandardMaterialParameters, 25 | MeshToonMaterialParameters, 26 | } from 'three' 27 | import CustomShaderMaterial from 'three-custom-shader-material/vanilla' 28 | 29 | type AllMaterialParams = 30 | | MeshPhongMaterialParameters 31 | | MeshPhysicalMaterialParameters 32 | | MeshToonMaterialParameters 33 | | MeshBasicMaterialParameters 34 | | MeshLambertMaterialParameters 35 | | MeshStandardMaterialParameters 36 | 37 | class LayerMaterial extends CustomShaderMaterial { 38 | name: string = 'LayerMaterial' 39 | layers: Abstract[] = [] 40 | lighting: ShadingType = 'basic' 41 | 42 | constructor({ color, alpha, lighting, layers, name, ...props }: LayerMaterialParameters & AllMaterialParams = {}) { 43 | super({ 44 | baseMaterial: ShadingTypes[lighting || 'basic'], 45 | ...props, 46 | }) 47 | 48 | const _baseColor = color || 'white' 49 | const _alpha = alpha ?? 1 50 | 51 | this.uniforms = { 52 | u_lamina_color: { 53 | value: typeof _baseColor === 'string' ? new THREE.Color(_baseColor).convertSRGBToLinear() : _baseColor, 54 | }, 55 | u_lamina_alpha: { 56 | value: _alpha, 57 | }, 58 | } 59 | 60 | this.layers = layers || this.layers 61 | this.lighting = lighting || this.lighting 62 | this.name = name || this.name 63 | 64 | this.refresh() 65 | } 66 | 67 | genShaders() { 68 | let vertexVariables = '' 69 | let fragmentVariables = '' 70 | let vertexShader = '' 71 | let fragmentShader = '' 72 | let uniforms: any = {} 73 | 74 | this.layers 75 | .filter((l) => l.visible) 76 | .forEach((l) => { 77 | // l.buildShaders(l.constructor) 78 | 79 | vertexVariables += l.vertexVariables + '\n' 80 | fragmentVariables += l.fragmentVariables + '\n' 81 | vertexShader += l.vertexShader + '\n' 82 | fragmentShader += l.fragmentShader + '\n' 83 | 84 | uniforms = { 85 | ...uniforms, 86 | ...l.uniforms, 87 | } 88 | }) 89 | 90 | uniforms = { 91 | ...uniforms, 92 | ...this.uniforms, 93 | } 94 | 95 | return { 96 | uniforms, 97 | vertexShader: ` 98 | ${HelpersChunk} 99 | ${NoiseChunk} 100 | ${vertexVariables} 101 | 102 | void main() { 103 | vec3 lamina_finalPosition = position; 104 | vec3 lamina_finalNormal = normal; 105 | 106 | ${vertexShader} 107 | 108 | csm_Position = lamina_finalPosition; 109 | csm_Normal = lamina_finalNormal; 110 | } 111 | `, 112 | fragmentShader: ` 113 | ${HelpersChunk} 114 | ${NoiseChunk} 115 | ${BlendModesChunk} 116 | ${fragmentVariables} 117 | 118 | uniform vec3 u_lamina_color; 119 | uniform float u_lamina_alpha; 120 | 121 | void main() { 122 | vec4 lamina_finalColor = vec4(u_lamina_color, u_lamina_alpha); 123 | 124 | ${fragmentShader} 125 | 126 | csm_DiffuseColor = lamina_finalColor; 127 | 128 | } 129 | `, 130 | } 131 | } 132 | 133 | refresh() { 134 | const { uniforms, fragmentShader, vertexShader } = this.genShaders() 135 | super.update({ fragmentShader, vertexShader, uniforms }) 136 | } 137 | 138 | serialize(): SerializedLayer { 139 | return { 140 | constructor: 'LayerMaterial', 141 | properties: { 142 | color: this.color, 143 | alpha: this.alpha, 144 | name: this.name, 145 | lighting: this.lighting, 146 | }, 147 | } 148 | } 149 | 150 | set color(v: ColorRepresentation) { 151 | if (this.uniforms?.u_lamina_color?.value) 152 | this.uniforms.u_lamina_color.value = typeof v === 'string' ? new THREE.Color(v).convertSRGBToLinear() : v 153 | } 154 | get color() { 155 | return this.uniforms?.u_lamina_color?.value 156 | } 157 | set alpha(v: number) { 158 | this.uniforms.u_lamina_alpha.value = v 159 | } 160 | get alpha() { 161 | return this.uniforms.u_lamina_alpha.value 162 | } 163 | } 164 | 165 | export { LayerMaterial, Abstract, Depth, Color, Noise, Fresnel, Gradient, Matcap, Texture, Displace, Normal } 166 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es2018", 5 | "allowSyntheticDefaultImports": true, 6 | "lib": ["ESNext", "dom"], 7 | "jsx": "react", 8 | "strict": true, 9 | "preserveSymlinks": true, 10 | "moduleResolution": "Node", 11 | "esModuleInterop": true, 12 | "declaration": true, 13 | "declarationDir": "dist", 14 | "skipLibCheck": true, 15 | "removeComments": false, 16 | "baseUrl": "." 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | --------------------------------------------------------------------------------