├── .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 |
17 |
18 |
19 |
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 |
17 |
18 |
19 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------