Back to shaders

Shader test bench

Video Snake.fs

vidvox-isf-files utility glsl runnable fragment MIT
Source
runnable fragment

Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.

Code

uniform sampler2D inputImage;
uniform int corner;
uniform int cols;
uniform int rows;
uniform int direction;
uniform int edgeMode;
uniform float fpsThrottle;
uniform int imageSizeMode;
uniform int clear;
uniform int autoClear;
precision mediump float;
/*{
	"DESCRIPTION": "Makes a grid of frame delayed images",
	"ISFVSN": "2",
	"CREDIT": "by VIDVOX",
	"CATEGORIES": [
		"Tile Effect", "Stylize"
	],
	"INPUTS": [
		{
			"NAME": "inputImage",
			"TYPE": "image"
		},
		{
			"NAME": "corner",
			"LABEL": "Start Corner",
			"VALUES": [
				0,
				1,
				2,
				3
			],
			"LABELS": [
				"Top Left",
				"Top Right",
				"Bottom Left",
				"Bottom Right"
			],
			"DEFAULT": 0,
			"TYPE": "long"
		},
		{
			"NAME": "cols",
			"LABEL": "Columns",
			"VALUES": [
				1,
				2,
				3,
				4,
				5,
				6,
				7,
				8,
				9,
				10
			],
			"LABELS": [
				"1",
				"2",
				"3",
				"4",
				"5",
				"6",
				"7",
				"8",
				"9",
				"10"
			],
			"DEFAULT": 4,
			"TYPE": "long"
		},
		{
			"NAME": "rows",
			"LABEL": "Rows",
			"VALUES": [
				1,
				2,
				3,
				4,
				5,
				6,
				7,
				8,
				9,
				10
			],
			"LABELS": [
				"1",
				"2",
				"3",
				"4",
				"5",
				"6",
				"7",
				"8",
				"9",
				"10"
			],
			"DEFAULT": 4,
			"TYPE": "long"
		},
		{
			"NAME": "direction",
			"LABEL": "Direction",
			"VALUES": [
				0,
				1
			],
			"LABELS": [
				"Horizontal",
				"Vertical"
			],
			"DEFAULT": 0,
			"TYPE": "long"
		},
		{
			"NAME": "edgeMode",
			"LABEL": "Edge Mode",
			"VALUES": [
				0,
				1
			],
			"LABELS": [
				"Wrap",
				"Snake"
			],
			"DEFAULT": 1,
			"TYPE": "long"
		},
		{
			"NAME": "fpsThrottle",
			"LABEL": "FPS Lag",
			"TYPE": "float",
			"DEFAULT": 1.0,
			"MIN": 0.0,
			"MAX": 5.0
		},
		{
			"NAME": "imageSizeMode",
			"LABEL": "Size Mode",
			"VALUES": [
				0,
				1,
				2
			],
			"LABELS": [
				"Fit",
				"Fill",
				"Stretch"
			],
			"DEFAULT": 1,
			"TYPE": "long"
		},
		{
			"NAME": "clear",
			"LABEL": "Clear Buffer",
			"TYPE": "event"
		},
		{
			"NAME": "autoClear",
			"LABEL": "Auto Clear",
			"TYPE": "bool",
			"DEFAULT": 1.0
		}
	],
	"PASSES": [
		{
			"TARGET":"drawState",
			"PERSISTENT": true,
			"FLOAT": true,
			"WIDTH": "1",
			"HEIGHT": "1"
		},
		{
			"TARGET":"frameCounter",
			"PERSISTENT": true,
			"FLOAT": true,
			"WIDTH": "1",
			"HEIGHT": "1"
		},
		{
			"TARGET":"lastBuffer",
			"FLOAT": true,
			"PERSISTENT": true
		}
	]
	
}*/


//	this returns which cell a particular pixel is in, ordered based on the direction / start corner / wrap mode
float cellForCoord(vec2 st, vec2 size, int dir, int corn, int edge){
	//	st contains the pixel coordinate
	//	size contains the grid size
	//	dir contains the direction (horizontal / vertical)
	//	corn contains the start corner (top left, top right, bottom left, bottom right)
	//	edge contains the edge mode (wrap, snake)
	
	//	here's the general gist of this logic...
	//	start by assuming that it's bottom left, horizontal, with wrap around
	//	then do modifications on the result based on the settings
	//	eg if the corner is top right, pt.x = 1.0 - st.x
	//	eg if doing snake, check which col / row, and reverse within that col / row if needed
	vec2	loc = st;
	if (corn == 0)	{
		loc.y = 1.0 - loc.y;
	}
	else if (corn == 1)	{
		loc = 1.0 - loc;
	}
	else if (corn == 2)	{
		//	this is actually the normal state for st!
	}
	else if (corn == 3)	{
		loc.x = 1.0 - loc.x;
	}
	
	//	figure out which cell we are in within 2D space
	//	st contains the normalized x & y value (0.0 to 1.0)
	//		say we have 4 cols and 3 rows
	//		the gridLoc should range from 0 to 3 for x and 0 to 2 for y
	//		and the cell indexes would range from 0 to 11
	vec2	gridLoc = floor(loc * size);
	
	//	now the is the row number times the number of cells per row (number of columns), plus the number of columns on the current row
	//	starting at 0, ending at total number of cells (grid.x * grid.y - 1)
	float	cellIndex = gridLoc.y * size.x + gridLoc.x;
	
	//	of course if we are doing this vertically...
	if (dir == 1)	{
		cellIndex = gridLoc.x * size.y + gridLoc.y;
	}
	
	//	if we're doing snake...
	if (edge == 1)	{
		//	horizontal, odd row?
		if (dir == 0)	{
			float	oddRow = mod(gridLoc.y, 2.0);
			if (oddRow == 1.0)	{
				cellIndex = (gridLoc.y + 1.0) * size.x - gridLoc.x - 1.0;
			}
		}
		//	vertical, odd col?
		else {
			float	oddCol = mod(gridLoc.x, 2.0);
			if (oddCol == 1.0)	{
				cellIndex = (gridLoc.x + 1.0) * size.y - gridLoc.y - 1.0;
			}
		}
	}
	
    return cellIndex;
}

//	this returns the normalized offset coordinates of a particular cell, ordered based on the direction / start corner / wrap mode
vec2 offsetCoordForCell(int cellIndex, vec2 size, int dir, int corn, int edge){

	//	convert this 1D cellIndex into a 2D grid cell based on the rules
	//	this will be ranged 0 to size - 1
	//	we can convert these to normalized values later
	vec2	gridCell = vec2(0.0);
	
	//	start by assuming bottom left...
	
	//	if the direction is horizontal
	if (dir == 0)	{
		gridCell.x = mod(float(cellIndex), size.x);
		gridCell.y = floor(float(cellIndex) / size.x);	
	}
	//	if the direction is vertical
	else	{
		gridCell.x = floor(float(cellIndex) / size.y);
		gridCell.y = mod(float(cellIndex), size.y);	
	}
	
	//	now deal with snake mode
	//	if we're doing snake...
	if (edge == 1)	{
		//	horizontal, odd row?
		if (dir == 0)	{
			float	oddRow = mod(gridCell.y, 2.0);
			if (oddRow == 1.0)	{
				gridCell.x = size.x - gridCell.x - 1.0;
			}
		}
		//	vertical, odd col?
		else {
			float	oddCol = mod(gridCell.x, 2.0);
			if (oddCol == 1.0)	{
				gridCell.y = size.y - gridCell.y - 1.0;
			}
		}
	}
	
	//	if this is top left...
	if (corn == 0)	{
		//	keep the x, flip the y
		gridCell.y = size.y - 1.0 - gridCell.y;
	}
	else if (corn == 1)	{
		gridCell.y = size.y - 1.0 - gridCell.y;
		gridCell.x = size.x - 1.0 - gridCell.x;
	}
	else if (corn == 2)	{
		//	this is our normal state!
	}
	else if (corn == 3)	{
		gridCell.x = size.x - 1.0 - gridCell.x;
	}
	
	vec2	cellOffset = gridCell / size;

	return cellOffset;
}

//	this returns the local normalized coordinates for a pixel relative to a specified cell, ordered based on the direction / start corner / wrap mode
vec2 localCoordWithinCell(vec2 st, int cellIndex, int indexOutOffset, vec2 size, int dir, int corn, int edge){
	//	figure out the local origin for this cell
	vec2	localOriginIn = offsetCoordForCell(cellIndex, size, dir, corn, edge);
	vec2	localOriginOut = offsetCoordForCell(cellIndex + indexOutOffset, size, dir, corn, edge);
	
	//	useful for debug
	//return localOrigin;
	
	//	start at our existing point offset by the local origin
	vec2	loc = st - localOriginIn;
	
	//	now figure how much we need to shrink the x / y down to normalize for scale here
	//loc /= (size);
	
	//	now add back the local origin
	loc = loc + localOriginOut;
	
	return loc;
}

//	'a' and 'b' are rects (x and y are the originx, z and w are the width and height)
//	'm' is the sizing mode as described above (fit/fill/stretch/copy)
vec4 RectThatFitsRectInRect(vec4 a, vec4 b, int m);



void main()
{
	//	first pass: read the "buffer7" into "buffer8"
	//	apply lag on each pass
	//	if this is the first pass, i'm going to read the position from the "lastRow" image, and write a new position based on this and the hold variables
	if (PASSINDEX == 0)	{
		vec4		srcPixel = texture2D(drawState, (vec2(0.5) / u_resolution.xy));
		float		doClear = 0.0;
		//	check to see if the manual clear event was set
		//	check to see if the number of rows & cols has changed
			//	if auto clear is enabled, we need to do a clear
		if (clear == true)	{
			doClear = 1.0;
		}
		else if (srcPixel.r != float(cols) || srcPixel.g != float(rows))	{
			doClear = (autoClear) ? 1.0 : 0.0;
		}
		
		//	fill into the r channel the new number of cols
		//	fill into the g channel the new number of rows
		//	fill into the b channel the doClear state
		gl_FragColor = vec4(cols, rows, doClear, 1.0);
	}
	else if (PASSINDEX == 1)	{
		vec4		srcPixel = texture2D(frameCounter, (vec2(0.5) / u_resolution.xy));
		float		frameCount = srcPixel.r;
		
		if (frameCount >= fpsThrottle)	{
			frameCount = 0.0;
		}
		else	{
			frameCount += 1.0;
		}
		
		gl_FragColor = vec4(frameCount, 0.0, 0.0, 1.0);
	}
	else if (PASSINDEX == 2)	{
		//	get the state pixel, this will tell us if we need to clear
		//	(either because there was a manual clear command or an auto-clear because the cols / rows changed)
		vec4	statePixel = texture2D(drawState, (vec2(0.5) / u_resolution.xy));
		vec4	fcPixel = texture2D(frameCounter, (vec2(0.5) / u_resolution.xy));
		float	doClear = statePixel.b;
		float	frameCount = fcPixel.r;
		
		//	prep our variables for the pixel coordinate, the grid size, and the final output color
		vec2 	st = (gl_FragCoord.xy / u_resolution.xy);
		vec2	grid = vec2(cols, rows);
		vec4	color = vec4(0.0);
		
		//	if clearing...
		if (doClear == 1.0)	{
			color = vec4(0.0);
		}
		//	if we're throttling the fps just return the last frame
		else if (frameCount > 0.0)	{
			color = IMG_THIS_PIXEL(lastBuffer);
		}
		//	if there's only one cell to draw...
		else if (cols <= 1 && rows <= 1)	{
			color = texture2D(inputImage, st);
		}
		//	otherwise let's do our thing!
		else	{
			//	get the cell index that we are in
			float	cellIndex = cellForCoord(st, grid, direction, corner, edgeMode);
			
			//	if the cellIndex is 0, we are reading from the inputImage
			if (cellIndex == 0.0)	{
				//	get the local coordinate within that cell
				vec2	localOrigin = offsetCoordForCell(int(cellIndex), grid, int(direction), int(corner), int(edgeMode));
				//	create a pt that is this pixel offset by the local origin
				vec2	loc = st - localOrigin;
				//	scale this up so that within this cell loc ranges from 0 to 1 in both x & y
				loc *= (grid);
				//	scale this up so that x & y are now within pixel coordinate space, taking into account our aspect ratio
				//loc *= u_resolution;
				
				//	now handle aspect ratio mismatches
				vec4	inputRect = vec4(0.0, 0.0, u_resolution);
				vec4	outputRect = vec4(localOrigin * u_resolution, u_resolution / grid);
				vec4	sizedRect = RectThatFitsRectInRect(outputRect, inputRect, imageSizeMode);
				
				loc *= sizedRect.zw;
				loc += sizedRect.xy;
				
				if (loc.x >= 0.0 && loc.x <= u_resolution.x && loc.y >= 0.0 && loc.y <= u_resolution.y)	{
					color = texture2D(inputImage, (loc) / u_resolution.xy);
				}
				//	use this for debug
				//color = vec4(loc / u_resolution,0.0,1.0);
				//color = vec4(loc.x, loc.y, cellIndex / float(cols * rows), 1.0);
			}
			//	for any other cell, if we aren't doing a clear...
			else	{
				//	get the local coordinate within that cell (cellIndex - 1)
				vec2	local = localCoordWithinCell(st, int(cellIndex), -1, grid, int(direction), int(corner), int(edgeMode));
				//	read this pixel from the last stored buffer
				color = texture2D(lastBuffer, local);
			
				//	use this for debug
				//local = localCoordWithinCell(st, int(cellIndex), grid, int(direction), int(corner), int(edgeMode));
				//color = vec4(local,0.0,1.0);
				//color = vec4(local.x, local.y, cellIndex / float(cols * rows), 1.0);
			}
		}
		
		gl_FragColor = color;
	}
}






//	rect that fits 'a' in 'b' using sizing mode 'm'
vec4 RectThatFitsRectInRect(vec4 a, vec4 b, int m)	{
	float		bAspect = b.z/b.w;
	float		aAspect = a.z/a.w;
	if (aAspect==bAspect)	{
		return b;
	}
	vec4		returnMe = vec4(0.0);
	//	fill
	if (m==1)	{
		//	if the rect i'm trying to fit stuff *into* is wider than the rect i'm resizing
		if (bAspect > aAspect)	{
			returnMe.w = b.w;
			returnMe.z = returnMe.w * aAspect;
		}
		//	else if the rect i'm resizing is wider than the rect it's going into
		else if (bAspect < aAspect)	{
			returnMe.z = b.z;
			returnMe.w = returnMe.z / aAspect;
		}
		else	{
			returnMe.z = b.z;
			returnMe.w = b.w;
		}
		returnMe.x = (b.z-returnMe.z)/2.0+b.x;
		returnMe.y = (b.w-returnMe.w)/2.0+b.y;
	}
	//	fit
	else if (m==0)	{
		//	if the rect i'm trying to fit stuff *into* is wider than the rect i'm resizing
		if (bAspect > aAspect)	{
			returnMe.z = b.z;
			returnMe.w = returnMe.z / aAspect;
		}
		//	else if the rect i'm resizing is wider than the rect it's going into
		else if (bAspect < aAspect)	{
			returnMe.w = b.w;
			returnMe.z = returnMe.w * aAspect;
		}
		else	{
			returnMe.z = b.z;
			returnMe.w = b.w;
		}
		returnMe.x = (b.z-returnMe.z)/2.0+b.x;
		returnMe.y = (b.w-returnMe.w)/2.0+b.y;
	}
	//	stretch
	else if (m==2)	{
		returnMe = vec4(b.x, b.y, b.z, b.w);
	}
	//	copy
	else if (m==3)	{
		returnMe.z = float(int(a.z));
		returnMe.w = float(int(a.w));
		returnMe.x = float(int((b.z-returnMe.z)/2.0+b.x));
		returnMe.y = float(int((b.w-returnMe.w)/2.0+b.y));
	}
	return returnMe;
}