Skip to content

Commit ad9f98b

Browse files
authored
feat: add configurable uniforms to tsl gradient wave (#67)
1 parent 9375caf commit ad9f98b

7 files changed

Lines changed: 110 additions & 16 deletions

File tree

apps/web/src/components/TslPreviewCanvas.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type TslPreviewCanvasProps = {
1111
previewModule: string
1212
pipeline: string
1313
fallbackSvg?: string | null
14+
uniformOverrides?: Record<string, number | number[] | boolean>
1415
onError?: (errors: string[]) => void
1516
onScreenshotReady?: (base64: string) => void
1617
}
@@ -118,6 +119,7 @@ export default function TslPreviewCanvas(props: TslPreviewCanvasProps) {
118119
width,
119120
height,
120121
pipeline: props.pipeline,
122+
uniforms: props.uniformOverrides ?? {},
121123
})
122124

123125
if (!nextPreview?.material || typeof nextPreview.material !== 'object') {
@@ -254,6 +256,18 @@ export default function TslPreviewCanvas(props: TslPreviewCanvasProps) {
254256
),
255257
)
256258

259+
createEffect(
260+
on(
261+
() => JSON.stringify(props.uniformOverrides ?? {}),
262+
async () => {
263+
if (!runtime || !renderer) return
264+
setLoading(true)
265+
await renderPreview(props.previewModule)
266+
},
267+
{ defer: true },
268+
),
269+
)
270+
257271
return (
258272
<div
259273
ref={containerRef}

apps/web/src/lib/server/shaders.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ async function main() {
182182
assert.equal(detail.material, 'node-material')
183183
assert.deepEqual(detail.renderers, ['webgpu'])
184184
assert.ok(detail.tags.includes('tsl'), 'Expected "tsl" tag')
185+
assert.equal(detail.uniforms.length, 5)
186+
assert.ok(detail.uniforms.some((u) => u.name === 'uColorA'))
187+
assert.ok(detail.uniforms.some((u) => u.name === 'uWaveSpeed'))
185188
})
186189

187190
await runTest('loadShaderDetail — TSL shader loads recipes', async () => {

apps/web/src/routes/shaders.$name.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ function ShaderDetailPage() {
9999
previewModule={s().previewModule}
100100
pipeline={s().pipeline}
101101
fallbackSvg={s().previewSvg}
102+
uniformOverrides={uniformOverrides()}
102103
/>
103104
) : (
104105
<ShaderPreviewCanvas
@@ -110,12 +111,14 @@ function ShaderDetailPage() {
110111
fallbackSvg={s().previewSvg}
111112
/>
112113
)}
113-
<SurfaceCard class="max-h-[500px] overflow-y-auto p-5">
114-
<UniformControls
115-
uniforms={s().uniforms}
116-
onUniformChange={handleUniformChange}
117-
/>
118-
</SurfaceCard>
114+
<Show when={s().uniforms.length > 0}>
115+
<SurfaceCard class="max-h-[500px] overflow-y-auto p-5">
116+
<UniformControls
117+
uniforms={s().uniforms}
118+
onUniformChange={handleUniformChange}
119+
/>
120+
</SurfaceCard>
121+
</Show>
119122
</div>
120123

121124
{/* Description */}

packages/schema/src/index.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ await runTest("buildTslPreviewModule binds runtime imports inside createPreview"
153153
import { color } from 'three/tsl';
154154
import { NodeMaterial } from 'three/webgpu';
155155
156-
export function createMaterial() {
156+
export function createMaterial(runtime) {
157157
const material = new NodeMaterial();
158-
material.colorNode = color(0xff0000);
158+
material.colorNode = color(runtime.uniforms.tint);
159159
return material;
160160
}
161161
`);
@@ -172,6 +172,7 @@ export function createMaterial() {
172172
width: 512,
173173
height: 512,
174174
pipeline: "surface",
175+
uniforms: { tint: 0xff0000 },
175176
});
176177

177178
assert.ok(preview.material instanceof FakeNodeMaterial);

packages/schema/src/tsl-preview-module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type TslPreviewModuleRuntime = {
99
width: number
1010
height: number
1111
pipeline: string
12+
uniforms: Record<string, unknown>
1213
}
1314

1415
export type TslPreviewModuleResult = {
@@ -87,7 +88,7 @@ export function buildTslPreviewModule(sourceCode: string) {
8788
buildDestructureLine('TSL', tslBindings),
8889
buildDestructureLine('THREE', webgpuBindings),
8990
normalizedSource.trim(),
90-
'const material = createMaterial();',
91+
'const material = createMaterial(runtime);',
9192
'return { material };',
9293
]
9394
.filter(Boolean)

shaders/tsl-gradient-wave/shader.json

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,48 @@
2424
"material": "node-material",
2525
"environments": ["three"]
2626
},
27-
"uniforms": [],
27+
"uniforms": [
28+
{
29+
"name": "uColorA",
30+
"type": "vec3",
31+
"defaultValue": [0.1019607843, 0.1019607843, 0.1803921569],
32+
"description": "Primary gradient color.",
33+
"min": 0,
34+
"max": 1
35+
},
36+
{
37+
"name": "uColorB",
38+
"type": "vec3",
39+
"defaultValue": [0.9137254902, 0.2705882353, 0.3764705882],
40+
"description": "Secondary gradient color.",
41+
"min": 0,
42+
"max": 1
43+
},
44+
{
45+
"name": "uWaveSpeed",
46+
"type": "float",
47+
"defaultValue": 2,
48+
"description": "Animation speed of the wave motion.",
49+
"min": 0.1,
50+
"max": 8
51+
},
52+
{
53+
"name": "uWaveMix",
54+
"type": "float",
55+
"defaultValue": 0.5,
56+
"description": "Blend between horizontal and vertical UV influence.",
57+
"min": 0,
58+
"max": 1
59+
},
60+
{
61+
"name": "uWaveFrequency",
62+
"type": "float",
63+
"defaultValue": 6,
64+
"description": "Frequency of the gradient wave pattern.",
65+
"min": 0.5,
66+
"max": 16
67+
}
68+
],
2869
"inputs": [
2970
{
3071
"name": "uv",
Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,44 @@
1-
import { color, mix, uv, sin, time } from 'three/tsl';
1+
import { mix, uv, sin, time, vec3 } from 'three/tsl';
22
import { NodeMaterial } from 'three/webgpu';
33

4-
export function createMaterial(): NodeMaterial {
4+
type PreviewRuntime = {
5+
uniforms?: Record<string, unknown>
6+
};
7+
8+
function readVec3(value: unknown, fallback: [number, number, number]): [number, number, number] {
9+
if (
10+
Array.isArray(value) &&
11+
value.length === 3 &&
12+
value.every((entry) => typeof entry === 'number')
13+
) {
14+
return value as [number, number, number];
15+
}
16+
17+
return fallback;
18+
}
19+
20+
function readNumber(value: unknown, fallback: number): number {
21+
return typeof value === 'number' ? value : fallback;
22+
}
23+
24+
export function createMaterial(runtime?: PreviewRuntime): NodeMaterial {
525
const material = new NodeMaterial();
6-
const t = sin(time.mul(2.0)).mul(0.5).add(0.5);
26+
27+
const uniforms = runtime?.uniforms ?? {};
28+
const colorA = readVec3(uniforms.uColorA, [0.1019607843, 0.1019607843, 0.1803921569]);
29+
const colorB = readVec3(uniforms.uColorB, [0.9137254902, 0.2705882353, 0.3764705882]);
30+
const waveSpeed = readNumber(uniforms.uWaveSpeed, 2.0);
31+
const waveMix = readNumber(uniforms.uWaveMix, 0.5);
32+
const waveFrequency = readNumber(uniforms.uWaveFrequency, 6.0);
33+
34+
const uvMix = mix(uv().x, uv().y, waveMix).mul(waveFrequency);
35+
const t = sin(uvMix.add(time.mul(waveSpeed))).mul(0.5).add(0.5);
36+
737
material.colorNode = mix(
8-
color(0x1a1a2e),
9-
color(0xe94560),
10-
mix(uv().x, uv().y, t),
38+
vec3(...colorA),
39+
vec3(...colorB),
40+
t,
1141
);
42+
1243
return material;
1344
}

0 commit comments

Comments
 (0)