Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 85 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,91 @@
# Shaderfrog Core

🚨 This library is experimental! 🚨
ShaderFrog Core is the core library that powers [Shaderfrog.com](https://shaderfrog.com/). You're proably here for exported materials from Shaderfrog using `FrogMaterial`.

# FrogMaterial: Three.js Material Export

`FrogMaterial` creates an extensible Three.js material with a high degree of control over the source code, making Three.js materials more customizable than the standard path using [onBeforeCompile](https://threejs.org/docs/#Material.onBeforeCompile).

Usage example:

```ts
import { MesHPhysicalMaterial } from 'three';
import { FrogMaterial } from '@shaderfrog/core/plugins/three';

const material = new FrogMaterial({
baseMaterial: MeshPhysicalMaterial,
materialName: 'MeshPhysicalMaterial',
fragmentShader,
fragmentOutput: "(main_Edge_Glow()+ main_MeshPhysicalMaterial())",
vertexShader,
vertexOutput: "main_Parallax();\n\n \n main_Edge_Glow();\n\n \n main_Striped_Mandelbrot();\n\n \n main_Julia();\n\n \n gl_Position = main_MeshPhysicalMaterial();\n",
uniforms,
map: "main_Parallax()",
fragmentInjections: [{
search: new RegExp("(normal = ).+;"), replace: "$1(vNormal + sampledDiffuseColor.rgb * 0.5);"
}],
vertexInjections: [{
search: new RegExp("(normal = ).+;"), replace: "$1(vNormal + sampledDiffuseColor.rgb * 0.5);"
}],
metalness: 0,
roughness: 0.065,
});
```

The FrogMaterial API:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `baseMaterial` | Three.js material constructor, e.g. `MeshPhysicalMaterial` | Yes | The Three.js material class to extend. `FrogMaterial` constructs an instance of this class and drives its `onBeforeCompile`. |
| `materialName` | `string` | No | Stable name used as the GLSL engine function prefix (e.g. `'MeshPhysicalMaterial'`). Falls back to `baseMaterial.name`, but that's mangled by minifiers, so set this explicitly in production bundles. |
| `fragmentShader` | `string` | Yes | GLSL source injected above `main()` in the fragment shader. Must declare a `main_<materialName>()`-style function per `fragmentOutput`/injections below. |
| `fragmentOutput` | `string` | Yes | GLSL expression assigned to `gl_FragColor` in the generated `main()`, e.g. `"(main_Edge_Glow() + main_MeshPhysicalMaterial())"`. |
| `vertexShader` | `string` | Yes | GLSL source injected above `main()` in the vertex shader, mirroring `fragmentShader`. |
| `vertexOutput` | `string` | Yes | Statement(s) run inside the generated vertex `main()`; should end by assigning `gl_Position`. |
| `uniforms` | `Record<string, IUniform>` | No | Custom uniforms merged into `shader.uniforms` in `onBeforeCompile`. |
| `fragmentInjections` | `ShaderInjection[]` | No | Raw `{ search, replace }` patches applied to the final fragment shader source, after chunk expansion and output wiring. |
| `vertexInjections` | `ShaderInjection[]` | No | Same as `fragmentInjections`, applied to the final vertex shader source. |
| `onBeforeCompile` | `(shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer) => void` | No | Called last, after all FrogMaterial transforms are applied to the shader. |
| *texture-injectable keys* (`map`, `normalMap`, `aoMap`, `emissiveMap`, `roughnessMap`, `specularMap`, `displacementMap`, `bumpMap`, `transmissionMap`, `gradientMap`, `thickness`, `transmission`, `position`) | `string \| Texture` | No | Any of `baseMaterial`'s own texture/property fields. Pass a `Texture` for normal Three.js behavior, or a GLSL expression string (e.g. `"main_Parallax()"`) to wire a generated function's output into that slot instead. |
| *(remaining fields)* | Whatever `baseMaterial`'s constructor accepts (e.g. `metalness`, `roughness`, `color`) | No | Any other property `baseMaterial`'s constructor takes is passed straight through, e.g. `metalness: 0, roughness: 0.065`. |

#### `uniforms`

Merged directly into the compiled shader's uniforms, using the same shape Three.js uniforms use:

```ts
uniforms: {
time: { value: 0 },
resolution: { value: new Vector2(1, 1) },
}
```

#### `fragmentInjections` / `vertexInjections`

Each entry is a `{ search: string | RegExp, replace: string }` pair applied via `shader.replace(search, replace)` against the fully assembled shader source, after chunk expansion and after `fragmentOutput`/`vertexOutput` wiring — use these for edits that can't be expressed as a plain injectable property:

```ts
fragmentInjections: [
{
search: new RegExp('(normal = ).+;'),
replace: '$1(vNormal + sampledDiffuseColor.rgb * 0.5);',
},
],
```

#### `onBeforeCompile`

Runs after FrogMaterial's own `onBeforeCompile` logic, so `shader.fragmentShader`/`shader.vertexShader` already reflect every injection above:

```ts
onBeforeCompile: (shader, renderer) => {
shader.uniforms.time.value = performance.now() / 1000;
},
```

# Core Shaderfrog Graph API

🚨 The API can change at any time! 🚨
🚨 This Core Graph API is experimental and can change at any time! 🚨

The core graph API that powers Shaderfrog. This API, built on top of the
[@Shaderfrog/glsl-parser](https://github.com/ShaderFrog/glsl-parser), compiles
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shaderfrog/core",
"version": "4.0.0",
"version": "4.0.2",
"description": "Shaderfrog core",
"type": "module",
"files": [
Expand Down
22 changes: 14 additions & 8 deletions src/plugins/three/FrogMaterial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type ConstructorParams<C extends MaterialConstructor> = C extends new (

type FrogSpecificKeys =
| 'baseMaterial'
| 'materialName'
| 'fragmentShader'
| 'fragmentOutput'
| 'vertexShader'
Expand All @@ -152,6 +153,9 @@ export type FrogMaterialParams<
C extends MaterialConstructor = MaterialConstructor,
> = {
baseMaterial: C;
/** Stable name used as the GLSL engine function prefix (e.g. 'MeshPhysicalMaterial').
* Required in minified/production bundles where Function.name is mangled. */
materialName?: string;
fragmentShader: string;
fragmentOutput: string;
vertexShader: string;
Expand All @@ -164,10 +168,12 @@ export type FrogMaterialParams<
shader: WebGLProgramParametersWithUniforms,
renderer: WebGLRenderer,
) => void;
} & Omit<WithInjectables<ConstructorParams<C>>, FrogSpecificKeys>;
} & Omit<WithInjectables<ConstructorParams<C>>, FrogSpecificKeys> &
Partial<Record<InjectableKey, string>>;

function _create<C extends MaterialConstructor>({
baseMaterial: BaseMaterial,
materialName,
fragmentShader,
fragmentOutput,
vertexShader,
Expand All @@ -179,7 +185,7 @@ function _create<C extends MaterialConstructor>({
...baseProps
}: FrogMaterialParams<C>): Material {
// Split baseProps: injectable strings become GLSL injections + dummy textures
const glslInjections: Array<{ find: RegExp; replace: string }> = [];
const glslInjections: ShaderInjection[] = [];
const materialProps: Record<string, unknown> = {};

for (const [key, value] of Object.entries(
Expand All @@ -190,7 +196,7 @@ function _create<C extends MaterialConstructor>({
FRAGMENT_INJECTABLE[key as InjectableKey] ||
VERTEX_INJECTABLES[key as InjectableKey];
if (inj) {
glslInjections.push({ find: inj.find, replace: inj.replace(value) });
glslInjections.push({ search: inj.find, replace: inj.replace(value) });
if (inj.forceProperty) {
materialProps[inj.forceProperty] = new Texture();
}
Expand All @@ -203,7 +209,7 @@ function _create<C extends MaterialConstructor>({
}

const mat = new BaseMaterial(materialProps as ConstructorParams<C>);
const engineFnName = `main_${BaseMaterial.name || 'BaseMaterial'}`;
const engineFnName = `main_${materialName || BaseMaterial.name || 'BaseMaterial'}`;

mat.onBeforeCompile = (shader, renderer) => {
Object.assign(shader.uniforms, uniforms);
Expand Down Expand Up @@ -231,8 +237,8 @@ function _create<C extends MaterialConstructor>({
`\n\nvec4 ${engineFnName}() {\n vec4 fragColor = vec4(0.0);`,
) + `\n\nvoid main() { gl_FragColor = ${fragmentOutput}; }`;

for (const { find, replace } of glslInjections) {
shader.fragmentShader = shader.fragmentShader.replace(find, replace);
for (const { search, replace } of glslInjections) {
shader.fragmentShader = shader.fragmentShader.replace(search, replace);
}

for (const { search, replace } of fragmentInjections) {
Expand Down Expand Up @@ -261,8 +267,8 @@ function _create<C extends MaterialConstructor>({
`\n\nvec4 ${engineFnName}() {\n vec4 fragPosition = vec4(0.0);`,
) + `\n\nvoid main() { ${vertexOutput} }`;

for (const { find, replace } of glslInjections) {
shader.vertexShader = shader.vertexShader.replace(find, replace);
for (const { search, replace } of glslInjections) {
shader.vertexShader = shader.vertexShader.replace(search, replace);
}

for (const { search, replace } of vertexInjections) {
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/three/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export { createMaterial, threngine as engine };
export {
createFrogMaterialResult,
engineNodeTypeToConstructor,
engineNodeTypeToConstructorName,
prepareFrogMaterialExport,
} from './threngine';
export type { FrogMaterialExport } from './threngine';
export { FrogMaterial, expandChunks } from './FrogMaterial';
export type { FrogMaterialParams } from './FrogMaterial';
Loading
Loading