Page 1 of 1

Using shader in Love2D

Posted: Wed Oct 21, 2020 10:12 am
by Harrylechienfou
Hi !
For my current project I need to use a "glitch effect" shader. I found a few of them on a website called shadertoy, but I don't understand how to use them in Love2D.
I've used shaders before but they were all designed to work on Love2D in the first place, which is not the case here.
Usually I make a new shader using love.graphics.newShader () and then send a few variable the shader needs with shader:send () but I guess there are a few other things to do in order to make it work when the shader is not specifically designed for Love2D ?

An example of the kind of shader I want to use on my project : https://www.shadertoy.com/view/XtK3W3
(It uses a video but it can use a simple image - which is the way I plan to use it in my project)

Can someone please explain to me what I'm supposed to do in order to make a shader work if it's not designed to be used in Love2D in the first place ?
Maybe share an example of the kind of "conversion" I need to do in order to make it work properly ?

Thanks for your help :)

Re: Using shader in Love2D

Posted: Wed Oct 21, 2020 10:54 am
by grump
See the "Shader language" part of love.graphics.newShader for basic differences. The variables provided by the Shadertoy runtime (buffers, time, etc.) should be documented on their site somewhere - you have to implement most of those yourself in LÖVE.

Re: Using shader in Love2D

Posted: Wed Oct 21, 2020 4:47 pm
by pgimeno

Re: Using shader in Love2D

Posted: Wed Oct 28, 2020 12:42 pm
by unixfreak
I can walk you through the steps to port this shader:

1. The main body of a shader such as

Code: Select all

void main()
typically becomes

Code: Select all

vec4 effect( vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords )
2. You'll want to switch out fragColor for color and fragCoord's for texture_coords

3. Often shadertoy shaders use normalized coordinates, probably dont need those;
so you can remove

Code: Select all

vec2 uv = fragCoord.xy / iResolution.xy;
and instead just define

Code: Select all

vec2 uv = texture_coords;
4. iChannel0 would be the input texture from shadertoy, so use tex here for love2d

5. then to pass in the iTime attribute something like;

Code: Select all

shader:send("millis", love.timer.getTime())
along with a

Code: Select all

uniform float millis;
input

6. texture() would also become Texel()

7. then you'll also want to make sure color is returned at the end

Code: Select all

return color



So with those changes in mind, you would get something like this:

Code: Select all

shader = [[
	uniform float millis;

	//
	// Description : Array and textureless GLSL 2D simplex noise function.
	//      Author : Ian McEwan, Ashima Arts.
	//  Maintainer : stegu
	//     Lastmod : 20110822 (ijm)
	//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
	//               Distributed under the MIT License. See LICENSE file.
	//               https://github.com/ashima/webgl-noise
	//               https://github.com/stegu/webgl-noise
	//

	vec3 mod289(vec3 x) {
	return x - floor(x * (1.0 / 289.0)) * 289.0;
	}

	vec2 mod289(vec2 x) {
	return x - floor(x * (1.0 / 289.0)) * 289.0;
	}

	vec3 permute(vec3 x) {
	return mod289(((x*34.0)+1.0)*x);
	}

	float snoise(vec2 v)
	{
	const vec4 C = vec4(0.211324865405187,  // (3.0-sqrt(3.0))/6.0
						0.366025403784439,  // 0.5*(sqrt(3.0)-1.0)
						-0.577350269189626,  // -1.0 + 2.0 * C.x
						0.024390243902439); // 1.0 / 41.0
	// First corner
	vec2 i  = floor(v + dot(v, C.yy) );
	vec2 x0 = v -   i + dot(i, C.xx);

	// Other corners
	vec2 i1;
	//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
	//i1.y = 1.0 - i1.x;
	i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
	// x0 = x0 - 0.0 + 0.0 * C.xx ;
	// x1 = x0 - i1 + 1.0 * C.xx ;
	// x2 = x0 - 1.0 + 2.0 * C.xx ;
	vec4 x12 = x0.xyxy + C.xxzz;
	x12.xy -= i1;

	// Permutations
	i = mod289(i); // Avoid truncation effects in permutation
	vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
			+ i.x + vec3(0.0, i1.x, 1.0 ));

	vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
	m = m*m ;
	m = m*m ;

	// Gradients: 41 points uniformly over a line, mapped onto a diamond.
	// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)

	vec3 x = 2.0 * fract(p * C.www) - 1.0;
	vec3 h = abs(x) - 0.5;
	vec3 ox = floor(x + 0.5);
	vec3 a0 = x - ox;

	// Normalise gradients implicitly by scaling m
	// Approximation of: m *= inversesqrt( a0*a0 + h*h );
	m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );

	// Compute final noise value at P
	vec3 g;
	g.x  = a0.x  * x0.x  + h.x  * x0.y;
	g.yz = a0.yz * x12.xz + h.yz * x12.yw;
	return 130.0 * dot(m, g);
	}

	float rand(vec2 co)
	{
	return fract(sin(dot(co.xy,vec2(12.9898,78.233))) * 43758.5453);
	}

	vec4 effect( vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords )
	{
		vec2 uv = texture_coords;
		float time = millis * 2.0;

		// Create large, incidental noise waves
		float noise = max(0.0, snoise(vec2(time, uv.y * 0.3)) - 0.3) * (1.0 / 0.7);

		// Offset by smaller, constant noise waves
		noise = noise + (snoise(vec2(time*10.0, uv.y * 2.4)) - 0.5) * 0.15;

		// Apply the noise as x displacement for every line
		float xpos = uv.x - noise * noise * 0.25;
		vec4 c = Texel(tex, vec2(xpos, uv.y));

		// Mix in some random interference for lines
		color.rgb = mix(c.rgb, vec3(rand(vec2(uv.y * time))), noise * 0.3).rgb;

		// Apply a line pattern every 4 pixels
		if (floor(mod(texture_coords.y * 0.25, 2.0)) == 0.0)
		{
			color.rgb *= 1.0 - (0.15 * noise);
		}

		// Shift green/blue channels (using the red channel)
		color.g = mix(color.r, Texel(tex, vec2(xpos + noise * 0.05, uv.y)).g, 0.25);
		color.b = mix(color.r, Texel(tex, vec2(xpos - noise * 0.05, uv.y)).b, 0.25);
		color.a = c.a //preserve alpha channel
		return color;
	}
]]
and also, a working example/love file here:
glitch.love
(12.08 KiB) Downloaded 306 times

Re: Using shader in Love2D

Posted: Wed Oct 28, 2020 3:10 pm
by grump
unixfreak wrote: Wed Oct 28, 2020 12:42 pm 5. then to pass in the iTime attribute something like;

Code: Select all

shader:send("millis", love.timer.getTime())
Due to precision errors after long uptime, passing the time like this can result in laggy animation. Get the timer value from love.timer.getTime() when the game starts, and subtract it from the current time when passing it to the shader.

Code: Select all

-- when game starts
local startTime = love.timer.getTime()

-- when sending the time
shader:send("iTime", love.timer.getTime() - startTime)
Accumulating dt in love.update would work too.

Re: Using shader in Love2D

Posted: Wed Oct 28, 2020 11:12 pm
by unixfreak
grump wrote: Wed Oct 28, 2020 3:10 pm Due to precision errors after long uptime, passing the time like this can result in laggy animation. Get the timer value from love.timer.getTime() when the game starts, and subtract it from the current time when passing it to the shader.
Hmm, interesting. Never thought about that one. :awesome: