refactor help
This commit is contained in:
@ -507,7 +507,7 @@ export class FakeShader {
|
||||
try {
|
||||
const bitmapPromises: Promise<ImageBitmap>[] = [];
|
||||
const positions: number[] = [];
|
||||
|
||||
|
||||
for (let i = 0; i < this.workerCount; i++) {
|
||||
const tileData = this.tileResults.get(i);
|
||||
if (tileData) {
|
||||
@ -515,9 +515,9 @@ export class FakeShader {
|
||||
positions.push(i * tileHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const bitmaps = await Promise.all(bitmapPromises);
|
||||
|
||||
|
||||
for (let i = 0; i < bitmaps.length; i++) {
|
||||
this.ctx.drawImage(bitmaps[i], 0, positions[i]);
|
||||
bitmaps[i].close(); // Free memory
|
||||
@ -549,7 +549,7 @@ export class FakeShader {
|
||||
|
||||
private fallbackCompositeTiles(): void {
|
||||
const tileHeight = Math.ceil(this.canvas.height / this.workerCount);
|
||||
|
||||
|
||||
for (let i = 0; i < this.workerCount; i++) {
|
||||
const tileData = this.tileResults.get(i);
|
||||
if (tileData) {
|
||||
@ -576,17 +576,21 @@ export class FakeShader {
|
||||
'x^y',
|
||||
'x&y',
|
||||
'x|y',
|
||||
'(x*y)%256',
|
||||
'a|d|r',
|
||||
'x|n*t^b*(t % 1.0)',
|
||||
'(x+y+t*10)%256',
|
||||
'((x>>4)^(y>>4))<<4',
|
||||
'(x^y^(x*y))%256',
|
||||
'd * t / 2.0',
|
||||
'((x&y)|(x^y))%256',
|
||||
'(x+y)&255',
|
||||
'a^d * [b, r**t][floor(t%2.0)]',
|
||||
'x%y',
|
||||
'(x^(y<<2))%256',
|
||||
'((x*t)^y)%256',
|
||||
'(x&(y|t*8))%256',
|
||||
'((x>>2)|(y<<2))%256',
|
||||
'a+d*t',
|
||||
'n*t*400',
|
||||
'((x>>2)|(y<<2))%88',
|
||||
'(x*y*t)%256',
|
||||
'(x+y*t)%256',
|
||||
'(x^y^(t*16))%256',
|
||||
@ -599,11 +603,14 @@ export class FakeShader {
|
||||
'((x|t)^(y|t))%256',
|
||||
];
|
||||
|
||||
const vars = ['x', 'y', 't', 'i'];
|
||||
const ops = ['^', '&', '|', '+', '-', '*', '%'];
|
||||
const vars = ['x', 'y', 't', 'i', 'a', 'd', 'n', 'r', 'u', 'v', 'd', 'b'];
|
||||
const ops = ['^', '&', '|', '+', '-', '*', '%', '**', '%'];
|
||||
const shifts = ['<<', '>>'];
|
||||
const numbers = ['2', '4', '8', '16', '32', '64', '128', '256'];
|
||||
|
||||
const numbers = [];
|
||||
for (let i = 0; i < Math.random(200); i++) {
|
||||
numbers.push(Math.floor(Math.random(400)))
|
||||
}
|
||||
const randomChoice = <T>(arr: T[]): T =>
|
||||
arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
@ -618,8 +625,7 @@ export class FakeShader {
|
||||
() => `${randomChoice(vars)}^${randomChoice(vars)}^${randomChoice(vars)}`,
|
||||
];
|
||||
|
||||
// 70% chance to pick from presets, 30% chance to generate dynamic
|
||||
if (Math.random() < 0.7) {
|
||||
if (Math.random() < 0.5) {
|
||||
return randomChoice(presets);
|
||||
} else {
|
||||
return randomChoice(dynamicExpressions)();
|
||||
|
||||
@ -62,6 +62,12 @@ class ShaderWorker {
|
||||
PERFORMANCE.COMPILATION_CACHE_SIZE
|
||||
);
|
||||
private feedbackBuffer: Float32Array | null = null;
|
||||
private previousFeedbackBuffer: Float32Array | null = null;
|
||||
private stateBuffer: Float32Array | null = null;
|
||||
private echoBuffers: Float32Array[] = [];
|
||||
private echoFrameCounter: number = 0;
|
||||
private echoInterval: number = 30; // Store echo every 30 frames (~0.5s at 60fps)
|
||||
private lastFrameTime: number = 0;
|
||||
|
||||
constructor() {
|
||||
self.onmessage = (e: MessageEvent<WorkerMessage>) => {
|
||||
@ -139,6 +145,22 @@ class ShaderWorker {
|
||||
'd',
|
||||
'n',
|
||||
'b',
|
||||
'bn',
|
||||
'bs',
|
||||
'be',
|
||||
'bw',
|
||||
'w',
|
||||
'h',
|
||||
'p',
|
||||
'z',
|
||||
'j',
|
||||
'o',
|
||||
'g',
|
||||
'm',
|
||||
'l',
|
||||
'k',
|
||||
's',
|
||||
'e',
|
||||
'mouseX',
|
||||
'mouseY',
|
||||
'mousePressed',
|
||||
@ -201,8 +223,8 @@ class ShaderWorker {
|
||||
|
||||
private isStaticExpression(code: string): boolean {
|
||||
// Check if code contains any variables using regex for better accuracy
|
||||
const variablePattern = /\b(x|y|t|i|r|a|u|v|c|f|d|n|b|bpm|mouse[XY]|mousePressed|mouseV[XY]|mouseClickTime|touchCount|touch[01][XY]|pinchScale|pinchRotation|accel[XYZ]|gyro[XYZ]|audioLevel|bassLevel|midLevel|trebleLevel)\b/;
|
||||
|
||||
const variablePattern = /\b(x|y|t|i|r|a|u|v|c|f|d|n|b|bn|bs|be|bw|m|l|k|s|e|w|h|p|z|j|o|g|bpm|mouse[XY]|mousePressed|mouseV[XY]|mouseClickTime|touchCount|touch[01][XY]|pinchScale|pinchRotation|accel[XYZ]|gyro[XYZ]|audioLevel|bassLevel|midLevel|trebleLevel)\b/;
|
||||
|
||||
return !variablePattern.test(code);
|
||||
}
|
||||
|
||||
@ -246,11 +268,23 @@ class ShaderWorker {
|
||||
const startTime = performance.now();
|
||||
const maxRenderTime = PERFORMANCE.MAX_RENDER_TIME_MS;
|
||||
|
||||
// Initialize feedback buffer if needed
|
||||
// Initialize feedback buffers if needed
|
||||
if (!this.feedbackBuffer || this.feedbackBuffer.length !== width * height) {
|
||||
this.feedbackBuffer = new Float32Array(width * height);
|
||||
this.previousFeedbackBuffer = new Float32Array(width * height);
|
||||
this.stateBuffer = new Float32Array(width * height);
|
||||
|
||||
// Initialize echo buffers (4 buffers for different time delays)
|
||||
this.echoBuffers = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this.echoBuffers.push(new Float32Array(width * height));
|
||||
}
|
||||
}
|
||||
|
||||
// Update frame timing for frame rate independence
|
||||
const deltaTime = time - this.lastFrameTime;
|
||||
this.lastFrameTime = time;
|
||||
|
||||
try {
|
||||
// Use tiled rendering for better timeout handling
|
||||
this.renderTiled(
|
||||
@ -263,8 +297,33 @@ class ShaderWorker {
|
||||
message,
|
||||
startTime,
|
||||
maxRenderTime,
|
||||
startY
|
||||
startY,
|
||||
deltaTime
|
||||
);
|
||||
|
||||
// Copy current feedback to previous for next frame momentum calculations
|
||||
if (this.feedbackBuffer && this.previousFeedbackBuffer) {
|
||||
this.previousFeedbackBuffer.set(this.feedbackBuffer);
|
||||
}
|
||||
|
||||
// Update echo buffers at regular intervals
|
||||
this.echoFrameCounter++;
|
||||
if (this.echoFrameCounter >= this.echoInterval && this.echoBuffers.length > 0) {
|
||||
this.echoFrameCounter = 0;
|
||||
|
||||
// Rotate echo buffers: shift all buffers forward and store current in first buffer
|
||||
for (let i = this.echoBuffers.length - 1; i > 0; i--) {
|
||||
if (this.echoBuffers[i] && this.echoBuffers[i - 1]) {
|
||||
this.echoBuffers[i].set(this.echoBuffers[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Store current feedback in first echo buffer
|
||||
if (this.feedbackBuffer && this.echoBuffers[0]) {
|
||||
this.echoBuffers[0].set(this.feedbackBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
this.postMessage({ id, type: 'rendered', success: true, imageData });
|
||||
} catch (error) {
|
||||
this.postError(
|
||||
@ -284,7 +343,8 @@ class ShaderWorker {
|
||||
message: WorkerMessage,
|
||||
startTime: number,
|
||||
maxRenderTime: number,
|
||||
yOffset: number = 0
|
||||
yOffset: number = 0,
|
||||
deltaTime: number = 0.016
|
||||
): void {
|
||||
const tileSize = PERFORMANCE.DEFAULT_TILE_SIZE;
|
||||
const tilesX = Math.ceil(width / tileSize);
|
||||
@ -316,7 +376,8 @@ class ShaderWorker {
|
||||
renderMode,
|
||||
valueMode,
|
||||
message,
|
||||
yOffset
|
||||
yOffset,
|
||||
deltaTime
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -333,7 +394,8 @@ class ShaderWorker {
|
||||
renderMode: string,
|
||||
valueMode: string,
|
||||
message: WorkerMessage,
|
||||
yOffset: number = 0
|
||||
yOffset: number = 0,
|
||||
deltaTime: number = 0.016
|
||||
): void {
|
||||
// Get full canvas dimensions for special modes (use provided full dimensions or fall back)
|
||||
const fullWidth = message.fullWidth || width;
|
||||
@ -362,9 +424,63 @@ class ShaderWorker {
|
||||
const manhattanDistance =
|
||||
Math.abs(x - centerX) + Math.abs(actualY - centerY);
|
||||
const noise = (Math.sin(x * 0.1) * Math.cos(actualY * 0.1) + 1) * 0.5;
|
||||
const feedbackValue = this.feedbackBuffer
|
||||
? this.feedbackBuffer[pixelIndex] || 0
|
||||
: 0;
|
||||
// Simple, efficient feedback system
|
||||
const currentFeedback = this.feedbackBuffer ? this.feedbackBuffer[pixelIndex] || 0 : 0;
|
||||
const feedbackValue = currentFeedback;
|
||||
|
||||
// Simple neighbor feedback with bounds checking
|
||||
let fbn = 0, fbs = 0, fbe = 0, fbw = 0;
|
||||
if (this.feedbackBuffer) {
|
||||
// North neighbor (bounds safe)
|
||||
if (y > 0) fbn = this.feedbackBuffer[(y - 1) * width + x] || 0;
|
||||
// South neighbor (bounds safe)
|
||||
if (y < fullHeight - 1) fbs = this.feedbackBuffer[(y + 1) * width + x] || 0;
|
||||
// East neighbor (bounds safe)
|
||||
if (x < width - 1) fbe = this.feedbackBuffer[y * width + (x + 1)] || 0;
|
||||
// West neighbor (bounds safe)
|
||||
if (x > 0) fbw = this.feedbackBuffer[y * width + (x - 1)] || 0;
|
||||
}
|
||||
|
||||
// Calculate feedback-based operators
|
||||
// m - Momentum/Velocity (change from previous frame)
|
||||
const previousValue = this.previousFeedbackBuffer ? this.previousFeedbackBuffer[pixelIndex] || 0 : 0;
|
||||
const momentum = (feedbackValue - previousValue) * 0.5; // Scale for stability
|
||||
|
||||
// l - Laplacian/Diffusion (spatial derivative)
|
||||
const laplacian = (fbn + fbs + fbe + fbw - feedbackValue * 4) * 0.25;
|
||||
|
||||
// k - Curvature/Contrast (gradient magnitude)
|
||||
const gradientX = (fbe - fbw) * 0.5;
|
||||
const gradientY = (fbs - fbn) * 0.5;
|
||||
const curvature = Math.sqrt(gradientX * gradientX + gradientY * gradientY);
|
||||
|
||||
// s - State/Memory (persistent accumulator)
|
||||
let currentState = this.stateBuffer ? this.stateBuffer[pixelIndex] || 0 : 0;
|
||||
// State accumulates when feedback is high, decays when low
|
||||
if (feedbackValue > 128) {
|
||||
currentState = Math.min(255, currentState + deltaTime * 200); // Accumulate
|
||||
} else {
|
||||
currentState = Math.max(0, currentState - deltaTime * 100); // Decay
|
||||
}
|
||||
const stateValue = currentState;
|
||||
|
||||
// e - Echo/History (temporal snapshots)
|
||||
let echoValue = 0;
|
||||
if (this.echoBuffers.length > 0) {
|
||||
// Cycle through different echo delays based on time
|
||||
const echoIndex = Math.floor(time * 2) % this.echoBuffers.length; // Change every 0.5 seconds
|
||||
const echoBuffer = this.echoBuffers[echoIndex];
|
||||
echoValue = echoBuffer ? echoBuffer[pixelIndex] || 0 : 0;
|
||||
}
|
||||
|
||||
// Calculate other variables
|
||||
const canvasWidth = fullWidth;
|
||||
const canvasHeight = fullHeight;
|
||||
const phase = (time * Math.PI * 2) % (Math.PI * 2); // 0 to 2π cycling
|
||||
const pseudoZ = Math.sin(radius * 0.01 + time) * 50; // depth based on radius and time
|
||||
const jitter = ((x * 73856093 + actualY * 19349663) % 256) / 255; // deterministic per-pixel random
|
||||
const oscillation = Math.sin(time * 2 * Math.PI + radius * 0.1); // wave oscillation
|
||||
const goldenRatio = 1.618033988749; // golden ratio constant
|
||||
|
||||
const value = this.compiledFunction!(
|
||||
x,
|
||||
@ -380,6 +496,22 @@ class ShaderWorker {
|
||||
manhattanDistance,
|
||||
noise,
|
||||
feedbackValue,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
phase,
|
||||
pseudoZ,
|
||||
jitter,
|
||||
oscillation,
|
||||
goldenRatio,
|
||||
momentum,
|
||||
laplacian,
|
||||
curvature,
|
||||
stateValue,
|
||||
echoValue,
|
||||
fbn,
|
||||
fbs,
|
||||
fbe,
|
||||
fbw,
|
||||
message.mouseX || 0,
|
||||
message.mouseY || 0,
|
||||
message.mousePressed ? 1 : 0,
|
||||
@ -422,9 +554,28 @@ class ShaderWorker {
|
||||
data[i + 2] = b;
|
||||
data[i + 3] = 255;
|
||||
|
||||
// Update feedback buffer with current processed value
|
||||
// Store feedback as luminance of displayed color for consistency
|
||||
if (this.feedbackBuffer) {
|
||||
this.feedbackBuffer[pixelIndex] = safeValue;
|
||||
// Use the actual displayed luminance as feedback (0-255 range)
|
||||
const luminance = (r * 0.299 + g * 0.587 + b * 0.114);
|
||||
|
||||
// Frame rate independent decay
|
||||
const decayFactor = Math.pow(0.95, deltaTime * 60); // 5% decay at 60fps
|
||||
|
||||
// Simple mixing to prevent oscillation
|
||||
const previousValue = this.feedbackBuffer[pixelIndex] || 0;
|
||||
const mixRatio = Math.min(deltaTime * 10, 0.3); // Max 30% new value per frame
|
||||
|
||||
let newFeedback = luminance * mixRatio + previousValue * (1 - mixRatio);
|
||||
newFeedback *= decayFactor;
|
||||
|
||||
// Clamp and store
|
||||
this.feedbackBuffer[pixelIndex] = Math.max(0, Math.min(255, newFeedback));
|
||||
}
|
||||
|
||||
// Update state buffer
|
||||
if (this.stateBuffer) {
|
||||
this.stateBuffer[pixelIndex] = stateValue;
|
||||
}
|
||||
} catch (error) {
|
||||
data[i] = 0;
|
||||
@ -584,8 +735,8 @@ class ShaderWorker {
|
||||
case 'cellular': {
|
||||
// Cellular automata-inspired patterns
|
||||
const cellSize = 16;
|
||||
const cellX = Math.floor(x / cellSize);
|
||||
const cellY = Math.floor(y / cellSize);
|
||||
const cellX = Math.floor(x / centerX);
|
||||
const cellY = Math.floor(y / centerY);
|
||||
const cellHash =
|
||||
(cellX * 73856093) ^ (cellY * 19349663) ^ Math.floor(Math.abs(value));
|
||||
|
||||
@ -802,8 +953,8 @@ class ShaderWorker {
|
||||
const latticeSize = 32 + Math.abs(value) * 0.1;
|
||||
const gridX = Math.floor(x / latticeSize);
|
||||
const gridY = Math.floor(y / latticeSize);
|
||||
const crystal = Math.sin(gridX + gridY + Math.abs(value) * 0.01) *
|
||||
Math.cos(gridX * gridY + Math.abs(value) * 0.005);
|
||||
const crystal = Math.sin(gridX + gridY + Math.abs(value) * 0.01) *
|
||||
Math.cos(gridX * gridY + Math.abs(value) * 0.005);
|
||||
processedValue = Math.floor((crystal * 0.5 + 0.5) * 255);
|
||||
break;
|
||||
}
|
||||
@ -812,7 +963,7 @@ class ShaderWorker {
|
||||
// Marble-like veining patterns
|
||||
const noiseFreq = 0.005 + Math.abs(value) * 0.00001;
|
||||
const turbulence = Math.sin(x * noiseFreq) * Math.cos(y * noiseFreq) +
|
||||
Math.sin(x * noiseFreq * 2) * Math.cos(y * noiseFreq * 2) * 0.5;
|
||||
Math.sin(x * noiseFreq * 2) * Math.cos(y * noiseFreq * 2) * 0.5;
|
||||
const marble = Math.sin((x + turbulence * 50) * 0.02 + Math.abs(value) * 0.001);
|
||||
processedValue = Math.floor((marble * 0.5 + 0.5) * 255);
|
||||
break;
|
||||
@ -884,12 +1035,12 @@ class ShaderWorker {
|
||||
const random = ((seed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
|
||||
const glitchThreshold = 0.95 - Math.abs(value) * 0.0001;
|
||||
let glitchValue = Math.abs(value) % 256;
|
||||
|
||||
|
||||
if (random > glitchThreshold) {
|
||||
// Digital corruption: bit shifts, XOR, scrambling
|
||||
glitchValue = (glitchValue << 1) ^ (glitchValue >> 3) ^ ((x + y) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
processedValue = glitchValue % 256;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -52,21 +52,21 @@ export function HelpPopup() {
|
||||
<strong>M</strong> - Cycle value mode
|
||||
</p>
|
||||
<p>
|
||||
<strong>Space</strong> - Tap tempo (when editor not focused)
|
||||
<strong>Space</strong> - Tap tempo
|
||||
</p>
|
||||
<p>
|
||||
<strong>Arrow Left/Right</strong> - Adjust hue shift (when editor not focused)
|
||||
<strong>Arrow Left/Right</strong> - Adjust hue shift
|
||||
</p>
|
||||
<p>
|
||||
<strong>Arrow Up/Down</strong> - Cycle value mode (when editor not focused)
|
||||
<strong>Arrow Up/Down</strong> - Cycle value mode
|
||||
</p>
|
||||
<p>
|
||||
<strong>Shift+Arrow Up/Down</strong> - Cycle render mode (when editor not focused)
|
||||
<strong>Shift+Arrow Up/Down</strong> - Cycle render mode
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Variables</h4>
|
||||
<h4>Core Variables - Basics</h4>
|
||||
<p>
|
||||
<strong>x, y</strong> - Pixel coordinates
|
||||
</p>
|
||||
@ -97,11 +97,30 @@ export function HelpPopup() {
|
||||
<p>
|
||||
<strong>d</strong> - Manhattan distance from center
|
||||
</p>
|
||||
<p>
|
||||
<strong>w, h</strong> - Canvas width and height (pixels)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Core Variables - Advanced</h4>
|
||||
<p>
|
||||
<strong>n</strong> - Noise value (0.0 to 1.0)
|
||||
</p>
|
||||
<p>
|
||||
<strong>b</strong> - Previous frame's value (feedback)
|
||||
<strong>p</strong> - Phase value (0 to 2π, cycles with time)
|
||||
</p>
|
||||
<p>
|
||||
<strong>z</strong> - Pseudo-depth coordinate (oscillates with distance and time)
|
||||
</p>
|
||||
<p>
|
||||
<strong>j</strong> - Per-pixel jitter/random value (0.0 to 1.0, deterministic)
|
||||
</p>
|
||||
<p>
|
||||
<strong>o</strong> - Oscillation value (wave function based on time and distance)
|
||||
</p>
|
||||
<p>
|
||||
<strong>g</strong> - Golden ratio constant (1.618... for natural spirals)
|
||||
</p>
|
||||
<p>
|
||||
<strong>mouseX, mouseY</strong> - Mouse position (0.0 to 1.0)
|
||||
@ -117,6 +136,34 @@ export function HelpPopup() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Feedback Variables</h4>
|
||||
<p>
|
||||
<strong>b</strong> - Previous frame's luminance at this pixel (0-255)
|
||||
</p>
|
||||
<p>
|
||||
<strong>bn, bs, be, bw</strong> - Neighbor luminance (North, South, East, West)
|
||||
</p>
|
||||
<p>
|
||||
<strong>m</strong> - Momentum/velocity: Detects motion and change between frames
|
||||
</p>
|
||||
<p>
|
||||
<strong>l</strong> - Laplacian/diffusion: Creates natural spreading and heat diffusion
|
||||
</p>
|
||||
<p>
|
||||
<strong>k</strong> - Curvature/contrast: Edge detection and gradient magnitude
|
||||
</p>
|
||||
<p>
|
||||
<strong>s</strong> - State/memory: Persistent accumulator that remembers bright areas
|
||||
</p>
|
||||
<p>
|
||||
<strong>e</strong> - Echo/history: Temporal snapshots that recall past brightness patterns
|
||||
</p>
|
||||
<p>
|
||||
<em>Feedback uses actual displayed brightness with natural decay and frame-rate independence for stable, evolving patterns.</em>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Touch & Gestures</h4>
|
||||
<p>
|
||||
@ -214,29 +261,6 @@ export function HelpPopup() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Value Modes</h4>
|
||||
<p>
|
||||
<strong>Integer (0-255):</strong> Traditional mode for large values
|
||||
</p>
|
||||
<p>
|
||||
<strong>Float (0.0-1.0):</strong> Bitfield shader mode, inverts and
|
||||
clamps values
|
||||
</p>
|
||||
<p>
|
||||
<strong>Polar (angle-based):</strong> Spiral patterns combining
|
||||
angle and radius
|
||||
</p>
|
||||
<p>
|
||||
<strong>Distance (radial):</strong> Concentric wave rings with
|
||||
variable frequency
|
||||
</p>
|
||||
<p>
|
||||
<strong>Wave (ripple):</strong> Multi-source interference with
|
||||
amplitude falloff
|
||||
</p>
|
||||
<p>Each mode transforms your expression differently!</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Advanced Features</h4>
|
||||
@ -261,40 +285,22 @@ export function HelpPopup() {
|
||||
<div className="help-section">
|
||||
<h4>Shader Library</h4>
|
||||
<p>
|
||||
Hover over the <strong>left edge</strong> of the screen to access
|
||||
the shader library
|
||||
<strong>Access:</strong> Hover over the left edge of the screen
|
||||
</p>
|
||||
<p>Save shaders with custom names and search through them</p>
|
||||
<p>
|
||||
Use <strong>edit</strong> to rename, <strong>del</strong> to delete
|
||||
<strong>Save:</strong> Click the save icon to store current shader
|
||||
</p>
|
||||
<p>
|
||||
<strong>Search:</strong> Filter saved shaders by name
|
||||
</p>
|
||||
<p>
|
||||
<strong>Manage:</strong> Edit names or delete with the buttons
|
||||
</p>
|
||||
<p>
|
||||
<strong>Load:</strong> Click any shader to apply it instantly
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Render Modes</h4>
|
||||
<p>
|
||||
<strong>Classic</strong> - Original colorful mode
|
||||
</p>
|
||||
<p>
|
||||
<strong>Grayscale</strong> - Black and white
|
||||
</p>
|
||||
<p>
|
||||
<strong>Red/Green/Blue</strong> - Single color channels
|
||||
</p>
|
||||
<p>
|
||||
<strong>HSV</strong> - Hue-based coloring
|
||||
</p>
|
||||
<p>
|
||||
<strong>Rainbow</strong> - Spectrum coloring
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="help-section">
|
||||
<h4>Export</h4>
|
||||
<p>
|
||||
<strong>Export PNG</strong> - Save current frame as image
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user