Shader graph: UV, Sample texture, Texture size, Time, Sin/Floor/Fract/Step; edge mode on Output (#78)
game-maker / suite (push) Successful in 8m38s
game-maker / suite (push) Successful in 8m38s
Fragment-side coordinate nodes instead of vertex nodes: the quad stays the quad, a UV read plus a sample at a computed coordinate covers flip, scroll, wave, pixelate and scanlines. Time is a uniform only when a Time node reaches the Output; a viewport that drew a Time shader redraws from the ticker so the Scene tab animates without a running world. Edge (Mirror/Repeat/Clamp) is a second texture variant in TextureLibrary because WebGL binds no TextureStyle resource of the shader. The compiler now emits #version 300 es first: Pixi only compiles as ES 3.00 when the fragment says so, and textureSize needs it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e5e5ef925
commit
f85606427b
12 files changed
+586
-38
No files matched your search
@@ -32,9 +32,14 @@ import {
|
||||
SHADER_NODES,
|
||||
SHADER_NUMBER,
|
||||
SHADER_OUTPUT,
|
||||
SHADER_SAMPLE,
|
||||
SHADER_TEXTURE_ALPHA,
|
||||
SHADER_TEXTURE_COLOR,
|
||||
SHADER_TEXTURE_SIZE,
|
||||
SHADER_TIME,
|
||||
SHADER_UV,
|
||||
ShaderOutput,
|
||||
ShaderSample,
|
||||
} from '../../src/core/shader/nodes';
|
||||
import { deserializeProject } from '../../src/core/store/serialize';
|
||||
import { paletteEntries } from '../../src/editor/graph/paletteEntries';
|
||||
@@ -205,7 +210,9 @@ describe('compileShader', () => {
|
||||
expect(fragment).toContain(`uniform float u_${name};`);
|
||||
// Drawn in the sprite's own pass: the tint goes in before the graph, premultiplied like the texel.
|
||||
expect(fragment).toContain('vec4 texel = texture(uTexture, vTextureCoord) * vColor;');
|
||||
expect(fragment).toContain('vec3 rgb = texel.a > 0.0 ? texel.rgb / texel.a : texel.rgb;');
|
||||
expect(fragment).toContain('vec3 straight(vec4 c) { return c.a > 0.0 ? c.rgb / c.a : c.rgb; }');
|
||||
expect(fragment).toContain('vec3 rgb = straight(texel);');
|
||||
expect(compiled).toMatchObject({ time: false, edge: 'mirror' });
|
||||
expect(fragment).toMatch(/float n\d+_result = \(n\d+_value - 1\.0\);/);
|
||||
expect(fragment).toMatch(/vec3 n\d+_result = \(n\d+_color \+ vec3\(n\d+_result\)\);/);
|
||||
expect(fragment).toMatch(/vec3 n\d+_result = \(n\d+_result - vec3\(0\.5\)\);/);
|
||||
@@ -355,6 +362,88 @@ describe('compileShader', () => {
|
||||
expect(build.compiled).toBeNull();
|
||||
expect(build.problems).toEqual({ [two]: 'Feeds itself' });
|
||||
});
|
||||
|
||||
it('gives the graph the pixel’s place, the picture at another spot, its size and the clock', () => {
|
||||
const f = makeProject();
|
||||
const s = shaderEditor(f, 'Flip');
|
||||
const uv = s.add(SHADER_UV);
|
||||
const flipped = s.add('ShaderSubtract', { a: 1 });
|
||||
const sample = s.add(SHADER_SAMPLE);
|
||||
const size = s.add(SHADER_TEXTURE_SIZE);
|
||||
const time = s.add(SHADER_TIME);
|
||||
const wave = s.add('ShaderSin');
|
||||
const sum = s.add('ShaderAdd');
|
||||
const output = s.add(SHADER_OUTPUT, { edge: 'repeat' });
|
||||
s.link(uv, 'u', flipped, 'b');
|
||||
s.link(flipped, 'result', sample, 'u');
|
||||
s.link(sample, 'color', output, 'color');
|
||||
s.link(time, 'value', wave, 'value');
|
||||
s.link(size, 'width', sum, 'a');
|
||||
s.link(wave, 'result', sum, 'b');
|
||||
s.link(sum, 'result', output, 'alpha');
|
||||
// Nothing to type on u and v: unconnected, each is the pixel's own, and the node is Texture color.
|
||||
expect(inlineInputs(ShaderSample)).toEqual([]);
|
||||
const build = compileShader(s.asset().graph);
|
||||
expect(build.problems).toEqual({});
|
||||
const compiled = build.compiled!;
|
||||
expect(compiled).toMatchObject({ time: true, edge: 'repeat', uniforms: [] });
|
||||
const { fragment } = compiled;
|
||||
expect(fragment).toContain('uniform float uTime;');
|
||||
expect(fragment).toMatch(/float n\d+_u = vTextureCoord\.x;/);
|
||||
expect(fragment).toMatch(/float n\d+_result = \(1\.0 - n\d+_u\);/);
|
||||
expect(fragment).toMatch(
|
||||
/vec3 n\d+_color = straight\(texture\(uTexture, vec2\(n\d+_result, vTextureCoord\.y\)\) \* vColor\);/,
|
||||
);
|
||||
expect(fragment).toMatch(
|
||||
/float n\d+_alpha = \(texture\(uTexture, vec2\(n\d+_result, vTextureCoord\.y\)\) \* vColor\)\.a;/,
|
||||
);
|
||||
expect(fragment).toMatch(/float n\d+_width = float\(textureSize\(uTexture, 0\)\.x\);/);
|
||||
expect(fragment).toMatch(/float n\d+_value = uTime;/);
|
||||
expect(fragment).toMatch(/float n\d+_result = sin\(n\d+_value\);/);
|
||||
});
|
||||
|
||||
it('takes the clock only for a Time node the Output reaches, and the Edge from the Output', () => {
|
||||
const f = makeProject();
|
||||
const s = shaderEditor(f, 'Idle');
|
||||
s.add(SHADER_TIME);
|
||||
const out = s.add(SHADER_OUTPUT);
|
||||
let compiled = compileShader(s.asset().graph).compiled!;
|
||||
expect(compiled).toMatchObject({ time: false, edge: 'mirror' });
|
||||
expect(compiled.fragment).not.toContain('uTime');
|
||||
const params = s.asset().graph.nodes.find((n) => n.id === out)!.params;
|
||||
params.edge = 'clamp';
|
||||
expect(compileShader(s.asset().graph).compiled!.edge).toBe('clamp');
|
||||
// A value the dropdown never offers reads as the default rather than as a mode.
|
||||
params.edge = 'sideways';
|
||||
compiled = compileShader(s.asset().graph).compiled!;
|
||||
expect(compiled.edge).toBe('mirror');
|
||||
});
|
||||
|
||||
it('writes Fract, Step and Floor as the GLSL calls, per channel for a colour', () => {
|
||||
const f = makeProject();
|
||||
const s = shaderEditor(f, 'Lines');
|
||||
const uv = s.add(SHADER_UV);
|
||||
const scaled = s.add('ShaderMultiply', { b: 4 });
|
||||
const saw = s.add('ShaderFract');
|
||||
const cut = s.add('ShaderStep', { edge: 0.5 });
|
||||
const tint = s.add('ShaderColor', { value: 0xff8000 });
|
||||
const whole = s.add('ShaderFloor');
|
||||
const output = s.add(SHADER_OUTPUT);
|
||||
s.link(uv, 'v', scaled, 'a');
|
||||
s.link(scaled, 'result', saw, 'value');
|
||||
s.link(saw, 'result', cut, 'value');
|
||||
s.link(cut, 'result', output, 'alpha');
|
||||
s.link(tint, 'color', whole, 'value');
|
||||
s.link(whole, 'result', output, 'color');
|
||||
const build = compileShader(s.asset().graph);
|
||||
expect(build.problems).toEqual({});
|
||||
const { fragment } = build.compiled!;
|
||||
expect(fragment).toMatch(/float n\d+_result = \(n\d+_v \* 4\.0\);/);
|
||||
expect(fragment).toMatch(/float n\d+_result = fract\(n\d+_result\);/);
|
||||
expect(fragment).toMatch(/float n\d+_result = step\(0\.5, n\d+_result\);/);
|
||||
expect(fragment).toMatch(/vec3 n\d+_result = floor\(n\d+_color\);/);
|
||||
expect(fragment).toMatch(/float alpha = clamp\(n\d+_result, 0\.0, 1\.0\);/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a sprite wearing a shader', () => {
|
||||
|
||||
Reference in new issue
Block a user