Shader graph: Pow node, base held at 0, exponent broadcast for colours (#80)
game-maker / suite (push) Failing after 8m13s
game-maker / suite (push) Failing after 8m13s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
102a649a35
commit
a399795772
4 files changed
+70
-1
No files matched your search
@@ -37,7 +37,7 @@ npm run build # writes dist/ and regenerates index.html; the folder is wh
|
||||
The browser suite and the three example games need a machine with a GPU:
|
||||
|
||||
```bash
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 760 checks (GM_STEPS=20 for one step)
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 762 checks (GM_STEPS=20 for one step)
|
||||
node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs
|
||||
node tools/seed-tetris.mjs && node tools/smoke-tetris.mjs
|
||||
node tools/seed-actor.mjs && node tools/smoke-actor.mjs
|
||||
|
||||
@@ -355,6 +355,22 @@ export const ShaderStep: ShaderNodeDef = {
|
||||
}),
|
||||
};
|
||||
|
||||
/** GLSL leaves `pow` undefined below 0, so the base is held at 0 there. */
|
||||
export const ShaderPow: ShaderNodeDef = {
|
||||
type: 'ShaderPow',
|
||||
label: 'Pow',
|
||||
category: 'shader',
|
||||
description:
|
||||
'value to the power of exponent: 2 squares it, 0.5 is the square root. Below 0 the value counts as 0. Per channel for colours.',
|
||||
inputs: [anyIn('value', 0), floatIn('exponent', 2)],
|
||||
outputs: [anyOut('result')],
|
||||
params: [],
|
||||
emit: ({ value, exponent }) => {
|
||||
const power = value!.type === 'vec3' ? `vec3(${exponent!.code})` : exponent!.code;
|
||||
return { result: { code: `pow(max(${value!.code}, 0.0), ${power})`, type: value!.type } };
|
||||
},
|
||||
};
|
||||
|
||||
/** Every node a shader graph offers. Registered by `graph/nodes`, offered by no behavior palette. */
|
||||
export const SHADER_NODES: readonly ShaderNodeDef[] = [
|
||||
ShaderTextureColor,
|
||||
@@ -377,6 +393,7 @@ export const SHADER_NODES: readonly ShaderNodeDef[] = [
|
||||
ShaderFloor,
|
||||
ShaderFract,
|
||||
ShaderStep,
|
||||
ShaderPow,
|
||||
];
|
||||
|
||||
/** A GLSL float literal: always with a decimal point, so `1` does not become an int. */
|
||||
|
||||
@@ -451,6 +451,25 @@ describe('compileShader', () => {
|
||||
expect(fragment).toMatch(/vec3 n\d+_result = floor\(n\d+_color\);/);
|
||||
expect(fragment).toMatch(/float alpha = clamp\(n\d+_result, 0\.0, 1\.0\);/);
|
||||
});
|
||||
|
||||
it('writes Pow as the GLSL call with the base held at 0, the exponent broadcast for a colour', () => {
|
||||
const f = makeProject();
|
||||
const s = shaderEditor(f, 'Gamma');
|
||||
const uv = s.add(SHADER_UV);
|
||||
const eased = s.add('ShaderPow', { exponent: 2 });
|
||||
const tint = s.add('ShaderColor', { value: 0xff8000 });
|
||||
const gamma = s.add('ShaderPow', { exponent: 2.2 });
|
||||
const output = s.add(SHADER_OUTPUT);
|
||||
s.link(uv, 'u', eased, 'value');
|
||||
s.link(eased, 'result', output, 'alpha');
|
||||
s.link(tint, 'color', gamma, 'value');
|
||||
s.link(gamma, 'result', output, 'color');
|
||||
const build = compileShader(s.asset().graph);
|
||||
expect(build.problems).toEqual({});
|
||||
const { fragment } = build.compiled!;
|
||||
expect(fragment).toMatch(/float n\d+_result = pow\(max\(n\d+_u, 0\.0\), 2\.0\);/);
|
||||
expect(fragment).toMatch(/vec3 n\d+_result = pow\(max\(n\d+_color, 0\.0\), vec3\(2\.2\)\);/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a sprite wearing a shader', () => {
|
||||
|
||||
@@ -470,6 +470,39 @@ export async function shader(t) {
|
||||
{ vTop, vBottom },
|
||||
);
|
||||
|
||||
// 28k-ii. Pow bends the cut: u squared crosses 0.5 at u = 0.707, its square root at u = 0.25.
|
||||
const cutAfterPow = (exponent) =>
|
||||
redOrBlue(
|
||||
[
|
||||
node('uv', 'ShaderUV'),
|
||||
node('pow', 'ShaderPow', { exponent }, 260, 0),
|
||||
node('cut', 'ShaderStep', { edge: 0.5 }, 260, 120),
|
||||
],
|
||||
[
|
||||
edge('uv', 'u', 'pow', 'value'),
|
||||
edge('pow', 'result', 'cut', 'value'),
|
||||
edge('cut', 'result', 'mix', 't'),
|
||||
],
|
||||
);
|
||||
await cutAfterPow(2);
|
||||
await sleep(400);
|
||||
const squaredNear = await pixel(0.1, -0.3);
|
||||
const squaredFar = await pixel(0.3, -0.3);
|
||||
check(
|
||||
'Pow 2 moves the cut to u = 0.707: still red at u = 0.6, blue at 0.8',
|
||||
colourDistance(squaredNear, '#ff0000') < 6 && colourDistance(squaredFar, '#0000ff') < 6,
|
||||
{ squaredNear, squaredFar },
|
||||
);
|
||||
await cutAfterPow(0.5);
|
||||
await sleep(400);
|
||||
const rootNear = await pixel(-0.3, -0.3);
|
||||
const rootFar = await pixel(-0.15, -0.3);
|
||||
check(
|
||||
'Pow 0.5 pulls it to u = 0.25: red at u = 0.2, blue at 0.35',
|
||||
colourDistance(rootNear, '#ff0000') < 6 && colourDistance(rootFar, '#0000ff') < 6,
|
||||
{ rootNear, rootFar },
|
||||
);
|
||||
|
||||
// 28l. Sample texture reads the picture where the graph says. On a pasted two-colour picture:
|
||||
// flipped u swaps the halves; u + 0.5 past the border shows the Output's Edge – Repeat continues
|
||||
// with the far half, Mirror with the near one.
|
||||
|
||||
Reference in new issue
Block a user