Using shader in Love2D

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
Harrylechienfou
Prole
Posts: 30
Joined: Tue May 21, 2019 2:02 pm

Using shader in Love2D

Post 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 :)
grump
Party member
Posts: 947
Joined: Sat Jul 22, 2017 7:43 pm

Re: Using shader in Love2D

Post 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.
User avatar
unixfreak
Citizen
Posts: 82
Joined: Thu Oct 15, 2015 6:25 am
Location: Bristol, UK
Contact:

Re: Using shader in Love2D

Post 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 216 times
grump
Party member
Posts: 947
Joined: Sat Jul 22, 2017 7:43 pm

Re: Using shader in Love2D

Post 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.
User avatar
unixfreak
Citizen
Posts: 82
Joined: Thu Oct 15, 2015 6:25 am
Location: Bristol, UK
Contact:

Re: Using shader in Love2D

Post 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:
Post Reply

Who is online

Users browsing this forum: _JM_ and 33 guests