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
@@ -325,6 +325,8 @@ export class Editor implements PlayHost {
|
||||
this.measureFps(dt);
|
||||
this.play.tick(dt);
|
||||
this.editorRun.tick(dt);
|
||||
this.viewport.animate();
|
||||
this.gameView.animate();
|
||||
this.console.refresh();
|
||||
if (this.tab === 'animation') this.animationEditor.tick(dt);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { resolveObjects } from '../core/model/instance';
|
||||
import { findObjectHolder } from '../core/model/Project';
|
||||
import type { FrameRect } from '../core/model/texture';
|
||||
import { activeDrag, Button, ui, Widget } from '../ui';
|
||||
import { shaderLibrary } from '../viewport/ShaderLibrary';
|
||||
import { textureLibrary } from '../viewport/TextureLibrary';
|
||||
import type { CentreTab, Editor } from './Editor';
|
||||
import { currentImagePicker } from './texture/ImagePicker';
|
||||
@@ -410,6 +411,10 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
walk(app.stage);
|
||||
return n;
|
||||
},
|
||||
/** Holds the shader clock at `at` seconds, so a Time shader's pixels can be measured; null releases it. */
|
||||
shaderTime: (at: number | null) => shaderLibrary.freezeTime(at),
|
||||
/** Which views redraw every tick because a Time shader is on screen. */
|
||||
animates: () => ({ scene: viewport.animates, game: gameView.animates }),
|
||||
lowLatency: () =>
|
||||
(app.renderer as unknown as { gl?: WebGL2RenderingContext }).gl?.getContextAttributes()
|
||||
?.desynchronized === true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { EditorState } from '../state';
|
||||
import { type Size, Theme, Widget } from '../../ui';
|
||||
import type { DrawItem } from '../../viewport/items';
|
||||
import { shaderLibrary } from '../../viewport/ShaderLibrary';
|
||||
import type { ShaderSource } from '../../viewport/shaders';
|
||||
import { type ShaderSource, wornShader } from '../../viewport/shaders';
|
||||
import { disposeSpriteMesh, drawItem, newSpriteMesh, syncSpriteMesh } from '../../viewport/symbols';
|
||||
import { textureLibrary } from '../../viewport/TextureLibrary';
|
||||
import { type View, worldBounds, worldToScreen } from '../../viewport/view';
|
||||
@@ -32,6 +32,7 @@ export abstract class WorldViewport extends Widget {
|
||||
/** Pictures of sprite items: siblings as well, each right under its node. */
|
||||
private readonly sprites = new Map<string, Mesh>();
|
||||
private visibleCount = 0;
|
||||
private animated = false;
|
||||
protected lastItems: DrawItem[] = [];
|
||||
|
||||
protected constructor(protected readonly state: EditorState) {
|
||||
@@ -45,8 +46,9 @@ export abstract class WorldViewport extends Widget {
|
||||
state.on('project', () => this.redraw());
|
||||
state.on('selection', () => this.redraw());
|
||||
state.on('mode', () => this.redraw());
|
||||
// While a Time shader is on screen the ticker redraws every frame anyway (`animate`).
|
||||
state.on('runtime', () => {
|
||||
if (this.visible) this.redraw();
|
||||
if (this.visible && !this.animated) this.redraw();
|
||||
});
|
||||
// Unconditional: a picture arriving fires once per image, not per frame.
|
||||
textureLibrary.onChange(() => this.redraw());
|
||||
@@ -87,6 +89,16 @@ export abstract class WorldViewport extends Widget {
|
||||
return { rendering: this.currentView() !== null, visibleItems: this.visibleCount };
|
||||
}
|
||||
|
||||
/** True after a redraw that drew a sprite wearing a Time shader: the picture moves by itself. */
|
||||
get animates(): boolean {
|
||||
return this.animated;
|
||||
}
|
||||
|
||||
/** Called every tick: redraws while the last redraw found a Time shader on screen. */
|
||||
animate(): void {
|
||||
if (this.visible && this.animated) this.redraw();
|
||||
}
|
||||
|
||||
layerOf(objectId: string): 'objects' | 'overlay' | null {
|
||||
const node = this.nodes.get(objectId);
|
||||
if (!node) return null;
|
||||
@@ -131,6 +143,7 @@ export abstract class WorldViewport extends Widget {
|
||||
this.contentMask.clear();
|
||||
this.content.visible = view !== null;
|
||||
this.visibleCount = 0;
|
||||
this.animated = false;
|
||||
if (!view) return;
|
||||
|
||||
const { items, background } = this.collectFrame();
|
||||
@@ -219,6 +232,7 @@ export abstract class WorldViewport extends Widget {
|
||||
this.objects.addChild(sprite);
|
||||
}
|
||||
sprite.visible = true;
|
||||
if (wornShader(item.props, shaders)?.time) this.animated = true;
|
||||
syncSpriteMesh(sprite, item, size, textureLibrary, shaders);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user