Warping shader (Inverse Bilinear Interpolation)

This is an implementation of the Inverse Bilinear Interpolation.

https://iquilezles.org/articles/ibilinear/

 

Also, It’s a corrected version of this shader (that has some errors):

https://godotshaders.com/shader/perspective-warp-skew-shader/

 

I used this shader as a key component for this mapping project:

https://surreal.asturnazari.com/2025/12/01/penpots-whac-a-mappole-a-floss-interactive-mapping/

Shader code
// based on https://iquilezles.org/articles/ibilinear/
shader_type canvas_item;

uniform float topleft_x : hint_range(-1.0, 1.0) = 0.0;
uniform float topleft_y : hint_range(-1.0, 1.0) = 0.0;
uniform float topright_x : hint_range(-1.0, 1.0) = 1.0;
uniform float topright_y : hint_range(-1.0, 1.0) = 0.0;
uniform float bottomleft_x : hint_range(-1.0, 1.0) = 0.0;
uniform float bottomleft_y : hint_range(-1.0, 1.0) = 1.0;
uniform float bottomright_x : hint_range(-1.0, 1.0) = 1.0;
uniform float bottomright_y : hint_range(-1.0, 1.0) = 1.0;

float border(vec2 uv, float border_width) {
	vec2 bottom_left = step(vec2(border_width), uv);
	vec2 top_right = step(vec2(border_width), 1.0 - uv);
	return bottom_left.x * bottom_left.y * top_right.x * top_right.y;
}


float _cross( in vec2 a, in vec2 b ) { return a.x*b.y - a.y*b.x; }

vec2 invBilinear( in vec2 p, in vec2 a, in vec2 b, in vec2 c, in vec2 d ) {
	vec2 res = vec2(-1.0);

	vec2 e = b-a;
	vec2 f = d-a;
	vec2 g = a-b+c-d;
	vec2 h = p-a;

	float k2 = _cross( g, f );
	float k1 = _cross( e, f ) + _cross( h, g );
	float k0 = _cross( h, e );

	// if edges are parallel, this is a linear equation
	if( abs(k2)<0.001 ) {
		res = vec2( (h.x*k1+f.x*k0)/(e.x*k1-g.x*k0), -k0/k1 );
	}
	// otherwise, it's a quadratic
	else {
		float w = k1*k1 - 4.0*k0*k2;
		if( w<0.0 ) return vec2(-1.0);
		w = sqrt( w );

		float ik2 = 0.5/k2;
		float v = (-k1 - w)*ik2;
		float u = (h.x - f.x*v)/(e.x + g.x*v);

		if( u<0.0 || u>1.0 || v<0.0 || v>1.0 ) {
		v = (-k1 + w)*ik2;
		   u = (h.x - f.x*v)/(e.x + g.x*v);
		}
		res = vec2( u, v );
	}

	return res;
}

void fragment(){
	vec2 newUV = invBilinear(UV, vec2(topleft_x, topleft_y), vec2(topright_x, topright_y),
		vec2(bottomright_x, bottomright_y), vec2(bottomleft_x, bottomleft_y));

	if (newUV.x < 0.0 || newUV.x > 1.0 || newUV.y < 0.0 || newUV.y > 1.0) {
		COLOR = vec4(0.0);
	} else {
		COLOR = texture(TEXTURE, newUV);
	}

}
Live Preview
Tags
corner pin, inverse bilinear, perspective, warp
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from asturnazari

Related shaders

guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
HGamesStudio
HGamesStudio
1 month ago

How would I apply this to the screen so it effects the entire viewport?